@substrat-run/adapter-cloudflare 0.127.0 → 0.128.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/host.d.ts +15 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +34 -4
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +76 -41
- package/dist/scope-do.js.map +1 -1
- package/package.json +6 -6
package/dist/scope-do.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;AAsF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAsbD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAgDxG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACxD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CA29JjE"}
|
package/dist/scope-do.js
CHANGED
|
@@ -5,9 +5,9 @@ import { ulid, DO_SQL_LIMITS, createUlid, assertAllowed, ConnectionSealingKeyUna
|
|
|
5
5
|
import { isUpgradeRequest, readSubscription, LIVE_FANOUT_LIMIT, LIVE_MODE_HEADER, LIVE_PRINCIPAL_HEADER, LIVE_SCOPE_HEADER, LIVE_SUBSCRIBE_PATH, LIVE_TENANT_HEADER, } from './live-reads.js';
|
|
6
6
|
import { OperationQueue } from './serialization.js';
|
|
7
7
|
import { doScopedSql, doSpineSql } from './sql.js';
|
|
8
|
-
import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
8
|
+
import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readLifecycleFlow, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
9
9
|
import { createDoTupleChecker, createLocalControlPlaneReader } from './checker.js';
|
|
10
|
-
import { repointScopeGrants } from '@substrat-run/kernel';
|
|
10
|
+
import { assertSpineTablesBuilt, dumpRowsInsert, isSpineTable, repointScopeGrants, spineColumnAdditions } from '@substrat-run/kernel';
|
|
11
11
|
/**
|
|
12
12
|
* The key marking a scope DO whose storage was destroyed (`destroyStorage`).
|
|
13
13
|
* Written after `deleteAll()` so it survives the wipe, and never cleared: `reaped`
|
|
@@ -1190,6 +1190,10 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1190
1190
|
deadLetters(input) {
|
|
1191
1191
|
return readDeadLetters({ sql: doScopedSql(this.sql) }, { limit: input.limit, cursor: input.cursor });
|
|
1192
1192
|
}
|
|
1193
|
+
/** #1744: one entity's lifecycle replayed over this scope's outbox, where it lives. */
|
|
1194
|
+
lifecycleFlow(input) {
|
|
1195
|
+
return readLifecycleFlow({ sql: doScopedSql(this.sql) }, input);
|
|
1196
|
+
}
|
|
1193
1197
|
migrationBookmarks(limit = 20) {
|
|
1194
1198
|
return this.sql
|
|
1195
1199
|
.exec(`SELECT bookmark, taken_at, pending FROM _substrat_migration_bookmarks
|
|
@@ -1337,6 +1341,13 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1337
1341
|
`the attachments of the entity its event names, and no others`);
|
|
1338
1342
|
}
|
|
1339
1343
|
}
|
|
1344
|
+
/** Does `subject` hold a role this scope can expand (#1665)? The #1659 lockout predicate
|
|
1345
|
+
* narrowed to one holder: a live role tuple, scope or tenant level, for a current role. */
|
|
1346
|
+
async hasEffectiveRoleGrantFor(tenantId, subject) {
|
|
1347
|
+
const q = effectiveRoleGrantQuery(tenantId, new Date().toISOString(), subject);
|
|
1348
|
+
const row = this.sql.exec(q.sql, ...q.params).toArray()[0];
|
|
1349
|
+
return row?.effective === 1;
|
|
1350
|
+
}
|
|
1340
1351
|
/** Tombstone a scope tuple (K-21) — the row stays, the walk skips it. Returns
|
|
1341
1352
|
* whether anything changed so a repeat revoke is a silent no-op. */
|
|
1342
1353
|
async revokeTuple(subject, relation, object, at) {
|
|
@@ -3300,8 +3311,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3300
3311
|
}
|
|
3301
3312
|
/**
|
|
3302
3313
|
* Load a dump into this (freshly-provisioned) scope — the write half of the fork
|
|
3303
|
-
* (host.ts importScope). Drop-then-replay: the
|
|
3304
|
-
*
|
|
3314
|
+
* (host.ts importScope). Drop-then-replay: the provisioning schema is wiped and rebuilt,
|
|
3315
|
+
* a vertical's tables from the dump's DDL and the `_substrat_*` spine from KERNEL_DDL
|
|
3316
|
+
* (#1883), then the dump's rows go back in.
|
|
3305
3317
|
*
|
|
3306
3318
|
* `destScopeId` is the scope being written INTO. A dump carries scope-level tuples
|
|
3307
3319
|
* naming the scope it was captured from, so restoring one anywhere else needs them
|
|
@@ -3312,14 +3324,18 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3312
3324
|
*/
|
|
3313
3325
|
/**
|
|
3314
3326
|
* The additive spine-column migrations. KERNEL_DDL is all IF NOT EXISTS, so a
|
|
3315
|
-
* scope DO created before a column keeps the old shape
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3327
|
+
* scope DO created before a column keeps the old shape until this runs on its next
|
|
3328
|
+
* wake. `importDump` runs it too, after rebuilding the spine from KERNEL_DDL: there every
|
|
3329
|
+
* ALTER is a duplicate, since a restore never brings a legacy spine table (#1883), but the
|
|
3330
|
+
* pass also creates `_substrat_outbox_invocation`, which KERNEL_DDL deliberately does not
|
|
3331
|
+
* and the restore's DROP of the outbox took with it. Attempt-and-tolerate: DO SQLite restricts PRAGMA, so there
|
|
3319
3332
|
* is no column probe, and a duplicate is the steady state after the first cold
|
|
3320
3333
|
* start (same argument as ControlPlaneDO.addColumn).
|
|
3321
3334
|
*/
|
|
3322
3335
|
applySpineColumnAdditions() {
|
|
3336
|
+
// Every column here is nullable with no DEFAULT (`attempts` is grandfathered): a restore may
|
|
3337
|
+
// have added it bare already, and then this ALTER is skipped as a duplicate (#1883).
|
|
3338
|
+
// `lint:spine-ddl` refuses one that is not.
|
|
3323
3339
|
for (const alter of [
|
|
3324
3340
|
'ALTER TABLE _substrat_tuples ADD COLUMN revoked_at TEXT',
|
|
3325
3341
|
// Executor retry state (#100). The defaults read as "terminal", which is
|
|
@@ -3440,7 +3456,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3440
3456
|
// cycles, and self-referencing rows within a single table.
|
|
3441
3457
|
// A dump written before indexes were excluded may still carry them; skipped
|
|
3442
3458
|
// rather than failing a restore over data about to be recomputed.
|
|
3443
|
-
|
|
3459
|
+
// Matched without case, as SQLite resolves a table name (#1883 review).
|
|
3460
|
+
const replayable = tables.filter((t) => !isSearchIndexTable(t.name.toLowerCase()));
|
|
3444
3461
|
// The dump is untrusted input (#1143). `SqlStorage.exec` runs every statement
|
|
3445
3462
|
// in the string it is given, so a `ddl` with anything appended to its CREATE
|
|
3446
3463
|
// TABLE executed that too — with entirely plain identifiers, which is why no
|
|
@@ -3462,47 +3479,53 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3462
3479
|
// shadow table directly is an error, and `sqlite_master` order would reach one
|
|
3463
3480
|
// before its virtual table.
|
|
3464
3481
|
for (const { name } of existing) {
|
|
3465
|
-
if (isSearchIndexTable(name))
|
|
3482
|
+
if (isSearchIndexTable(name.toLowerCase()))
|
|
3466
3483
|
continue;
|
|
3467
3484
|
this.sql.exec(`DROP TABLE IF EXISTS "${name}"`);
|
|
3468
3485
|
}
|
|
3486
|
+
// A vertical's tables take the dump's own DDL. The spine never does (#1883): a dump
|
|
3487
|
+
// that declared a column the checker compares on as COLLATE NOCASE would otherwise
|
|
3488
|
+
// decide how this DO's permission checks match. Every `_substrat_*` table is built
|
|
3489
|
+
// from KERNEL_DDL instead, and the dump contributes only rows, by column name
|
|
3490
|
+
// (`spineRowsInsert`), a missing column taking the kernel's default.
|
|
3469
3491
|
for (const t of replayable)
|
|
3470
|
-
|
|
3492
|
+
if (!isSpineTable(t.name))
|
|
3493
|
+
this.sql.exec(t.ddl);
|
|
3494
|
+
// KERNEL_DDL also builds what the dump did not carry (#321). A dump captured from a
|
|
3495
|
+
// WORLD that stores some `_substrat_*` tables ELSEWHERE carries only a subset — an
|
|
3496
|
+
// `@substrat-run/adapter-sqlite` scope file keeps `_substrat_roles` /
|
|
3497
|
+
// `_substrat_tenant_tuples` in its DIRECTORY database, so its per-scope dump omits
|
|
3498
|
+
// them, and without them the very next permission check would raise a bare `no such
|
|
3499
|
+
// table: _substrat_roles`. Roles land empty here and are re-projected by the
|
|
3500
|
+
// restore's repair leg (host.projectRolesLocal) — the spine's job is only to exist so
|
|
3501
|
+
// the checker can read it. The column pass follows, for the one outbox index KERNEL_DDL
|
|
3502
|
+
// leaves to it.
|
|
3503
|
+
for (const stmt of splitSqlStatements(KERNEL_DDL))
|
|
3504
|
+
this.sql.exec(stmt);
|
|
3505
|
+
this.applySpineColumnAdditions();
|
|
3506
|
+
const columnsOf = (name) => this.spineColumnsOf(name);
|
|
3507
|
+
assertSpineTablesBuilt(replayable.map((t) => t.name), columnsOf);
|
|
3508
|
+
// A spine column this kernel does not know (a dump from a newer one) is kept, as a plain
|
|
3509
|
+
// untyped column the checker never reads.
|
|
3471
3510
|
for (const t of replayable) {
|
|
3472
|
-
if (t.
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3511
|
+
if (isSpineTable(t.name))
|
|
3512
|
+
for (const alter of spineColumnAdditions(t, columnsOf(t.name)))
|
|
3513
|
+
this.sql.exec(alter);
|
|
3514
|
+
}
|
|
3515
|
+
for (const t of replayable) {
|
|
3516
|
+
const insert = dumpRowsInsert(t, columnsOf);
|
|
3477
3517
|
for (const row of t.rows)
|
|
3478
3518
|
this.sql.exec(insert, ...row);
|
|
3479
3519
|
}
|
|
3480
|
-
// Re-assert the kernel spine (#321). A dump captured from a WORLD that stores some
|
|
3481
|
-
// `_substrat_*` tables ELSEWHERE carries only a subset — an `@substrat-run/adapter-
|
|
3482
|
-
// sqlite` scope file, for instance, keeps `_substrat_roles`/`_substrat_tenant_tuples`
|
|
3483
|
-
// in its DIRECTORY database, so its per-scope dump omits them. Replaying such a dump
|
|
3484
|
-
// verbatim would leave this DO missing those spine tables, and the very next
|
|
3485
|
-
// permission check would raise a bare `no such table: _substrat_roles`. KERNEL_DDL is
|
|
3486
|
-
// all IF NOT EXISTS, so this recreates only what the dump did not carry (empty), and
|
|
3487
|
-
// never disturbs a table the dump DID bring. Roles land empty here and are re-projected
|
|
3488
|
-
// by the restore's repair leg (host.projectRolesLocal) — the spine's job is only to
|
|
3489
|
-
// exist so the checker can read it.
|
|
3490
|
-
for (const stmt of splitSqlStatements(KERNEL_DDL))
|
|
3491
|
-
this.sql.exec(stmt);
|
|
3492
|
-
// …and the additive columns, for the same reason KERNEL_DDL is re-asserted: a
|
|
3493
|
-
// dump captured before a column existed replays DDL WITHOUT it, and IF NOT
|
|
3494
|
-
// EXISTS cannot widen a table the dump did bring. Without this, the next emit
|
|
3495
|
-
// in this instance fails with `no such column` until a cold start re-runs the
|
|
3496
|
-
// constructor's pass.
|
|
3497
|
-
this.applySpineColumnAdditions();
|
|
3498
3520
|
// #1738: a dump's `provisioned_for` names the scope (and tenant) it was captured from,
|
|
3499
3521
|
// not this one. Dropped, so a restore never carries a receipt over; the repair
|
|
3500
3522
|
// projection that follows writes this scope's own.
|
|
3501
3523
|
this.sql.exec(`DELETE FROM _substrat_meta WHERE key = 'provisioned_for'`);
|
|
3502
3524
|
// Re-point the restored grants at THIS scope (after the spine exists, so a dump
|
|
3503
3525
|
// that carried no tuples table still finds one here).
|
|
3504
|
-
if (destScopeId)
|
|
3505
|
-
this.rewriteScopeTuples(destScopeId, sourceScopeId && { scopeId: sourceScopeId, exact });
|
|
3526
|
+
if (destScopeId) {
|
|
3527
|
+
this.rewriteScopeTuples(destScopeId, sourceScopeId && { scopeId: sourceScopeId, exact }, new Date().toISOString());
|
|
3528
|
+
}
|
|
3506
3529
|
// #1742: the recorded-off modules go back off INSIDE the replay's transaction, with
|
|
3507
3530
|
// the spine and the re-point. A switch that throws rolls the whole restore back, so
|
|
3508
3531
|
// the dump's grants never commit live without the switch that should cover them.
|
|
@@ -3574,12 +3597,24 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3574
3597
|
*
|
|
3575
3598
|
* Which rows move is `repointScopeGrants`'s (#1869): the dump's source scope, exactly.
|
|
3576
3599
|
*
|
|
3577
|
-
*
|
|
3578
|
-
*
|
|
3579
|
-
*
|
|
3600
|
+
* (subject, relation, object) is the primary key, so a moved row can meet one the dump
|
|
3601
|
+
* already holds for the destination scope. Which survives is that function's rule too
|
|
3602
|
+
* (#1882), with expiry judged at `now`, as this DO's checker judges it.
|
|
3580
3603
|
*/
|
|
3581
|
-
rewriteScopeTuples(destScopeId, source) {
|
|
3582
|
-
repointScopeGrants(this.switchSql(), destScopeId, source);
|
|
3604
|
+
rewriteScopeTuples(destScopeId, source, now) {
|
|
3605
|
+
repointScopeGrants(this.switchSql(), destScopeId, source, now);
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* The columns of spine table `name` as KERNEL_DDL built it here, or `undefined` when it built
|
|
3609
|
+
* no such table — what `spineRowsInsert` judges a dump's columns against (#1883). Read off an
|
|
3610
|
+
* empty `SELECT`, because DO SQLite restricts PRAGMA. The name is matched without case, as
|
|
3611
|
+
* SQLite resolves a table name: a dump's `_Substrat_tuples` is the kernel's tuples table.
|
|
3612
|
+
*/
|
|
3613
|
+
spineColumnsOf(name) {
|
|
3614
|
+
const built = this.sql
|
|
3615
|
+
.exec(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? COLLATE NOCASE`, name)
|
|
3616
|
+
.toArray();
|
|
3617
|
+
return built.length === 0 ? undefined : this.sql.exec(`SELECT * FROM "${name}" LIMIT 0`).columnNames;
|
|
3583
3618
|
}
|
|
3584
3619
|
/**
|
|
3585
3620
|
* Wipe this scope's storage — the reap half of deleteSnapshot (preview-and-
|