openworkgraph 0.2.12

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.
Files changed (178) hide show
  1. package/README.md +64 -0
  2. package/dist/api-image-agent.d.ts +32 -0
  3. package/dist/api-image-agent.js +132 -0
  4. package/dist/api-image-output.d.ts +8 -0
  5. package/dist/api-image-output.js +41 -0
  6. package/dist/api.d.ts +35 -0
  7. package/dist/api.js +153 -0
  8. package/dist/auth.d.ts +58 -0
  9. package/dist/auth.js +273 -0
  10. package/dist/backend/adapter.d.ts +35 -0
  11. package/dist/backend/adapter.js +439 -0
  12. package/dist/backend/capabilities.d.ts +23 -0
  13. package/dist/backend/capabilities.js +75 -0
  14. package/dist/backend/diagnostics.d.ts +9 -0
  15. package/dist/backend/diagnostics.js +80 -0
  16. package/dist/backend/index.d.ts +6 -0
  17. package/dist/backend/index.js +7 -0
  18. package/dist/backend/models.d.ts +15 -0
  19. package/dist/backend/models.js +43 -0
  20. package/dist/backend/paths.d.ts +6 -0
  21. package/dist/backend/paths.js +69 -0
  22. package/dist/backend/stdio.d.ts +66 -0
  23. package/dist/backend/stdio.js +172 -0
  24. package/dist/backend/types.d.ts +90 -0
  25. package/dist/backend/types.js +11 -0
  26. package/dist/backup-api.d.ts +10 -0
  27. package/dist/backup-api.js +40 -0
  28. package/dist/blob-store.d.ts +40 -0
  29. package/dist/blob-store.js +218 -0
  30. package/dist/cli-help.d.ts +1 -0
  31. package/dist/cli-help.js +63 -0
  32. package/dist/cli-output.d.ts +4 -0
  33. package/dist/cli-output.js +130 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +284 -0
  36. package/dist/client-pairing.d.ts +8 -0
  37. package/dist/client-pairing.js +30 -0
  38. package/dist/directories.d.ts +14 -0
  39. package/dist/directories.js +34 -0
  40. package/dist/errors.d.ts +12 -0
  41. package/dist/errors.js +7 -0
  42. package/dist/events.d.ts +22 -0
  43. package/dist/events.js +155 -0
  44. package/dist/execution-chains.d.ts +24 -0
  45. package/dist/execution-chains.js +231 -0
  46. package/dist/execution-settings.d.ts +10 -0
  47. package/dist/execution-settings.js +38 -0
  48. package/dist/graph-document-history.d.ts +43 -0
  49. package/dist/graph-document-history.js +275 -0
  50. package/dist/graph-files.d.ts +14 -0
  51. package/dist/graph-files.js +59 -0
  52. package/dist/graph-transfer.d.ts +53 -0
  53. package/dist/graph-transfer.js +501 -0
  54. package/dist/graphs.d.ts +68 -0
  55. package/dist/graphs.js +771 -0
  56. package/dist/history.d.ts +20 -0
  57. package/dist/history.js +61 -0
  58. package/dist/http.d.ts +2 -0
  59. package/dist/http.js +42 -0
  60. package/dist/image-credentials.d.ts +11 -0
  61. package/dist/image-credentials.js +130 -0
  62. package/dist/image-providers.d.ts +51 -0
  63. package/dist/image-providers.js +271 -0
  64. package/dist/input-materialization.d.ts +17 -0
  65. package/dist/input-materialization.js +95 -0
  66. package/dist/inputs.d.ts +74 -0
  67. package/dist/inputs.js +301 -0
  68. package/dist/local-control.d.ts +13 -0
  69. package/dist/local-control.js +37 -0
  70. package/dist/network.d.ts +11 -0
  71. package/dist/network.js +48 -0
  72. package/dist/openai-image-driver.d.ts +42 -0
  73. package/dist/openai-image-driver.js +368 -0
  74. package/dist/operations/backups.d.ts +40 -0
  75. package/dist/operations/backups.js +391 -0
  76. package/dist/operations/capacity.d.ts +7 -0
  77. package/dist/operations/capacity.js +35 -0
  78. package/dist/operations/cli-context.d.ts +7 -0
  79. package/dist/operations/cli-context.js +58 -0
  80. package/dist/operations/daemon.d.ts +36 -0
  81. package/dist/operations/daemon.js +159 -0
  82. package/dist/operations/files.d.ts +12 -0
  83. package/dist/operations/files.js +84 -0
  84. package/dist/operations/lifecycle.d.ts +28 -0
  85. package/dist/operations/lifecycle.js +155 -0
  86. package/dist/operations/local-admin.d.ts +6 -0
  87. package/dist/operations/local-admin.js +38 -0
  88. package/dist/operations/logs.d.ts +16 -0
  89. package/dist/operations/logs.js +37 -0
  90. package/dist/operations/plugins.d.ts +10 -0
  91. package/dist/operations/plugins.js +11 -0
  92. package/dist/operations/snapshot-worker.d.ts +1 -0
  93. package/dist/operations/snapshot-worker.js +18 -0
  94. package/dist/operations/snapshot.d.ts +1 -0
  95. package/dist/operations/snapshot.js +14 -0
  96. package/dist/operations/update.d.ts +26 -0
  97. package/dist/operations/update.js +216 -0
  98. package/dist/persistence/asset-business-schema.d.ts +2 -0
  99. package/dist/persistence/asset-business-schema.js +36 -0
  100. package/dist/persistence/database.d.ts +7 -0
  101. package/dist/persistence/database.js +91 -0
  102. package/dist/persistence/plugin-schema.d.ts +9 -0
  103. package/dist/persistence/plugin-schema.js +38 -0
  104. package/dist/persistence/repositories.d.ts +20 -0
  105. package/dist/persistence/repositories.js +106 -0
  106. package/dist/persistence/resource-schema.d.ts +2 -0
  107. package/dist/persistence/resource-schema.js +44 -0
  108. package/dist/persistence/schema.d.ts +6 -0
  109. package/dist/persistence/schema.js +236 -0
  110. package/dist/persistence/workflow-schema.d.ts +2 -0
  111. package/dist/persistence/workflow-schema.js +19 -0
  112. package/dist/plugin-api.d.ts +10 -0
  113. package/dist/plugin-api.js +40 -0
  114. package/dist/plugin-management.d.ts +36 -0
  115. package/dist/plugin-management.js +112 -0
  116. package/dist/plugins.d.ts +55 -0
  117. package/dist/plugins.js +239 -0
  118. package/dist/project-discovery.d.ts +3 -0
  119. package/dist/project-discovery.js +57 -0
  120. package/dist/project-files-api.d.ts +16 -0
  121. package/dist/project-files-api.js +243 -0
  122. package/dist/project-files.d.ts +97 -0
  123. package/dist/project-files.js +557 -0
  124. package/dist/project-lock.d.ts +4 -0
  125. package/dist/project-lock.js +35 -0
  126. package/dist/project-probe-worker.d.ts +1 -0
  127. package/dist/project-probe-worker.js +17 -0
  128. package/dist/project-probe.d.ts +22 -0
  129. package/dist/project-probe.js +67 -0
  130. package/dist/projects.d.ts +29 -0
  131. package/dist/projects.js +219 -0
  132. package/dist/publication.d.ts +31 -0
  133. package/dist/publication.js +642 -0
  134. package/dist/resource-api.d.ts +15 -0
  135. package/dist/resource-api.js +256 -0
  136. package/dist/resources-orphans.d.ts +41 -0
  137. package/dist/resources-orphans.js +235 -0
  138. package/dist/resources.d.ts +246 -0
  139. package/dist/resources.js +714 -0
  140. package/dist/run-api.d.ts +10 -0
  141. package/dist/run-api.js +308 -0
  142. package/dist/run-context.d.ts +54 -0
  143. package/dist/run-context.js +137 -0
  144. package/dist/runs.d.ts +82 -0
  145. package/dist/runs.js +506 -0
  146. package/dist/runtime-backend.d.ts +24 -0
  147. package/dist/runtime-backend.js +214 -0
  148. package/dist/runtime.d.ts +97 -0
  149. package/dist/runtime.js +553 -0
  150. package/dist/scheduler.d.ts +85 -0
  151. package/dist/scheduler.js +344 -0
  152. package/dist/service.d.ts +29 -0
  153. package/dist/service.js +515 -0
  154. package/dist/skills-api.d.ts +18 -0
  155. package/dist/skills-api.js +120 -0
  156. package/dist/storage-api.d.ts +21 -0
  157. package/dist/storage-api.js +92 -0
  158. package/dist/transfer-api.d.ts +10 -0
  159. package/dist/transfer-api.js +60 -0
  160. package/dist/vendor/protocol/contracts.typecheck.d.ts +1 -0
  161. package/dist/vendor/protocol/contracts.typecheck.js +35 -0
  162. package/dist/vendor/protocol/execution-chain.d.ts +6 -0
  163. package/dist/vendor/protocol/execution-chain.js +26 -0
  164. package/dist/vendor/protocol/graph-binary.d.ts +15 -0
  165. package/dist/vendor/protocol/graph-binary.js +121 -0
  166. package/dist/vendor/protocol/graph-title.d.ts +2 -0
  167. package/dist/vendor/protocol/graph-title.js +23 -0
  168. package/dist/vendor/protocol/index.d.ts +617 -0
  169. package/dist/vendor/protocol/index.js +20 -0
  170. package/dist/vendor/protocol/pairing.d.ts +29 -0
  171. package/dist/vendor/protocol/pairing.js +71 -0
  172. package/dist/vendor/protocol/preview.d.ts +2 -0
  173. package/dist/vendor/protocol/preview.js +9 -0
  174. package/dist/vendor/protocol/resource-mime.d.ts +2 -0
  175. package/dist/vendor/protocol/resource-mime.js +35 -0
  176. package/dist/video-thumbnail.d.ts +2 -0
  177. package/dist/video-thumbnail.js +34 -0
  178. package/package.json +9 -0
@@ -0,0 +1,216 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { lstat, realpath } from 'node:fs/promises';
3
+ import { join, relative, isAbsolute, sep } from 'node:path';
4
+ import { SERVICE_VERSION } from "../vendor/protocol/index.js";
5
+ import { readInstance, requestInstance, startBackground, waitUntilStopped } from './lifecycle.js';
6
+ const PACKAGE_NAME = 'openworkgraph';
7
+ /** Compare the running package against npm's actual global package location. */
8
+ export async function isGlobalNpmInstallation(cliPath, spawnProcess = spawn) {
9
+ const invocation = process.platform === 'win32'
10
+ ? { command: process.env.ComSpec ?? process.env.COMSPEC ?? 'cmd.exe', args: ['/d', '/s', '/c', 'npm root -g'] }
11
+ : { command: 'npm', args: ['root', '-g'] };
12
+ try {
13
+ const root = await new Promise((resolve, reject) => {
14
+ const child = spawnProcess(invocation.command, invocation.args, { windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 });
15
+ let output = '';
16
+ child.stdout?.on('data', chunk => { if (output.length < 8192)
17
+ output += chunk.toString('utf8'); });
18
+ child.once('error', reject);
19
+ child.once('close', code => code === 0 ? resolve(output.trim()) : reject(new Error('npm root failed')));
20
+ });
21
+ if (!root)
22
+ return false;
23
+ const installedPath = join(root, PACKAGE_NAME);
24
+ if ((await lstat(installedPath)).isSymbolicLink())
25
+ return false;
26
+ const packagePath = await realpath(installedPath);
27
+ const runningPath = await realpath(cliPath);
28
+ const inside = relative(packagePath, runningPath);
29
+ return inside !== '' && inside !== '..' && !inside.startsWith('..' + sep) && !isAbsolute(inside);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /** The updater must outlive the Runtime that it stops. */
36
+ export async function startWebUpdate(cliPath, spawnProcess = spawn, onExit) {
37
+ const child = spawnProcess(process.execPath, [cliPath, 'update', '--output-json'], {
38
+ detached: true, windowsHide: true, stdio: 'ignore',
39
+ });
40
+ await new Promise((resolve, reject) => {
41
+ child.once('error', reject);
42
+ child.once('spawn', resolve);
43
+ });
44
+ if (onExit)
45
+ child.once('close', onExit);
46
+ child.unref();
47
+ }
48
+ function parseSemVer(value) {
49
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
50
+ const prerelease = match?.[4]?.split('.');
51
+ if (!match || prerelease?.some(identifier => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith('0'))) {
52
+ throw new Error(`Invalid semantic version: ${value}`);
53
+ }
54
+ return { core: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], prerelease };
55
+ }
56
+ export function compareSemVer(left, right) {
57
+ const a = parseSemVer(left), b = parseSemVer(right);
58
+ for (let index = 0; index < a.core.length; index++) {
59
+ if (a.core[index] < b.core[index])
60
+ return -1;
61
+ if (a.core[index] > b.core[index])
62
+ return 1;
63
+ }
64
+ if (!a.prerelease && !b.prerelease)
65
+ return 0;
66
+ if (!a.prerelease)
67
+ return 1;
68
+ if (!b.prerelease)
69
+ return -1;
70
+ for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index++) {
71
+ const leftPart = a.prerelease[index], rightPart = b.prerelease[index];
72
+ if (leftPart === undefined)
73
+ return -1;
74
+ if (rightPart === undefined)
75
+ return 1;
76
+ if (leftPart === rightPart)
77
+ continue;
78
+ const leftNumeric = /^\d+$/.test(leftPart), rightNumeric = /^\d+$/.test(rightPart);
79
+ if (leftNumeric && rightNumeric)
80
+ return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1;
81
+ if (leftNumeric !== rightNumeric)
82
+ return leftNumeric ? -1 : 1;
83
+ return leftPart < rightPart ? -1 : 1;
84
+ }
85
+ return 0;
86
+ }
87
+ export function npmInstallInvocation(platform = process.platform, environment = process.env) {
88
+ if (platform === 'win32') {
89
+ // Recent Node versions reject direct .cmd execution with spawn EINVAL.
90
+ // Invoke npm through the Windows command interpreter explicitly instead.
91
+ return {
92
+ command: environment.ComSpec ?? environment.COMSPEC ?? 'cmd.exe',
93
+ args: ['/d', '/s', '/c', `npm install -g ${PACKAGE_NAME}`],
94
+ };
95
+ }
96
+ return { command: 'npm', args: ['install', '-g', PACKAGE_NAME] };
97
+ }
98
+ export function npmVersionInvocation(platform = process.platform, environment = process.env) {
99
+ if (platform === 'win32') {
100
+ return {
101
+ command: environment.ComSpec ?? environment.COMSPEC ?? 'cmd.exe',
102
+ args: ['/d', '/s', '/c', `npm view ${PACKAGE_NAME} version --json`],
103
+ };
104
+ }
105
+ return { command: 'npm', args: ['view', PACKAGE_NAME, 'version', '--json'] };
106
+ }
107
+ export async function latestAgentServiceVersion(spawnProcess = spawn) {
108
+ const invocation = npmVersionInvocation();
109
+ const child = spawnProcess(invocation.command, invocation.args, {
110
+ windowsHide: true,
111
+ stdio: ['ignore', 'pipe', 'pipe'],
112
+ });
113
+ let stdout = '', stderr = '';
114
+ child.stdout?.on('data', chunk => {
115
+ if (stdout.length < 64 * 1024)
116
+ stdout += chunk.toString('utf8');
117
+ });
118
+ child.stderr?.on('data', chunk => {
119
+ if (stderr.length < 64 * 1024)
120
+ stderr += chunk.toString('utf8');
121
+ });
122
+ await new Promise((resolve, reject) => {
123
+ child.once('error', reject);
124
+ child.once('close', (code, signal) => {
125
+ if (code === 0)
126
+ resolve();
127
+ else
128
+ reject(new Error(stderr.trim() || `npm version check failed (${signal ?? code ?? 'unknown'})`));
129
+ });
130
+ });
131
+ let response;
132
+ try {
133
+ response = JSON.parse(stdout);
134
+ }
135
+ catch {
136
+ throw new Error('npm returned an invalid package version response.');
137
+ }
138
+ // npm 12 wraps even a single scalar field in an array; older npm returns the string directly.
139
+ const version = Array.isArray(response) && response.length === 1 ? response[0] : response;
140
+ if (typeof version !== 'string')
141
+ throw new Error('npm returned an invalid package version response.');
142
+ parseSemVer(version);
143
+ return version;
144
+ }
145
+ export async function installGlobalAgentService(outputJson = false, spawnProcess = spawn) {
146
+ const invocation = npmInstallInvocation();
147
+ const child = spawnProcess(invocation.command, invocation.args, {
148
+ windowsHide: true,
149
+ stdio: outputJson ? ['ignore', 'ignore', 'pipe'] : 'inherit',
150
+ });
151
+ let stderr = '';
152
+ child.stderr?.on('data', chunk => {
153
+ if (stderr.length < 64 * 1024)
154
+ stderr += chunk.toString('utf8');
155
+ });
156
+ await new Promise((resolve, reject) => {
157
+ child.once('error', reject);
158
+ child.once('close', (code, signal) => {
159
+ if (code === 0)
160
+ resolve();
161
+ else
162
+ reject(new Error(stderr.trim() || `npm install failed (${signal ?? code ?? 'unknown'})`));
163
+ });
164
+ });
165
+ }
166
+ const defaultDependencies = {
167
+ latestAgentServiceVersion, readInstance, requestInstance, waitUntilStopped, installGlobalAgentService, startBackground,
168
+ };
169
+ export async function updateAgentService(dataDir, cliPath, interruptAndStop, outputJson, dependencies = defaultDependencies) {
170
+ const latestVersion = await dependencies.latestAgentServiceVersion();
171
+ const updateAvailable = compareSemVer(latestVersion, SERVICE_VERSION) > 0;
172
+ if (!updateAvailable) {
173
+ return {
174
+ packageName: PACKAGE_NAME, currentVersion: SERVICE_VERSION, latestVersion,
175
+ updateAvailable: false, updated: false, restarted: false,
176
+ };
177
+ }
178
+ let record;
179
+ let status;
180
+ if (dataDir) {
181
+ try {
182
+ record = await dependencies.readInstance(dataDir);
183
+ }
184
+ catch (error) {
185
+ if (error.code !== 'ENOENT')
186
+ throw error;
187
+ }
188
+ if (record)
189
+ try {
190
+ status = await dependencies.requestInstance(record, 'status');
191
+ }
192
+ catch (error) {
193
+ if (error.code !== 'ECONNREFUSED')
194
+ throw error;
195
+ }
196
+ }
197
+ let restartArgs;
198
+ if (record && status) {
199
+ if (!status.listenHost)
200
+ throw new Error('The older runtime did not report its listen address. Update it manually with npm install -g openworkgraph, then restart it with an explicit --host value. The runtime has not been stopped.');
201
+ const port = new URL(status.localEndpoint).port || '80';
202
+ if (!/^\d+$/.test(port) || Number(port) > 65535)
203
+ throw new Error('The running runtime reported an invalid listen port. The runtime has not been stopped.');
204
+ restartArgs = ['--data-dir', dataDir, '--host', status.listenHost, '--port', port];
205
+ await dependencies.requestInstance(record, interruptAndStop ? 'interrupt-and-stop' : 'stop');
206
+ await dependencies.waitUntilStopped(record);
207
+ }
208
+ await dependencies.installGlobalAgentService(outputJson);
209
+ const restarted = restartArgs ? await dependencies.startBackground(cliPath, restartArgs) : undefined;
210
+ return {
211
+ packageName: PACKAGE_NAME, currentVersion: SERVICE_VERSION, latestVersion,
212
+ updateAvailable: true, updated: true, restarted: Boolean(restartArgs),
213
+ ...(restarted && typeof restarted === 'object' ? restarted : {}),
214
+ };
215
+ }
216
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,2 @@
1
+ /** Additive schema 6 fragment. No legacy resource conversion or file migration. */
2
+ export declare const ASSET_BUSINESS_SCHEMA = "\nCREATE TABLE resource_uploads (\n id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id),\n name TEXT NOT NULL, mime TEXT NOT NULL, bytes INTEGER NOT NULL CHECK(bytes >= 0),\n expected_sha256 TEXT, received INTEGER NOT NULL DEFAULT 0 CHECK(received >= 0 AND received <= bytes),\n state TEXT NOT NULL DEFAULT 'uploading' CHECK(state IN ('uploading','finished')),\n sha256 TEXT REFERENCES blobs(sha256), created_at INTEGER NOT NULL,\n CHECK((state='finished' AND sha256 IS NOT NULL) OR (state='uploading' AND sha256 IS NULL))\n) STRICT;\nCREATE TABLE resource_upload_chunks (\n upload_id TEXT NOT NULL REFERENCES resource_uploads(id), offset INTEGER NOT NULL CHECK(offset >= 0),\n bytes INTEGER NOT NULL CHECK(bytes > 0), sha256 TEXT NOT NULL, path TEXT NOT NULL UNIQUE,\n PRIMARY KEY(upload_id,offset)\n) STRICT;\nCREATE TABLE resource_blob_files (sha256 TEXT PRIMARY KEY REFERENCES blobs(sha256), path TEXT NOT NULL UNIQUE) STRICT;\nCREATE TABLE resource_file_deletions (path TEXT PRIMARY KEY, created_at INTEGER NOT NULL, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;\nCREATE TABLE resource_read_leases (id TEXT PRIMARY KEY, sha256 TEXT NOT NULL REFERENCES blobs(sha256)) STRICT;\nCREATE TABLE resource_maintenance_leases (id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('backup','drain')), created_at INTEGER NOT NULL) STRICT;\nCREATE TABLE resource_representations (\n sha256 TEXT NOT NULL REFERENCES blobs(sha256), processor TEXT NOT NULL, version INTEGER NOT NULL CHECK(version > 0),\n state TEXT NOT NULL CHECK(state IN ('ready','unsupported','failed')), mime TEXT NOT NULL, text TEXT, reason TEXT,\n PRIMARY KEY(sha256,processor,version)\n) STRICT;\nCREATE TRIGGER resource_blob_file_immutable BEFORE UPDATE ON resource_blob_files BEGIN SELECT RAISE(ABORT,'immutable blob file'); END;\nCREATE TRIGGER resource_asset_ref_reset AFTER UPDATE OF asset_id ON asset_references BEGIN\n UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id);\nEND;\nCREATE TRIGGER resource_no_rebind_deleted_asset BEFORE UPDATE OF asset_id,asset_version ON asset_references\n WHEN (NEW.asset_id!=OLD.asset_id OR NEW.asset_version!=OLD.asset_version) AND (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL\n BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;\nCREATE TRIGGER resource_output_added AFTER INSERT ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;\nCREATE TRIGGER resource_output_removed AFTER DELETE ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;\nCREATE TRIGGER resource_output_changed AFTER UPDATE OF asset_id ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id); END;\n";
@@ -0,0 +1,36 @@
1
+ /** Additive schema 6 fragment. No legacy resource conversion or file migration. */
2
+ export const ASSET_BUSINESS_SCHEMA = `
3
+ CREATE TABLE resource_uploads (
4
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id),
5
+ name TEXT NOT NULL, mime TEXT NOT NULL, bytes INTEGER NOT NULL CHECK(bytes >= 0),
6
+ expected_sha256 TEXT, received INTEGER NOT NULL DEFAULT 0 CHECK(received >= 0 AND received <= bytes),
7
+ state TEXT NOT NULL DEFAULT 'uploading' CHECK(state IN ('uploading','finished')),
8
+ sha256 TEXT REFERENCES blobs(sha256), created_at INTEGER NOT NULL,
9
+ CHECK((state='finished' AND sha256 IS NOT NULL) OR (state='uploading' AND sha256 IS NULL))
10
+ ) STRICT;
11
+ CREATE TABLE resource_upload_chunks (
12
+ upload_id TEXT NOT NULL REFERENCES resource_uploads(id), offset INTEGER NOT NULL CHECK(offset >= 0),
13
+ bytes INTEGER NOT NULL CHECK(bytes > 0), sha256 TEXT NOT NULL, path TEXT NOT NULL UNIQUE,
14
+ PRIMARY KEY(upload_id,offset)
15
+ ) STRICT;
16
+ CREATE TABLE resource_blob_files (sha256 TEXT PRIMARY KEY REFERENCES blobs(sha256), path TEXT NOT NULL UNIQUE) STRICT;
17
+ CREATE TABLE resource_file_deletions (path TEXT PRIMARY KEY, created_at INTEGER NOT NULL, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;
18
+ CREATE TABLE resource_read_leases (id TEXT PRIMARY KEY, sha256 TEXT NOT NULL REFERENCES blobs(sha256)) STRICT;
19
+ CREATE TABLE resource_maintenance_leases (id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('backup','drain')), created_at INTEGER NOT NULL) STRICT;
20
+ CREATE TABLE resource_representations (
21
+ sha256 TEXT NOT NULL REFERENCES blobs(sha256), processor TEXT NOT NULL, version INTEGER NOT NULL CHECK(version > 0),
22
+ state TEXT NOT NULL CHECK(state IN ('ready','unsupported','failed')), mime TEXT NOT NULL, text TEXT, reason TEXT,
23
+ PRIMARY KEY(sha256,processor,version)
24
+ ) STRICT;
25
+ CREATE TRIGGER resource_blob_file_immutable BEFORE UPDATE ON resource_blob_files BEGIN SELECT RAISE(ABORT,'immutable blob file'); END;
26
+ CREATE TRIGGER resource_asset_ref_reset AFTER UPDATE OF asset_id ON asset_references BEGIN
27
+ UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id);
28
+ END;
29
+ CREATE TRIGGER resource_no_rebind_deleted_asset BEFORE UPDATE OF asset_id,asset_version ON asset_references
30
+ WHEN (NEW.asset_id!=OLD.asset_id OR NEW.asset_version!=OLD.asset_version) AND (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL
31
+ BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
32
+ CREATE TRIGGER resource_output_added AFTER INSERT ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
33
+ CREATE TRIGGER resource_output_removed AFTER DELETE ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
34
+ CREATE TRIGGER resource_output_changed AFTER UPDATE OF asset_id ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id); END;
35
+ `;
36
+ //# sourceMappingURL=asset-business-schema.js.map
@@ -0,0 +1,7 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import type { Migration } from './schema.js';
3
+ /** Nest safely in auth/business transactions without committing the caller's work. */
4
+ export declare function atomic<T>(db: DatabaseSync, work: () => T): T;
5
+ export declare function transaction<T>(db: DatabaseSync, work: () => T): T;
6
+ export declare function migrate(db: DatabaseSync, migrations?: readonly Migration[]): void;
7
+ export declare function openDatabase(path: string, migrations?: readonly Migration[]): DatabaseSync;
@@ -0,0 +1,91 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { ServiceError } from '../errors.js';
3
+ import { MIGRATIONS, SCHEMA_VERSION } from './schema.js';
4
+ import { randomUUID } from 'node:crypto';
5
+ /** Nest safely in auth/business transactions without committing the caller's work. */
6
+ export function atomic(db, work) {
7
+ if (!db.isTransaction)
8
+ return transaction(db, work);
9
+ const name = 'op_' + randomUUID().replaceAll('-', '');
10
+ db.exec(`SAVEPOINT ${name}`);
11
+ try {
12
+ const result = work();
13
+ if (result && typeof result.then === 'function')
14
+ throw new Error('Transactions must be synchronous');
15
+ db.exec(`RELEASE ${name}`);
16
+ return result;
17
+ }
18
+ catch (error) {
19
+ db.exec(`ROLLBACK TO ${name}; RELEASE ${name}`);
20
+ throw error;
21
+ }
22
+ }
23
+ export function transaction(db, work) {
24
+ db.exec('BEGIN IMMEDIATE');
25
+ try {
26
+ const result = work();
27
+ if (result && typeof result.then === 'function')
28
+ throw new Error('Transactions must be synchronous');
29
+ db.exec('COMMIT');
30
+ return result;
31
+ }
32
+ catch (error) {
33
+ db.exec('ROLLBACK');
34
+ throw error;
35
+ }
36
+ }
37
+ export function migrate(db, migrations = MIGRATIONS) {
38
+ transaction(db, () => {
39
+ const version = Number(db.prepare('PRAGMA user_version').get()['user_version']);
40
+ if (version > SCHEMA_VERSION || version < 0)
41
+ throw new ServiceError('SCHEMA_UNSUPPORTED', `Unsupported database schema ${version}; expected 0..${SCHEMA_VERSION}`);
42
+ if (version === 0 && db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").get())
43
+ throw new ServiceError('SCHEMA_UNSUPPORTED', 'Unversioned nonempty database; refusing to overwrite');
44
+ if (version > 0) {
45
+ for (const table of ['identity', 'settings', 'sessions', 'pairing_codes']) {
46
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(table))
47
+ throw new ServiceError('SCHEMA_UNSUPPORTED', `Missing schema table ${table}`);
48
+ }
49
+ }
50
+ for (let next = version + 1; next <= SCHEMA_VERSION; next++) {
51
+ const migration = migrations.find(item => item.version === next);
52
+ if (!migration)
53
+ throw new ServiceError('SCHEMA_UNSUPPORTED', `Missing migration ${next}`);
54
+ try {
55
+ db.exec(migration.sql);
56
+ db.exec(`PRAGMA user_version = ${next}`);
57
+ }
58
+ catch (cause) {
59
+ throw new ServiceError('MIGRATION_FAILED', `Migration ${next} failed; database unchanged`, { cause });
60
+ }
61
+ }
62
+ for (const table of ['identity', 'settings', 'sessions', 'pairing_codes', 'projects', 'graphs', 'nodes', 'node_versions', 'edges', 'blobs', 'assets', 'asset_versions', 'runs', 'snapshots', 'outputs', 'asset_references', 'events', 'interactions', 'occupancy', 'idempotency', 'plugin_contracts', 'backups', 'trusted_origins', 'canvas_resources', 'canvas_resource_versions', 'canvas_resource_references', 'canvas_outputs']) {
63
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(table))
64
+ throw new ServiceError('SCHEMA_UNSUPPORTED', `Missing schema table ${table}`);
65
+ }
66
+ for (const table of ['execution_input_snapshots', 'execution_outputs', 'project_file_bindings', 'project_file_observations', 'project_file_outputs', 'node_resource_history', 'graph_groups', 'group_members', 'run_runtime', 'run_process_records', 'run_notifications', 'generation_candidates', 'publication_manifests', 'resource_uploads', 'resource_upload_chunks', 'resource_blob_files', 'resource_file_deletions', 'resource_read_leases', 'resource_maintenance_leases']) {
67
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(table))
68
+ throw new ServiceError('SCHEMA_UNSUPPORTED', `Missing schema table ${table}`);
69
+ }
70
+ if (db.prepare('PRAGMA foreign_key_check').get())
71
+ throw new ServiceError('SCHEMA_UNSUPPORTED', 'Database has foreign key violations');
72
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='node_version_schemas'").get())
73
+ throw new ServiceError('SCHEMA_UNSUPPORTED', 'Missing node version schema history');
74
+ for (const table of ['image_providers', 'image_provider_models', 'image_provider_config_versions', 'image_provider_revoked_credentials'])
75
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(table))
76
+ throw new ServiceError('SCHEMA_UNSUPPORTED', 'Missing image provider directory');
77
+ });
78
+ }
79
+ export function openDatabase(path, migrations = MIGRATIONS) {
80
+ const db = new DatabaseSync(path, { enableForeignKeyConstraints: true, timeout: 5000, allowExtension: false });
81
+ try {
82
+ migrate(db, migrations);
83
+ db.exec('PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;');
84
+ return db;
85
+ }
86
+ catch (error) {
87
+ db.close();
88
+ throw error;
89
+ }
90
+ }
91
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1,9 @@
1
+ /** Additive schema7, wired by the main migration owner.
2
+ * Backfill is valid for the pre-plugin-management lineage: no node schema
3
+ * migration API could run before this table existed (it fails closed). Existing
4
+ * imported nodes may use schema >1, but all their retained versions use that
5
+ * same schema. A database with out-of-band historical schema mutations needs
6
+ * explicit recovery metadata; this migration cannot infer those mutations.
7
+ * Ambiguous preexisting read_only rows stay original, never auto-unlocked.
8
+ */
9
+ export declare const PLUGIN_SCHEMA = "\nALTER TABLE nodes ADD COLUMN read_only_reason TEXT NOT NULL DEFAULT 'none'\n CHECK(read_only_reason IN ('none','missing_plugin','original'));\nUPDATE nodes SET read_only_reason='original' WHERE read_only=1;\nCREATE TABLE node_version_schemas (\n node_id TEXT NOT NULL,\n version INTEGER NOT NULL CHECK(version > 0),\n schema_version INTEGER NOT NULL CHECK(schema_version > 0),\n PRIMARY KEY(node_id,version),\n FOREIGN KEY(node_id,version) REFERENCES node_versions(node_id,version) ON DELETE CASCADE\n) STRICT;\nINSERT INTO node_version_schemas(node_id,version,schema_version)\n SELECT v.node_id,v.version,n.schema_version FROM node_versions v JOIN nodes n ON n.id=v.node_id;\nCREATE TRIGGER node_version_schema_capture AFTER INSERT ON node_versions BEGIN\n INSERT INTO node_version_schemas(node_id,version,schema_version)\n SELECT NEW.node_id,NEW.version,schema_version FROM nodes WHERE id=NEW.node_id;\nEND;\nCREATE TRIGGER node_version_schema_immutable BEFORE UPDATE ON node_version_schemas BEGIN\n SELECT RAISE(ABORT,'immutable node version schema');\nEND;\nCREATE TRIGGER node_version_schema_delete_guard BEFORE DELETE ON node_version_schemas\nWHEN EXISTS(SELECT 1 FROM node_versions WHERE node_id=OLD.node_id AND version=OLD.version) BEGIN\n SELECT RAISE(ABORT,'retained node version requires schema history');\nEND;\nCREATE TRIGGER node_current_schema_guard BEFORE UPDATE OF schema_version,current_version ON nodes\nWHEN EXISTS(SELECT 1 FROM node_version_schemas s WHERE s.node_id=NEW.id AND s.version=NEW.current_version AND s.schema_version!=NEW.schema_version) BEGIN\n SELECT RAISE(ABORT,'current node schema does not match version history');\nEND;\n";
@@ -0,0 +1,38 @@
1
+ /** Additive schema7, wired by the main migration owner.
2
+ * Backfill is valid for the pre-plugin-management lineage: no node schema
3
+ * migration API could run before this table existed (it fails closed). Existing
4
+ * imported nodes may use schema >1, but all their retained versions use that
5
+ * same schema. A database with out-of-band historical schema mutations needs
6
+ * explicit recovery metadata; this migration cannot infer those mutations.
7
+ * Ambiguous preexisting read_only rows stay original, never auto-unlocked.
8
+ */
9
+ export const PLUGIN_SCHEMA = `
10
+ ALTER TABLE nodes ADD COLUMN read_only_reason TEXT NOT NULL DEFAULT 'none'
11
+ CHECK(read_only_reason IN ('none','missing_plugin','original'));
12
+ UPDATE nodes SET read_only_reason='original' WHERE read_only=1;
13
+ CREATE TABLE node_version_schemas (
14
+ node_id TEXT NOT NULL,
15
+ version INTEGER NOT NULL CHECK(version > 0),
16
+ schema_version INTEGER NOT NULL CHECK(schema_version > 0),
17
+ PRIMARY KEY(node_id,version),
18
+ FOREIGN KEY(node_id,version) REFERENCES node_versions(node_id,version) ON DELETE CASCADE
19
+ ) STRICT;
20
+ INSERT INTO node_version_schemas(node_id,version,schema_version)
21
+ SELECT v.node_id,v.version,n.schema_version FROM node_versions v JOIN nodes n ON n.id=v.node_id;
22
+ CREATE TRIGGER node_version_schema_capture AFTER INSERT ON node_versions BEGIN
23
+ INSERT INTO node_version_schemas(node_id,version,schema_version)
24
+ SELECT NEW.node_id,NEW.version,schema_version FROM nodes WHERE id=NEW.node_id;
25
+ END;
26
+ CREATE TRIGGER node_version_schema_immutable BEFORE UPDATE ON node_version_schemas BEGIN
27
+ SELECT RAISE(ABORT,'immutable node version schema');
28
+ END;
29
+ CREATE TRIGGER node_version_schema_delete_guard BEFORE DELETE ON node_version_schemas
30
+ WHEN EXISTS(SELECT 1 FROM node_versions WHERE node_id=OLD.node_id AND version=OLD.version) BEGIN
31
+ SELECT RAISE(ABORT,'retained node version requires schema history');
32
+ END;
33
+ CREATE TRIGGER node_current_schema_guard BEFORE UPDATE OF schema_version,current_version ON nodes
34
+ WHEN EXISTS(SELECT 1 FROM node_version_schemas s WHERE s.node_id=NEW.id AND s.version=NEW.current_version AND s.schema_version!=NEW.schema_version) BEGIN
35
+ SELECT RAISE(ABORT,'current node schema does not match version history');
36
+ END;
37
+ `;
38
+ //# sourceMappingURL=plugin-schema.js.map
@@ -0,0 +1,20 @@
1
+ import type { DatabaseSync, SQLInputValue } from 'node:sqlite';
2
+ import type { Json, ServiceEvent } from "../vendor/protocol/index.js";
3
+ export declare function canonicalJson(value: Json): string;
4
+ /** Same digest as canonicalJson, without joining an entire resource bundle. */
5
+ export declare function canonicalJsonHash(value: Json): string;
6
+ export declare class Repositories {
7
+ readonly db: DatabaseSync;
8
+ constructor(db: DatabaseSync);
9
+ identity(): string;
10
+ /** All business effects and the replay response commit in one short transaction. */
11
+ replay(scope: string, key: string, body: Json): Json | undefined;
12
+ idempotent(scope: string, key: string, body: Json, work: () => Json): Json;
13
+ setting(key: string): Json | undefined;
14
+ /** Internal persistence only; never expose this generic interface as an HTTP route. */
15
+ insert(table: StorageTable, values: Record<string, SQLInputValue>): void;
16
+ list(table: StorageTable, limit?: number): Record<string, unknown>[];
17
+ appendEvent(event: Omit<ServiceEvent, 'cursor' | 'serviceId'>): string;
18
+ }
19
+ export declare const STORAGE_TABLES: readonly ['sessions', 'pairing_codes', 'projects', 'graphs', 'nodes', 'node_versions', 'edges', 'blobs', 'assets', 'asset_versions', 'runs', 'run_notifications', 'snapshots', 'outputs', 'asset_references', 'events', 'interactions', 'occupancy', 'idempotency', 'plugin_contracts', 'backups', 'canvas_resources', 'canvas_resource_versions', 'canvas_resource_references', 'canvas_outputs', 'project_file_bindings', 'project_file_observations', 'project_file_outputs'];
20
+ export type StorageTable = typeof STORAGE_TABLES[number];
@@ -0,0 +1,106 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { ServiceError } from '../errors.js';
3
+ import { atomic, transaction } from './database.js';
4
+ export function canonicalJson(value) {
5
+ if (value === null || typeof value !== 'object') {
6
+ if (typeof value === 'number' && !Number.isFinite(value))
7
+ throw new ServiceError('INVALID_REQUEST', 'Non-finite JSON number');
8
+ const encoded = JSON.stringify(value);
9
+ if (encoded === undefined)
10
+ throw new ServiceError('INVALID_REQUEST', 'Invalid JSON value');
11
+ return encoded;
12
+ }
13
+ if (Array.isArray(value))
14
+ return '[' + value.map(canonicalJson).join(',') + ']';
15
+ return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonicalJson(value[key])).join(',') + '}';
16
+ }
17
+ /** Same digest as canonicalJson, without joining an entire resource bundle. */
18
+ export function canonicalJsonHash(value) {
19
+ const hash = createHash('sha256');
20
+ function visit(item) {
21
+ if (item === null || typeof item !== 'object') {
22
+ hash.update(canonicalJson(item));
23
+ return;
24
+ }
25
+ const array = Array.isArray(item);
26
+ hash.update(array ? '[' : '{');
27
+ const keys = array ? item.map((_, index) => String(index)) : Object.keys(item).sort();
28
+ keys.forEach((key, index) => {
29
+ if (index)
30
+ hash.update(',');
31
+ if (!array)
32
+ hash.update(JSON.stringify(key) + ':');
33
+ visit(item[key]);
34
+ });
35
+ hash.update(array ? ']' : '}');
36
+ }
37
+ visit(value);
38
+ return hash.digest('hex');
39
+ }
40
+ export class Repositories {
41
+ db;
42
+ constructor(db) { this.db = db; }
43
+ identity() {
44
+ return transaction(this.db, () => {
45
+ this.db.prepare('INSERT OR IGNORE INTO identity(singleton,service_id,created_at) VALUES(1,?,?)').run(randomUUID(), new Date().toISOString());
46
+ return String(this.db.prepare('SELECT service_id FROM identity WHERE singleton=1').get()['service_id']);
47
+ });
48
+ }
49
+ /** All business effects and the replay response commit in one short transaction. */
50
+ replay(scope, key, body) {
51
+ if (!scope || !key || key.length > 256)
52
+ throw new ServiceError('INVALID_REQUEST', 'Invalid idempotency scope or key');
53
+ const prior = this.db.prepare('SELECT request_hash,response,invalidated FROM idempotency WHERE scope=? AND key=?').get(scope, key);
54
+ if (!prior)
55
+ return undefined;
56
+ if (prior['request_hash'] !== createHash('sha256').update(canonicalJson(body)).digest('hex'))
57
+ throw new ServiceError('IDEMPOTENCY_CONFLICT', 'Idempotency key already used with a different request body');
58
+ if (prior['invalidated'])
59
+ throw new ServiceError('NOT_FOUND', 'The graph associated with this request was permanently deleted');
60
+ return JSON.parse(String(prior['response']));
61
+ }
62
+ idempotent(scope, key, body, work) {
63
+ if (!scope || !key || key.length > 256)
64
+ throw new ServiceError('INVALID_REQUEST', 'Invalid idempotency scope or key');
65
+ const hash = createHash('sha256').update(canonicalJson(body)).digest('hex');
66
+ return atomic(this.db, () => {
67
+ const prior = this.db.prepare('SELECT request_hash,response,invalidated FROM idempotency WHERE scope=? AND key=?').get(scope, key);
68
+ if (prior) {
69
+ if (prior['request_hash'] !== hash)
70
+ throw new ServiceError('IDEMPOTENCY_CONFLICT', 'Idempotency key already used with a different request body');
71
+ if (prior['invalidated'])
72
+ throw new ServiceError('NOT_FOUND', 'The graph associated with this request was permanently deleted');
73
+ return JSON.parse(String(prior['response']));
74
+ }
75
+ const result = work();
76
+ if (result && typeof result.then === 'function')
77
+ throw new Error('Idempotent work must be synchronous');
78
+ this.db.prepare('INSERT INTO idempotency(scope,key,request_hash,response) VALUES(?,?,?,?)').run(scope, key, hash, canonicalJson(result));
79
+ return result;
80
+ });
81
+ }
82
+ setting(key) {
83
+ const row = this.db.prepare('SELECT value FROM settings WHERE key=?').get(key);
84
+ return row ? JSON.parse(String(row['value'])) : undefined;
85
+ }
86
+ /** Internal persistence only; never expose this generic interface as an HTTP route. */
87
+ insert(table, values) {
88
+ const columns = Object.keys(values);
89
+ if (!STORAGE_TABLES.includes(table) || !columns.length || columns.some(key => !/^[a-z_][a-z0-9_]*$/.test(key)))
90
+ throw new Error('Invalid storage identifier');
91
+ this.db.prepare(`INSERT INTO ${table} (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')})`).run(...columns.map(key => values[key]));
92
+ }
93
+ list(table, limit = 100) {
94
+ if (!STORAGE_TABLES.includes(table) || !Number.isSafeInteger(limit) || limit < 1 || limit > 1000)
95
+ throw new Error('Invalid storage query');
96
+ return this.db.prepare(`SELECT * FROM ${table} LIMIT ?`).all(limit);
97
+ }
98
+ appendEvent(event) {
99
+ if (!this.db.isTransaction)
100
+ throw new Error('Events must be persisted inside the business transaction');
101
+ const result = this.db.prepare('INSERT INTO events(id,type,project_id,graph_id,entity_id,revision,occurred_at,payload) VALUES(?,?,?,?,?,?,?,?)').run(event.eventId, event.type, event.projectId, event.graphId, event.entityId, event.revision, event.occurredAt, canonicalJson(event.payload));
102
+ return String(result.lastInsertRowid);
103
+ }
104
+ }
105
+ export const STORAGE_TABLES = ['sessions', 'pairing_codes', 'projects', 'graphs', 'nodes', 'node_versions', 'edges', 'blobs', 'assets', 'asset_versions', 'runs', 'run_notifications', 'snapshots', 'outputs', 'asset_references', 'events', 'interactions', 'occupancy', 'idempotency', 'plugin_contracts', 'backups', 'canvas_resources', 'canvas_resource_versions', 'canvas_resource_references', 'canvas_outputs', 'project_file_bindings', 'project_file_observations', 'project_file_outputs'];
106
+ //# sourceMappingURL=repositories.js.map
@@ -0,0 +1,2 @@
1
+ /** Additive only: legacy asset references, outputs and snapshot JSON are preserved. */
2
+ export declare const RESOURCE_SCHEMA = "\nALTER TABLE assets ADD COLUMN name TEXT NOT NULL DEFAULT '';\nALTER TABLE assets ADD COLUMN current_version INTEGER NOT NULL DEFAULT 0 CHECK(current_version >= 0);\nUPDATE assets SET current_version=COALESCE((SELECT MAX(version) FROM asset_versions WHERE asset_id=assets.id),0);\nCREATE INDEX assets_project_name ON assets(project_id,name);\nCREATE UNIQUE INDEX runs_id_graph ON runs(id,graph_id);\nCREATE TABLE canvas_resources (\n id TEXT PRIMARY KEY, project_id TEXT NOT NULL, graph_id TEXT NOT NULL, name TEXT NOT NULL,\n current_version INTEGER NOT NULL CHECK(current_version > 0), UNIQUE(id,graph_id),\n FOREIGN KEY(graph_id,project_id) REFERENCES graphs(id,project_id),\n FOREIGN KEY(id,current_version) REFERENCES canvas_resource_versions(resource_id,version) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE TABLE canvas_resource_versions (\n resource_id TEXT NOT NULL REFERENCES canvas_resources(id) DEFERRABLE INITIALLY DEFERRED,\n version INTEGER NOT NULL CHECK(version > 0), sha256 TEXT NOT NULL REFERENCES blobs(sha256), mime TEXT NOT NULL,\n representation_version INTEGER, PRIMARY KEY(resource_id,version)\n) STRICT;\nCREATE TRIGGER canvas_version_immutable BEFORE UPDATE ON canvas_resource_versions BEGIN SELECT RAISE(ABORT,'immutable canvas resource version'); END;\nCREATE TRIGGER canvas_binding_immutable BEFORE UPDATE OF project_id,graph_id ON canvas_resources WHEN NEW.project_id != OLD.project_id OR NEW.graph_id != OLD.graph_id BEGIN SELECT RAISE(ABORT,'canvas resource binding immutable'); END;\nCREATE TABLE canvas_resource_references (\n id TEXT PRIMARY KEY, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL, graph_id TEXT NOT NULL,\n owner_kind TEXT NOT NULL CHECK(owner_kind IN ('graph','node','snapshot','history')),\n node_id TEXT, run_id TEXT,\n CHECK((owner_kind='graph' AND node_id IS NULL AND run_id IS NULL) OR (owner_kind='node' AND node_id IS NOT NULL AND run_id IS NULL) OR (owner_kind IN ('snapshot','history') AND node_id IS NULL AND run_id IS NOT NULL)),\n FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),\n FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id),\n FOREIGN KEY(node_id,graph_id) REFERENCES nodes(id,graph_id),\n FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)\n) STRICT;\nCREATE INDEX canvas_refs_resource ON canvas_resource_references(resource_id,resource_version);\nCREATE TRIGGER frozen_resource_reference_immutable BEFORE UPDATE ON canvas_resource_references WHEN OLD.owner_kind IN ('snapshot','history') BEGIN SELECT RAISE(ABORT,'frozen resource reference'); END;\nCREATE TABLE canvas_outputs (\n run_id TEXT NOT NULL, graph_id TEXT NOT NULL, output_key TEXT NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,\n PRIMARY KEY(run_id,output_key), FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),\n FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id), FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)\n) STRICT;\nCREATE TRIGGER no_new_canvas_asset_link BEFORE INSERT ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'canvas nodes must use independent canvas resources'); END;\nCREATE TRIGGER no_rebind_canvas_asset_link BEFORE UPDATE OF asset_id,asset_version,node_id ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'legacy canvas asset references cannot be rebound'); END;\nCREATE TRIGGER no_new_deleted_asset_reference BEFORE INSERT ON asset_references WHEN (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;\nCREATE TRIGGER asset_reference_added AFTER INSERT ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;\nCREATE TRIGGER asset_reference_removed AFTER DELETE ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;\n";
@@ -0,0 +1,44 @@
1
+ /** Additive only: legacy asset references, outputs and snapshot JSON are preserved. */
2
+ export const RESOURCE_SCHEMA = `
3
+ ALTER TABLE assets ADD COLUMN name TEXT NOT NULL DEFAULT '';
4
+ ALTER TABLE assets ADD COLUMN current_version INTEGER NOT NULL DEFAULT 0 CHECK(current_version >= 0);
5
+ UPDATE assets SET current_version=COALESCE((SELECT MAX(version) FROM asset_versions WHERE asset_id=assets.id),0);
6
+ CREATE INDEX assets_project_name ON assets(project_id,name);
7
+ CREATE UNIQUE INDEX runs_id_graph ON runs(id,graph_id);
8
+ CREATE TABLE canvas_resources (
9
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL, graph_id TEXT NOT NULL, name TEXT NOT NULL,
10
+ current_version INTEGER NOT NULL CHECK(current_version > 0), UNIQUE(id,graph_id),
11
+ FOREIGN KEY(graph_id,project_id) REFERENCES graphs(id,project_id),
12
+ FOREIGN KEY(id,current_version) REFERENCES canvas_resource_versions(resource_id,version) DEFERRABLE INITIALLY DEFERRED
13
+ ) STRICT;
14
+ CREATE TABLE canvas_resource_versions (
15
+ resource_id TEXT NOT NULL REFERENCES canvas_resources(id) DEFERRABLE INITIALLY DEFERRED,
16
+ version INTEGER NOT NULL CHECK(version > 0), sha256 TEXT NOT NULL REFERENCES blobs(sha256), mime TEXT NOT NULL,
17
+ representation_version INTEGER, PRIMARY KEY(resource_id,version)
18
+ ) STRICT;
19
+ CREATE TRIGGER canvas_version_immutable BEFORE UPDATE ON canvas_resource_versions BEGIN SELECT RAISE(ABORT,'immutable canvas resource version'); END;
20
+ CREATE TRIGGER canvas_binding_immutable BEFORE UPDATE OF project_id,graph_id ON canvas_resources WHEN NEW.project_id != OLD.project_id OR NEW.graph_id != OLD.graph_id BEGIN SELECT RAISE(ABORT,'canvas resource binding immutable'); END;
21
+ CREATE TABLE canvas_resource_references (
22
+ id TEXT PRIMARY KEY, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL, graph_id TEXT NOT NULL,
23
+ owner_kind TEXT NOT NULL CHECK(owner_kind IN ('graph','node','snapshot','history')),
24
+ node_id TEXT, run_id TEXT,
25
+ CHECK((owner_kind='graph' AND node_id IS NULL AND run_id IS NULL) OR (owner_kind='node' AND node_id IS NOT NULL AND run_id IS NULL) OR (owner_kind IN ('snapshot','history') AND node_id IS NULL AND run_id IS NOT NULL)),
26
+ FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
27
+ FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id),
28
+ FOREIGN KEY(node_id,graph_id) REFERENCES nodes(id,graph_id),
29
+ FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
30
+ ) STRICT;
31
+ CREATE INDEX canvas_refs_resource ON canvas_resource_references(resource_id,resource_version);
32
+ CREATE TRIGGER frozen_resource_reference_immutable BEFORE UPDATE ON canvas_resource_references WHEN OLD.owner_kind IN ('snapshot','history') BEGIN SELECT RAISE(ABORT,'frozen resource reference'); END;
33
+ CREATE TABLE canvas_outputs (
34
+ run_id TEXT NOT NULL, graph_id TEXT NOT NULL, output_key TEXT NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,
35
+ PRIMARY KEY(run_id,output_key), FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
36
+ FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id), FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
37
+ ) STRICT;
38
+ CREATE TRIGGER no_new_canvas_asset_link BEFORE INSERT ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'canvas nodes must use independent canvas resources'); END;
39
+ CREATE TRIGGER no_rebind_canvas_asset_link BEFORE UPDATE OF asset_id,asset_version,node_id ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'legacy canvas asset references cannot be rebound'); END;
40
+ CREATE TRIGGER no_new_deleted_asset_reference BEFORE INSERT ON asset_references WHEN (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
41
+ CREATE TRIGGER asset_reference_added AFTER INSERT ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
42
+ CREATE TRIGGER asset_reference_removed AFTER DELETE ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
43
+ `;
44
+ //# sourceMappingURL=resource-schema.js.map
@@ -0,0 +1,6 @@
1
+ export interface Migration {
2
+ version: number;
3
+ sql: string;
4
+ }
5
+ export declare const MIGRATIONS: readonly Migration[];
6
+ export declare const SCHEMA_VERSION = 24;