{"html_url": "https://github.com/simonw/datasette/issues/1164#issuecomment-752756612", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1164", "id": 752756612, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc1NjYxMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:59:54Z", "updated_at": "2020-12-30T20:59:54Z", "author_association": "OWNER", "body": "I tried a few different pure-Python JavaScript minifying libraries and none of them produced results as good as https://www.npmjs.com/package/uglify-js for the plugin code I'm considering in #983.\r\n\r\nSo I think I'll need to rely on a Node.js tool for this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776634318, "label": "Mechanism for minifying JavaScript that ships with Datasette"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1164#issuecomment-752757075", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1164", "id": 752757075, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc1NzA3NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T21:01:27Z", "updated_at": "2020-12-30T21:01:27Z", "author_association": "OWNER", "body": "I don't want Datasette contributors to need a working Node.js install to run the tests or work on Datasette unless they are explicitly working on the JavaScript.\r\n\r\nI think I'm going to do this with a unit test that runs only if `upglify-js` is available on the path and confirms that the `*.min.js` version of each script in the repository correctly matches the results from running `uglify-js` against it.\r\n\r\nThat way if anyone checks in a change to JavaScript but forgets to run the minifier the tests will fail in CI.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776634318, "label": "Mechanism for minifying JavaScript that ships with Datasette"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1164#issuecomment-752768652", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1164", "id": 752768652, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc2ODY1Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T21:46:29Z", "updated_at": "2020-12-30T21:46:29Z", "author_association": "OWNER", "body": "Running https://skalman.github.io/UglifyJS-online/ against https://github.com/simonw/datasette/blob/0.53/datasette/static/table.js knocks it down from 7810 characters to 4643.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776634318, "label": "Mechanism for minifying JavaScript that ships with Datasette"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1164#issuecomment-752768785", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1164", "id": 752768785, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc2ODc4NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T21:47:06Z", "updated_at": "2020-12-30T21:47:06Z", "author_association": "OWNER", "body": "If I'm going to minify `table.js` I'd like to offer a source map for it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776634318, "label": "Mechanism for minifying JavaScript that ships with Datasette"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1164#issuecomment-752769452", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1164", "id": 752769452, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc2OTQ1Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T21:50:16Z", "updated_at": "2020-12-30T21:50:16Z", "author_association": "OWNER", "body": "If I implement this I can automate the CodeMirror minification and remove the bit about running `uglify-js` against it from the documentation here: https://docs.datasette.io/en/0.53/contributing.html#upgrading-codemirror", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776634318, "label": "Mechanism for minifying JavaScript that ships with Datasette"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1165#issuecomment-752757910", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1165", "id": 752757910, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc1NzkxMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T21:04:18Z", "updated_at": "2020-12-30T21:04:18Z", "author_association": "OWNER", "body": "https://jestjs.io/ looks worth trying here.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776635426, "label": "Mechanism for executing JavaScript unit tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1165#issuecomment-752777744", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1165", "id": 752777744, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc3Nzc0NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T22:30:24Z", "updated_at": "2020-12-30T22:30:24Z", "author_association": "OWNER", "body": "https://www.valentinog.com/blog/jest/ was useful.\r\n\r\nI created a `static/__tests__` folder and added this file as `plugins.spec.js`:\r\n\r\n```javascript\r\nconst datasette = require(\"../plugins.js\");\r\n\r\ndescribe(\"Datasette Plugins\", () => {\r\n test(\"it should have datasette.plugins\", () => {\r\n expect(!!datasette.plugins).toEqual(true);\r\n });\r\n test(\"registering a plugin should work\", () => {\r\n datasette.plugins.register(\"numbers\", (a, b) => a + b, [\"a\", \"b\"]);\r\n var result = datasette.plugins.call(\"numbers\", { a: 1, b: 2 });\r\n expect(result).toEqual([3]);\r\n datasette.plugins.register(\"numbers\", (a, b) => a * b, [\"a\", \"b\"]);\r\n var result2 = datasette.plugins.call(\"numbers\", { a: 1, b: 2 });\r\n expect(result2).toEqual([3, 2]);\r\n });\r\n});\r\n```\r\n\r\nIn `static/plugins.js` I put this:\r\n```javascript\r\nvar datasette = datasette || {};\r\ndatasette.plugins = (() => {\r\n var registry = {};\r\n return {\r\n register: (hook, fn, parameters) => {\r\n if (!registry[hook]) {\r\n registry[hook] = [];\r\n }\r\n registry[hook].push([fn, parameters]);\r\n },\r\n call: (hook, args) => {\r\n args = args || {};\r\n var results = [];\r\n (registry[hook] || []).forEach(([fn, parameters]) => {\r\n /* Call with the correct arguments */\r\n var result = fn.apply(fn, parameters.map(parameter => args[parameter]));\r\n if (result !== undefined) {\r\n results.push(result);\r\n }\r\n });\r\n return results;\r\n }\r\n };\r\n})();\r\n\r\nmodule.exports = datasette;\r\n```\r\nNote the `module.exports` line at the end.\r\n\r\nThen inside `static/` I ran the following command:\r\n\r\n```\r\n% npx jest -c '{}'\r\n PASS __tests__/plugins.spec.js\r\n Datasette Plugins\r\n \u2713 it should have datasette.plugins (3 ms)\r\n \u2713 registering a plugin should work (1 ms)\r\n\r\nTest Suites: 1 passed, 1 total\r\nTests: 2 passed, 2 total\r\nSnapshots: 0 total\r\nTime: 1.163 s\r\nRan all test suites.\r\n```\r\nThe `-c {}` was necessary because I didn't have a Jest configuration or a `package.json`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776635426, "label": "Mechanism for executing JavaScript unit tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1165#issuecomment-752779490", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1165", "id": 752779490, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc3OTQ5MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T22:38:43Z", "updated_at": "2020-12-30T22:38:43Z", "author_association": "OWNER", "body": "Turned that into a TIL: https://til.simonwillison.net/javascript/jest-without-package-json", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776635426, "label": "Mechanism for executing JavaScript unit tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1165#issuecomment-752779820", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1165", "id": 752779820, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc3OTgyMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T22:40:28Z", "updated_at": "2020-12-30T22:40:28Z", "author_association": "OWNER", "body": "I don't know if Jest on the command-line is the right tool for this. It works for the `plugins.js` script but I'm increasingly going to want to start adding tests for browser JavaScript features - like the https://github.com/simonw/datasette/blob/0.53/datasette/static/table.js script - which will need to run in a browser.\r\n\r\nSo maybe I should just find a browser testing solution and figure out how to run that under CI in GitHub Actions. Maybe https://www.cypress.io/ ?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776635426, "label": "Mechanism for executing JavaScript unit tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1165#issuecomment-752780000", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1165", "id": 752780000, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc4MDAwMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T22:41:25Z", "updated_at": "2020-12-30T22:41:25Z", "author_association": "OWNER", "body": "Jest works with Puppeteer: https://jestjs.io/docs/en/puppeteer", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 776635426, "label": "Mechanism for executing JavaScript unit tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752715236", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752715236, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcxNTIzNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T18:24:54Z", "updated_at": "2020-12-30T18:24:54Z", "author_association": "OWNER", "body": "I think I'm going to try building a very lightweight clone of the core API design of Pluggy - not the advanced features, just the idea that plugins can register and a call to `plugin.nameOfHook()` will return the concatenated results of all of the registered hooks.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752715412", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752715412, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcxNTQxMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T18:25:31Z", "updated_at": "2020-12-30T18:25:31Z", "author_association": "OWNER", "body": "I'm going to introduce a global `datasette` object which holds all the documented JavaScript API for plugin authors.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752721069", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752721069, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcyMTA2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T18:46:10Z", "updated_at": "2020-12-30T18:46:10Z", "author_association": "OWNER", "body": "Pluggy does dependency injection by introspecting the named arguments to the Python function, which I really like.\r\n\r\nThat's tricker in JavaScript. It looks like the only way to introspect a function is to look at the `.toString()` representation of it and parse the `(parameter, list)` using a regular expression.\r\n\r\nEven more challenging: JavaScript developers love minifying their code, and minification can shorten the function parameter names.\r\n\r\nFrom https://code-maven.com/dependency-injection-in-angularjs it looks like Angular.js does dependency injection and solves this by letting you optionally provide a separate list of the arguments your function uses:\r\n\r\n```javascript\r\n angular.module('DemoApp', [])\r\n .controller('DemoController', ['$scope', '$log', function($scope, $log) {\r\n $scope.message = \"Hello World\";\r\n $log.debug('logging hello');\r\n }]);\r\n```\r\n\r\nI can copy that approach: I'll introspect by default, but provide a documented mechanism for explicitly listing your parameter names so that if you know your plugin code will be minified you can use that instead.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752721840", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752721840, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcyMTg0MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T18:48:53Z", "updated_at": "2020-12-30T18:51:51Z", "author_association": "OWNER", "body": "Potential design:\r\n```javascript\r\ndatasette.plugins.register('column_actions', function(database, table, column, actor) {\r\n /* ... *l\r\n})\r\n```\r\nOr if you want to be explicit to survive minification:\r\n```javascript\r\ndatasette.plugins.register('column_actions', function(database, table, column, actor) {\r\n /* ... *l\r\n}, ['database', 'table', 'column', 'actor'])\r\n```\r\nI'm making that list of parameter names an optional third argument to the `register()` function. If that argument isn't passed, introspection will be used to figure out the parameter names.\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752722863", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752722863, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcyMjg2Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T18:52:39Z", "updated_at": "2020-12-30T18:52:39Z", "author_association": "OWNER", "body": "Then to call the plugins:\r\n```javascript\r\ndatasette.plugins.call('column_actions', {database: 'database', table: 'table'})\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752729035", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752729035, "node_id": "MDEyOklzc3VlQ29tbWVudDc1MjcyOTAzNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T19:15:56Z", "updated_at": "2020-12-30T19:16:44Z", "author_association": "OWNER", "body": "The `column_actions` hook is the obvious first place to try this out. What are some demo plugins I could build for it?\r\n\r\n- Word cloud for this column\r\n- Count values (essentially linking to the SQL query for that column, as an extended version of the facet counts) - would be great if this could include pagination somehow, via #856.\r\n- Extract this column into a separate table\r\n- Add an index to this column", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752742669", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752742669, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0MjY2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:07:05Z", "updated_at": "2020-12-30T20:07:18Z", "author_association": "OWNER", "body": "Initial prototype:\r\n```javascript\r\nwindow.datasette = {};\r\nwindow.datasette.plugins = (function() {\r\n var registry = {};\r\n\r\n function extractParameters(fn) {\r\n var match = /\\((.*)\\)/.exec(fn.toString());\r\n if (match && match[1].trim()) {\r\n return match[1].split(',').map(s => s.trim());\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n function register(hook, fn, parameters) {\r\n parameters = parameters || extractParameters(fn);\r\n if (!registry[hook]) {\r\n registry[hook] = [];\r\n }\r\n registry[hook].push([fn, parameters]);\r\n }\r\n\r\n function call(hook, args) {\r\n args = args || {};\r\n var implementations = registry[hook] || [];\r\n var results = [];\r\n implementations.forEach(([fn, parameters]) => {\r\n /* Call with the correct arguments */\r\n var callWith = parameters.map(parameter => args[parameter]);\r\n var result = fn.apply(fn, callWith);\r\n if (result) {\r\n results.push(result);\r\n }\r\n });\r\n return results;\r\n }\r\n return {\r\n register: register,\r\n _registry: registry,\r\n call: call\r\n };\r\n})();\r\n```\r\nUsage example:\r\n```javascript\r\ndatasette.plugins.register('numbers', (a, b) => a + b)\r\ndatasette.plugins.register('numbers', (a, b) => a * b)\r\ndatasette.plugins.call('numbers', {a: 4, b: 6})\r\n/* Returns [10, 24] */\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752744195", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752744195, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NDE5NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:12:26Z", "updated_at": "2020-12-30T20:12:26Z", "author_association": "OWNER", "body": "This implementation doesn't have an equivalent of \"hookspecs\" which can identify if a registered plugin implementation matches a known signature. I should add that, it will provide a better developer experience if someone has a typo.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752744311", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752744311, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NDMxMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:12:50Z", "updated_at": "2020-12-30T20:13:02Z", "author_association": "OWNER", "body": "This could work to define a plugin hook:\r\n```javascript\r\ndatasette.plugins.define('numbers', ['a' ,'b'])\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752747169", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752747169, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0NzE2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:24:07Z", "updated_at": "2020-12-30T20:24:07Z", "author_association": "OWNER", "body": "This version adds `datasette.plugins.define()` plus extra validation of both `.register()` and `.call()`:\r\n```javascript\r\nwindow.datasette = {};\r\nwindow.datasette.plugins = (function() {\r\n var registry = {};\r\n var definitions = {};\r\n\r\n function extractParameters(fn) {\r\n var match = /\\((.*)\\)/.exec(fn.toString());\r\n if (match && match[1].trim()) {\r\n return match[1].split(',').map(s => s.trim());\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n function define(hook, parameters) {\r\n definitions[hook] = parameters || [];\r\n }\r\n\r\n function isSubSet(a, b) {\r\n return a.every(parameter => b.includes(parameter))\r\n }\r\n\r\n function register(hook, fn, parameters) {\r\n parameters = parameters || extractParameters(fn);\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" is not a defined plugin hook');\r\n }\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" is not a defined plugin hook');\r\n }\r\n /* Check parameters is a subset of definitions[hook] */\r\n var validParameters = definitions[hook];\r\n if (!isSubSet(parameters, validParameters)) {\r\n throw new Error('\"' + hook + '\" valid parameters are ' + JSON.stringify(validParameters));\r\n }\r\n if (!registry[hook]) {\r\n registry[hook] = [];\r\n }\r\n registry[hook].push([fn, parameters]);\r\n }\r\n\r\n function call(hook, args) {\r\n args = args || {};\r\n if (!definitions[hook]) {\r\n throw new Error('\"' + hook + '\" hook has not been defined');\r\n }\r\n if (!isSubSet(Object.keys(args), definitions[hook])) {\r\n throw new Error('\"' + hook + '\" valid arguments are ' + JSON.stringify(definitions[hook]));\r\n }\r\n \r\n var implementations = registry[hook] || [];\r\n var results = [];\r\n implementations.forEach(([fn, parameters]) => {\r\n /* Call with the correct arguments */\r\n var callWith = parameters.map(parameter => args[parameter]);\r\n var result = fn.apply(fn, callWith);\r\n if (result) {\r\n results.push(result);\r\n }\r\n });\r\n return results;\r\n }\r\n return {\r\n define: define,\r\n register: register,\r\n _registry: registry,\r\n call: call\r\n };\r\n})();\r\n```\r\nUsage:\r\n```javascript\r\ndatasette.plugins.define('numbers', ['a', 'b'])\r\ndatasette.plugins.register('numbers', (a, b) => a + b)\r\ndatasette.plugins.register('numbers', (a, b) => a * b)\r\ndatasette.plugins.call('numbers', {a: 4, b: 6})\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 712260429, "label": "JavaScript plugin hooks mechanism similar to pluggy"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/983#issuecomment-752747999", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/983", "id": 752747999, "node_id": "MDEyOklzc3VlQ29tbWVudDc1Mjc0Nzk5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-12-30T20:27:00Z", "updated_at": "2020-12-30T20:27:00Z", "author_association": "OWNER", "body": "I need to decide how this code is going to be loaded. Putting it in a blocking `