@tangleai/store 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/package.json +84 -0
- package/src/db.d.ts +27 -0
- package/src/db.js +28 -0
- package/src/document-store.d.ts +3 -0
- package/src/document-store.js +124 -0
- package/src/identities.d.ts +27 -0
- package/src/identities.js +43 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +10 -0
- package/src/ledger-storage.d.ts +3 -0
- package/src/ledger-storage.js +46 -0
- package/src/mas-jobs.d.ts +121 -0
- package/src/mas-jobs.js +194 -0
- package/src/mas-store.d.ts +28 -0
- package/src/mas-store.js +524 -0
- package/src/memory-store.d.ts +28 -0
- package/src/memory-store.js +52 -0
- package/src/model.d.ts +446 -0
- package/src/model.js +242 -0
- package/src/runs.d.ts +74 -0
- package/src/runs.js +104 -0
package/src/runs.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run log — what makes the DAG surface historical.
|
|
3
|
+
*
|
|
4
|
+
* A run is one pass of the pipeline; an event is one DAG node record
|
|
5
|
+
* inside it (`{ node, status, ms }`, the exact shape @jarenjs/flow's
|
|
6
|
+
* `onNode` observer emits, plus a timestamp). The desktop app renders
|
|
7
|
+
* the current run live from these and any past run from the same rows —
|
|
8
|
+
* one storage shape, both tenses.
|
|
9
|
+
*
|
|
10
|
+
* Run ids are content-addressed from (startedAt, kind, sequence) so an
|
|
11
|
+
* injected `now` makes tests deterministic; event ids are
|
|
12
|
+
* `<runId>:<seq>` with a zero-padded seq so lexicographic id order IS
|
|
13
|
+
* event order.
|
|
14
|
+
*/
|
|
15
|
+
import type { TangleDb } from './db.ts';
|
|
16
|
+
export interface RunRecord {
|
|
17
|
+
id: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
startedAt: string;
|
|
20
|
+
finishedAt: string | null;
|
|
21
|
+
status: 'running' | 'ok' | 'error';
|
|
22
|
+
summary: any;
|
|
23
|
+
/** The content-addressed config identity that produced this run. Absent on rows written before identities were recorded. */
|
|
24
|
+
identityId?: string;
|
|
25
|
+
}
|
|
26
|
+
/** How a run relates to the identity table when read back. */
|
|
27
|
+
export type RunIdentityStatus = 'run' | 'legacy-unrecorded';
|
|
28
|
+
/** A run row as reads return it: the stored record plus its honest identity status. */
|
|
29
|
+
export type RunView = RunRecord & {
|
|
30
|
+
identityStatus: RunIdentityStatus;
|
|
31
|
+
};
|
|
32
|
+
export interface RunEvent {
|
|
33
|
+
id: string;
|
|
34
|
+
runId: string;
|
|
35
|
+
seq: number;
|
|
36
|
+
node: string;
|
|
37
|
+
status: string;
|
|
38
|
+
ms: number;
|
|
39
|
+
at: string;
|
|
40
|
+
}
|
|
41
|
+
export interface RunLog {
|
|
42
|
+
startRun(kind: string, options?: {
|
|
43
|
+
identityId?: string;
|
|
44
|
+
}): Promise<RunRecord>;
|
|
45
|
+
recordEvent(runId: string, record: {
|
|
46
|
+
id: string;
|
|
47
|
+
status: string;
|
|
48
|
+
ms: number;
|
|
49
|
+
}): Promise<RunEvent>;
|
|
50
|
+
/** Attach the finalized identity to a running run — before any corpus write it authorizes. */
|
|
51
|
+
attachIdentity(runId: string, identityId: string): Promise<void>;
|
|
52
|
+
finishRun(runId: string, status: 'ok' | 'error', summary?: any): Promise<{
|
|
53
|
+
ok: true;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
reason: string;
|
|
57
|
+
}>;
|
|
58
|
+
listRuns(limit?: number): Promise<RunView[]>;
|
|
59
|
+
getRun(id: string): Promise<{
|
|
60
|
+
run: RunView;
|
|
61
|
+
events: RunEvent[];
|
|
62
|
+
} | undefined>;
|
|
63
|
+
}
|
|
64
|
+
export interface RunLogOptions {
|
|
65
|
+
now?: () => string;
|
|
66
|
+
/**
|
|
67
|
+
* Run kinds that must carry a config identity by the time they
|
|
68
|
+
* finish. A finish without one is refused as a value: the run is
|
|
69
|
+
* closed as an error naming the absence, never silently completed —
|
|
70
|
+
* a run that cannot say what stack produced it is not "ok".
|
|
71
|
+
*/
|
|
72
|
+
configAwareKinds?: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
export declare function createRunLog(db: TangleDb, options?: RunLogOptions): RunLog;
|
package/src/runs.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run log — what makes the DAG surface historical.
|
|
3
|
+
*
|
|
4
|
+
* A run is one pass of the pipeline; an event is one DAG node record
|
|
5
|
+
* inside it (`{ node, status, ms }`, the exact shape @jarenjs/flow's
|
|
6
|
+
* `onNode` observer emits, plus a timestamp). The desktop app renders
|
|
7
|
+
* the current run live from these and any past run from the same rows —
|
|
8
|
+
* one storage shape, both tenses.
|
|
9
|
+
*
|
|
10
|
+
* Run ids are content-addressed from (startedAt, kind, sequence) so an
|
|
11
|
+
* injected `now` makes tests deterministic; event ids are
|
|
12
|
+
* `<runId>:<seq>` with a zero-padded seq so lexicographic id order IS
|
|
13
|
+
* event order.
|
|
14
|
+
*/
|
|
15
|
+
import { hashContent } from '@jarenjs/core/string';
|
|
16
|
+
import { asRows } from "./memory-store.js";
|
|
17
|
+
const seqKey = (n) => String(n).padStart(4, '0');
|
|
18
|
+
const viewOf = (run) => ({
|
|
19
|
+
...run,
|
|
20
|
+
identityStatus: typeof run.identityId === 'string' ? 'run' : 'legacy-unrecorded',
|
|
21
|
+
});
|
|
22
|
+
export function createRunLog(db, options = {}) {
|
|
23
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
24
|
+
const configAware = new Set(options.configAwareKinds ?? []);
|
|
25
|
+
const runs = db.collection('runs');
|
|
26
|
+
const events = db.collection('events');
|
|
27
|
+
let sequence = 0;
|
|
28
|
+
const eventSeq = new Map();
|
|
29
|
+
return {
|
|
30
|
+
async startRun(kind, startOptions = {}) {
|
|
31
|
+
const startedAt = now();
|
|
32
|
+
sequence += 1;
|
|
33
|
+
const id = `r-${hashContent(`${startedAt}|${kind}|${sequence}`)}`;
|
|
34
|
+
const run = { id, kind, startedAt, finishedAt: null, status: 'running', summary: null };
|
|
35
|
+
if (startOptions.identityId !== undefined)
|
|
36
|
+
run.identityId = startOptions.identityId;
|
|
37
|
+
await runs.put(run);
|
|
38
|
+
eventSeq.set(id, 0);
|
|
39
|
+
return run;
|
|
40
|
+
},
|
|
41
|
+
async attachIdentity(runId, identityId) {
|
|
42
|
+
const run = await runs.get(runId);
|
|
43
|
+
if (run === undefined)
|
|
44
|
+
return;
|
|
45
|
+
await runs.put({ ...run, identityId });
|
|
46
|
+
},
|
|
47
|
+
async recordEvent(runId, record) {
|
|
48
|
+
const seq = (eventSeq.get(runId) ?? 0) + 1;
|
|
49
|
+
eventSeq.set(runId, seq);
|
|
50
|
+
const event = {
|
|
51
|
+
id: `${runId}:${seqKey(seq)}`,
|
|
52
|
+
runId,
|
|
53
|
+
seq,
|
|
54
|
+
node: record.id,
|
|
55
|
+
status: record.status,
|
|
56
|
+
ms: record.ms,
|
|
57
|
+
at: now(),
|
|
58
|
+
};
|
|
59
|
+
await events.put(event);
|
|
60
|
+
return event;
|
|
61
|
+
},
|
|
62
|
+
async finishRun(runId, status, summary = null) {
|
|
63
|
+
const run = await runs.get(runId);
|
|
64
|
+
if (run === undefined)
|
|
65
|
+
return { ok: false, reason: `run '${runId}' does not exist` };
|
|
66
|
+
eventSeq.delete(runId);
|
|
67
|
+
if (status === 'ok' && configAware.has(run.kind) && run.identityId === undefined) {
|
|
68
|
+
const reason = 'the run carries no config identity; a config-aware run cannot complete without saying what stack produced it';
|
|
69
|
+
await runs.put({ ...run, finishedAt: now(), status: 'error', summary: { ...(summary ?? {}), refused: reason } });
|
|
70
|
+
return { ok: false, reason };
|
|
71
|
+
}
|
|
72
|
+
await runs.put({ ...run, finishedAt: now(), status, summary });
|
|
73
|
+
return { ok: true };
|
|
74
|
+
},
|
|
75
|
+
async listRuns(limit = 50) {
|
|
76
|
+
if (!Number.isSafeInteger(limit) || limit < 0)
|
|
77
|
+
throw new TypeError('run limit must be a non-negative integer');
|
|
78
|
+
if (limit === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const rows = [];
|
|
81
|
+
const cursor = runs.query({
|
|
82
|
+
$for: { r: '$[*]' }, $orderby: { $key: '$r.startedAt', $dir: 'desc' }, $return: '$r',
|
|
83
|
+
});
|
|
84
|
+
for await (const row of cursor) {
|
|
85
|
+
rows.push(viewOf(row));
|
|
86
|
+
if (rows.length === limit)
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
return rows;
|
|
90
|
+
},
|
|
91
|
+
async getRun(id) {
|
|
92
|
+
const run = await runs.get(id);
|
|
93
|
+
if (run === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
const rows = asRows(await events.execute({
|
|
96
|
+
$for: { e: '$[*]' },
|
|
97
|
+
$where: { $eq: ['$e.runId', { $const: id }] },
|
|
98
|
+
$orderby: '$e.seq',
|
|
99
|
+
$return: '$e',
|
|
100
|
+
}));
|
|
101
|
+
return { run: viewOf(run), events: rows };
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|