@agent-native/core 0.7.82 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.js +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +8 -8
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +44 -18
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +87 -19
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/workspacify.d.ts.map +1 -1
- package/dist/cli/workspacify.js +12 -9
- package/dist/cli/workspacify.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +22 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts +3 -2
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +18 -14
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +254 -29
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +2 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +11 -2
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/builder-frame.d.ts +11 -0
- package/dist/client/builder-frame.d.ts.map +1 -1
- package/dist/client/builder-frame.js +40 -9
- package/dist/client/builder-frame.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +1 -1
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +2 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -3
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +25 -13
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/types.d.ts +1 -1
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +20 -0
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -0
- package/dist/client/{tools/EmbeddedTool.js → extensions/EmbeddedExtension.js} +41 -41
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -0
- package/dist/client/extensions/ExtensionEditor.d.ts +5 -0
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -0
- package/dist/client/extensions/ExtensionEditor.js +129 -0
- package/dist/client/extensions/ExtensionEditor.js.map +1 -0
- package/dist/client/{tools → extensions}/ExtensionSlot.d.ts +3 -3
- package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -0
- package/dist/client/{tools → extensions}/ExtensionSlot.js +14 -14
- package/dist/client/extensions/ExtensionSlot.js.map +1 -0
- package/dist/client/extensions/ExtensionViewer.d.ts +5 -0
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -0
- package/dist/client/{tools/ToolViewer.js → extensions/ExtensionViewer.js} +67 -65
- package/dist/client/extensions/ExtensionViewer.js.map +1 -0
- package/dist/client/extensions/ExtensionViewerPage.d.ts +2 -0
- package/dist/client/extensions/ExtensionViewerPage.d.ts.map +1 -0
- package/dist/client/{tools/ToolViewerPage.js → extensions/ExtensionViewerPage.js} +8 -8
- package/dist/client/extensions/ExtensionViewerPage.js.map +1 -0
- package/dist/client/extensions/ExtensionsListPage.d.ts +2 -0
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -0
- package/dist/client/extensions/ExtensionsListPage.js +67 -0
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -0
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts +2 -0
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -0
- package/dist/client/{tools/ToolsSidebarSection.js → extensions/ExtensionsSidebarSection.js} +58 -58
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -0
- package/dist/client/{tools/tool-order.d.ts → extensions/extension-order.d.ts} +2 -2
- package/dist/client/extensions/extension-order.d.ts.map +1 -0
- package/dist/client/{tools/tool-order.js → extensions/extension-order.js} +3 -3
- package/dist/client/extensions/extension-order.js.map +1 -0
- package/dist/client/{tools → extensions}/iframe-bridge.d.ts +11 -11
- package/dist/client/extensions/iframe-bridge.d.ts.map +1 -0
- package/dist/client/{tools → extensions}/iframe-bridge.js +24 -24
- package/dist/client/extensions/iframe-bridge.js.map +1 -0
- package/dist/client/extensions/index.d.ts +14 -0
- package/dist/client/extensions/index.d.ts.map +1 -0
- package/dist/client/extensions/index.js +19 -0
- package/dist/client/extensions/index.js.map +1 -0
- package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +4 -1
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +2 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +87 -6
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/extensions/actions.d.ts +3 -0
- package/dist/extensions/actions.d.ts.map +1 -0
- package/dist/{tools → extensions}/actions.js +54 -51
- package/dist/extensions/actions.js.map +1 -0
- package/dist/{tools → extensions}/fetch-tool.d.ts +4 -0
- package/dist/extensions/fetch-tool.d.ts.map +1 -0
- package/dist/{tools → extensions}/fetch-tool.js +12 -7
- package/dist/extensions/fetch-tool.js.map +1 -0
- package/dist/extensions/html-shell.d.ts +56 -0
- package/dist/extensions/html-shell.d.ts.map +1 -0
- package/dist/{tools → extensions}/html-shell.js +101 -83
- package/dist/extensions/html-shell.js.map +1 -0
- package/dist/{tools → extensions}/proxy-security.d.ts +2 -2
- package/dist/extensions/proxy-security.d.ts.map +1 -0
- package/dist/{tools → extensions}/proxy-security.js +3 -3
- package/dist/extensions/proxy-security.js.map +1 -0
- package/dist/extensions/routes.d.ts +2 -0
- package/dist/extensions/routes.d.ts.map +1 -0
- package/dist/{tools → extensions}/routes.js +73 -69
- package/dist/extensions/routes.js.map +1 -0
- package/dist/{tools → extensions}/schema.d.ts +44 -38
- package/dist/extensions/schema.d.ts.map +1 -0
- package/dist/{tools → extensions}/schema.js +41 -34
- package/dist/extensions/schema.js.map +1 -0
- package/dist/extensions/slots/routes.d.ts +15 -0
- package/dist/extensions/slots/routes.d.ts.map +1 -0
- package/dist/{tools → extensions}/slots/routes.js +26 -26
- package/dist/extensions/slots/routes.js.map +1 -0
- package/dist/{tools → extensions}/slots/schema.d.ts +24 -21
- package/dist/extensions/slots/schema.d.ts.map +1 -0
- package/dist/extensions/slots/schema.js +79 -0
- package/dist/extensions/slots/schema.js.map +1 -0
- package/dist/extensions/slots/store.d.ts +66 -0
- package/dist/extensions/slots/store.d.ts.map +1 -0
- package/dist/extensions/slots/store.js +238 -0
- package/dist/extensions/slots/store.js.map +1 -0
- package/dist/extensions/store.d.ts +40 -0
- package/dist/extensions/store.d.ts.map +1 -0
- package/dist/{tools → extensions}/store.js +59 -54
- package/dist/extensions/store.js.map +1 -0
- package/dist/extensions/theme.d.ts.map +1 -0
- package/dist/extensions/theme.js.map +1 -0
- package/dist/{tools → extensions}/url-safety.d.ts +5 -3
- package/dist/extensions/url-safety.d.ts.map +1 -0
- package/dist/{tools → extensions}/url-safety.js +11 -4
- package/dist/extensions/url-safety.js.map +1 -0
- package/dist/server/action-discovery.d.ts +15 -0
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +45 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +12 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +5 -4
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +80 -28
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +15 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -13
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/csrf.d.ts +3 -2
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +3 -2
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +15 -3
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/shared/workspace-app-id.d.ts +1 -1
- package/dist/shared/workspace-app-id.d.ts.map +1 -1
- package/dist/shared/workspace-app-id.js +5 -1
- package/dist/shared/workspace-app-id.js.map +1 -1
- package/dist/templates/workspace-root/README.md +5 -4
- package/dist/usage/store.d.ts +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +1 -1
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +17 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +10 -10
- package/docs/content/extensions.md +230 -0
- package/docs/content/key-concepts.md +2 -2
- package/docs/content/server.md +13 -13
- package/docs/content/sharing.md +2 -2
- package/docs/content/template-analytics.md +10 -0
- package/docs/content/template-calendar.md +10 -0
- package/docs/content/template-clips.md +10 -0
- package/docs/content/template-content.md +10 -0
- package/docs/content/template-dispatch.md +15 -0
- package/docs/content/template-forms.md +10 -0
- package/docs/content/template-mail.md +10 -0
- package/docs/content/template-slides.md +11 -1
- package/docs/content/template-starter.md +10 -0
- package/docs/content/template-video.md +10 -0
- package/docs/content/what-is-agent-native.md +1 -1
- package/package.json +22 -17
- package/src/templates/workspace-root/README.md +5 -4
- package/dist/client/tools/EmbeddedTool.d.ts +0 -20
- package/dist/client/tools/EmbeddedTool.d.ts.map +0 -1
- package/dist/client/tools/EmbeddedTool.js.map +0 -1
- package/dist/client/tools/ExtensionSlot.d.ts.map +0 -1
- package/dist/client/tools/ExtensionSlot.js.map +0 -1
- package/dist/client/tools/ToolEditor.d.ts +0 -5
- package/dist/client/tools/ToolEditor.d.ts.map +0 -1
- package/dist/client/tools/ToolEditor.js +0 -129
- package/dist/client/tools/ToolEditor.js.map +0 -1
- package/dist/client/tools/ToolViewer.d.ts +0 -5
- package/dist/client/tools/ToolViewer.d.ts.map +0 -1
- package/dist/client/tools/ToolViewer.js.map +0 -1
- package/dist/client/tools/ToolViewerPage.d.ts +0 -2
- package/dist/client/tools/ToolViewerPage.d.ts.map +0 -1
- package/dist/client/tools/ToolViewerPage.js.map +0 -1
- package/dist/client/tools/ToolsListPage.d.ts +0 -2
- package/dist/client/tools/ToolsListPage.d.ts.map +0 -1
- package/dist/client/tools/ToolsListPage.js +0 -67
- package/dist/client/tools/ToolsListPage.js.map +0 -1
- package/dist/client/tools/ToolsSidebarSection.d.ts +0 -2
- package/dist/client/tools/ToolsSidebarSection.d.ts.map +0 -1
- package/dist/client/tools/ToolsSidebarSection.js.map +0 -1
- package/dist/client/tools/iframe-bridge.d.ts.map +0 -1
- package/dist/client/tools/iframe-bridge.js.map +0 -1
- package/dist/client/tools/index.d.ts +0 -8
- package/dist/client/tools/index.d.ts.map +0 -1
- package/dist/client/tools/index.js +0 -8
- package/dist/client/tools/index.js.map +0 -1
- package/dist/client/tools/tool-order.d.ts.map +0 -1
- package/dist/client/tools/tool-order.js.map +0 -1
- package/dist/tools/actions.d.ts +0 -3
- package/dist/tools/actions.d.ts.map +0 -1
- package/dist/tools/actions.js.map +0 -1
- package/dist/tools/fetch-tool.d.ts.map +0 -1
- package/dist/tools/fetch-tool.js.map +0 -1
- package/dist/tools/html-shell.d.ts +0 -45
- package/dist/tools/html-shell.d.ts.map +0 -1
- package/dist/tools/html-shell.js.map +0 -1
- package/dist/tools/proxy-security.d.ts.map +0 -1
- package/dist/tools/proxy-security.js.map +0 -1
- package/dist/tools/routes.d.ts +0 -2
- package/dist/tools/routes.d.ts.map +0 -1
- package/dist/tools/routes.js.map +0 -1
- package/dist/tools/schema.d.ts.map +0 -1
- package/dist/tools/schema.js.map +0 -1
- package/dist/tools/slots/routes.d.ts +0 -15
- package/dist/tools/slots/routes.d.ts.map +0 -1
- package/dist/tools/slots/routes.js.map +0 -1
- package/dist/tools/slots/schema.d.ts.map +0 -1
- package/dist/tools/slots/schema.js +0 -76
- package/dist/tools/slots/schema.js.map +0 -1
- package/dist/tools/slots/store.d.ts +0 -66
- package/dist/tools/slots/store.d.ts.map +0 -1
- package/dist/tools/slots/store.js +0 -227
- package/dist/tools/slots/store.js.map +0 -1
- package/dist/tools/store.d.ts +0 -40
- package/dist/tools/store.d.ts.map +0 -1
- package/dist/tools/store.js.map +0 -1
- package/dist/tools/theme.d.ts.map +0 -1
- package/dist/tools/theme.js.map +0 -1
- package/dist/tools/url-safety.d.ts.map +0 -1
- package/dist/tools/url-safety.js.map +0 -1
- package/docs/content/tools.md +0 -205
- /package/dist/{tools → extensions}/theme.d.ts +0 -0
- /package/dist/{tools → extensions}/theme.js +0 -0
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drizzle schema for the
|
|
2
|
+
* Drizzle schema for the extension-points slot system.
|
|
3
3
|
*
|
|
4
4
|
* Two tables:
|
|
5
5
|
*
|
|
6
|
-
* - `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* - `extension_slots` — declarations: "extension X can render in slot Y".
|
|
7
|
+
* Authored once per extension, regardless of installer.
|
|
8
|
+
* Physical SQL name stays `tool_slots` (additive-only).
|
|
9
|
+
* - `extension_slot_installs` — per-user installs: "user U wants extension X in
|
|
10
|
+
* slot Y at position N". Always scoped by
|
|
11
|
+
* owner_email. Physical SQL name stays
|
|
12
|
+
* `tool_slot_installs`.
|
|
10
13
|
*
|
|
11
14
|
* Neither table spreads `ownableColumns()` — they're not first-class shareable
|
|
12
|
-
* resources. Access to the underlying
|
|
13
|
-
* table sharing model; install rows are personal preferences
|
|
14
|
-
* installing user.
|
|
15
|
+
* resources. Access to the underlying extension flows through the existing
|
|
16
|
+
* `extensions` table sharing model; install rows are personal preferences
|
|
17
|
+
* scoped to the installing user.
|
|
15
18
|
*/
|
|
16
|
-
export declare const
|
|
19
|
+
export declare const extensionSlots: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
17
20
|
name: "tool_slots";
|
|
18
21
|
schema: undefined;
|
|
19
22
|
columns: {
|
|
@@ -36,7 +39,7 @@ export declare const toolSlots: import("drizzle-orm/sqlite-core").SQLiteTableWit
|
|
|
36
39
|
}, {}, {
|
|
37
40
|
length: number;
|
|
38
41
|
}>;
|
|
39
|
-
|
|
42
|
+
extensionId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
40
43
|
name: "tool_id";
|
|
41
44
|
tableName: "tool_slots";
|
|
42
45
|
dataType: "string";
|
|
@@ -115,7 +118,7 @@ export declare const toolSlots: import("drizzle-orm/sqlite-core").SQLiteTableWit
|
|
|
115
118
|
};
|
|
116
119
|
dialect: "sqlite";
|
|
117
120
|
}>;
|
|
118
|
-
export declare const
|
|
121
|
+
export declare const extensionSlotInstalls: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
119
122
|
name: "tool_slot_installs";
|
|
120
123
|
schema: undefined;
|
|
121
124
|
columns: {
|
|
@@ -138,7 +141,7 @@ export declare const toolSlotInstalls: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
138
141
|
}, {}, {
|
|
139
142
|
length: number;
|
|
140
143
|
}>;
|
|
141
|
-
|
|
144
|
+
extensionId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
142
145
|
name: "tool_id";
|
|
143
146
|
tableName: "tool_slot_installs";
|
|
144
147
|
dataType: "string";
|
|
@@ -291,13 +294,13 @@ export declare const toolSlotInstalls: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
291
294
|
};
|
|
292
295
|
dialect: "sqlite";
|
|
293
296
|
}>;
|
|
294
|
-
export declare const
|
|
295
|
-
export declare const
|
|
296
|
-
export declare const
|
|
297
|
-
export declare const
|
|
298
|
-
export declare const
|
|
299
|
-
export declare const
|
|
300
|
-
export declare const
|
|
301
|
-
export declare const
|
|
302
|
-
export declare const
|
|
297
|
+
export declare const EXTENSION_SLOTS_CREATE_SQL = "CREATE TABLE IF NOT EXISTS tool_slots (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)";
|
|
298
|
+
export declare const EXTENSION_SLOTS_CREATE_SQL_PG = "CREATE TABLE IF NOT EXISTS tool_slots (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT now()\n)";
|
|
299
|
+
export declare const EXTENSION_SLOTS_BY_SLOT_INDEX_SQL = "CREATE INDEX IF NOT EXISTS tool_slots_by_slot_idx ON tool_slots (slot_id)";
|
|
300
|
+
export declare const EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL = "CREATE INDEX IF NOT EXISTS tool_slots_by_tool_idx ON tool_slots (tool_id)";
|
|
301
|
+
export declare const EXTENSION_SLOTS_UNIQUE_INDEX_SQL = "CREATE UNIQUE INDEX IF NOT EXISTS tool_slots_unique_idx ON tool_slots (tool_id, slot_id)";
|
|
302
|
+
export declare const EXTENSION_SLOT_INSTALLS_CREATE_SQL = "CREATE TABLE IF NOT EXISTS tool_slot_installs (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n position INTEGER NOT NULL DEFAULT 0,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n)";
|
|
303
|
+
export declare const EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG = "CREATE TABLE IF NOT EXISTS tool_slot_installs (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n position INTEGER NOT NULL DEFAULT 0,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now()\n)";
|
|
304
|
+
export declare const EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL = "CREATE INDEX IF NOT EXISTS tool_slot_installs_by_user_slot_idx ON tool_slot_installs (owner_email, slot_id)";
|
|
305
|
+
export declare const EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL = "CREATE UNIQUE INDEX IF NOT EXISTS tool_slot_installs_unique_idx ON tool_slot_installs (owner_email, tool_id, slot_id)";
|
|
303
306
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/slots/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMzB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUhC,CAAC;AAEH,eAAO,MAAM,0BAA0B,iMAMrC,CAAC;AAEH,eAAO,MAAM,6BAA6B,qLAMxC,CAAC;AAEH,eAAO,MAAM,iCAAiC,8EAA8E,CAAC;AAC7H,eAAO,MAAM,sCAAsC,8EAA8E,CAAC;AAClI,eAAO,MAAM,gCAAgC,6FAA6F,CAAC;AAE3I,eAAO,MAAM,kCAAkC,sVAU7C,CAAC;AAEH,eAAO,MAAM,qCAAqC,8TAUhD,CAAC;AAEH,eAAO,MAAM,8CAA8C,gHAAgH,CAAC;AAC5K,eAAO,MAAM,wCAAwC,0HAA0H,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle schema for the extension-points slot system.
|
|
3
|
+
*
|
|
4
|
+
* Two tables:
|
|
5
|
+
*
|
|
6
|
+
* - `extension_slots` — declarations: "extension X can render in slot Y".
|
|
7
|
+
* Authored once per extension, regardless of installer.
|
|
8
|
+
* Physical SQL name stays `tool_slots` (additive-only).
|
|
9
|
+
* - `extension_slot_installs` — per-user installs: "user U wants extension X in
|
|
10
|
+
* slot Y at position N". Always scoped by
|
|
11
|
+
* owner_email. Physical SQL name stays
|
|
12
|
+
* `tool_slot_installs`.
|
|
13
|
+
*
|
|
14
|
+
* Neither table spreads `ownableColumns()` — they're not first-class shareable
|
|
15
|
+
* resources. Access to the underlying extension flows through the existing
|
|
16
|
+
* `extensions` table sharing model; install rows are personal preferences
|
|
17
|
+
* scoped to the installing user.
|
|
18
|
+
*/
|
|
19
|
+
import { table, text, integer, now } from "../../db/schema.js";
|
|
20
|
+
export const extensionSlots = table("tool_slots", {
|
|
21
|
+
id: text("id").primaryKey(),
|
|
22
|
+
extensionId: text("tool_id").notNull(),
|
|
23
|
+
slotId: text("slot_id").notNull(),
|
|
24
|
+
config: text("config"),
|
|
25
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
26
|
+
});
|
|
27
|
+
export const extensionSlotInstalls = table("tool_slot_installs", {
|
|
28
|
+
id: text("id").primaryKey(),
|
|
29
|
+
extensionId: text("tool_id").notNull(),
|
|
30
|
+
slotId: text("slot_id").notNull(),
|
|
31
|
+
ownerEmail: text("owner_email").notNull(),
|
|
32
|
+
orgId: text("org_id"),
|
|
33
|
+
position: integer("position").notNull().default(0),
|
|
34
|
+
config: text("config"),
|
|
35
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
36
|
+
updatedAt: text("updated_at").notNull().default(now()),
|
|
37
|
+
});
|
|
38
|
+
export const EXTENSION_SLOTS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_slots (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
tool_id TEXT NOT NULL,
|
|
41
|
+
slot_id TEXT NOT NULL,
|
|
42
|
+
config TEXT,
|
|
43
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
44
|
+
)`;
|
|
45
|
+
export const EXTENSION_SLOTS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_slots (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
tool_id TEXT NOT NULL,
|
|
48
|
+
slot_id TEXT NOT NULL,
|
|
49
|
+
config TEXT,
|
|
50
|
+
created_at TEXT NOT NULL DEFAULT now()
|
|
51
|
+
)`;
|
|
52
|
+
export const EXTENSION_SLOTS_BY_SLOT_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slots_by_slot_idx ON tool_slots (slot_id)`;
|
|
53
|
+
export const EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slots_by_tool_idx ON tool_slots (tool_id)`;
|
|
54
|
+
export const EXTENSION_SLOTS_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_slots_unique_idx ON tool_slots (tool_id, slot_id)`;
|
|
55
|
+
export const EXTENSION_SLOT_INSTALLS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_slot_installs (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
tool_id TEXT NOT NULL,
|
|
58
|
+
slot_id TEXT NOT NULL,
|
|
59
|
+
owner_email TEXT NOT NULL,
|
|
60
|
+
org_id TEXT,
|
|
61
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
config TEXT,
|
|
63
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
64
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
65
|
+
)`;
|
|
66
|
+
export const EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_slot_installs (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
tool_id TEXT NOT NULL,
|
|
69
|
+
slot_id TEXT NOT NULL,
|
|
70
|
+
owner_email TEXT NOT NULL,
|
|
71
|
+
org_id TEXT,
|
|
72
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
config TEXT,
|
|
74
|
+
created_at TEXT NOT NULL DEFAULT now(),
|
|
75
|
+
updated_at TEXT NOT NULL DEFAULT now()
|
|
76
|
+
)`;
|
|
77
|
+
export const EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slot_installs_by_user_slot_idx ON tool_slot_installs (owner_email, slot_id)`;
|
|
78
|
+
export const EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_slot_installs_unique_idx ON tool_slot_installs (owner_email, tool_id, slot_id)`;
|
|
79
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/slots/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE/D,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC,YAAY,EAAE;IAChD,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACtC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACjC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,oBAAoB,EAAE;IAC/D,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACtC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IACjC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE;IACzC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACtD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;;;;EAMxC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;;EAM3C,CAAC;AAEH,MAAM,CAAC,MAAM,iCAAiC,GAAG,2EAA2E,CAAC;AAC7H,MAAM,CAAC,MAAM,sCAAsC,GAAG,2EAA2E,CAAC;AAClI,MAAM,CAAC,MAAM,gCAAgC,GAAG,0FAA0F,CAAC;AAE3I,MAAM,CAAC,MAAM,kCAAkC,GAAG;;;;;;;;;;EAUhD,CAAC;AAEH,MAAM,CAAC,MAAM,qCAAqC,GAAG;;;;;;;;;;EAUnD,CAAC;AAEH,MAAM,CAAC,MAAM,8CAA8C,GAAG,6GAA6G,CAAC;AAC5K,MAAM,CAAC,MAAM,wCAAwC,GAAG,uHAAuH,CAAC","sourcesContent":["/**\n * Drizzle schema for the extension-points slot system.\n *\n * Two tables:\n *\n * - `extension_slots` — declarations: \"extension X can render in slot Y\".\n * Authored once per extension, regardless of installer.\n * Physical SQL name stays `tool_slots` (additive-only).\n * - `extension_slot_installs` — per-user installs: \"user U wants extension X in\n * slot Y at position N\". Always scoped by\n * owner_email. Physical SQL name stays\n * `tool_slot_installs`.\n *\n * Neither table spreads `ownableColumns()` — they're not first-class shareable\n * resources. Access to the underlying extension flows through the existing\n * `extensions` table sharing model; install rows are personal preferences\n * scoped to the installing user.\n */\n\nimport { table, text, integer, now } from \"../../db/schema.js\";\n\nexport const extensionSlots = table(\"tool_slots\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n slotId: text(\"slot_id\").notNull(),\n config: text(\"config\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n});\n\nexport const extensionSlotInstalls = table(\"tool_slot_installs\", {\n id: text(\"id\").primaryKey(),\n extensionId: text(\"tool_id\").notNull(),\n slotId: text(\"slot_id\").notNull(),\n ownerEmail: text(\"owner_email\").notNull(),\n orgId: text(\"org_id\"),\n position: integer(\"position\").notNull().default(0),\n config: text(\"config\"),\n createdAt: text(\"created_at\").notNull().default(now()),\n updatedAt: text(\"updated_at\").notNull().default(now()),\n});\n\nexport const EXTENSION_SLOTS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_slots (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_SLOTS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_slots (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_SLOTS_BY_SLOT_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slots_by_slot_idx ON tool_slots (slot_id)`;\nexport const EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slots_by_tool_idx ON tool_slots (tool_id)`;\nexport const EXTENSION_SLOTS_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_slots_unique_idx ON tool_slots (tool_id, slot_id)`;\n\nexport const EXTENSION_SLOT_INSTALLS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS tool_slot_installs (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n position INTEGER NOT NULL DEFAULT 0,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n)`;\n\nexport const EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG = `CREATE TABLE IF NOT EXISTS tool_slot_installs (\n id TEXT PRIMARY KEY,\n tool_id TEXT NOT NULL,\n slot_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n position INTEGER NOT NULL DEFAULT 0,\n config TEXT,\n created_at TEXT NOT NULL DEFAULT now(),\n updated_at TEXT NOT NULL DEFAULT now()\n)`;\n\nexport const EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL = `CREATE INDEX IF NOT EXISTS tool_slot_installs_by_user_slot_idx ON tool_slot_installs (owner_email, slot_id)`;\nexport const EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tool_slot_installs_unique_idx ON tool_slot_installs (owner_email, tool_id, slot_id)`;\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export declare function ensureSlotTables(): Promise<void>;
|
|
2
|
+
export interface ExtensionSlotRow {
|
|
3
|
+
id: string;
|
|
4
|
+
extensionId: string;
|
|
5
|
+
slotId: string;
|
|
6
|
+
config: string | null;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ExtensionSlotInstallRow {
|
|
10
|
+
id: string;
|
|
11
|
+
extensionId: string;
|
|
12
|
+
slotId: string;
|
|
13
|
+
ownerEmail: string;
|
|
14
|
+
orgId: string | null;
|
|
15
|
+
position: number;
|
|
16
|
+
config: string | null;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Declare that a extension can render in a slot. Caller must have editor access on
|
|
22
|
+
* the extension (only people who can edit a extension can change its slot targets).
|
|
23
|
+
*/
|
|
24
|
+
export declare function addExtensionSlotTarget(extensionId: string, slotId: string, config?: string): Promise<ExtensionSlotRow>;
|
|
25
|
+
export declare function removeExtensionSlotTarget(extensionId: string, slotId: string): Promise<boolean>;
|
|
26
|
+
export declare function listSlotsForExtension(extensionId: string): Promise<ExtensionSlotRow[]>;
|
|
27
|
+
/**
|
|
28
|
+
* List extensions that declare a slot — but only extensions the current user has access
|
|
29
|
+
* to. Joins through the extensions access filter.
|
|
30
|
+
*/
|
|
31
|
+
export declare function listExtensionsForSlot(slotId: string): Promise<Array<{
|
|
32
|
+
extensionId: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
icon: string | null;
|
|
36
|
+
config: string | null;
|
|
37
|
+
}>>;
|
|
38
|
+
/**
|
|
39
|
+
* Install a extension into a slot for the current user. Verifies the user has at
|
|
40
|
+
* least viewer access to the extension. Idempotent — re-installing returns the
|
|
41
|
+
* existing row.
|
|
42
|
+
*/
|
|
43
|
+
export declare function installExtensionSlot(extensionId: string, slotId: string, opts?: {
|
|
44
|
+
position?: number;
|
|
45
|
+
config?: string;
|
|
46
|
+
}): Promise<ExtensionSlotInstallRow>;
|
|
47
|
+
export declare function uninstallExtensionSlot(extensionId: string, slotId: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* List the current user's installs for a slot. Joins with `extensions` so the
|
|
50
|
+
* caller gets extension name/description/icon/updatedAt without a second query.
|
|
51
|
+
* Extensions the user has lost access to are silently skipped (lazy garbage
|
|
52
|
+
* collection).
|
|
53
|
+
*/
|
|
54
|
+
export declare function listSlotInstallsForUser(slotId: string): Promise<Array<{
|
|
55
|
+
installId: string;
|
|
56
|
+
extensionId: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
icon: string | null;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
position: number;
|
|
62
|
+
config: string | null;
|
|
63
|
+
}>>;
|
|
64
|
+
/** Delete every slot/install row referencing a extension. Called from deleteExtension. */
|
|
65
|
+
export declare function cascadeDeleteExtensionSlots(extensionId: string): Promise<void>;
|
|
66
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/extensions/slots/store.ts"],"names":[],"mappings":"AAiCA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBtD;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,CAAC,CAoC3B;AAED,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAS7B;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAClE,KAAK,CAAC;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC,CACH,CAmCA;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,uBAAuB,CAAC,CA+ClC;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAclB;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CACpE,KAAK,CAAC;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC,CACH,CA4CA;AAED,0FAA0F;AAC1F,wBAAsB,2BAA2B,CAC/C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
3
|
+
import { getDbExec, isPostgres } from "../../db/client.js";
|
|
4
|
+
import { createGetDb } from "../../db/create-get-db.js";
|
|
5
|
+
import { accessFilter, assertAccess } from "../../sharing/access.js";
|
|
6
|
+
import { getRequestUserEmail, getRequestOrgId, } from "../../server/request-context.js";
|
|
7
|
+
import { extensions, extensionShares } from "../schema.js";
|
|
8
|
+
import { extensionSlots, extensionSlotInstalls, EXTENSION_SLOTS_CREATE_SQL, EXTENSION_SLOTS_CREATE_SQL_PG, EXTENSION_SLOTS_BY_SLOT_INDEX_SQL, EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL, EXTENSION_SLOTS_UNIQUE_INDEX_SQL, EXTENSION_SLOT_INSTALLS_CREATE_SQL, EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG, EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL, EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL, } from "./schema.js";
|
|
9
|
+
const getDb = createGetDb({
|
|
10
|
+
extensions,
|
|
11
|
+
extensionShares,
|
|
12
|
+
extensionSlots,
|
|
13
|
+
extensionSlotInstalls,
|
|
14
|
+
});
|
|
15
|
+
let _initPromise;
|
|
16
|
+
export async function ensureSlotTables() {
|
|
17
|
+
if (!_initPromise) {
|
|
18
|
+
_initPromise = (async () => {
|
|
19
|
+
const client = getDbExec();
|
|
20
|
+
const pg = isPostgres();
|
|
21
|
+
await client.execute(pg ? EXTENSION_SLOTS_CREATE_SQL_PG : EXTENSION_SLOTS_CREATE_SQL);
|
|
22
|
+
await client.execute(EXTENSION_SLOTS_BY_SLOT_INDEX_SQL);
|
|
23
|
+
await client.execute(EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL);
|
|
24
|
+
await client.execute(EXTENSION_SLOTS_UNIQUE_INDEX_SQL);
|
|
25
|
+
await client.execute(pg
|
|
26
|
+
? EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG
|
|
27
|
+
: EXTENSION_SLOT_INSTALLS_CREATE_SQL);
|
|
28
|
+
await client.execute(EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL);
|
|
29
|
+
await client.execute(EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL);
|
|
30
|
+
})();
|
|
31
|
+
}
|
|
32
|
+
return _initPromise;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Declare that a extension can render in a slot. Caller must have editor access on
|
|
36
|
+
* the extension (only people who can edit a extension can change its slot targets).
|
|
37
|
+
*/
|
|
38
|
+
export async function addExtensionSlotTarget(extensionId, slotId, config) {
|
|
39
|
+
await ensureSlotTables();
|
|
40
|
+
await assertAccess("extension", extensionId, "editor");
|
|
41
|
+
const db = getDb();
|
|
42
|
+
const id = randomUUID();
|
|
43
|
+
const createdAt = new Date().toISOString();
|
|
44
|
+
const row = {
|
|
45
|
+
id,
|
|
46
|
+
extensionId,
|
|
47
|
+
slotId,
|
|
48
|
+
config: config ?? null,
|
|
49
|
+
createdAt,
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
await db.insert(extensionSlots).values(row);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// Unique index hit — already declared. Treat as idempotent: return existing.
|
|
56
|
+
if (String(err?.message ?? err)
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.includes("unique")) {
|
|
59
|
+
const existing = await db
|
|
60
|
+
.select()
|
|
61
|
+
.from(extensionSlots)
|
|
62
|
+
.where(and(eq(extensionSlots.extensionId, extensionId), eq(extensionSlots.slotId, slotId)));
|
|
63
|
+
if (existing[0])
|
|
64
|
+
return existing[0];
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
return row;
|
|
69
|
+
}
|
|
70
|
+
export async function removeExtensionSlotTarget(extensionId, slotId) {
|
|
71
|
+
await ensureSlotTables();
|
|
72
|
+
await assertAccess("extension", extensionId, "editor");
|
|
73
|
+
const db = getDb();
|
|
74
|
+
await db
|
|
75
|
+
.delete(extensionSlots)
|
|
76
|
+
.where(and(eq(extensionSlots.extensionId, extensionId), eq(extensionSlots.slotId, slotId)));
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
export async function listSlotsForExtension(extensionId) {
|
|
80
|
+
await ensureSlotTables();
|
|
81
|
+
await assertAccess("extension", extensionId, "viewer");
|
|
82
|
+
const db = getDb();
|
|
83
|
+
const rows = await db
|
|
84
|
+
.select()
|
|
85
|
+
.from(extensionSlots)
|
|
86
|
+
.where(eq(extensionSlots.extensionId, extensionId));
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* List extensions that declare a slot — but only extensions the current user has access
|
|
91
|
+
* to. Joins through the extensions access filter.
|
|
92
|
+
*/
|
|
93
|
+
export async function listExtensionsForSlot(slotId) {
|
|
94
|
+
await ensureSlotTables();
|
|
95
|
+
const db = getDb();
|
|
96
|
+
// Pull extensions the user can see, then narrow to ones declaring this slot.
|
|
97
|
+
const accessible = await db
|
|
98
|
+
.select({
|
|
99
|
+
id: extensions.id,
|
|
100
|
+
name: extensions.name,
|
|
101
|
+
description: extensions.description,
|
|
102
|
+
icon: extensions.icon,
|
|
103
|
+
})
|
|
104
|
+
.from(extensions)
|
|
105
|
+
.where(accessFilter(extensions, extensionShares));
|
|
106
|
+
if (accessible.length === 0)
|
|
107
|
+
return [];
|
|
108
|
+
const ids = accessible.map((t) => t.id);
|
|
109
|
+
const declarations = await db
|
|
110
|
+
.select()
|
|
111
|
+
.from(extensionSlots)
|
|
112
|
+
.where(and(eq(extensionSlots.slotId, slotId), inArray(extensionSlots.extensionId, ids)));
|
|
113
|
+
const byId = new Map(accessible.map((t) => [t.id, t]));
|
|
114
|
+
return declarations.map((d) => {
|
|
115
|
+
const t = byId.get(d.extensionId);
|
|
116
|
+
return {
|
|
117
|
+
extensionId: d.extensionId,
|
|
118
|
+
name: t.name,
|
|
119
|
+
description: t.description,
|
|
120
|
+
icon: t.icon,
|
|
121
|
+
config: d.config,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Install a extension into a slot for the current user. Verifies the user has at
|
|
127
|
+
* least viewer access to the extension. Idempotent — re-installing returns the
|
|
128
|
+
* existing row.
|
|
129
|
+
*/
|
|
130
|
+
export async function installExtensionSlot(extensionId, slotId, opts) {
|
|
131
|
+
await ensureSlotTables();
|
|
132
|
+
await assertAccess("extension", extensionId, "viewer");
|
|
133
|
+
const userEmail = requireUserEmail();
|
|
134
|
+
const orgId = getRequestOrgId();
|
|
135
|
+
const db = getDb();
|
|
136
|
+
const existing = await db
|
|
137
|
+
.select()
|
|
138
|
+
.from(extensionSlotInstalls)
|
|
139
|
+
.where(and(eq(extensionSlotInstalls.ownerEmail, userEmail), eq(extensionSlotInstalls.extensionId, extensionId), eq(extensionSlotInstalls.slotId, slotId)));
|
|
140
|
+
if (existing[0])
|
|
141
|
+
return existing[0];
|
|
142
|
+
const id = randomUUID();
|
|
143
|
+
const now = new Date().toISOString();
|
|
144
|
+
let position = opts?.position;
|
|
145
|
+
if (position === undefined) {
|
|
146
|
+
const rows = await db
|
|
147
|
+
.select({ pos: sql `MAX(${extensionSlotInstalls.position})` })
|
|
148
|
+
.from(extensionSlotInstalls)
|
|
149
|
+
.where(and(eq(extensionSlotInstalls.ownerEmail, userEmail), eq(extensionSlotInstalls.slotId, slotId)));
|
|
150
|
+
const maxPos = Number(rows[0]?.pos ?? -1);
|
|
151
|
+
position = Number.isFinite(maxPos) ? maxPos + 1 : 0;
|
|
152
|
+
}
|
|
153
|
+
const row = {
|
|
154
|
+
id,
|
|
155
|
+
extensionId,
|
|
156
|
+
slotId,
|
|
157
|
+
ownerEmail: userEmail,
|
|
158
|
+
orgId: orgId ?? null,
|
|
159
|
+
position,
|
|
160
|
+
config: opts?.config ?? null,
|
|
161
|
+
createdAt: now,
|
|
162
|
+
updatedAt: now,
|
|
163
|
+
};
|
|
164
|
+
await db.insert(extensionSlotInstalls).values(row);
|
|
165
|
+
return row;
|
|
166
|
+
}
|
|
167
|
+
export async function uninstallExtensionSlot(extensionId, slotId) {
|
|
168
|
+
await ensureSlotTables();
|
|
169
|
+
const userEmail = requireUserEmail();
|
|
170
|
+
const db = getDb();
|
|
171
|
+
await db
|
|
172
|
+
.delete(extensionSlotInstalls)
|
|
173
|
+
.where(and(eq(extensionSlotInstalls.ownerEmail, userEmail), eq(extensionSlotInstalls.extensionId, extensionId), eq(extensionSlotInstalls.slotId, slotId)));
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* List the current user's installs for a slot. Joins with `extensions` so the
|
|
178
|
+
* caller gets extension name/description/icon/updatedAt without a second query.
|
|
179
|
+
* Extensions the user has lost access to are silently skipped (lazy garbage
|
|
180
|
+
* collection).
|
|
181
|
+
*/
|
|
182
|
+
export async function listSlotInstallsForUser(slotId) {
|
|
183
|
+
await ensureSlotTables();
|
|
184
|
+
const userEmail = requireUserEmail();
|
|
185
|
+
const db = getDb();
|
|
186
|
+
const installs = await db
|
|
187
|
+
.select()
|
|
188
|
+
.from(extensionSlotInstalls)
|
|
189
|
+
.where(and(eq(extensionSlotInstalls.ownerEmail, userEmail), eq(extensionSlotInstalls.slotId, slotId)));
|
|
190
|
+
if (installs.length === 0)
|
|
191
|
+
return [];
|
|
192
|
+
const accessible = await db
|
|
193
|
+
.select({
|
|
194
|
+
id: extensions.id,
|
|
195
|
+
name: extensions.name,
|
|
196
|
+
description: extensions.description,
|
|
197
|
+
icon: extensions.icon,
|
|
198
|
+
updatedAt: extensions.updatedAt,
|
|
199
|
+
})
|
|
200
|
+
.from(extensions)
|
|
201
|
+
.where(accessFilter(extensions, extensionShares));
|
|
202
|
+
const byId = new Map(accessible.map((t) => [t.id, t]));
|
|
203
|
+
return installs
|
|
204
|
+
.filter((i) => byId.has(i.extensionId))
|
|
205
|
+
.sort((a, b) => a.position - b.position)
|
|
206
|
+
.map((i) => {
|
|
207
|
+
const t = byId.get(i.extensionId);
|
|
208
|
+
return {
|
|
209
|
+
installId: i.id,
|
|
210
|
+
extensionId: i.extensionId,
|
|
211
|
+
name: t.name,
|
|
212
|
+
description: t.description,
|
|
213
|
+
icon: t.icon,
|
|
214
|
+
updatedAt: t.updatedAt,
|
|
215
|
+
position: i.position,
|
|
216
|
+
config: i.config,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/** Delete every slot/install row referencing a extension. Called from deleteExtension. */
|
|
221
|
+
export async function cascadeDeleteExtensionSlots(extensionId) {
|
|
222
|
+
await ensureSlotTables();
|
|
223
|
+
const db = getDb();
|
|
224
|
+
await db
|
|
225
|
+
.delete(extensionSlots)
|
|
226
|
+
.where(eq(extensionSlots.extensionId, extensionId));
|
|
227
|
+
await db
|
|
228
|
+
.delete(extensionSlotInstalls)
|
|
229
|
+
.where(eq(extensionSlotInstalls.extensionId, extensionId));
|
|
230
|
+
}
|
|
231
|
+
function requireUserEmail() {
|
|
232
|
+
const email = getRequestUserEmail();
|
|
233
|
+
if (!email) {
|
|
234
|
+
throw new Error("Slot operations require an authenticated user.");
|
|
235
|
+
}
|
|
236
|
+
return email;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/extensions/slots/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { and, eq, inArray, sql } from \"drizzle-orm\";\nimport { getDbExec, isPostgres } from \"../../db/client.js\";\nimport { createGetDb } from \"../../db/create-get-db.js\";\nimport { accessFilter, assertAccess } from \"../../sharing/access.js\";\nimport {\n getRequestUserEmail,\n getRequestOrgId,\n} from \"../../server/request-context.js\";\nimport { extensions, extensionShares } from \"../schema.js\";\nimport {\n extensionSlots,\n extensionSlotInstalls,\n EXTENSION_SLOTS_CREATE_SQL,\n EXTENSION_SLOTS_CREATE_SQL_PG,\n EXTENSION_SLOTS_BY_SLOT_INDEX_SQL,\n EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL,\n EXTENSION_SLOTS_UNIQUE_INDEX_SQL,\n EXTENSION_SLOT_INSTALLS_CREATE_SQL,\n EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG,\n EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL,\n EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL,\n} from \"./schema.js\";\n\nconst getDb = createGetDb({\n extensions,\n extensionShares,\n extensionSlots,\n extensionSlotInstalls,\n});\n\nlet _initPromise: Promise<void> | undefined;\n\nexport async function ensureSlotTables(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const pg = isPostgres();\n await client.execute(\n pg ? EXTENSION_SLOTS_CREATE_SQL_PG : EXTENSION_SLOTS_CREATE_SQL,\n );\n await client.execute(EXTENSION_SLOTS_BY_SLOT_INDEX_SQL);\n await client.execute(EXTENSION_SLOTS_BY_EXTENSION_INDEX_SQL);\n await client.execute(EXTENSION_SLOTS_UNIQUE_INDEX_SQL);\n await client.execute(\n pg\n ? EXTENSION_SLOT_INSTALLS_CREATE_SQL_PG\n : EXTENSION_SLOT_INSTALLS_CREATE_SQL,\n );\n await client.execute(EXTENSION_SLOT_INSTALLS_BY_USER_SLOT_INDEX_SQL);\n await client.execute(EXTENSION_SLOT_INSTALLS_UNIQUE_INDEX_SQL);\n })();\n }\n return _initPromise;\n}\n\nexport interface ExtensionSlotRow {\n id: string;\n extensionId: string;\n slotId: string;\n config: string | null;\n createdAt: string;\n}\n\nexport interface ExtensionSlotInstallRow {\n id: string;\n extensionId: string;\n slotId: string;\n ownerEmail: string;\n orgId: string | null;\n position: number;\n config: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Declare that a extension can render in a slot. Caller must have editor access on\n * the extension (only people who can edit a extension can change its slot targets).\n */\nexport async function addExtensionSlotTarget(\n extensionId: string,\n slotId: string,\n config?: string,\n): Promise<ExtensionSlotRow> {\n await ensureSlotTables();\n await assertAccess(\"extension\", extensionId, \"editor\");\n const db = getDb();\n const id = randomUUID();\n const createdAt = new Date().toISOString();\n const row: ExtensionSlotRow = {\n id,\n extensionId,\n slotId,\n config: config ?? null,\n createdAt,\n };\n try {\n await db.insert(extensionSlots).values(row);\n } catch (err: any) {\n // Unique index hit — already declared. Treat as idempotent: return existing.\n if (\n String(err?.message ?? err)\n .toLowerCase()\n .includes(\"unique\")\n ) {\n const existing = await db\n .select()\n .from(extensionSlots)\n .where(\n and(\n eq(extensionSlots.extensionId, extensionId),\n eq(extensionSlots.slotId, slotId),\n ),\n );\n if (existing[0]) return existing[0] as ExtensionSlotRow;\n }\n throw err;\n }\n return row;\n}\n\nexport async function removeExtensionSlotTarget(\n extensionId: string,\n slotId: string,\n): Promise<boolean> {\n await ensureSlotTables();\n await assertAccess(\"extension\", extensionId, \"editor\");\n const db = getDb();\n await db\n .delete(extensionSlots)\n .where(\n and(\n eq(extensionSlots.extensionId, extensionId),\n eq(extensionSlots.slotId, slotId),\n ),\n );\n return true;\n}\n\nexport async function listSlotsForExtension(\n extensionId: string,\n): Promise<ExtensionSlotRow[]> {\n await ensureSlotTables();\n await assertAccess(\"extension\", extensionId, \"viewer\");\n const db = getDb();\n const rows = await db\n .select()\n .from(extensionSlots)\n .where(eq(extensionSlots.extensionId, extensionId));\n return rows as ExtensionSlotRow[];\n}\n\n/**\n * List extensions that declare a slot — but only extensions the current user has access\n * to. Joins through the extensions access filter.\n */\nexport async function listExtensionsForSlot(slotId: string): Promise<\n Array<{\n extensionId: string;\n name: string;\n description: string;\n icon: string | null;\n config: string | null;\n }>\n> {\n await ensureSlotTables();\n const db = getDb();\n // Pull extensions the user can see, then narrow to ones declaring this slot.\n const accessible = await db\n .select({\n id: extensions.id,\n name: extensions.name,\n description: extensions.description,\n icon: extensions.icon,\n })\n .from(extensions)\n .where(accessFilter(extensions, extensionShares));\n if (accessible.length === 0) return [];\n const ids = accessible.map((t: any) => t.id);\n const declarations = await db\n .select()\n .from(extensionSlots)\n .where(\n and(\n eq(extensionSlots.slotId, slotId),\n inArray(extensionSlots.extensionId, ids),\n ),\n );\n const byId = new Map(accessible.map((t: any) => [t.id, t]));\n return (declarations as ExtensionSlotRow[]).map((d) => {\n const t = byId.get(d.extensionId)!;\n return {\n extensionId: d.extensionId,\n name: t.name,\n description: t.description,\n icon: t.icon,\n config: d.config,\n };\n });\n}\n\n/**\n * Install a extension into a slot for the current user. Verifies the user has at\n * least viewer access to the extension. Idempotent — re-installing returns the\n * existing row.\n */\nexport async function installExtensionSlot(\n extensionId: string,\n slotId: string,\n opts?: { position?: number; config?: string },\n): Promise<ExtensionSlotInstallRow> {\n await ensureSlotTables();\n await assertAccess(\"extension\", extensionId, \"viewer\");\n const userEmail = requireUserEmail();\n const orgId = getRequestOrgId();\n const db = getDb();\n const existing = await db\n .select()\n .from(extensionSlotInstalls)\n .where(\n and(\n eq(extensionSlotInstalls.ownerEmail, userEmail),\n eq(extensionSlotInstalls.extensionId, extensionId),\n eq(extensionSlotInstalls.slotId, slotId),\n ),\n );\n if (existing[0]) return existing[0] as ExtensionSlotInstallRow;\n\n const id = randomUUID();\n const now = new Date().toISOString();\n let position = opts?.position;\n if (position === undefined) {\n const rows = await db\n .select({ pos: sql<number>`MAX(${extensionSlotInstalls.position})` })\n .from(extensionSlotInstalls)\n .where(\n and(\n eq(extensionSlotInstalls.ownerEmail, userEmail),\n eq(extensionSlotInstalls.slotId, slotId),\n ),\n );\n const maxPos = Number((rows[0] as any)?.pos ?? -1);\n position = Number.isFinite(maxPos) ? maxPos + 1 : 0;\n }\n const row: ExtensionSlotInstallRow = {\n id,\n extensionId,\n slotId,\n ownerEmail: userEmail,\n orgId: orgId ?? null,\n position,\n config: opts?.config ?? null,\n createdAt: now,\n updatedAt: now,\n };\n await db.insert(extensionSlotInstalls).values(row);\n return row;\n}\n\nexport async function uninstallExtensionSlot(\n extensionId: string,\n slotId: string,\n): Promise<boolean> {\n await ensureSlotTables();\n const userEmail = requireUserEmail();\n const db = getDb();\n await db\n .delete(extensionSlotInstalls)\n .where(\n and(\n eq(extensionSlotInstalls.ownerEmail, userEmail),\n eq(extensionSlotInstalls.extensionId, extensionId),\n eq(extensionSlotInstalls.slotId, slotId),\n ),\n );\n return true;\n}\n\n/**\n * List the current user's installs for a slot. Joins with `extensions` so the\n * caller gets extension name/description/icon/updatedAt without a second query.\n * Extensions the user has lost access to are silently skipped (lazy garbage\n * collection).\n */\nexport async function listSlotInstallsForUser(slotId: string): Promise<\n Array<{\n installId: string;\n extensionId: string;\n name: string;\n description: string;\n icon: string | null;\n updatedAt: string;\n position: number;\n config: string | null;\n }>\n> {\n await ensureSlotTables();\n const userEmail = requireUserEmail();\n const db = getDb();\n\n const installs = await db\n .select()\n .from(extensionSlotInstalls)\n .where(\n and(\n eq(extensionSlotInstalls.ownerEmail, userEmail),\n eq(extensionSlotInstalls.slotId, slotId),\n ),\n );\n if (installs.length === 0) return [];\n\n const accessible = await db\n .select({\n id: extensions.id,\n name: extensions.name,\n description: extensions.description,\n icon: extensions.icon,\n updatedAt: extensions.updatedAt,\n })\n .from(extensions)\n .where(accessFilter(extensions, extensionShares));\n const byId = new Map(accessible.map((t: any) => [t.id, t]));\n\n return (installs as ExtensionSlotInstallRow[])\n .filter((i) => byId.has(i.extensionId))\n .sort((a, b) => a.position - b.position)\n .map((i) => {\n const t = byId.get(i.extensionId)!;\n return {\n installId: i.id,\n extensionId: i.extensionId,\n name: t.name,\n description: t.description,\n icon: t.icon,\n updatedAt: t.updatedAt,\n position: i.position,\n config: i.config,\n };\n });\n}\n\n/** Delete every slot/install row referencing a extension. Called from deleteExtension. */\nexport async function cascadeDeleteExtensionSlots(\n extensionId: string,\n): Promise<void> {\n await ensureSlotTables();\n const db = getDb();\n await db\n .delete(extensionSlots)\n .where(eq(extensionSlots.extensionId, extensionId));\n await db\n .delete(extensionSlotInstalls)\n .where(eq(extensionSlotInstalls.extensionId, extensionId));\n}\n\nfunction requireUserEmail(): string {\n const email = getRequestUserEmail();\n if (!email) {\n throw new Error(\"Slot operations require an authenticated user.\");\n }\n return email;\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare function ensureExtensionsTables(): Promise<void>;
|
|
2
|
+
export declare function registerExtensionsShareable(): void;
|
|
3
|
+
export interface ExtensionRow {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
content: string;
|
|
8
|
+
icon: string | null;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
ownerEmail: string;
|
|
12
|
+
orgId: string | null;
|
|
13
|
+
visibility: "private" | "org" | "public";
|
|
14
|
+
}
|
|
15
|
+
export declare function listExtensions(): Promise<ExtensionRow[]>;
|
|
16
|
+
export declare function getExtension(id: string): Promise<ExtensionRow | null>;
|
|
17
|
+
export interface CreateExtensionData {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
icon?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function createExtension(data: CreateExtensionData): Promise<ExtensionRow>;
|
|
24
|
+
export interface UpdateExtensionData {
|
|
25
|
+
name?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
icon?: string;
|
|
28
|
+
visibility?: "private" | "org" | "public";
|
|
29
|
+
}
|
|
30
|
+
export declare function updateExtension(id: string, data: UpdateExtensionData): Promise<ExtensionRow | null>;
|
|
31
|
+
export interface UpdateExtensionContentOpts {
|
|
32
|
+
content?: string;
|
|
33
|
+
patches?: Array<{
|
|
34
|
+
find: string;
|
|
35
|
+
replace: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export declare function updateExtensionContent(id: string, opts: UpdateExtensionContentOpts): Promise<ExtensionRow | null>;
|
|
39
|
+
export declare function deleteExtension(id: string): Promise<boolean>;
|
|
40
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/extensions/store.ts"],"names":[],"mappings":"AAuCA,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmD5D;AA4DD,wBAAgB,2BAA2B,SAS1C;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;CAC1C;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAS9D;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAI3E;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,CAAC,CA2BvB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;CAC3C;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAc9B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA4B9B;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAelE"}
|