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,92 @@
1
+ import { lstat, readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { Auth } from './auth.js';
4
+ import { ServiceError } from './errors.js';
5
+ import { readJson } from './http.js';
6
+ import { transaction } from './persistence/database.js';
7
+ import { Resources } from './resources.js';
8
+ const category = (name) => {
9
+ if (name === 'service.sqlite' || name.startsWith('service.sqlite-'))
10
+ return 'database';
11
+ if (name === 'blobs')
12
+ return 'resources';
13
+ if (name === 'runs')
14
+ return 'runs';
15
+ if (name === 'backups')
16
+ return 'backups';
17
+ if (name === 'config')
18
+ return 'config';
19
+ if (name === 'staging')
20
+ return 'staging';
21
+ return 'other';
22
+ };
23
+ /** Count managed files below the Runtime data root; never follow symlinks. */
24
+ export async function measureStorage(directories) {
25
+ const categories = { database: 0, resources: 0, runs: 0, backups: 0, config: 0, staging: 0, other: 0 };
26
+ const pending = [{ path: directories.root, category: 'other' }];
27
+ while (pending.length) {
28
+ const directory = pending.pop();
29
+ let entries;
30
+ try {
31
+ entries = await readdir(directory.path, { withFileTypes: true });
32
+ }
33
+ catch (error) {
34
+ if (error.code === 'ENOENT' && directory.path !== directories.root)
35
+ continue;
36
+ throw error;
37
+ }
38
+ for (const entry of entries) {
39
+ const path = join(directory.path, entry.name);
40
+ const group = directory.path === directories.root ? category(entry.name) : directory.category;
41
+ let info;
42
+ try {
43
+ info = await lstat(path);
44
+ }
45
+ catch (error) {
46
+ if (error.code === 'ENOENT')
47
+ continue;
48
+ throw error;
49
+ }
50
+ if (info.isSymbolicLink())
51
+ continue;
52
+ if (info.isDirectory())
53
+ pending.push({ path, category: group });
54
+ else if (info.isFile())
55
+ categories[group] += info.size;
56
+ }
57
+ }
58
+ return { totalBytes: Object.values(categories).reduce((sum, bytes) => sum + bytes, 0), categories, measuredAt: new Date().toISOString() };
59
+ }
60
+ export class StorageApi {
61
+ db;
62
+ directories;
63
+ resources;
64
+ auth;
65
+ constructor(db, directories, resources, auth) {
66
+ this.db = db;
67
+ this.directories = directories;
68
+ this.resources = resources;
69
+ this.auth = auth;
70
+ }
71
+ async handle(request, path, token, origin) {
72
+ if (path !== '/v1/storage' && path !== '/v1/storage/cleanup')
73
+ return { handled: false };
74
+ this.auth.withSession(token, origin, () => undefined);
75
+ if (path === '/v1/storage' && request.method === 'GET') {
76
+ const usage = await measureStorage(this.directories);
77
+ return this.auth.withSession(token, origin, () => ({ handled: true, body: usage }));
78
+ }
79
+ if (path === '/v1/storage/cleanup' && request.method === 'POST') {
80
+ const body = await readJson(request, 1024);
81
+ if (Object.keys(body).length)
82
+ throw new ServiceError('INVALID_REQUEST', '缓存清理不接受额外参数。');
83
+ this.auth.withSession(token, origin, () => undefined);
84
+ const collected = transaction(this.db, () => this.resources.collectGarbage(undefined, true));
85
+ const deletion = await this.resources.drainFileDeletions();
86
+ const usage = await measureStorage(this.directories);
87
+ return this.auth.withSession(token, origin, () => ({ handled: true, body: { collectedResources: collected.canvas, collectedBlobs: collected.blobs, removedFiles: deletion.removed, failedFiles: deletion.failed, usage } }));
88
+ }
89
+ throw new ServiceError('NOT_FOUND', '存储数据接口不存在。');
90
+ }
91
+ }
92
+ //# sourceMappingURL=storage-api.js.map
@@ -0,0 +1,10 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import type { ApiResult } from './api.js';
3
+ import { Auth } from './auth.js';
4
+ import { GraphTransfer } from './graph-transfer.js';
5
+ export declare class TransferApi {
6
+ readonly transfer: GraphTransfer;
7
+ readonly auth: Auth;
8
+ constructor(transfer: GraphTransfer, auth: Auth);
9
+ handle(request: IncomingMessage, path: string, token: string, origin: string): Promise<ApiResult>;
10
+ }
@@ -0,0 +1,60 @@
1
+ import { Auth } from './auth.js';
2
+ import { GraphTransfer } from './graph-transfer.js';
3
+ import { readJson } from './http.js';
4
+ import { ServiceError } from './errors.js';
5
+ import { GRAPH_BINARY_MIME, GRAPH_METADATA_MAX_BYTES, graphBinaryParts, readGraphBinary, jsonByteLength, WORKGRAPH_TRANSFER_TOTAL_BYTES } from "./vendor/protocol/index.js";
6
+ export class TransferApi {
7
+ transfer;
8
+ auth;
9
+ constructor(transfer, auth) {
10
+ this.transfer = transfer;
11
+ this.auth = auth;
12
+ }
13
+ async handle(request, path, token, origin) {
14
+ const match = /^[/]v1[/]projects[/]([a-zA-Z0-9_-]+)[/]graphs[/](import|[a-zA-Z0-9_-]+[/](?:export|copied-provenance))$/.exec(path);
15
+ if (!match)
16
+ return { handled: false };
17
+ const principal = this.auth.withSession(token, origin, s => s.id), scope = { serviceId: this.transfer.graphs.serviceId, projectId: match[1] };
18
+ if (request.method === 'GET' && match[2] !== 'import') {
19
+ if (match[2].endsWith('/copied-provenance'))
20
+ return this.auth.withSession(token, origin, () => ({ handled: true, body: this.transfer.getCopiedProvenance({ ...scope, graphId: match[2].split('/')[0] }) }));
21
+ const value = await this.transfer.exportGraph({ ...scope, graphId: match[2].split('/')[0] });
22
+ if (request.headers.accept === GRAPH_BINARY_MIME) {
23
+ const binaryParts = graphBinaryParts(value);
24
+ return this.auth.withSession(token, origin, () => ({ handled: true, binaryParts, headers: { 'Content-Type': GRAPH_BINARY_MIME, 'Content-Length': String(binaryParts.reduce((sum, part) => sum + part.length, 0)) } }));
25
+ }
26
+ if (jsonByteLength(value) > GRAPH_METADATA_MAX_BYTES)
27
+ throw new ServiceError('PAYLOAD_TOO_LARGE', '大工作图需要二进制传输,请更新 Web 客户端后重试。');
28
+ return this.auth.withSession(token, origin, () => ({ handled: true, body: value }));
29
+ }
30
+ if (request.method === 'POST' && match[2] === 'import') {
31
+ let body;
32
+ if (request.headers['content-type'] === GRAPH_BINARY_MIME) {
33
+ if (request.headers['content-encoding'])
34
+ throw new ServiceError('UNSUPPORTED_MEDIA_TYPE', '不支持压缩的工作图传输请求。');
35
+ if (Number(request.headers['content-length'] ?? 0) > WORKGRAPH_TRANSFER_TOTAL_BYTES + 8)
36
+ throw new ServiceError('PAYLOAD_TOO_LARGE', '工作图导入不能超过 1 GB。');
37
+ try {
38
+ body = await readGraphBinary(request);
39
+ }
40
+ catch {
41
+ throw new ServiceError('INVALID_REQUEST', '工作图二进制资源不完整、格式无效或超过大小限制。');
42
+ }
43
+ }
44
+ else
45
+ body = await readJson(request, Math.min(this.transfer.limits.maxBundleBytes + 4096, GRAPH_METADATA_MAX_BYTES));
46
+ if (Object.keys(body).some(k => !['bundle', 'idempotencyKey'].includes(k)) || typeof body.idempotencyKey !== 'string' || !body.idempotencyKey || body.idempotencyKey.length > 256)
47
+ throw new ServiceError('INVALID_REQUEST', '导入需要图包和幂等键。');
48
+ this.auth.withSession(token, origin, () => undefined);
49
+ const prepared = await this.transfer.prepareImport(scope, body.bundle);
50
+ try {
51
+ return this.auth.withSession(token, origin, () => ({ handled: true, body: this.transfer.commitImport(scope, prepared, { idempotencyKey: body.idempotencyKey, principal }) }));
52
+ }
53
+ finally {
54
+ await this.transfer.disposePreparedImport(prepared);
55
+ }
56
+ }
57
+ throw new ServiceError('NOT_FOUND', '图传输接口不存在。');
58
+ }
59
+ }
60
+ //# sourceMappingURL=transfer-api.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ function contracts(browser, local, graph, selection) {
2
+ void browser.setModelDefaults(selection, 0);
3
+ void browser.commandGraph(graph);
4
+ void browser.previewHistoryCleanup(['run']);
5
+ void browser.continueRestoredRun('run', 'request');
6
+ void local.previewRestore('/backup');
7
+ // @ts-expect-error Browser must not expose backup restoration.
8
+ void browser.restore('/backup', 'sha256');
9
+ // @ts-expect-error Restored runs require an idempotency key.
10
+ void browser.continueRestoredRun('run');
11
+ // @ts-expect-error Backend-private states must not leak into DTOs.
12
+ const state = 'backend_turn_finished';
13
+ void state;
14
+ }
15
+ void contracts;
16
+ function resourceContracts(resources, envelope) {
17
+ // @ts-expect-error Canvas inputs must not bind directly to library AssetVersion.
18
+ void envelope.asset;
19
+ // @ts-expect-error Explicit update target and CAS version are required for library updates.
20
+ const update = { serviceId: 's', projectId: 'p', uploadId: 'u', name: 'file', idempotencyKey: 'k', mode: 'update' };
21
+ // @ts-expect-error Automatic propagation/accept-pending-version is not an API.
22
+ void resources.acceptAssetUpdate('node', 'asset');
23
+ void update;
24
+ }
25
+ void resourceContracts;
26
+ const imageApiRun = { serviceId: 's', projectId: 'p', graphId: 'g', nodeId: 'n', kind: 'image_generation', idempotencyKey: 'k', expectedExecutionRevision: 0, imageRoute: { type: 'api', providerId: 'provider', modelId: 'image-model' } };
27
+ // @ts-expect-error API image tasks cannot select a Codex model.
28
+ const invalidApiRun = { ...imageApiRun, modelOverride: { model: 'codex', reasoningEffort: null } };
29
+ // @ts-expect-error Text tasks cannot select an image provider.
30
+ const invalidTextRoute = { ...imageApiRun, kind: 'text_generation' };
31
+ void imageApiRun;
32
+ void invalidApiRun;
33
+ void invalidTextRoute;
34
+ export {};
35
+ //# sourceMappingURL=contracts.typecheck.js.map
@@ -0,0 +1,6 @@
1
+ /** Kahn ordering for execution dependencies only. Reference feedback is unrelated. */
2
+ export declare function executionOrder(edges: readonly {
3
+ sourceId: string;
4
+ targetId: string;
5
+ kind?: string;
6
+ }[]): string[] | null;
@@ -0,0 +1,26 @@
1
+ /** Kahn ordering for execution dependencies only. Reference feedback is unrelated. */
2
+ export function executionOrder(edges) {
3
+ const indegree = new Map();
4
+ const outgoing = new Map();
5
+ for (const edge of edges) {
6
+ if (edge.kind !== 'execution')
7
+ continue;
8
+ if (!indegree.has(edge.sourceId))
9
+ indegree.set(edge.sourceId, 0);
10
+ indegree.set(edge.targetId, (indegree.get(edge.targetId) ?? 0) + 1);
11
+ const next = outgoing.get(edge.sourceId) ?? [];
12
+ next.push(edge.targetId);
13
+ outgoing.set(edge.sourceId, next);
14
+ }
15
+ const ready = [...indegree].filter(([, count]) => count === 0).map(([id]) => id);
16
+ for (let index = 0; index < ready.length; index++) {
17
+ for (const id of outgoing.get(ready[index]) ?? []) {
18
+ const count = indegree.get(id) - 1;
19
+ indegree.set(id, count);
20
+ if (count === 0)
21
+ ready.push(id);
22
+ }
23
+ }
24
+ return ready.length === indegree.size ? ready : null;
25
+ }
26
+ //# sourceMappingURL=execution-chain.js.map
@@ -0,0 +1,15 @@
1
+ import type { GraphBundle } from './index.js';
2
+ /** HTTP representation: an 8-byte header, JSON metadata, then raw assets.
3
+ * The user-facing ZIP archive format remains separate and unchanged. */
4
+ export declare const GRAPH_BINARY_MIME = "application/vnd.openworkgraph.bundle+octet-stream";
5
+ export declare const GRAPH_METADATA_MAX_BYTES: number;
6
+ export declare function base64Bytes(value: string): Uint8Array;
7
+ export declare function bytesBase64(value: Uint8Array): string;
8
+ export declare function graphBinaryParts(bundle: GraphBundle, idempotencyKey?: string): Uint8Array[];
9
+ /** Bounded incremental reads avoid an aggregate JSON string or binary buffer. */
10
+ export declare function readGraphBinary(source: AsyncIterable<Uint8Array>): Promise<{
11
+ bundle: GraphBundle;
12
+ idempotencyKey?: string;
13
+ }>;
14
+ /** Count JSON bytes without stringifying the aggregate bundle. */
15
+ export declare function jsonByteLength(value: unknown): number;
@@ -0,0 +1,121 @@
1
+ import { WORKGRAPH_TRANSFER_TOTAL_BYTES, WORKGRAPH_UPLOAD_MAX_BYTES } from './index.js';
2
+ /** HTTP representation: an 8-byte header, JSON metadata, then raw assets.
3
+ * The user-facing ZIP archive format remains separate and unchanged. */
4
+ export const GRAPH_BINARY_MIME = 'application/vnd.openworkgraph.bundle+octet-stream';
5
+ export const GRAPH_METADATA_MAX_BYTES = 128 * 1024 * 1024;
6
+ const encoder = new TextEncoder();
7
+ function invalid() { throw new Error('Invalid or oversized Work Graph binary bundle'); }
8
+ export function base64Bytes(value) {
9
+ const raw = atob(value);
10
+ const result = new Uint8Array(raw.length);
11
+ for (let i = 0; i < raw.length; i++)
12
+ result[i] = raw.charCodeAt(i);
13
+ return result;
14
+ }
15
+ export function bytesBase64(value) {
16
+ const parts = [];
17
+ // Divisible by three: concatenation preserves base64 padding semantics.
18
+ for (let i = 0; i < value.length; i += 49152)
19
+ parts.push(btoa(String.fromCharCode(...value.subarray(i, i + 49152))));
20
+ return parts.join('');
21
+ }
22
+ export function graphBinaryParts(bundle, idempotencyKey) {
23
+ let total = 0;
24
+ if (!Array.isArray(bundle.resources) || bundle.resources.length > 256)
25
+ invalid();
26
+ const resources = bundle.resources.map(({ base64: _base64, ...resource }) => {
27
+ if (!Number.isSafeInteger(resource.bytes) || resource.bytes < 0 || resource.bytes > WORKGRAPH_UPLOAD_MAX_BYTES)
28
+ invalid();
29
+ total += resource.bytes;
30
+ if (total > WORKGRAPH_TRANSFER_TOTAL_BYTES)
31
+ invalid();
32
+ return resource;
33
+ });
34
+ const metadata = encoder.encode(JSON.stringify({ bundle: { ...bundle, resources }, ...(idempotencyKey === undefined ? {} : { idempotencyKey }) }));
35
+ if (metadata.length > GRAPH_METADATA_MAX_BYTES || total + metadata.length > WORKGRAPH_TRANSFER_TOTAL_BYTES)
36
+ invalid();
37
+ const header = new Uint8Array(8);
38
+ header.set([79, 87, 71, 66]); // OWGB
39
+ new DataView(header.buffer).setUint32(4, metadata.length);
40
+ return [header, metadata, ...bundle.resources.map(resource => {
41
+ if (resource.base64.length !== Math.ceil(resource.bytes / 3) * 4)
42
+ invalid();
43
+ const bytes = base64Bytes(resource.base64);
44
+ if (bytes.length !== resource.bytes)
45
+ invalid();
46
+ return bytes;
47
+ })];
48
+ }
49
+ /** Bounded incremental reads avoid an aggregate JSON string or binary buffer. */
50
+ export async function readGraphBinary(source) {
51
+ const iterator = source[Symbol.asyncIterator]();
52
+ let chunk = new Uint8Array(0), offset = 0, received = 0;
53
+ async function next() {
54
+ const item = await iterator.next();
55
+ if (item.done)
56
+ return false;
57
+ chunk = item.value;
58
+ offset = 0;
59
+ received += chunk.length;
60
+ if (received > WORKGRAPH_TRANSFER_TOTAL_BYTES + 8)
61
+ invalid();
62
+ return true;
63
+ }
64
+ async function read(size) {
65
+ const result = new Uint8Array(size);
66
+ let filled = 0;
67
+ while (filled < size) {
68
+ if (offset === chunk.length) {
69
+ if (!await next())
70
+ invalid();
71
+ continue;
72
+ }
73
+ const length = Math.min(size - filled, chunk.length - offset);
74
+ result.set(chunk.subarray(offset, offset + length), filled);
75
+ filled += length;
76
+ offset += length;
77
+ }
78
+ return result;
79
+ }
80
+ try {
81
+ const header = await read(8);
82
+ if (header[0] !== 79 || header[1] !== 87 || header[2] !== 71 || header[3] !== 66)
83
+ invalid();
84
+ const length = new DataView(header.buffer).getUint32(4);
85
+ if (!length || length > GRAPH_METADATA_MAX_BYTES)
86
+ invalid();
87
+ const envelope = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await read(length)));
88
+ if (!envelope || Object.keys(envelope).some(key => !['bundle', 'idempotencyKey'].includes(key)) || !envelope.bundle || !Array.isArray(envelope.bundle.resources) || envelope.bundle.resources.length > 256)
89
+ invalid();
90
+ let total = length;
91
+ // Validate all lengths before allocating any asset buffers.
92
+ for (const resource of envelope.bundle.resources) {
93
+ if (!resource || Object.hasOwn(resource, 'base64') || !Number.isSafeInteger(resource.bytes) || resource.bytes < 0 || resource.bytes > WORKGRAPH_UPLOAD_MAX_BYTES)
94
+ invalid();
95
+ total += resource.bytes;
96
+ if (total > WORKGRAPH_TRANSFER_TOTAL_BYTES)
97
+ invalid();
98
+ }
99
+ for (const resource of envelope.bundle.resources)
100
+ resource.base64 = bytesBase64(await read(resource.bytes));
101
+ if (offset !== chunk.length)
102
+ invalid();
103
+ while (await next())
104
+ if (chunk.length)
105
+ invalid();
106
+ return envelope;
107
+ }
108
+ finally {
109
+ await iterator.return?.();
110
+ }
111
+ }
112
+ /** Count JSON bytes without stringifying the aggregate bundle. */
113
+ export function jsonByteLength(value) {
114
+ if (value === null || typeof value !== 'object')
115
+ return encoder.encode(JSON.stringify(value)).length;
116
+ if (Array.isArray(value))
117
+ return 2 + Math.max(0, value.length - 1) + value.reduce((sum, item) => sum + jsonByteLength(item), 0);
118
+ const entries = Object.entries(value).filter(([, item]) => item !== undefined);
119
+ return 2 + Math.max(0, entries.length - 1) + entries.reduce((sum, [key, item]) => sum + encoder.encode(JSON.stringify(key)).length + 1 + jsonByteLength(item), 0);
120
+ }
121
+ //# sourceMappingURL=graph-binary.js.map
@@ -0,0 +1,2 @@
1
+ /** Resolve an imported graph title against every graph in the target project. */
2
+ export declare function importedGraphTitle(title: string, existingTitles: Iterable<string>, maxLength?: number): string;
@@ -0,0 +1,23 @@
1
+ const DEFAULT_GRAPH_TITLE_LIMIT = 1024;
2
+ function truncateTitle(value, length) {
3
+ let result = value.slice(0, length);
4
+ const last = result.charCodeAt(result.length - 1);
5
+ if (last >= 0xd800 && last <= 0xdbff)
6
+ result = result.slice(0, -1);
7
+ return result.replace(/\s+$/u, '');
8
+ }
9
+ /** Resolve an imported graph title against every graph in the target project. */
10
+ export function importedGraphTitle(title, existingTitles, maxLength = DEFAULT_GRAPH_TITLE_LIMIT) {
11
+ const existing = new Set(existingTitles);
12
+ if (!existing.has(title))
13
+ return title;
14
+ for (let suffix = 1; suffix < Number.MAX_SAFE_INTEGER; suffix++) {
15
+ const marker = ` (${suffix})`;
16
+ const base = truncateTitle(title, Math.max(0, maxLength - marker.length));
17
+ const candidate = (base || title.slice(0, Math.max(0, maxLength - marker.length))) + marker;
18
+ if (!existing.has(candidate))
19
+ return candidate;
20
+ }
21
+ throw new Error('无法为导入的工作图生成唯一名称。');
22
+ }
23
+ //# sourceMappingURL=graph-title.js.map