@substrat-run/adapter-cloudflare 0.130.1 → 0.132.1
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/control-plane-do.d.ts +3 -1
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +8 -2
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +16 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +57 -2
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +73 -12
- package/dist/scope-do.js.map +1 -1
- package/dist/scope-sweeper-do.d.ts +11 -2
- package/dist/scope-sweeper-do.d.ts.map +1 -1
- package/dist/scope-sweeper-do.js +35 -1
- package/dist/scope-sweeper-do.js.map +1 -1
- package/package.json +7 -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,
|
|
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,EAsCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAoCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAuEtB,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;AA0bD;;;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,CAklKjE"}
|
package/dist/scope-do.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { REWIND_REFUSED } from './rewind-refusal.js';
|
|
2
2
|
import { DurableObject } from 'cloudflare:workers';
|
|
3
3
|
import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, toWireFailure, grantRefFromProof, principalId, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, requestFingerprint, substratError, assertReplayableDump, REDRAIN_BATCH, } from '@substrat-run/contracts';
|
|
4
|
-
import { ulid, DO_SQL_LIMITS, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexDdl, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, IDEMPOTENCY_DDL, REFUSALS_DDL, refusalInsert, refusedTransitionOf, assertIdempotencyKey, assertPermissionKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, jobRunListLimit, } from '@substrat-run/kernel';
|
|
4
|
+
import { ulid, DO_SQL_LIMITS, unknownRoleError, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexDdl, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, IDEMPOTENCY_DDL, REFUSALS_DDL, refusalInsert, refusedTransitionOf, assertIdempotencyKey, assertPermissionKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, jobRunListLimit, } from '@substrat-run/kernel';
|
|
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, doBuiltColumnsOf, doSpineSql } from './sql.js';
|
|
@@ -160,6 +160,8 @@ const KERNEL_DDL = `
|
|
|
160
160
|
module_id TEXT NOT NULL,
|
|
161
161
|
version TEXT NOT NULL,
|
|
162
162
|
applied_at TEXT NOT NULL,
|
|
163
|
+
duration_ms INTEGER,
|
|
164
|
+
rows_changed INTEGER,
|
|
163
165
|
PRIMARY KEY (module_id, version)
|
|
164
166
|
);
|
|
165
167
|
-- #286: the PITR bookmark taken immediately BEFORE a migration pass runs on a
|
|
@@ -833,13 +835,15 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
833
835
|
*/
|
|
834
836
|
appliedMigrations(limit = 100) {
|
|
835
837
|
return this.sql
|
|
836
|
-
.exec(`SELECT module_id, version, applied_at FROM _substrat_migrations
|
|
838
|
+
.exec(`SELECT module_id, version, applied_at, duration_ms, rows_changed FROM _substrat_migrations
|
|
837
839
|
ORDER BY applied_at DESC, module_id, version LIMIT ?`, limit)
|
|
838
840
|
.toArray()
|
|
839
841
|
.map((r) => ({
|
|
840
842
|
moduleId: r.module_id,
|
|
841
843
|
version: r.version,
|
|
842
844
|
appliedAt: r.applied_at ?? null,
|
|
845
|
+
durationMs: r.duration_ms ?? null,
|
|
846
|
+
rowsChanged: r.rows_changed ?? null,
|
|
843
847
|
}));
|
|
844
848
|
}
|
|
845
849
|
/**
|
|
@@ -2201,6 +2205,28 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2201
2205
|
}
|
|
2202
2206
|
return record;
|
|
2203
2207
|
}
|
|
2208
|
+
/** Authorize a job's read as its registered module, never as a person. */
|
|
2209
|
+
async systemAttachmentAuthorize(attachmentId, moduleId, tenantId, scopeId) {
|
|
2210
|
+
await this.ensureMigrations();
|
|
2211
|
+
if (!this.modules.has(moduleId)) {
|
|
2212
|
+
throw toRpcError(substratError('not_found', `module not registered in this scope: ${moduleId}`));
|
|
2213
|
+
}
|
|
2214
|
+
const record = this.attachmentRow(attachmentId);
|
|
2215
|
+
if (!record)
|
|
2216
|
+
return null;
|
|
2217
|
+
const gate = this.attachmentGate(record.entity.entityType);
|
|
2218
|
+
try {
|
|
2219
|
+
const ctx = this.operationContext(this.systemPrincipal, tenantId, scopeId, undefined, undefined, moduleId, undefined, undefined, 'attachments.open');
|
|
2220
|
+
assertAllowed(await ctx.check(gate.read, record.entity));
|
|
2221
|
+
}
|
|
2222
|
+
catch (err) {
|
|
2223
|
+
if (err instanceof PermissionDenied) {
|
|
2224
|
+
this.recordDenial({ kind: 'system', id: moduleId }, tenantId, 'attachments.open', err, null);
|
|
2225
|
+
}
|
|
2226
|
+
throw toRpcError(err);
|
|
2227
|
+
}
|
|
2228
|
+
return record;
|
|
2229
|
+
}
|
|
2204
2230
|
/** Remove an attachment's metadata fact: write gate + delete + `attachment.removed`. */
|
|
2205
2231
|
async attachmentRemove(attachmentId, principal, tenantId, scopeId, connectionId) {
|
|
2206
2232
|
await this.ensureMigrations();
|
|
@@ -2402,6 +2428,40 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2402
2428
|
return permissions.map((permission) => ({ permission, held: !missing.has(permission) }));
|
|
2403
2429
|
});
|
|
2404
2430
|
}
|
|
2431
|
+
/**
|
|
2432
|
+
* `ctx.canAssign`'s bound for a principal the host names (#1931), queued like `peerCovers`
|
|
2433
|
+
* so no invoke's open transaction is read half-done. `null` when the tenant defines no such
|
|
2434
|
+
* role: a typed error thrown here would reach the host flattened, so the host types it.
|
|
2435
|
+
*/
|
|
2436
|
+
async canAssignFor(tenantId, scopeId, principal, roleKey) {
|
|
2437
|
+
await this.ensureMigrations();
|
|
2438
|
+
return this.queue.enqueue(() => this.assignmentBound({ kind: 'principal', id: principal }, tenantId, scopeId, roleKey));
|
|
2439
|
+
}
|
|
2440
|
+
/** Check and grant in one serialized scope task; a refusal never writes a tuple. */
|
|
2441
|
+
async assignScopeRoleBoundedFor(tenantId, scopeId, caller, assignee, roleKey) {
|
|
2442
|
+
await this.ensureMigrations();
|
|
2443
|
+
return this.queue.enqueue(async () => {
|
|
2444
|
+
const bound = await this.assignmentBound({ kind: 'principal', id: caller }, tenantId, scopeId, roleKey);
|
|
2445
|
+
if (bound?.covered) {
|
|
2446
|
+
this.sql.exec(`INSERT OR REPLACE INTO _substrat_tuples (subject, relation, object, expires_at)
|
|
2447
|
+
VALUES (?, ?, ?, NULL)`, `principal:${assignee}`, `role:${roleKey}`, `scope:${scopeId}`);
|
|
2448
|
+
}
|
|
2449
|
+
return bound;
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* §5.1's assignment bound, resolved against the SAME role table the checker
|
|
2454
|
+
* expands — the tenant's projected role, not a vertical's compile-time `ROLES`
|
|
2455
|
+
* array, because the projected role is what assignment would actually confer.
|
|
2456
|
+
* The one resolution both `ctx.canAssign` and `canAssignFor` answer from; `null`
|
|
2457
|
+
* when the tenant defines no such role.
|
|
2458
|
+
*/
|
|
2459
|
+
async assignmentBound(subject, tenantId, scopeId, roleKey) {
|
|
2460
|
+
const role = await this.controlPlaneReader().getRole(tenantId, roleKey);
|
|
2461
|
+
if (!role)
|
|
2462
|
+
return null;
|
|
2463
|
+
return this.checker.covers(subject, role.permissions, { tenantId, scopeId });
|
|
2464
|
+
}
|
|
2405
2465
|
/**
|
|
2406
2466
|
* Every module this scope holds or has held system authority for, and where each
|
|
2407
2467
|
* stands (#1674) — the kernel's `systemGrantsStatus`, over this DO's own storage. A
|
|
@@ -2933,13 +2993,16 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2933
2993
|
.exec('SELECT 1 FROM _substrat_migrations WHERE module_id = ? AND version = ?', moduleId, migration.version)
|
|
2934
2994
|
.toArray()[0];
|
|
2935
2995
|
if (!already) {
|
|
2996
|
+
const started = performance.now();
|
|
2997
|
+
const before = this.sql.exec('SELECT total_changes() AS n').toArray()[0].n;
|
|
2936
2998
|
// #1898: a migration runs on this DO's own handle, not `ctx.sql`, so the
|
|
2937
2999
|
// spine guard's REFERENCES rule is applied here.
|
|
2938
3000
|
assertNoSpineReference(migration.sql, `migration ${key}`);
|
|
2939
3001
|
for (const stmt of splitSqlStatements(migration.sql)) {
|
|
2940
3002
|
this.sql.exec(stmt);
|
|
2941
3003
|
}
|
|
2942
|
-
this.sql.exec('
|
|
3004
|
+
const after = this.sql.exec('SELECT total_changes() AS n').toArray()[0].n;
|
|
3005
|
+
this.sql.exec('INSERT INTO _substrat_migrations (module_id, version, applied_at, duration_ms, rows_changed) VALUES (?, ?, ?, ?, ?)', moduleId, migration.version, new Date().toISOString(), Math.max(0, Math.round(performance.now() - started)), after - before);
|
|
2943
3006
|
}
|
|
2944
3007
|
});
|
|
2945
3008
|
}
|
|
@@ -3349,6 +3412,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3349
3412
|
// have added it bare already, and then this ALTER is skipped as a duplicate (#1883).
|
|
3350
3413
|
// `lint:spine-ddl` refuses one that is not.
|
|
3351
3414
|
for (const alter of [
|
|
3415
|
+
// #1763: rows written before these fields keep NULL, meaning unrecorded.
|
|
3416
|
+
'ALTER TABLE _substrat_migrations ADD COLUMN duration_ms INTEGER',
|
|
3417
|
+
'ALTER TABLE _substrat_migrations ADD COLUMN rows_changed INTEGER',
|
|
3352
3418
|
'ALTER TABLE _substrat_tuples ADD COLUMN revoked_at TEXT',
|
|
3353
3419
|
// Executor retry state (#100). The defaults read as "terminal", which is
|
|
3354
3420
|
// right for every row already there: each is a completed delivery or a
|
|
@@ -3991,17 +4057,12 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3991
4057
|
}
|
|
3992
4058
|
return decision;
|
|
3993
4059
|
};
|
|
3994
|
-
/**
|
|
3995
|
-
* §5.1's assignment bound, resolved against the SAME role table the checker
|
|
3996
|
-
* expands — the tenant's projected role, not a vertical's compile-time `ROLES`
|
|
3997
|
-
* array, because the projected role is what assignment would actually confer.
|
|
3998
|
-
*/
|
|
3999
4060
|
const runCanAssign = async (roleKey) => {
|
|
4000
|
-
const
|
|
4001
|
-
if (!
|
|
4002
|
-
throw
|
|
4061
|
+
const bound = await this.assignmentBound(subject, tenantId, scopeId, roleKey);
|
|
4062
|
+
if (!bound) {
|
|
4063
|
+
throw unknownRoleError(roleKey);
|
|
4003
4064
|
}
|
|
4004
|
-
return
|
|
4065
|
+
return bound;
|
|
4005
4066
|
};
|
|
4006
4067
|
// ctx.emit's writer, and the kernel's own events' (#1864): one path to the outbox, with the
|
|
4007
4068
|
// reserved-type refusal applied to module code only. Never handed to module code as-is.
|