@substrat-run/kernel 0.120.0 → 0.122.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 +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -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 +216 -4
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +648 -45
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-host.d.ts +148 -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/system-switch.d.ts +2 -2
- package/dist/system-switch.d.ts.map +1 -1
- 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/version-migrations.d.ts +108 -0
- package/dist/version-migrations.d.ts.map +1 -0
- package/dist/version-migrations.js +187 -0
- package/dist/version-migrations.js.map +1 -0
- package/dist/vertical-events.d.ts +96 -2
- package/dist/vertical-events.d.ts.map +1 -1
- package/dist/vertical-events.js +239 -4
- package/dist/vertical-events.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directory's record of the schedule kill switch (#1674), shared by both adapters.
|
|
3
|
+
*
|
|
4
|
+
* The switch itself (`system-switch.ts`) lives in the scope's own storage: a `switch:off`
|
|
5
|
+
* marker beside the module's grants, which is what the runner gates on. That is the right
|
|
6
|
+
* place for the gate, and the wrong place for the only copy. A scope whose storage is wiped
|
|
7
|
+
* and re-provisioned (#321/#332), or restored from a dump taken before the switch was pulled,
|
|
8
|
+
* loses the marker, and the next reconcile seats the grants live again. The switch is then
|
|
9
|
+
* silently on, on exactly the scopes an operator switched off.
|
|
10
|
+
*
|
|
11
|
+
* So the directory keeps one row per (tenant, scope, module): the position the switch was
|
|
12
|
+
* last moved to, by whom, why, and when. It is the model `_substrat_connection_grants` set
|
|
13
|
+
* (#592): the enforcement stays where it is checked, and the directory holds what a
|
|
14
|
+
* reconcile re-asserts FROM. It also answers the fleet question the scope cannot: "which
|
|
15
|
+
* scopes have a module switched off", without walking every scope's store.
|
|
16
|
+
*
|
|
17
|
+
* **OFF wins from either side, and a record never turns anything on.**
|
|
18
|
+
* - A record of `off` and a scope that has lost its marker: the record wins, and
|
|
19
|
+
* `HostAdmin.reassertSystemSwitches` switches it off again, after the seat, so the grants
|
|
20
|
+
* the seat recreated are the ones OFF tombstones and a later ON gives back.
|
|
21
|
+
* - A record of `on` (or none) and a live marker: the marker wins, and it stays off.
|
|
22
|
+
* Restore is the lever (#1666), and a reconcile that could turn a kill switch back on
|
|
23
|
+
* would be a second one.
|
|
24
|
+
*
|
|
25
|
+
* The admin log is still the history. This is only the current position.
|
|
26
|
+
*/
|
|
27
|
+
import type { ListPage } from '@substrat-run/contracts';
|
|
28
|
+
import type { SwitchSql, SystemScheduleState } from './system-switch.js';
|
|
29
|
+
/**
|
|
30
|
+
* The table. Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees the
|
|
31
|
+
* one spelling on each side. No CHECK on `position`: the drift gate does not compare
|
|
32
|
+
* constraints, and the writers below are the only thing that sets it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SYSTEM_SWITCHES_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_system_switches (\n tenant_id TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n module_id TEXT NOT NULL,\n -- 'on' or 'off'. A row exists only for a module some switch call actually held.\n position TEXT NOT NULL,\n actor TEXT NOT NULL,\n reason TEXT NOT NULL,\n -- The switch call's own id, the one its admin-log intent and outcome rows carry.\n -- A ULID, so it is also the fleet read's chronological keyset cursor.\n operation_id TEXT NOT NULL,\n switched_at TEXT NOT NULL,\n PRIMARY KEY (tenant_id, scope_id, module_id)\n );\n CREATE INDEX IF NOT EXISTS _substrat_system_switches_position\n ON _substrat_system_switches (position, operation_id);\n CREATE INDEX IF NOT EXISTS _substrat_system_switches_operation\n ON _substrat_system_switches (operation_id);\n";
|
|
35
|
+
/**
|
|
36
|
+
* The one-time backfill from the admin log: every switch moved before the record existed.
|
|
37
|
+
* Without it, those are exactly the switches a wipe would still lose.
|
|
38
|
+
*
|
|
39
|
+
* It reads the admin-log shape `revokeFromSystem` / `restoreToSystem` have written since
|
|
40
|
+
* #1666, two rows per call sharing `after.operationId`: an `intent` row carrying
|
|
41
|
+
* `after.moduleId` and `after.reason`, then an outcome row whose `after.phase` is
|
|
42
|
+
* `applied`, `refused` or `failed`. Only calls whose outcome is `applied` count, because a
|
|
43
|
+
* refused call moved nothing and a failed one may not have. Of those, only the LATEST per
|
|
44
|
+
* (tenant, scope, module) is taken, by the intent's ULID id. `at` is the intent's, the
|
|
45
|
+
* same instant the status read's who/why join reports. A key whose calls include no
|
|
46
|
+
* applied OFF gets no row, even when its latest applied call is an ON: a live
|
|
47
|
+
* `restoreToSystem` of a module never switched off writes none either
|
|
48
|
+
* (`recordSystemSwitchedOn`), and the backfill must not record what the live path would not.
|
|
49
|
+
*
|
|
50
|
+
* `INSERT OR IGNORE`, so a row the switch wrote itself is never overwritten by history.
|
|
51
|
+
* The adapters run it only on the construction that creates the table, so it runs once.
|
|
52
|
+
*/
|
|
53
|
+
export declare const SYSTEM_SWITCHES_BACKFILL_SQL = "\n WITH applied AS (\n SELECT DISTINCT action, json_extract(after, '$.operationId') AS operation_id\n FROM _substrat_admin_log\n WHERE action IN ('revokeFromSystem', 'restoreToSystem')\n AND json_extract(after, '$.phase') = 'applied'\n ),\n intents AS (\n SELECT i.id AS id, i.tenant_id AS tenant_id, i.scope_id AS scope_id,\n json_extract(i.after, '$.moduleId') AS module_id,\n CASE i.action WHEN 'revokeFromSystem' THEN 'off' ELSE 'on' END AS position,\n i.actor AS actor,\n json_extract(i.after, '$.reason') AS reason,\n json_extract(i.after, '$.operationId') AS operation_id,\n i.at AS at\n FROM _substrat_admin_log i\n JOIN applied a\n ON a.action = i.action AND a.operation_id = json_extract(i.after, '$.operationId')\n WHERE i.action IN ('revokeFromSystem', 'restoreToSystem')\n AND i.tenant_id IS NOT NULL AND i.scope_id IS NOT NULL\n AND json_extract(i.after, '$.phase') = 'intent'\n AND json_extract(i.after, '$.moduleId') IS NOT NULL\n AND json_extract(i.after, '$.reason') IS NOT NULL\n ),\n ranked AS (\n SELECT *,\n ROW_NUMBER() OVER (PARTITION BY tenant_id, scope_id, module_id ORDER BY id DESC) AS latest,\n MAX(position = 'off') OVER (PARTITION BY tenant_id, scope_id, module_id) AS ever_off\n FROM intents\n )\n INSERT OR IGNORE INTO _substrat_system_switches\n (tenant_id, scope_id, module_id, position, actor, reason, operation_id, switched_at)\n SELECT tenant_id, scope_id, module_id, position, actor, reason, operation_id, at\n FROM ranked\n WHERE latest = 1 AND ever_off = 1\n";
|
|
54
|
+
/** "Does the record table exist yet?" — asked BEFORE the DDL, so the backfill runs once. */
|
|
55
|
+
export declare function systemSwitchesTableExists(db: SwitchSql): boolean;
|
|
56
|
+
/** One record row as the directory holds it, with the scope's binding joined on. */
|
|
57
|
+
export interface SystemSwitchRecordRow {
|
|
58
|
+
tenantId: string;
|
|
59
|
+
scopeId: string;
|
|
60
|
+
moduleId: string;
|
|
61
|
+
vertical: string | null;
|
|
62
|
+
position: 'on' | 'off';
|
|
63
|
+
actor: string;
|
|
64
|
+
reason: string;
|
|
65
|
+
operationId: string;
|
|
66
|
+
at: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Filter for the fleet read. Unset `position` means both; the HTTP route defaults it to
|
|
70
|
+
* `off`. Paged by `operationId` (the exclusive cursor), oldest first unless `order: 'desc'`.
|
|
71
|
+
*/
|
|
72
|
+
export interface SystemSwitchRecordFilter extends ListPage {
|
|
73
|
+
position?: 'on' | 'off';
|
|
74
|
+
tenantId?: string;
|
|
75
|
+
scopeId?: string;
|
|
76
|
+
moduleId?: string;
|
|
77
|
+
vertical?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The fleet read (`GET /system-switches`): records, keyset-paged by `operation_id`.
|
|
81
|
+
*
|
|
82
|
+
* The key is the call that last moved the switch, so a row whose switch moves during a walk
|
|
83
|
+
* is re-keyed to a newer id and jumps to the end of the order. Ascending (the default) never
|
|
84
|
+
* skips a row for it; at worst it is seen twice, once per position. A DESCENDING walk can
|
|
85
|
+
* miss a row that moved after the walk started, so use ascending for "every switch".
|
|
86
|
+
*/
|
|
87
|
+
export declare function listSystemSwitchRecords(db: SwitchSql, filter?: SystemSwitchRecordFilter): SystemSwitchRecordRow[];
|
|
88
|
+
/** One scope's record rows by module — what the per-scope status read joins as `recorded`. */
|
|
89
|
+
export declare function systemSwitchRecordsOf(db: SwitchSql, tenantId: string, scopeId: string): Map<string, 'on' | 'off'>;
|
|
90
|
+
/**
|
|
91
|
+
* The per-scope status read's rows with the record joined on. Each module the scope
|
|
92
|
+
* reports gets its recorded position (or null). A module the record holds that the scope
|
|
93
|
+
* no longer reports at all is ADDED, as `ungranted`: that is the scope a wipe emptied, and
|
|
94
|
+
* the drift is the whole reason to show it. Sorted by module id, as the scope's rows are.
|
|
95
|
+
*/
|
|
96
|
+
export declare function withRecorded(states: {
|
|
97
|
+
moduleId: string;
|
|
98
|
+
schedules: SystemScheduleState;
|
|
99
|
+
}[], recorded: Map<string, 'on' | 'off'>): {
|
|
100
|
+
moduleId: string;
|
|
101
|
+
schedules: SystemScheduleState;
|
|
102
|
+
recorded: 'on' | 'off' | null;
|
|
103
|
+
}[];
|
|
104
|
+
/** The modules a reconcile must re-assert OFF on this scope. */
|
|
105
|
+
export declare function switchedOffModulesOf(db: SwitchSql, tenantId: string, scopeId: string): string[];
|
|
106
|
+
/** What one switch call writes into the record. */
|
|
107
|
+
export interface SystemSwitchRecordWrite {
|
|
108
|
+
tenantId: string;
|
|
109
|
+
scopeId: string;
|
|
110
|
+
moduleId: string;
|
|
111
|
+
actor: string;
|
|
112
|
+
reason: string;
|
|
113
|
+
operationId: string;
|
|
114
|
+
at: string;
|
|
115
|
+
}
|
|
116
|
+
/** The row as it stood before a write, so a failed switch can put it back. */
|
|
117
|
+
export type SystemSwitchRecordPrior = Pick<SystemSwitchRecordRow, 'position' | 'actor' | 'reason' | 'operationId' | 'at'> | null;
|
|
118
|
+
/** What a re-assert did for one recorded-off module — `HostAdmin.reassertSystemSwitches`' answer. */
|
|
119
|
+
export interface SystemSwitchReassert {
|
|
120
|
+
moduleId: string;
|
|
121
|
+
held: boolean;
|
|
122
|
+
changed: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* OFF's write, made AFTER the scope's switch held. An upsert: a repeat OFF refreshes the
|
|
126
|
+
* actor, reason and time, as the status read reports the latest reason. Never made for a
|
|
127
|
+
* call that held nothing, because a row saying `off` for a module the scope never ran
|
|
128
|
+
* would switch it off on the day it is installed (the kernel's `held` check, for the same
|
|
129
|
+
* reason).
|
|
130
|
+
*/
|
|
131
|
+
export declare function recordSystemSwitchedOff(db: SwitchSql, row: SystemSwitchRecordWrite): void;
|
|
132
|
+
/**
|
|
133
|
+
* ON's write, made BEFORE the scope's switch moves, and only to a row that exists. Before,
|
|
134
|
+
* because the other order fails unsafely: a scope switched on whose record write then
|
|
135
|
+
* failed would still read `off`, and the next reconcile would switch the operator's
|
|
136
|
+
* restore back off. This order fails toward a record of `on` beside a live marker, which
|
|
137
|
+
* the marker wins. Answers the row as it was, for `restoreSystemSwitchRecord`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function recordSystemSwitchedOn(db: SwitchSql, row: SystemSwitchRecordWrite): SystemSwitchRecordPrior;
|
|
140
|
+
/**
|
|
141
|
+
* Put a row back as `recordSystemSwitchedOn` found it — the ON that wrote it threw, or held
|
|
142
|
+
* nothing. A compare-and-set on the ON's own `operationId`: if another switch call has
|
|
143
|
+
* written the row since (a concurrent OFF), the row is left as that call wrote it, so an
|
|
144
|
+
* ON's undo can never clobber a newer position.
|
|
145
|
+
*/
|
|
146
|
+
export declare function restoreSystemSwitchRecord(db: SwitchSql, on: {
|
|
147
|
+
tenantId: string;
|
|
148
|
+
scopeId: string;
|
|
149
|
+
moduleId: string;
|
|
150
|
+
operationId: string;
|
|
151
|
+
}, prior: SystemSwitchRecordPrior): void;
|
|
152
|
+
/**
|
|
153
|
+
* Forget one scope's switch records — the scope's directory row is going too (a reaped
|
|
154
|
+
* preview or fork, a reaped scope). Without this the fleet read keeps listing a switch on
|
|
155
|
+
* a scope that no longer exists. The admin log keeps the history, as it keeps the scope's.
|
|
156
|
+
*/
|
|
157
|
+
export declare function forgetSystemSwitchesOf(db: SwitchSql, scopeId: string): void;
|
|
158
|
+
//# sourceMappingURL=system-switch-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-switch-record.d.ts","sourceRoot":"","sources":["../src/system-switch-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -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/system-switch.d.ts
CHANGED
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
export declare const SYSTEM_SWITCH_OFF_RELATION = "switch:off";
|
|
48
48
|
/** The two statement shapes the switch needs, over either adapter's SQLite handle. */
|
|
49
49
|
export interface SwitchSql {
|
|
50
|
-
all(sql: string, ...params: (string | null)[]): Record<string, unknown>[];
|
|
51
|
-
run(sql: string, ...params: (string | null)[]): void;
|
|
50
|
+
all(sql: string, ...params: (string | number | null)[]): Record<string, unknown>[];
|
|
51
|
+
run(sql: string, ...params: (string | number | null)[]): void;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Where one module's schedules stand on one scope.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-switch.d.ts","sourceRoot":"","sources":["../src/system-switch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAKvD,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"system-switch.d.ts","sourceRoot":"","sources":["../src/system-switch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAKvD,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACnF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;CAC/D;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC;AAI7D;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,oHACmD,CAAC;AAE5F;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAErG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAalG;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAWlF;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,uGAAuG;AACvG,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK1E;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMlF;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACzE,aAAa,CAOf;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACxE,aAAa,CA4Ff"}
|
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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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A version's SQL migrations, stored apart from its manifest (#1764), shared by both adapters.
|
|
3
|
+
*
|
|
4
|
+
* Since #1677 a pushed deploy manifest carries its whole cumulative migration set
|
|
5
|
+
* (`migrations: { moduleId, version, sql }[]`). The manifest is one `manifest_json` column on
|
|
6
|
+
* `vertical_versions`, so every read of a version moved that SQL too: `readVersion` on every
|
|
7
|
+
* admit, promote and bind, and every `storedDeployManifest.parse` behind `/registry`, `/model`,
|
|
8
|
+
* `/schedules` and the serving upload. Only the promote review reads the SQL.
|
|
9
|
+
*
|
|
10
|
+
* So `publishVersion` splits the field off: the SQL goes to `vertical_version_migrations`, one
|
|
11
|
+
* row per migration, and the manifest is stored without it. The wire is unchanged, since the
|
|
12
|
+
* CLI still sends `migrations` inside the manifest. Two columns on `vertical_versions` carry
|
|
13
|
+
* what the rows alone cannot:
|
|
14
|
+
*
|
|
15
|
+
* - `migration_count`: how many rows the version stored. **NULL means the version carries no
|
|
16
|
+
* SQL** (pushed before #1677, over the push's caps, or malformed), which a reader must show
|
|
17
|
+
* as "not available", never as "no migrations". `0` is a version whose modules ship none. A
|
|
18
|
+
* read whose row count disagrees with it answers null rather than a short list.
|
|
19
|
+
* - `migrations_split`: 1 once the version's SQL is out of its manifest. NULL is a version
|
|
20
|
+
* stored before this, whose manifest may still carry it. The backfill below moves those a
|
|
21
|
+
* bounded batch at a time, and until it reaches a version the read falls back to that
|
|
22
|
+
* version's manifest. The fallback is only for those rows. A version published by this code
|
|
23
|
+
* is split on the way in.
|
|
24
|
+
*/
|
|
25
|
+
import { type DeclaredMigration } from '@substrat-run/contracts';
|
|
26
|
+
import type { SwitchSql } from './system-switch.js';
|
|
27
|
+
/**
|
|
28
|
+
* The table, and the index the backfill walks. Interpolated into both adapters' directory DDL,
|
|
29
|
+
* so `lint:spine-ddl` sees one spelling on each side. Anything that references this table or
|
|
30
|
+
* this index goes in this fragment, which is what the adapters run after their column additions.
|
|
31
|
+
*
|
|
32
|
+
* The index is partial: it holds only versions still waiting for the backfill, so "is there
|
|
33
|
+
* anything left" is one probe that costs nothing once the backfill is done. It names a column
|
|
34
|
+
* a directory from before #1764 lacks, so the adapters run these statements after their
|
|
35
|
+
* column additions, not in their DDL loop.
|
|
36
|
+
*/
|
|
37
|
+
export declare const VERSION_MIGRATIONS_DDL = "\n CREATE TABLE IF NOT EXISTS vertical_version_migrations (\n version_id TEXT NOT NULL,\n -- The migration's place in the order the host runs them, from 0.\n ordinal INTEGER NOT NULL,\n module_id TEXT NOT NULL,\n -- The module's SqlMigration.version, e.g. 0001-init.\n version TEXT NOT NULL,\n sql TEXT NOT NULL,\n PRIMARY KEY (version_id, ordinal)\n );\n CREATE INDEX IF NOT EXISTS vertical_versions_unsplit\n ON vertical_versions (id) WHERE migrations_split IS NULL;\n";
|
|
38
|
+
/** A manifest with its SQL taken out, and the SQL. */
|
|
39
|
+
export interface SplitManifest {
|
|
40
|
+
/** The manifest to store: without `migrations`, or exactly as given when there was nothing to take. */
|
|
41
|
+
manifestJson: string | null;
|
|
42
|
+
/** The migrations to store, or null when the version carries none that can be stored. */
|
|
43
|
+
migrations: DeclaredMigration[] | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Take `migrations` out of a manifest.
|
|
47
|
+
*
|
|
48
|
+
* `push` holds the field to the push boundary's caps (`deployManifest`): at most 2000 entries
|
|
49
|
+
* and 512 KiB of SQL, which also bounds every row under a Durable Object's 2 MB row limit.
|
|
50
|
+
* `/deploy` already refuses an over-cap set, before the upload, because it parses the whole
|
|
51
|
+
* manifest with `deployManifest`. This split caps whatever reaches `publishVersion` by any
|
|
52
|
+
* route, including `POST /verticals/:slug/versions`, which stores `manifestJson` unparsed
|
|
53
|
+
* (#1765). There, a field over the caps or not shaped like migrations is dropped and the
|
|
54
|
+
* version reads as "SQL not available", so the promote dialog still asks. `stored` is for a
|
|
55
|
+
* manifest already in the directory, whose history the caps must never make unreadable
|
|
56
|
+
* (`storedDeployManifest`).
|
|
57
|
+
*
|
|
58
|
+
* A manifest that is not a JSON object is returned as it came, with no migrations. The
|
|
59
|
+
* manifest is only rewritten when it has a `migrations` key to remove.
|
|
60
|
+
*/
|
|
61
|
+
export declare function splitManifestMigrations(manifestJson: string | null, mode: 'push' | 'stored'): SplitManifest;
|
|
62
|
+
/** Replace one version's migration rows. The caller sets its `migration_count` and `migrations_split`. */
|
|
63
|
+
export declare function writeVersionMigrations(db: SwitchSql, versionId: string, migrations: readonly DeclaredMigration[] | null): void;
|
|
64
|
+
/**
|
|
65
|
+
* The two reads that find unsplit versions. Named so the kernel's test can EXPLAIN these
|
|
66
|
+
* exact strings: each must be answered from `vertical_versions_unsplit`, never by a scan of
|
|
67
|
+
* every version's row (and its manifest's overflow pages).
|
|
68
|
+
*/
|
|
69
|
+
export declare const UNSPLIT_IDS_SQL = "SELECT id FROM vertical_versions WHERE migrations_split IS NULL ORDER BY id LIMIT ?";
|
|
70
|
+
export declare const UNSPLIT_PROBE_SQL = "SELECT 1 AS present FROM vertical_versions WHERE migrations_split IS NULL LIMIT 1";
|
|
71
|
+
/**
|
|
72
|
+
* How much manifest one batch reads at most, in UTF-8 bytes (the first version is always
|
|
73
|
+
* moved). A stored manifest can be up to about 1.5 MiB, and a batch holds the directory DO.
|
|
74
|
+
* Bytes, because what a batch costs is the bytes it reads: a manifest of four-byte characters
|
|
75
|
+
* is four times its character count.
|
|
76
|
+
*/
|
|
77
|
+
export declare const BATCH_MANIFEST_BYTES: number;
|
|
78
|
+
/**
|
|
79
|
+
* Move the SQL out of the next versions stored before #1764: at most `limit` of them, and
|
|
80
|
+
* at most `BATCH_MANIFEST_BYTES` of manifest. Returns how many it moved, and whether any are
|
|
81
|
+
* left.
|
|
82
|
+
*
|
|
83
|
+
* Bounded so that no one run has to read the whole version history: the Durable-Object
|
|
84
|
+
* adapter runs one batch per alarm and re-arms while any are left, so its constructor never
|
|
85
|
+
* does per-version work. Resumable because each version is marked as it is moved, in the
|
|
86
|
+
* caller's transaction with its rows. A version is moved by the same `splitManifestMigrations`
|
|
87
|
+
* a push goes through, in `stored` mode, so history is read without the push caps.
|
|
88
|
+
*
|
|
89
|
+
* Idempotent: a version that is already split is never selected, and moving one replaces its
|
|
90
|
+
* rows. A directory restored from a dump taken before the backfill comes back unsplit and is
|
|
91
|
+
* simply moved again.
|
|
92
|
+
*/
|
|
93
|
+
export declare function splitVersionMigrationsBatch(db: SwitchSql, limit?: number): {
|
|
94
|
+
moved: number;
|
|
95
|
+
more: boolean;
|
|
96
|
+
};
|
|
97
|
+
/** Whether any version still waits for the backfill. One probe of the partial index. */
|
|
98
|
+
export declare function versionsAwaitSplit(db: SwitchSql): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* One version's migrations, in the order the host runs them. `undefined` when there is no
|
|
101
|
+
* such version. `migrations` is null when the version carries none that can be shown, which
|
|
102
|
+
* a reader must say as "not available", never as "no migrations".
|
|
103
|
+
*/
|
|
104
|
+
export declare function versionMigrationsOf(db: SwitchSql, versionId: string): {
|
|
105
|
+
verticalSlug: string;
|
|
106
|
+
migrations: DeclaredMigration[] | null;
|
|
107
|
+
} | undefined;
|
|
108
|
+
//# sourceMappingURL=version-migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-migrations.d.ts","sourceRoot":"","sources":["../src/version-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,ugBAalC,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,uGAAuG;IACvG,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yFAAyF;IACzF,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAC;CACxC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,aAAa,CAkB3G;AAED,0GAA0G;AAC1G,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,GAC9C,IAAI,CASN;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,wFAAwF,CAAC;AACrH,eAAO,MAAM,iBAAiB,sFAAsF,CAAC;AAIrH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAAkB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,SAAS,EACb,KAAK,GAAE,MAAuB,GAC7B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAiClC;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,GAChB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,CA4B9E"}
|