@substrat-run/kernel 0.119.0 → 0.121.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/list-index.d.ts.map +1 -1
- package/dist/list-index.js +5 -2
- package/dist/list-index.js.map +1 -1
- package/dist/module-migrations.d.ts +26 -0
- package/dist/module-migrations.d.ts.map +1 -0
- package/dist/module-migrations.js +21 -0
- package/dist/module-migrations.js.map +1 -0
- package/dist/platform-call.d.ts +20 -0
- package/dist/platform-call.d.ts.map +1 -1
- package/dist/platform-call.js +24 -0
- package/dist/platform-call.js.map +1 -1
- package/dist/platform-sweep.d.ts +157 -4
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +574 -45
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-host.d.ts +110 -5
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js.map +1 -1
- package/dist/sql-limits.d.ts +70 -0
- package/dist/sql-limits.d.ts.map +1 -0
- package/dist/sql-limits.js +197 -0
- package/dist/sql-limits.js.map +1 -0
- package/dist/system-switch-record.d.ts +158 -0
- package/dist/system-switch-record.d.ts.map +1 -0
- package/dist/system-switch-record.js +227 -0
- package/dist/system-switch-record.js.map +1 -0
- package/dist/ulid.d.ts +6 -0
- package/dist/ulid.d.ts.map +1 -1
- package/dist/ulid.js +21 -7
- package/dist/ulid.js.map +1 -1
- package/dist/vertical-events.d.ts +96 -2
- package/dist/vertical-events.d.ts.map +1 -1
- package/dist/vertical-events.js +230 -1
- package/dist/vertical-events.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The table. Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees the
|
|
3
|
+
* one spelling on each side. No CHECK on `position`: the drift gate does not compare
|
|
4
|
+
* constraints, and the writers below are the only thing that sets it.
|
|
5
|
+
*/
|
|
6
|
+
export const SYSTEM_SWITCHES_DDL = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS _substrat_system_switches (
|
|
8
|
+
tenant_id TEXT NOT NULL,
|
|
9
|
+
scope_id TEXT NOT NULL,
|
|
10
|
+
module_id TEXT NOT NULL,
|
|
11
|
+
-- 'on' or 'off'. A row exists only for a module some switch call actually held.
|
|
12
|
+
position TEXT NOT NULL,
|
|
13
|
+
actor TEXT NOT NULL,
|
|
14
|
+
reason TEXT NOT NULL,
|
|
15
|
+
-- The switch call's own id, the one its admin-log intent and outcome rows carry.
|
|
16
|
+
-- A ULID, so it is also the fleet read's chronological keyset cursor.
|
|
17
|
+
operation_id TEXT NOT NULL,
|
|
18
|
+
switched_at TEXT NOT NULL,
|
|
19
|
+
PRIMARY KEY (tenant_id, scope_id, module_id)
|
|
20
|
+
);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS _substrat_system_switches_position
|
|
22
|
+
ON _substrat_system_switches (position, operation_id);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS _substrat_system_switches_operation
|
|
24
|
+
ON _substrat_system_switches (operation_id);
|
|
25
|
+
`;
|
|
26
|
+
/**
|
|
27
|
+
* The one-time backfill from the admin log: every switch moved before the record existed.
|
|
28
|
+
* Without it, those are exactly the switches a wipe would still lose.
|
|
29
|
+
*
|
|
30
|
+
* It reads the admin-log shape `revokeFromSystem` / `restoreToSystem` have written since
|
|
31
|
+
* #1666, two rows per call sharing `after.operationId`: an `intent` row carrying
|
|
32
|
+
* `after.moduleId` and `after.reason`, then an outcome row whose `after.phase` is
|
|
33
|
+
* `applied`, `refused` or `failed`. Only calls whose outcome is `applied` count, because a
|
|
34
|
+
* refused call moved nothing and a failed one may not have. Of those, only the LATEST per
|
|
35
|
+
* (tenant, scope, module) is taken, by the intent's ULID id. `at` is the intent's, the
|
|
36
|
+
* same instant the status read's who/why join reports. A key whose calls include no
|
|
37
|
+
* applied OFF gets no row, even when its latest applied call is an ON: a live
|
|
38
|
+
* `restoreToSystem` of a module never switched off writes none either
|
|
39
|
+
* (`recordSystemSwitchedOn`), and the backfill must not record what the live path would not.
|
|
40
|
+
*
|
|
41
|
+
* `INSERT OR IGNORE`, so a row the switch wrote itself is never overwritten by history.
|
|
42
|
+
* The adapters run it only on the construction that creates the table, so it runs once.
|
|
43
|
+
*/
|
|
44
|
+
export const SYSTEM_SWITCHES_BACKFILL_SQL = `
|
|
45
|
+
WITH applied AS (
|
|
46
|
+
SELECT DISTINCT action, json_extract(after, '$.operationId') AS operation_id
|
|
47
|
+
FROM _substrat_admin_log
|
|
48
|
+
WHERE action IN ('revokeFromSystem', 'restoreToSystem')
|
|
49
|
+
AND json_extract(after, '$.phase') = 'applied'
|
|
50
|
+
),
|
|
51
|
+
intents AS (
|
|
52
|
+
SELECT i.id AS id, i.tenant_id AS tenant_id, i.scope_id AS scope_id,
|
|
53
|
+
json_extract(i.after, '$.moduleId') AS module_id,
|
|
54
|
+
CASE i.action WHEN 'revokeFromSystem' THEN 'off' ELSE 'on' END AS position,
|
|
55
|
+
i.actor AS actor,
|
|
56
|
+
json_extract(i.after, '$.reason') AS reason,
|
|
57
|
+
json_extract(i.after, '$.operationId') AS operation_id,
|
|
58
|
+
i.at AS at
|
|
59
|
+
FROM _substrat_admin_log i
|
|
60
|
+
JOIN applied a
|
|
61
|
+
ON a.action = i.action AND a.operation_id = json_extract(i.after, '$.operationId')
|
|
62
|
+
WHERE i.action IN ('revokeFromSystem', 'restoreToSystem')
|
|
63
|
+
AND i.tenant_id IS NOT NULL AND i.scope_id IS NOT NULL
|
|
64
|
+
AND json_extract(i.after, '$.phase') = 'intent'
|
|
65
|
+
AND json_extract(i.after, '$.moduleId') IS NOT NULL
|
|
66
|
+
AND json_extract(i.after, '$.reason') IS NOT NULL
|
|
67
|
+
),
|
|
68
|
+
ranked AS (
|
|
69
|
+
SELECT *,
|
|
70
|
+
ROW_NUMBER() OVER (PARTITION BY tenant_id, scope_id, module_id ORDER BY id DESC) AS latest,
|
|
71
|
+
MAX(position = 'off') OVER (PARTITION BY tenant_id, scope_id, module_id) AS ever_off
|
|
72
|
+
FROM intents
|
|
73
|
+
)
|
|
74
|
+
INSERT OR IGNORE INTO _substrat_system_switches
|
|
75
|
+
(tenant_id, scope_id, module_id, position, actor, reason, operation_id, switched_at)
|
|
76
|
+
SELECT tenant_id, scope_id, module_id, position, actor, reason, operation_id, at
|
|
77
|
+
FROM ranked
|
|
78
|
+
WHERE latest = 1 AND ever_off = 1
|
|
79
|
+
`;
|
|
80
|
+
/** "Does the record table exist yet?" — asked BEFORE the DDL, so the backfill runs once. */
|
|
81
|
+
export function systemSwitchesTableExists(db) {
|
|
82
|
+
return (db.all(`SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = '_substrat_system_switches'`)
|
|
83
|
+
.length > 0);
|
|
84
|
+
}
|
|
85
|
+
const positionOf = (v) => (v === 'on' ? 'on' : 'off');
|
|
86
|
+
const COLUMNS = `r.tenant_id, r.scope_id, r.module_id, s.vertical, r.position, r.actor, r.reason,
|
|
87
|
+
r.operation_id, r.switched_at`;
|
|
88
|
+
const rowOf = (r) => ({
|
|
89
|
+
tenantId: String(r.tenant_id),
|
|
90
|
+
scopeId: String(r.scope_id),
|
|
91
|
+
moduleId: String(r.module_id),
|
|
92
|
+
vertical: r.vertical == null ? null : String(r.vertical),
|
|
93
|
+
position: positionOf(r.position),
|
|
94
|
+
actor: String(r.actor),
|
|
95
|
+
reason: String(r.reason),
|
|
96
|
+
operationId: String(r.operation_id),
|
|
97
|
+
at: String(r.switched_at),
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* The fleet read (`GET /system-switches`): records, keyset-paged by `operation_id`.
|
|
101
|
+
*
|
|
102
|
+
* The key is the call that last moved the switch, so a row whose switch moves during a walk
|
|
103
|
+
* is re-keyed to a newer id and jumps to the end of the order. Ascending (the default) never
|
|
104
|
+
* skips a row for it; at worst it is seen twice, once per position. A DESCENDING walk can
|
|
105
|
+
* miss a row that moved after the walk started, so use ascending for "every switch".
|
|
106
|
+
*/
|
|
107
|
+
export function listSystemSwitchRecords(db, filter = {}) {
|
|
108
|
+
const where = [];
|
|
109
|
+
const params = [];
|
|
110
|
+
const eq = (col, v) => {
|
|
111
|
+
if (v !== undefined) {
|
|
112
|
+
where.push(`${col} = ?`);
|
|
113
|
+
params.push(v);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
eq('r.position', filter.position);
|
|
117
|
+
eq('r.tenant_id', filter.tenantId);
|
|
118
|
+
eq('r.scope_id', filter.scopeId);
|
|
119
|
+
eq('r.module_id', filter.moduleId);
|
|
120
|
+
eq('s.vertical', filter.vertical);
|
|
121
|
+
const order = filter.order === 'desc' ? 'DESC' : 'ASC';
|
|
122
|
+
if (filter.cursor !== undefined) {
|
|
123
|
+
where.push(order === 'DESC' ? 'r.operation_id < ?' : 'r.operation_id > ?');
|
|
124
|
+
params.push(filter.cursor);
|
|
125
|
+
}
|
|
126
|
+
// LEFT JOIN: a record outlives nothing it needs from `scopes`, and a scope row the
|
|
127
|
+
// directory lost must not hide a switch that is still off in the scope's store.
|
|
128
|
+
let sql = `SELECT ${COLUMNS} FROM _substrat_system_switches r LEFT JOIN scopes s ON s.scope_id = r.scope_id` +
|
|
129
|
+
(where.length ? ` WHERE ${where.join(' AND ')}` : '') +
|
|
130
|
+
` ORDER BY r.operation_id ${order}`;
|
|
131
|
+
if (filter.limit !== undefined) {
|
|
132
|
+
sql += ` LIMIT ${Math.max(0, Math.floor(filter.limit))}`;
|
|
133
|
+
}
|
|
134
|
+
return db.all(sql, ...params).map(rowOf);
|
|
135
|
+
}
|
|
136
|
+
/** One scope's record rows by module — what the per-scope status read joins as `recorded`. */
|
|
137
|
+
export function systemSwitchRecordsOf(db, tenantId, scopeId) {
|
|
138
|
+
const rows = db.all(`SELECT module_id, position FROM _substrat_system_switches WHERE tenant_id = ? AND scope_id = ?`, tenantId, scopeId);
|
|
139
|
+
return new Map(rows.map((r) => [String(r.module_id), positionOf(r.position)]));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The per-scope status read's rows with the record joined on. Each module the scope
|
|
143
|
+
* reports gets its recorded position (or null). A module the record holds that the scope
|
|
144
|
+
* no longer reports at all is ADDED, as `ungranted`: that is the scope a wipe emptied, and
|
|
145
|
+
* the drift is the whole reason to show it. Sorted by module id, as the scope's rows are.
|
|
146
|
+
*/
|
|
147
|
+
export function withRecorded(states, recorded) {
|
|
148
|
+
const rows = states.map((s) => ({ ...s, recorded: recorded.get(s.moduleId) ?? null }));
|
|
149
|
+
const reported = new Set(states.map((s) => s.moduleId));
|
|
150
|
+
for (const [moduleId, position] of recorded) {
|
|
151
|
+
if (!reported.has(moduleId))
|
|
152
|
+
rows.push({ moduleId, schedules: 'ungranted', recorded: position });
|
|
153
|
+
}
|
|
154
|
+
return rows.sort((a, b) => (a.moduleId < b.moduleId ? -1 : a.moduleId > b.moduleId ? 1 : 0));
|
|
155
|
+
}
|
|
156
|
+
/** The modules a reconcile must re-assert OFF on this scope. */
|
|
157
|
+
export function switchedOffModulesOf(db, tenantId, scopeId) {
|
|
158
|
+
return db
|
|
159
|
+
.all(`SELECT module_id FROM _substrat_system_switches
|
|
160
|
+
WHERE tenant_id = ? AND scope_id = ? AND position = 'off' ORDER BY module_id`, tenantId, scopeId)
|
|
161
|
+
.map((r) => String(r.module_id));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Overwrite one existing row's position and provenance — ON's write, and its undo. With
|
|
165
|
+
* `ifOperationId`, only while the row is still the one that operation wrote (a CAS).
|
|
166
|
+
*/
|
|
167
|
+
function setRecordRow(db, key, row, ifOperationId) {
|
|
168
|
+
db.run(`UPDATE _substrat_system_switches
|
|
169
|
+
SET position = ?, actor = ?, reason = ?, operation_id = ?, switched_at = ?
|
|
170
|
+
WHERE tenant_id = ? AND scope_id = ? AND module_id = ?` +
|
|
171
|
+
(ifOperationId === undefined ? '' : ' AND operation_id = ?'), row.position, row.actor, row.reason, row.operationId, row.at, key.tenantId, key.scopeId, key.moduleId, ...(ifOperationId === undefined ? [] : [ifOperationId]));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* OFF's write, made AFTER the scope's switch held. An upsert: a repeat OFF refreshes the
|
|
175
|
+
* actor, reason and time, as the status read reports the latest reason. Never made for a
|
|
176
|
+
* call that held nothing, because a row saying `off` for a module the scope never ran
|
|
177
|
+
* would switch it off on the day it is installed (the kernel's `held` check, for the same
|
|
178
|
+
* reason).
|
|
179
|
+
*/
|
|
180
|
+
export function recordSystemSwitchedOff(db, row) {
|
|
181
|
+
db.run(`INSERT INTO _substrat_system_switches
|
|
182
|
+
(tenant_id, scope_id, module_id, position, actor, reason, operation_id, switched_at)
|
|
183
|
+
VALUES (?, ?, ?, 'off', ?, ?, ?, ?)
|
|
184
|
+
ON CONFLICT (tenant_id, scope_id, module_id) DO UPDATE SET
|
|
185
|
+
position = 'off', actor = excluded.actor, reason = excluded.reason,
|
|
186
|
+
operation_id = excluded.operation_id, switched_at = excluded.switched_at`, row.tenantId, row.scopeId, row.moduleId, row.actor, row.reason, row.operationId, row.at);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* ON's write, made BEFORE the scope's switch moves, and only to a row that exists. Before,
|
|
190
|
+
* because the other order fails unsafely: a scope switched on whose record write then
|
|
191
|
+
* failed would still read `off`, and the next reconcile would switch the operator's
|
|
192
|
+
* restore back off. This order fails toward a record of `on` beside a live marker, which
|
|
193
|
+
* the marker wins. Answers the row as it was, for `restoreSystemSwitchRecord`.
|
|
194
|
+
*/
|
|
195
|
+
export function recordSystemSwitchedOn(db, row) {
|
|
196
|
+
const prior = db.all(`SELECT position, actor, reason, operation_id, switched_at FROM _substrat_system_switches
|
|
197
|
+
WHERE tenant_id = ? AND scope_id = ? AND module_id = ?`, row.tenantId, row.scopeId, row.moduleId)[0];
|
|
198
|
+
if (!prior)
|
|
199
|
+
return null;
|
|
200
|
+
setRecordRow(db, row, { position: 'on', actor: row.actor, reason: row.reason, operationId: row.operationId, at: row.at });
|
|
201
|
+
return {
|
|
202
|
+
position: positionOf(prior.position),
|
|
203
|
+
actor: String(prior.actor),
|
|
204
|
+
reason: String(prior.reason),
|
|
205
|
+
operationId: String(prior.operation_id),
|
|
206
|
+
at: String(prior.switched_at),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Put a row back as `recordSystemSwitchedOn` found it — the ON that wrote it threw, or held
|
|
211
|
+
* nothing. A compare-and-set on the ON's own `operationId`: if another switch call has
|
|
212
|
+
* written the row since (a concurrent OFF), the row is left as that call wrote it, so an
|
|
213
|
+
* ON's undo can never clobber a newer position.
|
|
214
|
+
*/
|
|
215
|
+
export function restoreSystemSwitchRecord(db, on, prior) {
|
|
216
|
+
if (prior)
|
|
217
|
+
setRecordRow(db, on, prior, on.operationId);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Forget one scope's switch records — the scope's directory row is going too (a reaped
|
|
221
|
+
* preview or fork, a reaped scope). Without this the fleet read keeps listing a switch on
|
|
222
|
+
* a scope that no longer exists. The admin log keeps the history, as it keeps the scope's.
|
|
223
|
+
*/
|
|
224
|
+
export function forgetSystemSwitchesOf(db, scopeId) {
|
|
225
|
+
db.run(`DELETE FROM _substrat_system_switches WHERE scope_id = ?`, scopeId);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=system-switch-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-switch-record.js","sourceRoot":"","sources":["../src/system-switch-record.ts"],"names":[],"mappings":"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"}
|
package/dist/ulid.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export interface UlidMint {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function createUlid(): UlidMint;
|
|
29
29
|
export declare function ulid(now?: number): string;
|
|
30
|
+
/**
|
|
31
|
+
* The greatest ULID a millisecond can carry (#1705 PR 3): every id minted at or before `now`
|
|
32
|
+
* sorts at or below it, and every id minted after sorts above. It is what "skip to now" moves
|
|
33
|
+
* a watermark to, because a watermark is compared with `id > cursor`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function ulidCeiling(now: number): string;
|
|
30
36
|
/**
|
|
31
37
|
* The epoch-millisecond timestamp a ULID carries in its first ten characters.
|
|
32
38
|
*
|
package/dist/ulid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ulid.d.ts","sourceRoot":"","sources":["../src/ulid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ulid.d.ts","sourceRoot":"","sources":["../src/ulid.ts"],"names":[],"mappings":"AAqDA,yFAAyF;AACzF,MAAM,WAAW,QAAQ;IACvB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,QAAQ,CA6ErC;AAKD,wBAAgB,IAAI,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO3C"}
|
package/dist/ulid.js
CHANGED
|
@@ -30,6 +30,16 @@ const MAX_ULID_TIME = 2 ** 48 - 1;
|
|
|
30
30
|
/** All 26 digits, Crockford's alphabet — `I`, `L`, `O` and `U` are not in it. */
|
|
31
31
|
const ULID_SHAPE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
32
32
|
const unencodable = (t) => `not an encodable ULID instant: ${t} (want 0..${MAX_ULID_TIME})`;
|
|
33
|
+
/** The ten base32 digits of an instant 48 bits can hold. The caller has range-checked it. */
|
|
34
|
+
function encodeTime(time) {
|
|
35
|
+
let ts = '';
|
|
36
|
+
let t = time;
|
|
37
|
+
for (let i = 0; i < 10; i++) {
|
|
38
|
+
ts = B32[t % 32] + ts;
|
|
39
|
+
t = Math.floor(t / 32);
|
|
40
|
+
}
|
|
41
|
+
return ts;
|
|
42
|
+
}
|
|
33
43
|
/**
|
|
34
44
|
* A ULID mint with its OWN monotonic state — one writer's floor, not the process's.
|
|
35
45
|
*
|
|
@@ -85,16 +95,10 @@ export function createUlid() {
|
|
|
85
95
|
if (time < 0 || time > MAX_ULID_TIME)
|
|
86
96
|
throw new RangeError(unencodable(time));
|
|
87
97
|
lastTime = time;
|
|
88
|
-
let ts = '';
|
|
89
|
-
let t = time;
|
|
90
|
-
for (let i = 0; i < 10; i++) {
|
|
91
|
-
ts = B32[t % 32] + ts;
|
|
92
|
-
t = Math.floor(t / 32);
|
|
93
|
-
}
|
|
94
98
|
let r = '';
|
|
95
99
|
for (const d of lastRand)
|
|
96
100
|
r += B32[d];
|
|
97
|
-
return
|
|
101
|
+
return encodeTime(time) + r;
|
|
98
102
|
});
|
|
99
103
|
mint.seedFrom = (id) => {
|
|
100
104
|
const time = ulidTime(id); // refuses anything that is not a ULID
|
|
@@ -129,6 +133,16 @@ const processUlid = createUlid();
|
|
|
129
133
|
export function ulid(now = Date.now()) {
|
|
130
134
|
return processUlid(now);
|
|
131
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* The greatest ULID a millisecond can carry (#1705 PR 3): every id minted at or before `now`
|
|
138
|
+
* sorts at or below it, and every id minted after sorts above. It is what "skip to now" moves
|
|
139
|
+
* a watermark to, because a watermark is compared with `id > cursor`.
|
|
140
|
+
*/
|
|
141
|
+
export function ulidCeiling(now) {
|
|
142
|
+
if (!Number.isSafeInteger(now) || now < 0 || now > MAX_ULID_TIME)
|
|
143
|
+
throw new RangeError(unencodable(now));
|
|
144
|
+
return encodeTime(now) + 'Z'.repeat(16);
|
|
145
|
+
}
|
|
132
146
|
/**
|
|
133
147
|
* The epoch-millisecond timestamp a ULID carries in its first ten characters.
|
|
134
148
|
*
|
package/dist/ulid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ulid.js","sourceRoot":"","sources":["../src/ulid.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,gEAAgE;AAChE,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,8EAA8E;AAC9E,iFAAiF;AACjF,mEAAmE;AACnE,EAAE;AACF,kFAAkF;AAClF,qFAAqF;AACrF,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,iFAAiF;AACjF,iFAAiF;AACjF,EAAE;AACF,sFAAsF;AACtF,yFAAyF;AACzF,uFAAuF;AACvF,oFAAoF;AACpF,oFAAoF;AACpF,oFAAoF;AACpF,qFAAqF;AACrF,yEAAyE;AACzE,4DAA4D;AAM5D,MAAM,GAAG,GAAG,kCAAkC,CAAC;AAE/C,kFAAkF;AAClF,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAElC,iFAAiF;AACjF,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAE9C,MAAM,WAAW,GAAG,CAAC,CAAS,EAAU,EAAE,CACxC,kCAAkC,CAAC,aAAa,aAAa,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"ulid.js","sourceRoot":"","sources":["../src/ulid.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
* production builds them, and one definition is what keeps the two identical.
|
|
24
24
|
* `lint:spine-ddl` holds each adapter to including it.
|
|
25
25
|
*/
|
|
26
|
-
import { type EventExport, type ModuleManifest, type ExportedBatch, type WantedEvent, type WithheldEvent } from '@substrat-run/contracts';
|
|
26
|
+
import { type ImportCursorMove, type ImportCursorMoved, type ScopeId, type TenantId, type VerticalResolution, type EventExport, type ModuleManifest, type ExportedBatch, type WantedEvent, type WithheldEvent } from '@substrat-run/contracts';
|
|
27
27
|
import { type OutboxEnvelopeRow } from './outbox-event.js';
|
|
28
28
|
import type { ImportHandler } from './scope-host.js';
|
|
29
|
-
|
|
29
|
+
import type { SwitchSql } from './system-switch.js';
|
|
30
|
+
export declare const VERTICAL_EVENTS_DDL = "\n -- #1705: every event this scope has received from another vertical. The envelope only, and\n -- never the payload. The producer's outbox keeps the one copy, where erasure already reaches.\n CREATE TABLE IF NOT EXISTS _substrat_imports (\n -- The PRODUCER's event id. A ULID the producer's host minted, so it is unique across\n -- every source this scope imports from, and it is the dedupe key: a batch delivered\n -- twice finds its events here and its handlers' journal rows in _substrat_deliveries.\n event_id TEXT PRIMARY KEY,\n source_scope_id TEXT NOT NULL,\n source_vertical TEXT NOT NULL,\n type TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n occurred_at TEXT NOT NULL,\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n -- How many vertical boundaries this event's cause chain has crossed, this one included.\n -- An export read in THIS scope counts from here when one of its own events was caused by\n -- an import, which is how a loop between two verticals is stopped.\n hops INTEGER NOT NULL,\n -- NULL: released, and handed to this scope's handlers. Otherwise the producer's reason\n -- for NOT releasing it ('pii', 'version', 'cascade', 'undecodable'). Such a row was\n -- named but never carried.\n withheld TEXT,\n imported_at TEXT NOT NULL\n );\n -- #1705: how far this scope has read each producer. Written in the same store as the\n -- handlers' writes, so a restore of this scope rewinds the watermark with the data and the\n -- producer re-delivers what the restore undid. A watermark the platform held instead would\n -- skip it.\n CREATE TABLE IF NOT EXISTS _substrat_import_cursors (\n source_scope_id TEXT PRIMARY KEY,\n source_vertical TEXT NOT NULL,\n -- The id of the last producer row the reads walked (released or withheld). NULL is not\n -- stored: a producer with no row here has never been read, which the absence says.\n cursor TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n -- #1705: the export read, on the PRODUCER side. It asks for a few types after a watermark,\n -- in id order. Without this index SQLite walks the primary key from the watermark forward and\n -- steps over every row of every other type. A consumer that is caught up would then pay, on\n -- every pass, for all the unexported activity since the last exported event. With it, the read\n -- seeks each type's range and costs what it returns.\n CREATE INDEX IF NOT EXISTS _substrat_outbox_type_id ON _substrat_outbox (type, id);\n -- #1705 PR 3: what a REPLAY moved out of the live journal. A replay re-delivers events this\n -- scope already took, and the live tables are also the dedupe (a row in _substrat_deliveries\n -- means \"do not run this handler again\"). So the rows for the replayed range leave them. They\n -- are moved here rather than dropped, because they are the record that a handler ran on an\n -- event, when, and what it reported: evidence the spine keeps. One row per moved row, the\n -- whole row as JSON under the replay that moved it. Nothing reads this to deliver.\n -- ERASURE: today no erasure path touches imported copies, because only piiClass 'none' crosses\n -- and the delivery-error rewrite (#1632) reaches only this scope's OWN outbox events. If a\n -- consumer-side erasure of imported copies is ever added, it must reach `row` here too (a\n -- moved delivery row's `error` is a handler's own sentence), or the history keeps what the\n -- live tables gave up. Follow-up: #1757.\n CREATE TABLE IF NOT EXISTS _substrat_import_replays (\n -- The act (a ULID). The admin log's row for the replay carries the same id.\n replay_id TEXT NOT NULL,\n replayed_at TEXT NOT NULL,\n source_scope_id TEXT NOT NULL,\n -- 'journal': a _substrat_imports row. 'delivery': a _substrat_deliveries row.\n kind TEXT NOT NULL,\n event_id TEXT NOT NULL,\n -- The handler's module on a 'delivery' row, and '' on a 'journal' row.\n consumer_module TEXT NOT NULL,\n row TEXT NOT NULL,\n PRIMARY KEY (replay_id, kind, event_id, consumer_module)\n );\n";
|
|
30
31
|
/**
|
|
31
32
|
* The producer-side read: rows of the given types after the watermark, oldest first.
|
|
32
33
|
*
|
|
@@ -96,6 +97,27 @@ export declare function exportsOf(manifests: readonly {
|
|
|
96
97
|
exports?: readonly EventExport[];
|
|
97
98
|
};
|
|
98
99
|
}[]): Map<string, EventExport>;
|
|
100
|
+
/**
|
|
101
|
+
* The outbox's insertion mark (#1705 PR 2): the kick's "before", taken ahead of an invoke.
|
|
102
|
+
*
|
|
103
|
+
* `rowid`, not the event id. A ULID minted in the same millisecond as the newest row can sort
|
|
104
|
+
* BELOW it, so "ids above the newest id" can miss an event this invoke wrote. SQLite assigns a
|
|
105
|
+
* new row `max(rowid) + 1` while the newest row is still there. No code path deletes outbox rows
|
|
106
|
+
* inside an invoke: the only removals are a restore and a wipe, and they replace the whole table
|
|
107
|
+
* outside any invocation. So every row the invoke and its consumers add sits above the mark. If
|
|
108
|
+
* that ever stopped being true, the effect is bounded: an exported event would miss its kick and
|
|
109
|
+
* wait for the sweep, and no event would be lost or sent twice.
|
|
110
|
+
*/
|
|
111
|
+
export declare const OUTBOX_MARK_SQL = "SELECT COALESCE(MAX(rowid), 0) AS mark FROM _substrat_outbox";
|
|
112
|
+
/**
|
|
113
|
+
* How many rows of the exported `types` were added after `mark` (#1705 PR 2). This is the
|
|
114
|
+
* count `ScopeStubOptions.onExportedEvents` reports. A seek on the rowid, so it walks only
|
|
115
|
+
* what the invoke added. Callers skip it entirely when the deployment exports nothing.
|
|
116
|
+
*/
|
|
117
|
+
export declare function exportedSinceQuery(types: readonly string[], mark: number): {
|
|
118
|
+
sql: string;
|
|
119
|
+
params: unknown[];
|
|
120
|
+
};
|
|
99
121
|
/** The consumer's watermark per producer, oldest source first. */
|
|
100
122
|
export declare const IMPORT_CURSORS_SQL = "SELECT source_scope_id, source_vertical, cursor, updated_at FROM _substrat_import_cursors ORDER BY source_scope_id";
|
|
101
123
|
/** One producer's watermark, for the compare-and-set a batch is applied under. */
|
|
@@ -115,6 +137,74 @@ export declare const IMPORT_CURSOR_ADVANCE_SQL = "\n INSERT INTO _substrat_impo
|
|
|
115
137
|
* `imported_at` forward or rewrite what was withheld and why.
|
|
116
138
|
*/
|
|
117
139
|
export declare const IMPORT_RECORD_SQL = "\n INSERT OR IGNORE INTO _substrat_imports\n (event_id, source_scope_id, source_vertical, type, schema_version, occurred_at,\n entity_type, entity_id, hops, withheld, imported_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n";
|
|
140
|
+
/**
|
|
141
|
+
* The producer end of the edge a lever moves, resolved by the sweep's own rule (#1705 PR 3).
|
|
142
|
+
*
|
|
143
|
+
* Both ends must be their tenant's one primary instance, exactly as `sweepEdge` requires. A
|
|
144
|
+
* watermark on any other pair is one no pass reads, so a move there would report success and
|
|
145
|
+
* change nothing. `resolve` is the directory's `resolveVerticalInstance`, and it takes the
|
|
146
|
+
* CONSUMER's tenant, so a producer in another tenant is not found rather than moved.
|
|
147
|
+
*/
|
|
148
|
+
export declare function importCursorSourceOf(resolve: (tenantId: TenantId, vertical: string) => Promise<VerticalResolution>, consumer: {
|
|
149
|
+
tenantId: TenantId;
|
|
150
|
+
scopeId: ScopeId;
|
|
151
|
+
vertical: string | null;
|
|
152
|
+
}, from: string): Promise<{
|
|
153
|
+
vertical: string;
|
|
154
|
+
scopeId: ScopeId;
|
|
155
|
+
}>;
|
|
156
|
+
/**
|
|
157
|
+
* Move one edge's watermark (#1705 PR 3). The adapter supplies the store (as the switches'
|
|
158
|
+
* `SwitchSql`), the transaction, the queue, the clock and the act's id. Run on the scope's own
|
|
159
|
+
* queue, so a pass delivering on the same edge can never interleave with the move. Everything the move decides is decided here, once, for
|
|
160
|
+
* both adapters.
|
|
161
|
+
*
|
|
162
|
+
* Why a bare rewind is not a replay. Three guards stand between an event and a second run of
|
|
163
|
+
* its handler, and a replay must pass each one on purpose:
|
|
164
|
+
*
|
|
165
|
+
* 1. `IMPORT_CURSOR_ADVANCE_SQL` moves the watermark forward only. The lever sets it here.
|
|
166
|
+
* 2. `_substrat_deliveries` holds a row per (event, module) that ran, and the delivery path
|
|
167
|
+
* skips any pair it finds. Rewound but not cleared, every replayed event would come back
|
|
168
|
+
* as a `duplicate` and nothing would run.
|
|
169
|
+
* 3. `_substrat_imports` is `INSERT OR IGNORE`. Left in place, an event first withheld for its
|
|
170
|
+
* version would stay recorded as withheld after the consumer's upgrade released it.
|
|
171
|
+
*
|
|
172
|
+
* So a replay moves the range's rows in (2) and (3) into `_substrat_import_replays` under
|
|
173
|
+
* `replayId`, then clears them, then sets the watermark. The evidence stays, and the live tables
|
|
174
|
+
* take the events again. Two things are true between the move and the redelivery, and are
|
|
175
|
+
* stated rather than hidden: a cause walk through a moved event's id ends at `missing`, and an
|
|
176
|
+
* export read in this scope counts fewer hops for a chain through it. Both heal when the next
|
|
177
|
+
* pass redelivers. The undercount only loosens the loop bound (the hop cap bounds loops, and
|
|
178
|
+
* authorizes nothing), so a loop may run a round longer; no event crosses that exports, grants
|
|
179
|
+
* and the PII rule would not already release.
|
|
180
|
+
*
|
|
181
|
+
* Refused, with the reason a person reads:
|
|
182
|
+
* - a replay whose `after` is AHEAD of the watermark (that is a skip), or a skip whose `through`
|
|
183
|
+
* is BEHIND it (that is a replay). The acknowledgement names one direction, and it must be
|
|
184
|
+
* the direction that moves;
|
|
185
|
+
* - a skip past now, which would pass over events not yet written while the edge read as
|
|
186
|
+
* caught up;
|
|
187
|
+
* - a replay on an edge that has delivered nothing yet: there is nothing to run again.
|
|
188
|
+
*/
|
|
189
|
+
export declare function moveImportCursor(sql: SwitchSql, input: {
|
|
190
|
+
move: ImportCursorMove;
|
|
191
|
+
source: {
|
|
192
|
+
vertical: string;
|
|
193
|
+
scopeId: string;
|
|
194
|
+
};
|
|
195
|
+
replayId: string;
|
|
196
|
+
/** Epoch ms: the rows' time, and "now" for a skip, as the watermark's ceiling. */
|
|
197
|
+
now: number;
|
|
198
|
+
/**
|
|
199
|
+
* What the code running this scope imports (`CrossVerticalRegistry.consumes()`). A move names
|
|
200
|
+
* an edge only if the consumer declares it. Otherwise a skip would plant a watermark for an
|
|
201
|
+
* edge that does not exist yet, and the backfill of a later version that adds the import would
|
|
202
|
+
* silently start from it.
|
|
203
|
+
*/
|
|
204
|
+
imports: readonly {
|
|
205
|
+
from: string;
|
|
206
|
+
}[];
|
|
207
|
+
}): ImportCursorMoved;
|
|
118
208
|
/** The note a withheld event's dead letter carries at the consumer. Names the reason, never the content. */
|
|
119
209
|
export declare function withheldNote(reason: WithheldEvent['reason'], vertical: string): string;
|
|
120
210
|
/** One registered import handler: which module, from which vertical, for which (type, version). */
|
|
@@ -145,9 +235,13 @@ export declare class CrossVerticalRegistry {
|
|
|
145
235
|
private readonly exportsByType;
|
|
146
236
|
private readonly importVersion;
|
|
147
237
|
private readonly registered;
|
|
238
|
+
/** `exportTypes()`, kept current by `register`: an invoke reads it, and must not rebuild a map to. */
|
|
239
|
+
private exportTypeList;
|
|
148
240
|
register(manifest: Pick<ModuleManifest, 'id' | 'events'>, handlers: Record<string, Record<string, ImportHandler>> | undefined): void;
|
|
149
241
|
/** type → export, over every registered module: what this deployment releases. */
|
|
150
242
|
exports(): Map<string, EventExport>;
|
|
243
|
+
/** The exported type names (#1705 PR 2), for the per-invoke kick count. Empty: exports nothing. */
|
|
244
|
+
exportTypes(): readonly string[];
|
|
151
245
|
/** What this deployment imports, one row per (source, type), sorted. */
|
|
152
246
|
consumes(): {
|
|
153
247
|
from: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertical-events.d.ts","sourceRoot":"","sources":["../src/vertical-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"vertical-events.d.ts","sourceRoot":"","sources":["../src/vertical-events.ts"],"names":[],"mappings":"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"}
|