@vercel/factory 0.0.17 → 0.0.18
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/CHANGELOG.md +46 -0
- package/dist/api-contracts.d.mts +665 -715
- package/dist/api-contracts.mjs +91 -4
- package/dist/api-contracts.mjs.map +1 -1
- package/dist/api-task-graph.mjs +14 -5
- package/dist/api-task-graph.mjs.map +1 -1
- package/dist/api.d.mts +58 -11
- package/dist/api.mjs +715 -399
- package/dist/api.mjs.map +1 -1
- package/dist/approval-access.d.mts +1 -1
- package/dist/approval-contracts.d.mts +3 -4
- package/dist/approval-contracts.mjs +1 -2
- package/dist/approval-contracts.mjs.map +1 -1
- package/dist/blob/index.d.mts +1 -1
- package/dist/budget.mjs +20 -3
- package/dist/budget.mjs.map +1 -1
- package/dist/change-verification/brief.d.mts +1 -1
- package/dist/change-verification/dispatch.mjs +8 -1
- package/dist/change-verification/dispatch.mjs.map +1 -1
- package/dist/changes/eve-record-change.mjs +4 -1
- package/dist/changes/eve-record-change.mjs.map +1 -1
- package/dist/client-stream.d.mts +19 -2
- package/dist/client-stream.mjs +115 -53
- package/dist/client-stream.mjs.map +1 -1
- package/dist/client-transcript.d.mts +25 -2
- package/dist/client-transcript.mjs +95 -13
- package/dist/client-transcript.mjs.map +1 -1
- package/dist/client.d.mts +145 -19
- package/dist/client.mjs +89 -16
- package/dist/client.mjs.map +1 -1
- package/dist/code-review/contracts.d.mts +1 -0
- package/dist/code-review/eve-post-review.d.mts +4 -2
- package/dist/code-review/eve-post-review.mjs +8 -0
- package/dist/code-review/eve-post-review.mjs.map +1 -1
- package/dist/code-review/github-reporter.d.mts +6 -2
- package/dist/code-review/github-reporter.mjs +86 -35
- package/dist/code-review/github-reporter.mjs.map +1 -1
- package/dist/code-review/preparation.d.mts +2 -1
- package/dist/code-review/preparation.mjs +2 -1
- package/dist/code-review/preparation.mjs.map +1 -1
- package/dist/code-review/stall.mjs +11 -2
- package/dist/code-review/stall.mjs.map +1 -1
- package/dist/connectors/github.d.mts +1 -1
- package/dist/connectors/index.d.mts +1 -1
- package/dist/connectors/slack.d.mts +1 -1
- package/dist/delivery-metrics/metrics.d.mts +1 -1
- package/dist/dispatch.mjs +3 -3
- package/dist/dispatch.mjs.map +1 -1
- package/dist/eve/index.d.mts +1 -1
- package/dist/eve/index.mjs +8 -1
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/task-execution.d.mts +4 -4
- package/dist/eve/transcript.d.mts +3 -1
- package/dist/eve/transcript.mjs.map +1 -1
- package/dist/finding-remediation/admission.d.mts +1 -1
- package/dist/finding-remediation/brief.d.mts +1 -1
- package/dist/github-app.d.mts +1 -1
- package/dist/github-publication.d.mts +1 -1
- package/dist/github.d.mts +9 -1
- package/dist/github.mjs +29 -11
- package/dist/github.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/intake-contracts.d.mts +1 -0
- package/dist/intake-contracts.mjs +2 -1
- package/dist/intake-contracts.mjs.map +1 -1
- package/dist/integrations/github-issues.d.mts +53 -0
- package/dist/integrations/github-issues.mjs +170 -0
- package/dist/integrations/github-issues.mjs.map +1 -0
- package/dist/integrations/github.d.mts +2 -1
- package/dist/integrations/github.mjs +2 -1
- package/dist/multi-repository-changes/coordination.mjs +1 -1
- package/dist/planning/reconcile.mjs +15 -2
- package/dist/planning/reconcile.mjs.map +1 -1
- package/dist/postgres/index.d.mts +2 -1
- package/dist/postgres/index.mjs +11 -5
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/postgres/indexed-codec.mjs +137 -0
- package/dist/postgres/indexed-codec.mjs.map +1 -0
- package/dist/postgres/indexed-schema.mjs +99 -0
- package/dist/postgres/indexed-schema.mjs.map +1 -0
- package/dist/postgres/indexed.d.mts +28 -0
- package/dist/postgres/indexed.mjs +277 -0
- package/dist/postgres/indexed.mjs.map +1 -0
- package/dist/postgres/record-predicates.mjs +23 -0
- package/dist/postgres/record-predicates.mjs.map +1 -0
- package/dist/presets/software-development/deepsec-brief.d.mts +1 -1
- package/dist/presets/software-development/dispatch.mjs +1 -1
- package/dist/presets/software-development/dispatch.mjs.map +1 -1
- package/dist/presets/software-development/recovery.mjs +9 -2
- package/dist/presets/software-development/recovery.mjs.map +1 -1
- package/dist/pull-requests/eve-fetch.d.mts +4 -1
- package/dist/pull-requests/eve-fetch.mjs +9 -0
- package/dist/pull-requests/eve-fetch.mjs.map +1 -1
- package/dist/pull-requests/github-publisher.d.mts +1 -1
- package/dist/pull-requests/github-publisher.mjs +19 -7
- package/dist/pull-requests/github-publisher.mjs.map +1 -1
- package/dist/repositories.mjs +20 -13
- package/dist/repositories.mjs.map +1 -1
- package/dist/sandbox/execution-run-ledger.d.mts +2 -2
- package/dist/sandbox/execution-run-ledger.mjs.map +1 -1
- package/dist/sandbox/execution.d.mts +2 -2
- package/dist/sandbox/execution.mjs.map +1 -1
- package/dist/schema/catalog.mjs +1 -1
- package/dist/schema/change.d.mts +1 -1
- package/dist/schema/communication.d.mts +2 -2
- package/dist/schema/factory-config.d.mts +1 -1
- package/dist/schema/outbox.d.mts +6 -6
- package/dist/schema/session-summary.d.mts +218 -0
- package/dist/schema/session-summary.mjs +68 -0
- package/dist/schema/session-summary.mjs.map +1 -0
- package/dist/schema/session.d.mts +39 -59
- package/dist/schema/session.mjs +6 -6
- package/dist/schema/session.mjs.map +1 -1
- package/dist/schema/task-graph.d.mts +24 -24
- package/dist/schema/transcript.d.mts +141 -3
- package/dist/schema/transcript.mjs +19 -3
- package/dist/schema/transcript.mjs.map +1 -1
- package/dist/schema/transitions.d.mts +1 -1
- package/dist/schema/transitions.mjs +1 -1
- package/dist/schema/transitions.mjs.map +1 -1
- package/dist/signals.d.mts +1 -1
- package/dist/stall.mjs +10 -2
- package/dist/stall.mjs.map +1 -1
- package/dist/storage.d.mts +10 -1
- package/dist/storage.mjs +7 -1
- package/dist/store/approvals.d.mts +14 -0
- package/dist/store/approvals.mjs +78 -0
- package/dist/store/approvals.mjs.map +1 -0
- package/dist/store/conversations.d.mts +59 -0
- package/dist/store/conversations.mjs +549 -0
- package/dist/store/conversations.mjs.map +1 -0
- package/dist/store/driver.d.mts +1 -1
- package/dist/store/driver.mjs.map +1 -1
- package/dist/store/engine.d.mts +38 -16
- package/dist/store/engine.mjs +229 -106
- package/dist/store/engine.mjs.map +1 -1
- package/dist/store/indexed-engine.d.mts +57 -0
- package/dist/store/indexed-engine.mjs +155 -0
- package/dist/store/indexed-engine.mjs.map +1 -0
- package/dist/store/indexed-memory.d.mts +7 -0
- package/dist/store/indexed-memory.mjs +200 -0
- package/dist/store/indexed-memory.mjs.map +1 -0
- package/dist/store/indexed-projections.d.mts +280 -0
- package/dist/store/indexed-projections.mjs +481 -0
- package/dist/store/indexed-projections.mjs.map +1 -0
- package/dist/store/indexed.d.mts +101 -0
- package/dist/store/indexed.mjs +19 -0
- package/dist/store/indexed.mjs.map +1 -0
- package/dist/store/iterate.d.mts +9 -0
- package/dist/store/iterate.mjs +23 -0
- package/dist/store/iterate.mjs.map +1 -0
- package/dist/store/query-scope.mjs +62 -0
- package/dist/store/query-scope.mjs.map +1 -0
- package/dist/store/query.d.mts +32 -0
- package/dist/store/query.mjs +103 -0
- package/dist/store/query.mjs.map +1 -0
- package/dist/store/session-history.mjs +24 -0
- package/dist/store/session-history.mjs.map +1 -0
- package/dist/store/slack-pr-notifications.d.mts +2 -2
- package/dist/store/slack-pr-notifications.mjs.map +1 -1
- package/dist/store/state.d.mts +26 -0
- package/dist/store/state.mjs +95 -0
- package/dist/store/state.mjs.map +1 -0
- package/dist/store/task-effects.d.mts +1 -1
- package/dist/store/task-graphs.d.mts +1 -1
- package/dist/store/task-messages.d.mts +1 -1
- package/dist/store/task-work.d.mts +1 -1
- package/dist/store/task-work.mjs +1 -1
- package/dist/store/transcripts.mjs +308 -0
- package/dist/store/transcripts.mjs.map +1 -0
- package/dist/sweep.d.mts +1 -1
- package/dist/sweep.mjs +12 -7
- package/dist/sweep.mjs.map +1 -1
- package/dist/task-communication.mjs +15 -1
- package/dist/task-communication.mjs.map +1 -1
- package/dist/task-protocols.d.mts +3 -3
- package/dist/tasks.d.mts +1 -1
- package/dist/vercel.d.mts +1 -1
- package/dist/work-triage/find-existing-work.d.mts +1 -1
- package/dist/work-triage/find-existing-work.mjs +12 -2
- package/dist/work-triage/find-existing-work.mjs.map +1 -1
- package/docs/index.md +4 -0
- package/docs/recipes/persistence-recovery.md +15 -9
- package/docs/recipes/postgres-indexed-engine.md +108 -0
- package/docs/recipes/postgres-indexed-storage.md +145 -0
- package/package.json +3 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/store/indexed.ts
|
|
2
|
+
/** A durable operation identity was reused for a different mutation. */
|
|
3
|
+
var StoreOperationConflictError = class extends Error {
|
|
4
|
+
constructor(operationId) {
|
|
5
|
+
super(`Store operation "${operationId}" already committed a different write`);
|
|
6
|
+
this.name = "StoreOperationConflictError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/** Invalid or mismatched storage cursor supplied by a query caller. */
|
|
10
|
+
var InvalidStoreQueryError = class extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("Invalid cursor for this query");
|
|
13
|
+
this.name = "InvalidStoreQueryError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { InvalidStoreQueryError, StoreOperationConflictError };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=indexed.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexed.mjs","names":[],"sources":["../../src/store/indexed.ts"],"sourcesContent":["import type { CollectionName, StoredRecord } from \"./driver\";\nimport type { FactoryRecordQuery } from \"./query\";\n\n/** One compact, ordered membership in an application-defined query index. */\nexport interface StoreIndexEntry {\n /** Exact query partition, including the authorized workspace or owner scope. */\n readonly index: string;\n /** Lexically ordered key, such as an ISO creation timestamp. IDs break ties. */\n readonly sortKey: string;\n /** Small display projection; keep Task inputs, outputs, and transcript bodies out. */\n readonly summary: unknown;\n /** Optional engine-owned facet group; each record contributes once per distinct facet. */\n readonly countGroup?: { readonly group: string; readonly attributes: unknown };\n}\n\n/** Atomic replacement of a record and its complete set of index memberships. */\nexport interface WriteIndexedRecordInput {\n readonly collection: CollectionName;\n readonly id: string;\n /** Durable retry identity. Reuse only for the identical write intent. */\n readonly operationId: string;\n /** Zero inserts a new record; a positive version replaces that exact snapshot. */\n readonly expectedVersion: number;\n /** Engine-validated canonical JSON. Storage does not decide lifecycle policy. */\n readonly value: unknown;\n /** Complete replacement, including an empty array when removing all memberships. */\n readonly indexes: readonly StoreIndexEntry[];\n}\n\n/** Committed version returned both for a new write and a durable idempotent replay. */\nexport interface WriteIndexedRecordResult {\n readonly version: number;\n}\n\n/** One bounded page from an exact index partition; filtering must be encoded in the index. */\nexport interface QueryStoreIndexInput {\n readonly index: string;\n /** Required page bound, from 1 through 100. */\n readonly limit: number;\n /** Opaque continuation from the same index and ordering. */\n readonly cursor?: string;\n /** Defaults to descending order for newest-first listings. */\n readonly order?: \"asc\" | \"desc\";\n}\n\n/** Compact index result with the source record identity and committed version. */\nexport interface StoreIndexRecord extends StoreIndexEntry {\n readonly collection: CollectionName;\n readonly id: string;\n readonly version: number;\n}\n\n/** A bounded summary page and an opaque continuation when more results may remain. */\nexport interface QueryStoreIndexResult {\n readonly entries: readonly StoreIndexRecord[];\n readonly nextCursor: string | null;\n}\n\n/** Bounded canonical records selected through a source index and indexed JSON containment. */\nexport interface QueryStoreRecordsInput extends FactoryRecordQuery {\n readonly sources: readonly { readonly collection: CollectionName; readonly index: string }[];\n}\n\n/** Versioned canonical page returned for background work and explicit record inspection. */\nexport interface QueryStoreRecordsResult {\n readonly totalCount?: number;\n readonly entries: readonly {\n readonly collection: CollectionName;\n readonly id: string;\n readonly record: StoredRecord;\n }[];\n readonly nextCursor: string | null;\n}\n\n/**\n * Bounded persistence for new indexed consumers. Deliberately separate from StoreDriver:\n * it has no list/scan operation and cannot back the catalog-based engine unchanged.\n * Callers own schema validation, authorization, lifecycle rules, and index selection.\n */\nexport interface IndexedStore {\n readonly name: string;\n /**\n * Commit engine-owned related writes together under serializable isolation.\n * The callback can repeat after serialization failures and must contain only storage operations.\n * Retain operation IDs when retrying an uncertain result; external effects belong after commit.\n */\n transaction<T>(run: (transaction: IndexedStore) => Promise<T>): Promise<T>;\n get(collection: CollectionName, id: string): Promise<StoredRecord | null>;\n write(input: WriteIndexedRecordInput): Promise<WriteIndexedRecordResult>;\n query(input: QueryStoreIndexInput): Promise<QueryStoreIndexResult>;\n queryRecords(input: QueryStoreRecordsInput): Promise<QueryStoreRecordsResult>;\n /** Strongly consistent maintained membership count; never counts by querying items. */\n count(index: string): Promise<number>;\n /** Sums maintained facet counts, never canonical records. */\n sumCounts(group: string, query: Pick<FactoryRecordQuery, \"match\" | \"fields\">): Promise<number>;\n}\n\n/** A durable operation identity was reused for a different mutation. */\nexport class StoreOperationConflictError extends Error {\n constructor(operationId: string) {\n super(`Store operation \"${operationId}\" already committed a different write`);\n this.name = \"StoreOperationConflictError\";\n }\n}\n\n/** Invalid or mismatched storage cursor supplied by a query caller. */\nexport class InvalidStoreQueryError extends Error {\n constructor() {\n super(\"Invalid cursor for this query\");\n this.name = \"InvalidStoreQueryError\";\n }\n}\n"],"mappings":";;AAkGA,IAAa,8BAAb,cAAiD,MAAM;CACrD,YAAY,aAAqB;EAC/B,MAAM,oBAAoB,YAAY,sCAAsC;EAC5E,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,yBAAb,cAA4C,MAAM;CAChD,cAAc;EACZ,MAAM,+BAA+B;EACrC,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FactoryRecordPage, FactoryRecordQuery } from "./query.mjs";
|
|
2
|
+
//#region src/store/iterate.d.ts
|
|
3
|
+
/** Streams explicit bounded pages for background reconciliation or export; never use for UI lists. */
|
|
4
|
+
declare function iterateFactoryRecords<T>(read: (query: FactoryRecordQuery) => Promise<FactoryRecordPage<T>>, query: Omit<FactoryRecordQuery, "cursor" | "includeTotal">): AsyncGenerator<T>;
|
|
5
|
+
/** Collects a deliberately scoped reconciliation snapshot from bounded pages. UI readers must return pages directly. */
|
|
6
|
+
declare function collectFactoryRecords<T>(read: (query: FactoryRecordQuery) => Promise<FactoryRecordPage<T>>, query: Omit<FactoryRecordQuery, "cursor" | "includeTotal">): Promise<readonly T[]>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { collectFactoryRecords, iterateFactoryRecords };
|
|
9
|
+
//# sourceMappingURL=iterate.d.mts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/store/iterate.ts
|
|
2
|
+
/** Streams explicit bounded pages for background reconciliation or export; never use for UI lists. */
|
|
3
|
+
async function* iterateFactoryRecords(read, query) {
|
|
4
|
+
let cursor;
|
|
5
|
+
do {
|
|
6
|
+
const page = await read({
|
|
7
|
+
...query,
|
|
8
|
+
cursor
|
|
9
|
+
});
|
|
10
|
+
for (const item of page.items) yield item;
|
|
11
|
+
cursor = page.nextCursor ?? void 0;
|
|
12
|
+
} while (cursor);
|
|
13
|
+
}
|
|
14
|
+
/** Collects a deliberately scoped reconciliation snapshot from bounded pages. UI readers must return pages directly. */
|
|
15
|
+
async function collectFactoryRecords(read, query) {
|
|
16
|
+
const records = [];
|
|
17
|
+
for await (const record of iterateFactoryRecords(read, query)) records.push(record);
|
|
18
|
+
return records;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { collectFactoryRecords, iterateFactoryRecords };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=iterate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iterate.mjs","names":[],"sources":["../../src/store/iterate.ts"],"sourcesContent":["import type { FactoryRecordPage, FactoryRecordQuery } from \"./query\";\n\n/** Streams explicit bounded pages for background reconciliation or export; never use for UI lists. */\nexport async function* iterateFactoryRecords<T>(\n read: (query: FactoryRecordQuery) => Promise<FactoryRecordPage<T>>,\n query: Omit<FactoryRecordQuery, \"cursor\" | \"includeTotal\">,\n): AsyncGenerator<T> {\n let cursor: string | undefined;\n do {\n const page = await read({ ...query, cursor });\n for (const item of page.items) yield item;\n cursor = page.nextCursor ?? undefined;\n } while (cursor);\n}\n\n/** Collects a deliberately scoped reconciliation snapshot from bounded pages. UI readers must return pages directly. */\nexport async function collectFactoryRecords<T>(\n read: (query: FactoryRecordQuery) => Promise<FactoryRecordPage<T>>,\n query: Omit<FactoryRecordQuery, \"cursor\" | \"includeTotal\">,\n): Promise<readonly T[]> {\n const records: T[] = [];\n for await (const record of iterateFactoryRecords(read, query)) records.push(record);\n return records;\n}\n"],"mappings":";;AAGA,gBAAuB,sBACrB,MACA,OACmB;CACnB,IAAI;CACJ,GAAG;EACD,MAAM,OAAO,MAAM,KAAK;GAAE,GAAG;GAAO;EAAO,CAAC;EAC5C,KAAK,MAAM,QAAQ,KAAK,OAAO,MAAM;EACrC,SAAS,KAAK,cAAc,KAAA;CAC9B,SAAS;AACX;;AAGA,eAAsB,sBACpB,MACA,OACuB;CACvB,MAAM,UAAe,CAAC;CACtB,WAAW,MAAM,UAAU,sBAAsB,MAAM,KAAK,GAAG,QAAQ,KAAK,MAAM;CAClF,OAAO;AACT"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { factoryIndexScopeSchema } from "./indexed-projections.mjs";
|
|
2
|
+
//#region src/store/query-scope.ts
|
|
3
|
+
function recordQueryScope(collection, query) {
|
|
4
|
+
const alternatives = query.match === void 0 ? [] : Array.isArray(query.match) ? query.match : [query.match];
|
|
5
|
+
function shared(key) {
|
|
6
|
+
const value = alternatives[0]?.[key];
|
|
7
|
+
return value !== void 0 && alternatives.every((item) => JSON.stringify(item[key]) === JSON.stringify(value)) ? value : void 0;
|
|
8
|
+
}
|
|
9
|
+
const repositoryIds = shared("repositoryIds");
|
|
10
|
+
const repositoryId = shared("repositoryId") ?? (Array.isArray(repositoryIds) ? repositoryIds[0] : void 0);
|
|
11
|
+
const candidates = [
|
|
12
|
+
{
|
|
13
|
+
collection,
|
|
14
|
+
by: "reply",
|
|
15
|
+
replyTo: shared("replyTo")
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
collection,
|
|
19
|
+
by: "root",
|
|
20
|
+
rootTaskId: shared("rootTaskId")
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
collection,
|
|
24
|
+
by: "task",
|
|
25
|
+
taskId: shared("taskId")
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
collection,
|
|
29
|
+
by: "session",
|
|
30
|
+
sessionId: shared("sessionId")
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
collection,
|
|
34
|
+
by: "owner",
|
|
35
|
+
owner: shared("owner"),
|
|
36
|
+
repositoryId
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
collection,
|
|
40
|
+
by: "state",
|
|
41
|
+
state: shared("state"),
|
|
42
|
+
repositoryId
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
collection,
|
|
46
|
+
by: "repository",
|
|
47
|
+
repositoryId
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
const scope = factoryIndexScopeSchema.safeParse(candidate);
|
|
52
|
+
if (scope.success) return scope.data;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
collection,
|
|
56
|
+
by: "all"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { recordQueryScope };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=query-scope.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-scope.mjs","names":[],"sources":["../../src/store/query-scope.ts"],"sourcesContent":["import type { FactoryRecordQuery } from \"./query\";\nimport { factoryIndexScopeSchema, type FactoryIndexScope } from \"./indexed-projections\";\n\nexport function recordQueryScope(\n collection: FactoryIndexScope[\"collection\"],\n query: FactoryRecordQuery,\n): FactoryIndexScope {\n const alternatives =\n query.match === undefined ? [] : Array.isArray(query.match) ? query.match : [query.match];\n function shared(key: string): unknown {\n const value: unknown = alternatives[0]?.[key];\n return value !== undefined &&\n alternatives.every((item) => JSON.stringify(item[key]) === JSON.stringify(value))\n ? value\n : undefined;\n }\n const repositoryIds = shared(\"repositoryIds\");\n const repositoryId =\n shared(\"repositoryId\") ?? (Array.isArray(repositoryIds) ? repositoryIds[0] : undefined);\n const candidates = [\n { collection, by: \"reply\", replyTo: shared(\"replyTo\") },\n { collection, by: \"root\", rootTaskId: shared(\"rootTaskId\") },\n { collection, by: \"task\", taskId: shared(\"taskId\") },\n { collection, by: \"session\", sessionId: shared(\"sessionId\") },\n { collection, by: \"owner\", owner: shared(\"owner\"), repositoryId },\n { collection, by: \"state\", state: shared(\"state\"), repositoryId },\n { collection, by: \"repository\", repositoryId },\n ];\n for (const candidate of candidates) {\n const scope = factoryIndexScopeSchema.safeParse(candidate);\n if (scope.success) return scope.data;\n }\n return { collection, by: \"all\" };\n}\n"],"mappings":";;AAGA,SAAgB,iBACd,YACA,OACmB;CACnB,MAAM,eACJ,MAAM,UAAU,KAAA,IAAY,CAAC,IAAI,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,MAAM,KAAK;CAC1F,SAAS,OAAO,KAAsB;EACpC,MAAM,QAAiB,aAAa,EAAE,GAAG;EACzC,OAAO,UAAU,KAAA,KACf,aAAa,OAAO,SAAS,KAAK,UAAU,KAAK,IAAI,MAAM,KAAK,UAAU,KAAK,CAAC,IAC9E,QACA,KAAA;CACN;CACA,MAAM,gBAAgB,OAAO,eAAe;CAC5C,MAAM,eACJ,OAAO,cAAc,MAAM,MAAM,QAAQ,aAAa,IAAI,cAAc,KAAK,KAAA;CAC/E,MAAM,aAAa;EACjB;GAAE;GAAY,IAAI;GAAS,SAAS,OAAO,SAAS;EAAE;EACtD;GAAE;GAAY,IAAI;GAAQ,YAAY,OAAO,YAAY;EAAE;EAC3D;GAAE;GAAY,IAAI;GAAQ,QAAQ,OAAO,QAAQ;EAAE;EACnD;GAAE;GAAY,IAAI;GAAW,WAAW,OAAO,WAAW;EAAE;EAC5D;GAAE;GAAY,IAAI;GAAS,OAAO,OAAO,OAAO;GAAG;EAAa;EAChE;GAAE;GAAY,IAAI;GAAS,OAAO,OAAO,OAAO;GAAG;EAAa;EAChE;GAAE;GAAY,IAAI;GAAc;EAAa;CAC/C;CACA,KAAK,MAAM,aAAa,YAAY;EAClC,MAAM,QAAQ,wBAAwB,UAAU,SAAS;EACzD,IAAI,MAAM,SAAS,OAAO,MAAM;CAClC;CACA,OAAO;EAAE;EAAY,IAAI;CAAM;AACjC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/store/query.d.ts
|
|
3
|
+
/** Additional JSON-field predicates, combined with containment matches using AND. */
|
|
4
|
+
type FactoryRecordFieldFilter = {
|
|
5
|
+
readonly path: readonly string[];
|
|
6
|
+
readonly test: "exists" | "missing" | "notEmpty";
|
|
7
|
+
} | {
|
|
8
|
+
readonly path: readonly string[];
|
|
9
|
+
readonly test: "eq" | "ne" | "subset" | "oneOf";
|
|
10
|
+
readonly value: unknown;
|
|
11
|
+
};
|
|
12
|
+
/** Bounded canonical-record discovery; each match is JSON containment and alternatives are ORed. */
|
|
13
|
+
interface FactoryRecordQuery {
|
|
14
|
+
readonly limit: number;
|
|
15
|
+
readonly cursor?: string;
|
|
16
|
+
readonly order?: "asc" | "desc";
|
|
17
|
+
readonly match?: Readonly<Record<string, unknown>> | readonly Readonly<Record<string, unknown>>[];
|
|
18
|
+
readonly since?: string;
|
|
19
|
+
readonly until?: string;
|
|
20
|
+
readonly includeTotal?: boolean;
|
|
21
|
+
readonly search?: string;
|
|
22
|
+
readonly fields?: readonly FactoryRecordFieldFilter[];
|
|
23
|
+
}
|
|
24
|
+
/** One bounded page of validated canonical records. */
|
|
25
|
+
interface FactoryRecordPage<T> {
|
|
26
|
+
readonly items: readonly T[];
|
|
27
|
+
readonly nextCursor: string | null;
|
|
28
|
+
readonly totalCount?: number;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { FactoryRecordFieldFilter, FactoryRecordPage, FactoryRecordQuery };
|
|
32
|
+
//# sourceMappingURL=query.d.mts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
//#region src/store/query.ts
|
|
5
|
+
const recordQuerySchema = z.strictObject({
|
|
6
|
+
limit: z.number().int().min(1).max(100).describe("Required maximum records per page."),
|
|
7
|
+
cursor: z.string().max(8192).optional().describe("Continuation bound to this exact query."),
|
|
8
|
+
order: z.enum(["asc", "desc"]).default("desc").describe("Creation order within the query."),
|
|
9
|
+
match: z.union([z.record(z.string(), z.json()), z.array(z.record(z.string(), z.json())).min(1).max(100)]).optional().describe("Exact JSON containment alternatives, evaluated by storage."),
|
|
10
|
+
since: z.iso.datetime({ precision: 3 }).optional().describe("Inclusive creation time, in UTC with milliseconds."),
|
|
11
|
+
until: z.iso.datetime({ precision: 3 }).optional().describe("Exclusive creation time, in UTC with milliseconds."),
|
|
12
|
+
includeTotal: z.boolean().optional(),
|
|
13
|
+
search: z.string().trim().min(1).max(200).transform((value) => value.toLowerCase()).optional(),
|
|
14
|
+
fields: z.array(z.discriminatedUnion("test", [z.strictObject({
|
|
15
|
+
path: z.array(z.string().min(1).max(128)).min(1).max(8),
|
|
16
|
+
test: z.enum([
|
|
17
|
+
"exists",
|
|
18
|
+
"missing",
|
|
19
|
+
"notEmpty"
|
|
20
|
+
])
|
|
21
|
+
}), z.strictObject({
|
|
22
|
+
path: z.array(z.string().min(1).max(128)).min(1).max(8),
|
|
23
|
+
test: z.enum([
|
|
24
|
+
"eq",
|
|
25
|
+
"ne",
|
|
26
|
+
"subset",
|
|
27
|
+
"oneOf"
|
|
28
|
+
]),
|
|
29
|
+
value: z.json()
|
|
30
|
+
})])).max(16).optional().describe("Additional bounded JSON-field predicates.")
|
|
31
|
+
});
|
|
32
|
+
function compareStorageText(left, right) {
|
|
33
|
+
return Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
34
|
+
}
|
|
35
|
+
function queryIdentity(collection, input) {
|
|
36
|
+
const { cursor: _cursor, limit: _limit, includeTotal: _includeTotal, ...query } = recordQuerySchema.parse(input);
|
|
37
|
+
function ordered(value) {
|
|
38
|
+
if (Array.isArray(value)) return value.map(ordered);
|
|
39
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => compareStorageText(a, b)).map(([key, entry]) => [key, ordered(entry)]));
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
return createHash("sha256").update(JSON.stringify([collection, ordered(query)])).digest("hex");
|
|
43
|
+
}
|
|
44
|
+
const recordTimeSchema = z.object({
|
|
45
|
+
createdAt: z.string().optional(),
|
|
46
|
+
receivedAt: z.string().optional(),
|
|
47
|
+
connectedAt: z.string().optional(),
|
|
48
|
+
recordedAt: z.string().optional(),
|
|
49
|
+
updatedAt: z.string().optional()
|
|
50
|
+
});
|
|
51
|
+
function recordTime(value) {
|
|
52
|
+
const fields = recordTimeSchema.parse(value);
|
|
53
|
+
return fields.createdAt ?? fields.receivedAt ?? fields.connectedAt ?? fields.recordedAt ?? fields.updatedAt ?? "1970-01-01T00:00:00.000Z";
|
|
54
|
+
}
|
|
55
|
+
function contains(value, match) {
|
|
56
|
+
if (Array.isArray(match)) return Array.isArray(value) && match.every((entry) => value.some((candidate) => contains(candidate, entry)));
|
|
57
|
+
if (match !== null && typeof match === "object") return value !== null && typeof value === "object" && !Array.isArray(value) && Object.entries(match).every(([key, entry]) => contains(Reflect.get(value, key), entry));
|
|
58
|
+
return value === match;
|
|
59
|
+
}
|
|
60
|
+
function matchesRecord(value, input) {
|
|
61
|
+
const alternatives = input.match === void 0 ? [{}] : Array.isArray(input.match) ? input.match : [input.match];
|
|
62
|
+
const time = input.since !== void 0 || input.until !== void 0 ? recordTime(value) : "";
|
|
63
|
+
return alternatives.some((match) => contains(value, match)) && (!input.search || input.search.toLowerCase().split(/[^\p{L}\p{N}_]+/u).filter(Boolean).every((term) => String(value !== null && typeof value === "object" ? Reflect.get(value, "searchText") ?? "" : "").toLowerCase().split(/[^\p{L}\p{N}_]+/u).includes(term))) && (input.fields ?? []).every((filter) => {
|
|
64
|
+
let selected = value;
|
|
65
|
+
for (const key of filter.path) selected = selected !== null && typeof selected === "object" && Object.hasOwn(selected, key) ? Reflect.get(selected, key) : void 0;
|
|
66
|
+
if (filter.test === "exists") return selected !== void 0;
|
|
67
|
+
if (filter.test === "missing") return selected === void 0;
|
|
68
|
+
if (filter.test === "notEmpty") return Array.isArray(selected) && selected.length > 0;
|
|
69
|
+
if (!("value" in filter)) throw new Error("Invalid field filter");
|
|
70
|
+
if (filter.test === "oneOf") return Array.isArray(filter.value) && filter.value.some((item) => isDeepStrictEqual(selected, item));
|
|
71
|
+
if (filter.test === "subset") return contains(filter.value, selected);
|
|
72
|
+
return filter.test === "eq" ? isDeepStrictEqual(selected, filter.value) : !isDeepStrictEqual(selected, filter.value);
|
|
73
|
+
}) && (input.since === void 0 || time >= input.since) && (input.until === void 0 || time < input.until);
|
|
74
|
+
}
|
|
75
|
+
function pageRecords(collection, values, input, identity) {
|
|
76
|
+
const query = recordQuerySchema.parse(input);
|
|
77
|
+
const scope = queryIdentity(collection, query);
|
|
78
|
+
const cursor = query.cursor === void 0 ? void 0 : z.strictObject({
|
|
79
|
+
scope: z.literal(scope),
|
|
80
|
+
time: z.string(),
|
|
81
|
+
id: z.string()
|
|
82
|
+
}).parse(JSON.parse(Buffer.from(query.cursor, "base64url").toString("utf8")));
|
|
83
|
+
const sign = query.order === "asc" ? 1 : -1;
|
|
84
|
+
const rows = values.filter((value) => matchesRecord(value, query)).map((value) => ({
|
|
85
|
+
value,
|
|
86
|
+
time: recordTime(value),
|
|
87
|
+
id: identity(value)
|
|
88
|
+
})).sort((left, right) => sign * (compareStorageText(left.time, right.time) || compareStorageText(left.id, right.id))).filter((row) => !cursor || sign * (compareStorageText(row.time, cursor.time) || compareStorageText(row.id, cursor.id)) > 0);
|
|
89
|
+
const page = rows.slice(0, query.limit);
|
|
90
|
+
const last = page.at(-1);
|
|
91
|
+
return {
|
|
92
|
+
items: page.map((row) => row.value),
|
|
93
|
+
nextCursor: rows.length > page.length && last ? Buffer.from(JSON.stringify({
|
|
94
|
+
scope,
|
|
95
|
+
time: last.time,
|
|
96
|
+
id: last.id
|
|
97
|
+
})).toString("base64url") : null
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { compareStorageText, matchesRecord, pageRecords, queryIdentity, recordQuerySchema };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=query.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.mjs","names":[],"sources":["../../src/store/query.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\nimport { isDeepStrictEqual } from \"node:util\";\n\n/** Additional JSON-field predicates, combined with containment matches using AND. */\nexport type FactoryRecordFieldFilter =\n | { readonly path: readonly string[]; readonly test: \"exists\" | \"missing\" | \"notEmpty\" }\n | {\n readonly path: readonly string[];\n readonly test: \"eq\" | \"ne\" | \"subset\" | \"oneOf\";\n readonly value: unknown;\n };\n\n/** Bounded canonical-record discovery; each match is JSON containment and alternatives are ORed. */\nexport interface FactoryRecordQuery {\n readonly limit: number;\n readonly cursor?: string;\n readonly order?: \"asc\" | \"desc\";\n readonly match?: Readonly<Record<string, unknown>> | readonly Readonly<Record<string, unknown>>[];\n readonly since?: string;\n readonly until?: string;\n readonly includeTotal?: boolean;\n readonly search?: string;\n readonly fields?: readonly FactoryRecordFieldFilter[];\n}\n\n/** One bounded page of validated canonical records. */\nexport interface FactoryRecordPage<T> {\n readonly items: readonly T[];\n readonly nextCursor: string | null;\n readonly totalCount?: number;\n}\n\nexport const recordQuerySchema = z.strictObject({\n limit: z.number().int().min(1).max(100).describe(\"Required maximum records per page.\"),\n cursor: z.string().max(8192).optional().describe(\"Continuation bound to this exact query.\"),\n order: z.enum([\"asc\", \"desc\"]).default(\"desc\").describe(\"Creation order within the query.\"),\n match: z\n .union([\n z.record(z.string(), z.json()),\n z.array(z.record(z.string(), z.json())).min(1).max(100),\n ])\n .optional()\n .describe(\"Exact JSON containment alternatives, evaluated by storage.\"),\n since: z.iso\n .datetime({ precision: 3 })\n .optional()\n .describe(\"Inclusive creation time, in UTC with milliseconds.\"),\n until: z.iso\n .datetime({ precision: 3 })\n .optional()\n .describe(\"Exclusive creation time, in UTC with milliseconds.\"),\n includeTotal: z.boolean().optional(),\n search: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .transform((value) => value.toLowerCase())\n .optional(),\n fields: z\n .array(\n z.discriminatedUnion(\"test\", [\n z.strictObject({\n path: z.array(z.string().min(1).max(128)).min(1).max(8),\n test: z.enum([\"exists\", \"missing\", \"notEmpty\"]),\n }),\n z.strictObject({\n path: z.array(z.string().min(1).max(128)).min(1).max(8),\n test: z.enum([\"eq\", \"ne\", \"subset\", \"oneOf\"]),\n value: z.json(),\n }),\n ]),\n )\n .max(16)\n .optional()\n .describe(\"Additional bounded JSON-field predicates.\"),\n});\n\nexport function compareStorageText(left: string, right: string): number {\n return Buffer.compare(Buffer.from(left), Buffer.from(right));\n}\n\nexport function queryIdentity(collection: string, input: FactoryRecordQuery): string {\n const {\n cursor: _cursor,\n limit: _limit,\n includeTotal: _includeTotal,\n ...query\n } = recordQuerySchema.parse(input);\n function ordered(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(ordered);\n if (value !== null && typeof value === \"object\")\n return Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => compareStorageText(a, b))\n .map(([key, entry]) => [key, ordered(entry)]),\n );\n return value;\n }\n return createHash(\"sha256\")\n .update(JSON.stringify([collection, ordered(query)]))\n .digest(\"hex\");\n}\n\nconst recordTimeSchema = z.object({\n createdAt: z.string().optional(),\n receivedAt: z.string().optional(),\n connectedAt: z.string().optional(),\n recordedAt: z.string().optional(),\n updatedAt: z.string().optional(),\n});\n\nfunction recordTime(value: unknown): string {\n const fields = recordTimeSchema.parse(value);\n return (\n fields.createdAt ??\n fields.receivedAt ??\n fields.connectedAt ??\n fields.recordedAt ??\n fields.updatedAt ??\n \"1970-01-01T00:00:00.000Z\"\n );\n}\n\nfunction contains(value: unknown, match: unknown): boolean {\n if (Array.isArray(match))\n return (\n Array.isArray(value) &&\n match.every((entry) => value.some((candidate) => contains(candidate, entry)))\n );\n if (match !== null && typeof match === \"object\")\n return (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.entries(match).every(([key, entry]) => contains(Reflect.get(value, key), entry))\n );\n return value === match;\n}\n\nexport function matchesRecord(value: unknown, input: FactoryRecordQuery): boolean {\n const alternatives =\n input.match === undefined ? [{}] : Array.isArray(input.match) ? input.match : [input.match];\n const time = input.since !== undefined || input.until !== undefined ? recordTime(value) : \"\";\n return (\n alternatives.some((match) => contains(value, match)) &&\n (!input.search ||\n input.search\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}_]+/u)\n .filter(Boolean)\n .every((term) =>\n String(\n value !== null && typeof value === \"object\"\n ? (Reflect.get(value, \"searchText\") ?? \"\")\n : \"\",\n )\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}_]+/u)\n .includes(term),\n )) &&\n (input.fields ?? []).every((filter) => {\n let selected = value;\n for (const key of filter.path)\n selected =\n selected !== null && typeof selected === \"object\" && Object.hasOwn(selected, key)\n ? Reflect.get(selected, key)\n : undefined;\n if (filter.test === \"exists\") return selected !== undefined;\n if (filter.test === \"missing\") return selected === undefined;\n if (filter.test === \"notEmpty\") return Array.isArray(selected) && selected.length > 0;\n if (!(\"value\" in filter)) throw new Error(\"Invalid field filter\");\n if (filter.test === \"oneOf\")\n return (\n Array.isArray(filter.value) &&\n filter.value.some((item) => isDeepStrictEqual(selected, item))\n );\n if (filter.test === \"subset\") return contains(filter.value, selected);\n return filter.test === \"eq\"\n ? isDeepStrictEqual(selected, filter.value)\n : !isDeepStrictEqual(selected, filter.value);\n }) &&\n (input.since === undefined || time >= input.since) &&\n (input.until === undefined || time < input.until)\n );\n}\n\nexport function pageRecords<T>(\n collection: string,\n values: readonly T[],\n input: FactoryRecordQuery,\n identity: (value: T) => string,\n): FactoryRecordPage<T> {\n const query = recordQuerySchema.parse(input);\n const scope = queryIdentity(collection, query);\n const cursor =\n query.cursor === undefined\n ? undefined\n : z\n .strictObject({\n scope: z.literal(scope),\n time: z.string(),\n id: z.string(),\n })\n .parse(JSON.parse(Buffer.from(query.cursor, \"base64url\").toString(\"utf8\")));\n const sign = query.order === \"asc\" ? 1 : -1;\n const rows = values\n .filter((value) => matchesRecord(value, query))\n .map((value) => ({ value, time: recordTime(value), id: identity(value) }))\n .sort(\n (left, right) =>\n sign * (compareStorageText(left.time, right.time) || compareStorageText(left.id, right.id)),\n )\n .filter(\n (row) =>\n !cursor ||\n sign *\n (compareStorageText(row.time, cursor.time) || compareStorageText(row.id, cursor.id)) >\n 0,\n );\n const page = rows.slice(0, query.limit);\n const last = page.at(-1);\n return {\n items: page.map((row) => row.value),\n nextCursor:\n rows.length > page.length && last\n ? Buffer.from(JSON.stringify({ scope, time: last.time, id: last.id })).toString(\"base64url\")\n : null,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { taskIdSchema } from "../schema/id.mjs";
|
|
2
|
+
import { agentSessionIdSchema, sessionContinuationSchema } from "../schema/session.mjs";
|
|
3
|
+
import { communicationEventSchema } from "../schema/communication.mjs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/store/session-history.ts
|
|
6
|
+
const sessionContinuationRecordSchema = z.strictObject({
|
|
7
|
+
id: z.string().regex(/^scont_[a-f0-9]{32}$/u),
|
|
8
|
+
sessionId: agentSessionIdSchema,
|
|
9
|
+
taskId: taskIdSchema,
|
|
10
|
+
previousId: z.string().regex(/^scont_[a-f0-9]{32}$/u).optional(),
|
|
11
|
+
committed: z.boolean(),
|
|
12
|
+
createdAt: z.iso.datetime(),
|
|
13
|
+
continuation: sessionContinuationSchema
|
|
14
|
+
});
|
|
15
|
+
const sessionNotificationRecordSchema = z.strictObject({
|
|
16
|
+
id: z.string().regex(/^snotice_[a-f0-9]{32}$/u),
|
|
17
|
+
sessionId: agentSessionIdSchema,
|
|
18
|
+
createdAt: z.iso.datetime(),
|
|
19
|
+
event: communicationEventSchema
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { sessionContinuationRecordSchema, sessionNotificationRecordSchema };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=session-history.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-history.mjs","names":[],"sources":["../../src/store/session-history.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { agentSessionIdSchema, sessionContinuationSchema } from \"../schema/session\";\nimport { communicationEventSchema } from \"../schema/communication\";\nimport { taskIdSchema } from \"../schema/id\";\n\nexport const sessionContinuationRecordSchema = z.strictObject({\n id: z.string().regex(/^scont_[a-f0-9]{32}$/u),\n sessionId: agentSessionIdSchema,\n taskId: taskIdSchema,\n previousId: z\n .string()\n .regex(/^scont_[a-f0-9]{32}$/u)\n .optional(),\n committed: z.boolean(),\n createdAt: z.iso.datetime(),\n continuation: sessionContinuationSchema,\n});\n\nexport const sessionNotificationRecordSchema = z.strictObject({\n id: z.string().regex(/^snotice_[a-f0-9]{32}$/u),\n sessionId: agentSessionIdSchema,\n createdAt: z.iso.datetime(),\n event: communicationEventSchema,\n});\n"],"mappings":";;;;;AAKA,MAAa,kCAAkC,EAAE,aAAa;CAC5D,IAAI,EAAE,OAAO,CAAC,CAAC,MAAM,uBAAuB;CAC5C,WAAW;CACX,QAAQ;CACR,YAAY,EACT,OAAO,CAAC,CACR,MAAM,uBAAuB,CAAC,CAC9B,SAAS;CACZ,WAAW,EAAE,QAAQ;CACrB,WAAW,EAAE,IAAI,SAAS;CAC1B,cAAc;AAChB,CAAC;AAED,MAAa,kCAAkC,EAAE,aAAa;CAC5D,IAAI,EAAE,OAAO,CAAC,CAAC,MAAM,yBAAyB;CAC9C,WAAW;CACX,WAAW,EAAE,IAAI,SAAS;CAC1B,OAAO;AACT,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RepositorySlug } from "../schema/repository.mjs";
|
|
2
1
|
import { StoreDriver } from "./driver.mjs";
|
|
2
|
+
import { RepositorySlug } from "../schema/repository.mjs";
|
|
3
3
|
import { SlackPrNotifications } from "../schema/slack-pr-notifications.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/store/slack-pr-notifications.d.ts
|
|
@@ -29,7 +29,7 @@ interface SlackPrNotificationStore {
|
|
|
29
29
|
}
|
|
30
30
|
/** Persistence scope and Git-configured fallback for PR notification preferences. */
|
|
31
31
|
interface SlackPrNotificationStoreOptions {
|
|
32
|
-
readonly driver: StoreDriver
|
|
32
|
+
readonly driver: Pick<StoreDriver, "get" | "insert" | "update">;
|
|
33
33
|
readonly connector: string;
|
|
34
34
|
readonly workspaceId: string;
|
|
35
35
|
/** Omit for the factory-wide default; supply a slug to isolate one repository's preferences. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-pr-notifications.mjs","names":[],"sources":["../../src/store/slack-pr-notifications.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\nimport {\n slackPrNotificationsSchema,\n type SlackPrNotifications,\n} from \"../schema/slack-pr-notifications\";\nimport type { StoreDriver } from \"./driver\";\nimport { repositorySlugSchema, type RepositorySlug } from \"../schema/repository\";\n\nconst preferenceSchema = z.union([slackPrNotificationsSchema, z.null(), z.literal(\"config\")]);\nconst commandSchema = z.strictObject({\n operationId: z\n .string()\n .min(1)\n .max(512)\n .describe(\"Stable identity of the authenticated Slack message.\"),\n actor: z\n .string()\n .regex(/^slack:T[A-Z0-9]+:[UW][A-Z0-9]+$/u)\n .describe(\"Authenticated Slack operator making this choice.\"),\n preference: preferenceSchema.describe(\n \"Destination override, null to disable, or config to restore the file default.\",\n ),\n});\nconst recordSchema = commandSchema.extend({\n schemaVersion: z.literal(1).describe(\"Persisted settings format version.\"),\n expectedRevision: z\n .number()\n .int()\n .nonnegative()\n .describe(\"Settings revision observed when this command was first recorded.\"),\n updatedAt: z.iso.datetime().describe(\"Time the command was first recorded.\"),\n});\n\n// Drivers loaded through separate package entry points have distinct error constructors.\nfunction isConflict(error: unknown, ...names: string[]): boolean {\n return error instanceof Error && names.includes(error.name);\n}\n\n/** Effective PR notification settings and the source of the current choice. */\nexport interface SlackPrNotificationSnapshot {\n readonly revision: number;\n readonly source: \"config\" | \"slack\";\n readonly settings: SlackPrNotifications | null;\n readonly updatedBy?: string;\n readonly updatedAt?: string;\n}\n\n/** A trusted Slack command: an object enables notifications, null disables them, and config resets. */\nexport type UpdateSlackPrNotificationsInput = z.infer<typeof commandSchema>;\n\n/** Durable PR notification preferences scoped to a connector, workspace, and optional repository. */\nexport interface SlackPrNotificationStore {\n get(): Promise<SlackPrNotificationSnapshot>;\n /** The caller authenticates the operator; operationId must identify the original Slack message. */\n update(input: UpdateSlackPrNotificationsInput): Promise<SlackPrNotificationSnapshot>;\n}\n\n/** Persistence scope and Git-configured fallback for PR notification preferences. */\nexport interface SlackPrNotificationStoreOptions {\n readonly driver: StoreDriver
|
|
1
|
+
{"version":3,"file":"slack-pr-notifications.mjs","names":[],"sources":["../../src/store/slack-pr-notifications.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\nimport {\n slackPrNotificationsSchema,\n type SlackPrNotifications,\n} from \"../schema/slack-pr-notifications\";\nimport type { StoreDriver } from \"./driver\";\nimport { repositorySlugSchema, type RepositorySlug } from \"../schema/repository\";\n\nconst preferenceSchema = z.union([slackPrNotificationsSchema, z.null(), z.literal(\"config\")]);\nconst commandSchema = z.strictObject({\n operationId: z\n .string()\n .min(1)\n .max(512)\n .describe(\"Stable identity of the authenticated Slack message.\"),\n actor: z\n .string()\n .regex(/^slack:T[A-Z0-9]+:[UW][A-Z0-9]+$/u)\n .describe(\"Authenticated Slack operator making this choice.\"),\n preference: preferenceSchema.describe(\n \"Destination override, null to disable, or config to restore the file default.\",\n ),\n});\nconst recordSchema = commandSchema.extend({\n schemaVersion: z.literal(1).describe(\"Persisted settings format version.\"),\n expectedRevision: z\n .number()\n .int()\n .nonnegative()\n .describe(\"Settings revision observed when this command was first recorded.\"),\n updatedAt: z.iso.datetime().describe(\"Time the command was first recorded.\"),\n});\n\n// Drivers loaded through separate package entry points have distinct error constructors.\nfunction isConflict(error: unknown, ...names: string[]): boolean {\n return error instanceof Error && names.includes(error.name);\n}\n\n/** Effective PR notification settings and the source of the current choice. */\nexport interface SlackPrNotificationSnapshot {\n readonly revision: number;\n readonly source: \"config\" | \"slack\";\n readonly settings: SlackPrNotifications | null;\n readonly updatedBy?: string;\n readonly updatedAt?: string;\n}\n\n/** A trusted Slack command: an object enables notifications, null disables them, and config resets. */\nexport type UpdateSlackPrNotificationsInput = z.infer<typeof commandSchema>;\n\n/** Durable PR notification preferences scoped to a connector, workspace, and optional repository. */\nexport interface SlackPrNotificationStore {\n get(): Promise<SlackPrNotificationSnapshot>;\n /** The caller authenticates the operator; operationId must identify the original Slack message. */\n update(input: UpdateSlackPrNotificationsInput): Promise<SlackPrNotificationSnapshot>;\n}\n\n/** Persistence scope and Git-configured fallback for PR notification preferences. */\nexport interface SlackPrNotificationStoreOptions {\n readonly driver: Pick<StoreDriver, \"get\" | \"insert\" | \"update\">;\n readonly connector: string;\n readonly workspaceId: string;\n /** Omit for the factory-wide default; supply a slug to isolate one repository's preferences. */\n readonly repository?: RepositorySlug;\n readonly defaults?: SlackPrNotifications | null;\n readonly now?: () => string;\n}\n\n/** Creates directly addressed settings with immutable command intents and compare-and-swap updates. */\nexport function createSlackPrNotificationStore({\n driver,\n connector,\n workspaceId,\n repository,\n defaults,\n now = () => new Date().toISOString(),\n}: SlackPrNotificationStoreOptions): SlackPrNotificationStore {\n z.string()\n .regex(/^slack\\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u)\n .parse(connector);\n z.string()\n .regex(/^T[A-Z0-9]+$/u)\n .parse(workspaceId);\n if (repository !== undefined) repositorySlugSchema.parse(repository);\n const fallback = defaults == null ? null : slackPrNotificationsSchema.parse(defaults);\n const hash = (value: string) => createHash(\"sha256\").update(value).digest(\"hex\");\n const scope =\n repository === undefined ? [connector, workspaceId] : [connector, workspaceId, repository];\n const id = `slack-pr-${hash(JSON.stringify(scope))}`;\n const collection = \"communicationSettings\";\n\n async function read() {\n const record = await driver.get(collection, id);\n return record === null\n ? null\n : { version: record.version, value: recordSchema.parse(record.value) };\n }\n\n function snapshot(record: Awaited<ReturnType<typeof read>>): SlackPrNotificationSnapshot {\n const preference = record === null ? \"config\" : record.value.preference;\n return {\n revision: record?.version ?? 0,\n source: preference === \"config\" ? \"config\" : \"slack\",\n settings: preference === \"config\" ? fallback : preference,\n ...(record && { updatedBy: record.value.actor, updatedAt: record.value.updatedAt }),\n };\n }\n\n async function update(value: UpdateSlackPrNotificationsInput) {\n const input = commandSchema.parse(value);\n if (!input.actor.startsWith(`slack:${workspaceId}:`))\n throw new Error(\"PR notification operator belongs to another Slack workspace.\");\n const commandId = `${id}-command-${hash(input.operationId)}`;\n let command = await driver.get(collection, commandId);\n if (command === null) {\n const current = await read();\n const intent = recordSchema.parse({\n ...input,\n schemaVersion: 1,\n expectedRevision: current?.version ?? 0,\n updatedAt: now(),\n });\n try {\n await driver.insert({ collection, id: commandId, value: intent });\n } catch (error) {\n if (!isConflict(error, \"RecordExistsError\")) throw error;\n }\n command = await driver.get(collection, commandId);\n }\n if (command === null) throw new Error(\"PR notification command was not saved.\");\n const intent = recordSchema.parse(command.value);\n if (\n JSON.stringify({\n operationId: intent.operationId,\n actor: intent.actor,\n preference: intent.preference,\n }) !== JSON.stringify(input)\n )\n throw new Error(\"This Slack message already requested different PR notification settings.\");\n\n const current = await read();\n if (current?.value.operationId === input.operationId) return snapshot(current);\n if ((current?.version ?? 0) !== intent.expectedRevision)\n throw new Error(\n \"PR notification settings changed after this command. Send a new Slack message to change them again.\",\n );\n try {\n if (current === null) await driver.insert({ collection, id, value: intent });\n else await driver.update({ collection, id, value: intent, expectedVersion: current.version });\n } catch (error) {\n if (!isConflict(error, \"RecordExistsError\", \"VersionConflictError\")) throw error;\n const winner = await read();\n if (winner?.value.operationId === input.operationId) return snapshot(winner);\n throw new Error(\n \"Another command changed PR notification settings. Send a new Slack message to change them again.\",\n { cause: error },\n );\n }\n return snapshot({ value: intent, version: intent.expectedRevision + 1 });\n }\n\n return { get: async () => snapshot(await read()), update };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IndexedStore } from "./indexed.mjs";
|
|
2
|
+
//#region src/store/state.d.ts
|
|
3
|
+
/** Conditional, directly addressed application JSON state; independent of artifact storage. */
|
|
4
|
+
interface ConditionalStateStore {
|
|
5
|
+
read(input: {
|
|
6
|
+
pathname: string;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
body: string;
|
|
9
|
+
etag: string;
|
|
10
|
+
} | null>;
|
|
11
|
+
write(input: {
|
|
12
|
+
pathname: string;
|
|
13
|
+
body: string;
|
|
14
|
+
overwrite: boolean;
|
|
15
|
+
ifMatch?: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
/** Background-only prefix discovery; callers must never use this for operator listings. */
|
|
18
|
+
listPaths(input: {
|
|
19
|
+
prefix: string;
|
|
20
|
+
}): Promise<readonly string[]>;
|
|
21
|
+
}
|
|
22
|
+
/** Persists application-owned JSON state and lossless chunks using indexed CAS transactions. */
|
|
23
|
+
declare function createIndexedStateStore(store: IndexedStore): ConditionalStateStore;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ConditionalStateStore, createIndexedStateStore };
|
|
26
|
+
//# sourceMappingURL=state.d.mts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/store/state.ts
|
|
4
|
+
const headSchema = z.object({
|
|
5
|
+
pathname: z.string(),
|
|
6
|
+
digest: z.string(),
|
|
7
|
+
chunksId: z.string().uuid(),
|
|
8
|
+
parts: z.number().int().min(1).max(1024)
|
|
9
|
+
});
|
|
10
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
11
|
+
const stateKey = (pathname) => `state_${hash(pathname)}`;
|
|
12
|
+
const prefixKey = (prefix) => `state-prefix:${hash(prefix)}`;
|
|
13
|
+
/** Persists application-owned JSON state and lossless chunks using indexed CAS transactions. */
|
|
14
|
+
function createIndexedStateStore(store) {
|
|
15
|
+
return {
|
|
16
|
+
async read({ pathname }) {
|
|
17
|
+
const row = await store.get("applicationState", stateKey(pathname));
|
|
18
|
+
if (!row) return null;
|
|
19
|
+
const head = headSchema.parse(row.value);
|
|
20
|
+
if (head.pathname !== pathname) throw new Error("Application state key mismatch");
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (let part = 0; part < head.parts; part++) {
|
|
23
|
+
const chunk = await store.get("applicationChunks", `${head.chunksId}_${part}`);
|
|
24
|
+
parts.push(Buffer.from(z.object({ data: z.string() }).parse(chunk?.value).data, "base64"));
|
|
25
|
+
}
|
|
26
|
+
const body = Buffer.concat(parts).toString("utf8");
|
|
27
|
+
if (hash(body) !== head.digest) throw new Error("Application state checksum mismatch");
|
|
28
|
+
return {
|
|
29
|
+
body,
|
|
30
|
+
etag: String(row.version)
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
async write({ pathname, body, overwrite, ifMatch }) {
|
|
34
|
+
z.string().min(1).max(2e3).parse(pathname);
|
|
35
|
+
JSON.parse(body);
|
|
36
|
+
const bytes = Buffer.from(body);
|
|
37
|
+
const parts = Math.max(1, Math.ceil(bytes.length / 49152));
|
|
38
|
+
const digest = hash(body);
|
|
39
|
+
const operationId = randomUUID();
|
|
40
|
+
const value = headSchema.parse({
|
|
41
|
+
pathname,
|
|
42
|
+
digest,
|
|
43
|
+
parts,
|
|
44
|
+
chunksId: operationId
|
|
45
|
+
});
|
|
46
|
+
const expectedVersion = overwrite ? z.coerce.number().int().positive().parse(ifMatch) : 0;
|
|
47
|
+
await store.transaction(async (tx) => {
|
|
48
|
+
for (let part = 0; part < parts; part++) {
|
|
49
|
+
const id = `${operationId}_${part}`;
|
|
50
|
+
await tx.write({
|
|
51
|
+
collection: "applicationChunks",
|
|
52
|
+
id,
|
|
53
|
+
expectedVersion: 0,
|
|
54
|
+
operationId: `${operationId}:${part}`,
|
|
55
|
+
value: { data: bytes.subarray(part * 48 * 1024, (part + 1) * 48 * 1024).toString("base64") },
|
|
56
|
+
indexes: []
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const prefixes = [...pathname.matchAll(/\//gu)].map((match) => pathname.slice(0, match.index + 1));
|
|
60
|
+
if (prefixes.length > 24) throw new Error("Application state path exceeds the prefix bound");
|
|
61
|
+
await tx.write({
|
|
62
|
+
collection: "applicationState",
|
|
63
|
+
id: stateKey(pathname),
|
|
64
|
+
expectedVersion,
|
|
65
|
+
operationId,
|
|
66
|
+
value,
|
|
67
|
+
indexes: prefixes.map((prefix) => ({
|
|
68
|
+
index: prefixKey(prefix),
|
|
69
|
+
sortKey: hash(pathname),
|
|
70
|
+
summary: { pathname }
|
|
71
|
+
}))
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
async listPaths({ prefix }) {
|
|
76
|
+
if (!prefix.endsWith("/")) throw new Error("State discovery requires a directory prefix ending in /");
|
|
77
|
+
const paths = [];
|
|
78
|
+
let cursor;
|
|
79
|
+
do {
|
|
80
|
+
const page = await store.query({
|
|
81
|
+
index: prefixKey(prefix),
|
|
82
|
+
limit: 100,
|
|
83
|
+
cursor
|
|
84
|
+
});
|
|
85
|
+
paths.push(...page.entries.map((entry) => z.object({ pathname: z.string() }).parse(entry.summary).pathname));
|
|
86
|
+
cursor = page.nextCursor ?? void 0;
|
|
87
|
+
} while (cursor);
|
|
88
|
+
return paths;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { createIndexedStateStore };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/store/state.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { z } from \"zod\";\nimport type { IndexedStore } from \"./indexed\";\n\n/** Conditional, directly addressed application JSON state; independent of artifact storage. */\nexport interface ConditionalStateStore {\n read(input: { pathname: string }): Promise<{ body: string; etag: string } | null>;\n write(input: {\n pathname: string;\n body: string;\n overwrite: boolean;\n ifMatch?: string;\n }): Promise<void>;\n /** Background-only prefix discovery; callers must never use this for operator listings. */\n listPaths(input: { prefix: string }): Promise<readonly string[]>;\n}\nconst headSchema = z.object({\n pathname: z.string(),\n digest: z.string(),\n chunksId: z.string().uuid(),\n parts: z.number().int().min(1).max(1024),\n});\nconst hash = (value: string) => createHash(\"sha256\").update(value).digest(\"hex\");\nconst stateKey = (pathname: string) => `state_${hash(pathname)}`;\nconst prefixKey = (prefix: string) => `state-prefix:${hash(prefix)}`;\n\n/** Persists application-owned JSON state and lossless chunks using indexed CAS transactions. */\nexport function createIndexedStateStore(store: IndexedStore): ConditionalStateStore {\n return {\n async read({ pathname }) {\n const row = await store.get(\"applicationState\", stateKey(pathname));\n if (!row) return null;\n const head = headSchema.parse(row.value);\n if (head.pathname !== pathname) throw new Error(\"Application state key mismatch\");\n const parts: Buffer[] = [];\n for (let part = 0; part < head.parts; part++) {\n const chunk = await store.get(\"applicationChunks\", `${head.chunksId}_${part}`);\n parts.push(Buffer.from(z.object({ data: z.string() }).parse(chunk?.value).data, \"base64\"));\n }\n const body = Buffer.concat(parts).toString(\"utf8\");\n if (hash(body) !== head.digest) throw new Error(\"Application state checksum mismatch\");\n return { body, etag: String(row.version) };\n },\n async write({ pathname, body, overwrite, ifMatch }) {\n z.string().min(1).max(2000).parse(pathname);\n // Validate JSON before acquiring a connection. Schemas belong to the owning application.\n JSON.parse(body);\n const bytes = Buffer.from(body);\n const parts = Math.max(1, Math.ceil(bytes.length / (48 * 1024)));\n const digest = hash(body);\n const operationId = randomUUID();\n const value = headSchema.parse({ pathname, digest, parts, chunksId: operationId });\n const expectedVersion = overwrite ? z.coerce.number().int().positive().parse(ifMatch) : 0;\n await store.transaction(async (tx) => {\n for (let part = 0; part < parts; part++) {\n const id = `${operationId}_${part}`;\n await tx.write({\n collection: \"applicationChunks\",\n id,\n expectedVersion: 0,\n operationId: `${operationId}:${part}`,\n value: {\n data: bytes.subarray(part * 48 * 1024, (part + 1) * 48 * 1024).toString(\"base64\"),\n },\n indexes: [],\n });\n }\n const prefixes = [...pathname.matchAll(/\\//gu)].map((match) =>\n pathname.slice(0, match.index + 1),\n );\n if (prefixes.length > 24)\n throw new Error(\"Application state path exceeds the prefix bound\");\n await tx.write({\n collection: \"applicationState\",\n id: stateKey(pathname),\n expectedVersion,\n operationId,\n value,\n indexes: prefixes.map((prefix) => ({\n index: prefixKey(prefix),\n sortKey: hash(pathname),\n summary: { pathname },\n })),\n });\n });\n },\n async listPaths({ prefix }) {\n if (!prefix.endsWith(\"/\"))\n throw new Error(\"State discovery requires a directory prefix ending in /\");\n const paths: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await store.query({ index: prefixKey(prefix), limit: 100, cursor });\n paths.push(\n ...page.entries.map(\n (entry) => z.object({ pathname: z.string() }).parse(entry.summary).pathname,\n ),\n );\n cursor = page.nextCursor ?? undefined;\n } while (cursor);\n return paths;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { StoreDriver } from "./driver.mjs";
|
|
2
3
|
import { Task } from "../schema/task.mjs";
|
|
3
4
|
import { IsoDateTimeClock } from "../schema/common.mjs";
|
|
4
|
-
import { StoreDriver } from "./driver.mjs";
|
|
5
5
|
import { TaskEffect, TaskEffectKey, TaskEffectReference } from "../schema/task-effect.mjs";
|
|
6
6
|
//#region src/store/task-effects.d.ts
|
|
7
7
|
/** Persistence, clock, and Task lookup dependencies for the Task effect store. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { StoreDriver } from "./driver.mjs";
|
|
2
3
|
import { Task } from "../schema/task.mjs";
|
|
3
4
|
import { WorkNotification } from "./work-notifications.mjs";
|
|
4
5
|
import { IsoDateTimeClock } from "../schema/common.mjs";
|
|
5
|
-
import { StoreDriver } from "./driver.mjs";
|
|
6
6
|
import { TaskGraph } from "../schema/task-graph.mjs";
|
|
7
7
|
//#region src/store/task-graphs.d.ts
|
|
8
8
|
/** Engine-owned persistence and validation dependencies for canonical Task graphs. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { StoreDriver } from "./driver.mjs";
|
|
2
3
|
import { Task } from "../schema/task.mjs";
|
|
3
4
|
import { SendTaskMessageInput, TaskMessage, TaskMessageClaim, TaskMessageDiscard, TaskMessageId, TaskMessagePrincipal, TaskMessageProcessing, TaskMessageProtocolBinding, TaskMessageProtocols } from "../schema/task-message.mjs";
|
|
4
5
|
import { WorkNotification } from "./work-notifications.mjs";
|
|
5
6
|
import { DateClock } from "../schema/common.mjs";
|
|
6
7
|
import { TaskAttemptRef } from "../schema/execution.mjs";
|
|
7
|
-
import { StoreDriver } from "./driver.mjs";
|
|
8
8
|
//#region src/store/task-messages.d.ts
|
|
9
9
|
/** Operation categories evaluated by the Task message authorization boundary. */
|
|
10
10
|
type TaskMessageAction = "send" | "send_result" | "read" | "claim" | "complete" | "discard";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { StoreDriver } from "./driver.mjs";
|
|
2
3
|
import { JsonValue, WorkflowBinding } from "../schema/work.mjs";
|
|
3
4
|
import { ExecutionReference, Task, TaskState } from "../schema/task.mjs";
|
|
4
5
|
import { WorkflowRegistry } from "../workflows.mjs";
|
|
5
6
|
import { IsoDateTimeClock } from "../schema/common.mjs";
|
|
6
7
|
import { TaskAttemptRef } from "../schema/execution.mjs";
|
|
7
|
-
import { StoreDriver } from "./driver.mjs";
|
|
8
8
|
import { FactoryStores } from "./engine.mjs";
|
|
9
9
|
//#region src/store/task-work.d.ts
|
|
10
10
|
/** Workflow-facing Task operations for completion, message waiting, recovery, and usage aggregation. */
|
package/dist/store/task-work.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { taskSchema } from "../schema/task.mjs";
|
|
2
|
-
import { InvalidTransitionError } from "../schema/transitions.mjs";
|
|
3
2
|
import { RecordNotFoundError, VersionConflictError } from "./driver.mjs";
|
|
3
|
+
import { InvalidTransitionError } from "../schema/transitions.mjs";
|
|
4
4
|
import { assertTaskAdmission } from "./task-graphs.mjs";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { isDeepStrictEqual } from "node:util";
|