@substrat-run/kernel 0.134.0 → 0.136.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/async-invocation-log.d.ts +61 -0
- package/dist/async-invocation-log.d.ts.map +1 -0
- package/dist/async-invocation-log.js +149 -0
- package/dist/async-invocation-log.js.map +1 -0
- package/dist/attachment-extractor.d.ts +10 -2
- package/dist/attachment-extractor.d.ts.map +1 -1
- package/dist/attachment-extractor.js +16 -3
- package/dist/attachment-extractor.js.map +1 -1
- package/dist/base64url.d.ts +6 -0
- package/dist/base64url.d.ts.map +1 -0
- package/dist/base64url.js +19 -0
- package/dist/base64url.js.map +1 -0
- package/dist/capability.d.ts +36 -1
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +67 -24
- package/dist/capability.js.map +1 -1
- package/dist/carried-copy.d.ts +117 -0
- package/dist/carried-copy.d.ts.map +1 -0
- package/dist/carried-copy.js +134 -0
- package/dist/carried-copy.js.map +1 -0
- package/dist/index.d.ts +98 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -12
- package/dist/index.js.map +1 -1
- package/dist/invocation-log.d.ts +90 -8
- package/dist/invocation-log.d.ts.map +1 -1
- package/dist/invocation-log.js +34 -10
- package/dist/invocation-log.js.map +1 -1
- package/dist/lifecycle-flow.d.ts +6 -1
- package/dist/lifecycle-flow.d.ts.map +1 -1
- package/dist/lifecycle-flow.js +1 -1
- package/dist/lifecycle-flow.js.map +1 -1
- package/dist/list-index.d.ts +19 -4
- package/dist/list-index.d.ts.map +1 -1
- package/dist/list-index.js +150 -8
- package/dist/list-index.js.map +1 -1
- package/dist/platform-call.d.ts +4 -37
- package/dist/platform-call.d.ts.map +1 -1
- package/dist/platform-call.js +5 -37
- package/dist/platform-call.js.map +1 -1
- package/dist/platform-sweep.d.ts +17 -0
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +22 -4
- package/dist/platform-sweep.js.map +1 -1
- package/dist/provider-error.d.ts.map +1 -1
- package/dist/provider-error.js +2 -1
- package/dist/provider-error.js.map +1 -1
- package/dist/refusal-query.d.ts +42 -0
- package/dist/refusal-query.d.ts.map +1 -0
- package/dist/refusal-query.js +79 -0
- package/dist/refusal-query.js.map +1 -0
- package/dist/routed-node.d.ts +3 -2
- package/dist/routed-node.d.ts.map +1 -1
- package/dist/routed-node.js.map +1 -1
- package/dist/scope-copy.d.ts +122 -0
- package/dist/scope-copy.d.ts.map +1 -0
- package/dist/scope-copy.js +187 -0
- package/dist/scope-copy.js.map +1 -0
- package/dist/scope-host.d.ts +142 -21
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +50 -14
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-lifecycle.d.ts +70 -0
- package/dist/scope-lifecycle.d.ts.map +1 -0
- package/dist/scope-lifecycle.js +124 -0
- package/dist/scope-lifecycle.js.map +1 -0
- package/dist/timeline.d.ts +65 -1
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +136 -1
- package/dist/timeline.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { InvocationLevel } from '@substrat-run/contracts/invocation-record';
|
|
2
|
+
import { type AsyncInvocationKind, type AsyncOutcome, type InvocationLogLine } from './invocation-log.js';
|
|
3
|
+
import type { EmittedReport } from './scope-host.js';
|
|
4
|
+
/** One unit of async work, as the host saw it end. */
|
|
5
|
+
export interface AsyncUnit {
|
|
6
|
+
kind: AsyncInvocationKind;
|
|
7
|
+
tenantId: string;
|
|
8
|
+
scopeId: string;
|
|
9
|
+
/**
|
|
10
|
+
* The id the unit's `ctx.log` lines and the line itself share. The call's own id when the
|
|
11
|
+
* unit ran in a call's tail, so the call's drill-down shows it; otherwise one minted for
|
|
12
|
+
* the unit ({@link asyncInvocationId}).
|
|
13
|
+
*/
|
|
14
|
+
invocationId: string;
|
|
15
|
+
/** A consumer's name (its module, or `executor:<id>`), or a schedule's operation. */
|
|
16
|
+
operation: string;
|
|
17
|
+
/** `Date.now()` when the unit started — host code, so the real clock. */
|
|
18
|
+
startedAt: number;
|
|
19
|
+
outcome: Exclude<AsyncOutcome, 'suppressed'>;
|
|
20
|
+
/** The handler threw. Only its `errorCode` is kept; see the header. */
|
|
21
|
+
error?: unknown;
|
|
22
|
+
eventType?: string;
|
|
23
|
+
eventId?: string;
|
|
24
|
+
attempt?: number;
|
|
25
|
+
dueAt?: string | null;
|
|
26
|
+
latenessMs?: number | null;
|
|
27
|
+
/** Who the work ran as. A consumer and a schedule run as `system`; an import as its peer. */
|
|
28
|
+
principalKind?: string;
|
|
29
|
+
emitted?: EmittedReport;
|
|
30
|
+
versionId?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** Where a host sends an async line. The default writes it to the runtime's console. */
|
|
33
|
+
export type InvocationLineSink = (line: InvocationLogLine) => void;
|
|
34
|
+
/** The default sink: one JSON line, as the request line is written. */
|
|
35
|
+
export declare const consoleInvocationLineSink: InvocationLineSink;
|
|
36
|
+
/** The most unit lines one pass writes before it starts counting instead. */
|
|
37
|
+
export declare const ASYNC_LINES_PER_PASS = 100;
|
|
38
|
+
/**
|
|
39
|
+
* The level of an async unit. A thrown handler is an error whatever happens next — it broke,
|
|
40
|
+
* retry or not. A unit that ended without its handler doing the work — dead-lettered without
|
|
41
|
+
* a throw, held inert on a copy, refused by a schedule — is a warning: nothing broke, and it
|
|
42
|
+
* is still not success. Everything else is info.
|
|
43
|
+
*/
|
|
44
|
+
export declare function asyncLevelOf(outcome: AsyncOutcome, threw: boolean): InvocationLevel;
|
|
45
|
+
/** The invocation id for a unit: the call it ran in, or a fresh one. */
|
|
46
|
+
export declare function asyncInvocationId(callId: string | null | undefined): string;
|
|
47
|
+
/** The line for one unit — through the request line's own builder. */
|
|
48
|
+
export declare function asyncInvocationLine(unit: AsyncUnit): InvocationLogLine;
|
|
49
|
+
/** One pass's writer: the cap, and the line that stands in for what the cap withheld. */
|
|
50
|
+
export interface AsyncLinePass {
|
|
51
|
+
/** Write a unit's line, or count it once the pass is over its cap. Never throws. */
|
|
52
|
+
write(unit: AsyncUnit): void;
|
|
53
|
+
/** Write the `suppressed` line, if anything was withheld. Call once, when the pass ends. */
|
|
54
|
+
end(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Open a pass. Every write is swallowed on failure: a line that cannot be written is one
|
|
58
|
+
* missing line, never a failed delivery — the delivery has already been journaled.
|
|
59
|
+
*/
|
|
60
|
+
export declare function asyncLinePass(sink?: InvocationLineSink, cap?: number): AsyncLinePass;
|
|
61
|
+
//# sourceMappingURL=async-invocation-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-invocation-log.d.ts","sourceRoot":"","sources":["../src/async-invocation-log.ts"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,sDAAsD;AACtD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IAC7C,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wFAAwF;AACxF,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAKnE,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,EAAE,kBAAgE,CAAC;AAEzG,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,CAInF;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE3E;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,iBAAiB,CAyBtE;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,4FAA4F;IAC5F,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,GAAE,kBAA8C,EACpD,GAAG,GAAE,MAA6B,GACjC,aAAa,CA6Cf"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One invocation line per unit of ASYNC work the scope host runs (#1901): a consumer
|
|
3
|
+
* delivery (a module consumer, an executor, an imported event's handler) and a schedule run.
|
|
4
|
+
*
|
|
5
|
+
* ## Why the scope host writes it
|
|
6
|
+
*
|
|
7
|
+
* A request's line is written by the platform's entry around the worker's `fetch` (#1893),
|
|
8
|
+
* and that entry trusts a tenant only from the router's verified assertion. A delivery or a
|
|
9
|
+
* sweep pass carries none, so before this a consumer that failed every attempt was visible
|
|
10
|
+
* only as a dead-letter count. The scope host needs no assertion: it runs the dispatch, for
|
|
11
|
+
* a scope it was asked about by tenant, so it knows the tenant, the scope, the consumer or
|
|
12
|
+
* schedule, the event, the attempt and the outcome first-hand.
|
|
13
|
+
*
|
|
14
|
+
* ## Same line, one more field
|
|
15
|
+
*
|
|
16
|
+
* The line is the request line's shape, built by the same `invocationLine`, plus `kind` and
|
|
17
|
+
* the fields only async work has. Readers that knew the request line read this one without
|
|
18
|
+
* change: the level histogram, the operation facet and the patterns all count it, and a
|
|
19
|
+
* reader that treats a missing `kind` as `request` keeps every older line's meaning.
|
|
20
|
+
*
|
|
21
|
+
* ## Ids, names and codes — nothing else
|
|
22
|
+
*
|
|
23
|
+
* No payload, no error message, no stack: a handler's error text can quote the event it
|
|
24
|
+
* failed on, and the log store is read by a wider audience than the scope's own data. The
|
|
25
|
+
* error stays where it already is, on the delivery journal row the line's `eventId` names.
|
|
26
|
+
* The kernel's `errorCode` of a thrown error is the one thing taken from it. And no entity
|
|
27
|
+
* id either: a schedule's emitted report names the entities it touched, and an entity id is
|
|
28
|
+
* whatever the vertical chose — an email address is a valid one. The line keeps the count
|
|
29
|
+
* and the declared event types.
|
|
30
|
+
*
|
|
31
|
+
* Field by field: `tenantId`, `scopeId`, `invocationId`, `eventId` are platform-minted ids;
|
|
32
|
+
* `operation` is a declared consumer, executor or schedule name; `eventType` and
|
|
33
|
+
* `eventTypes` are declared types; `problemCode` is the kernel's closed `errorCode`
|
|
34
|
+
* vocabulary; `principalKind`, `outcome` and `kind` are closed sets; the rest are numbers
|
|
35
|
+
* and times.
|
|
36
|
+
*
|
|
37
|
+
* ## Bounded
|
|
38
|
+
*
|
|
39
|
+
* A pass writes at most {@link ASYNC_LINES_PER_PASS} unit lines. Past that it writes one
|
|
40
|
+
* `suppressed` line counting what it withheld PER `<kind>:<outcome>`, so a storm of
|
|
41
|
+
* dead-letters is named in the line that stands in for it, never averaged into a total.
|
|
42
|
+
*
|
|
43
|
+
* ## Ships with the vertical
|
|
44
|
+
*
|
|
45
|
+
* This is the scope host's code, which a vertical bundles. A vertical writes these lines
|
|
46
|
+
* from the release of its adapter that has them — not from the platform's deploy, unlike the
|
|
47
|
+
* request line's wrapper (#1893).
|
|
48
|
+
*/
|
|
49
|
+
import { errorCodeOf } from '@substrat-run/contracts';
|
|
50
|
+
import { ulid } from './ulid.js';
|
|
51
|
+
import { invocationLine, } from './invocation-log.js';
|
|
52
|
+
/** The default sink: one JSON line, as the request line is written. */
|
|
53
|
+
export const consoleInvocationLineSink = (line) => console.log(JSON.stringify(line));
|
|
54
|
+
/** The most unit lines one pass writes before it starts counting instead. */
|
|
55
|
+
export const ASYNC_LINES_PER_PASS = 100;
|
|
56
|
+
/**
|
|
57
|
+
* The level of an async unit. A thrown handler is an error whatever happens next — it broke,
|
|
58
|
+
* retry or not. A unit that ended without its handler doing the work — dead-lettered without
|
|
59
|
+
* a throw, held inert on a copy, refused by a schedule — is a warning: nothing broke, and it
|
|
60
|
+
* is still not success. Everything else is info.
|
|
61
|
+
*/
|
|
62
|
+
export function asyncLevelOf(outcome, threw) {
|
|
63
|
+
if (threw)
|
|
64
|
+
return 'error';
|
|
65
|
+
if (outcome === 'dead-lettered' || outcome === 'inert' || outcome === 'failed')
|
|
66
|
+
return 'warn';
|
|
67
|
+
return 'info';
|
|
68
|
+
}
|
|
69
|
+
/** The invocation id for a unit: the call it ran in, or a fresh one. */
|
|
70
|
+
export function asyncInvocationId(callId) {
|
|
71
|
+
return callId ?? ulid();
|
|
72
|
+
}
|
|
73
|
+
/** The line for one unit — through the request line's own builder. */
|
|
74
|
+
export function asyncInvocationLine(unit) {
|
|
75
|
+
const threw = unit.error !== undefined;
|
|
76
|
+
return invocationLine({
|
|
77
|
+
kind: unit.kind,
|
|
78
|
+
tenantId: unit.tenantId,
|
|
79
|
+
scopeId: unit.scopeId,
|
|
80
|
+
invocationId: unit.invocationId,
|
|
81
|
+
threw,
|
|
82
|
+
durationMs: Math.max(0, Date.now() - unit.startedAt),
|
|
83
|
+
level: asyncLevelOf(unit.outcome, threw),
|
|
84
|
+
operation: unit.operation,
|
|
85
|
+
problemCode: threw ? (errorCodeOf(unit.error) ?? null) : null,
|
|
86
|
+
principalKind: unit.principalKind ?? 'system',
|
|
87
|
+
// The count and the declared event types only: never the entities, whose ids are the
|
|
88
|
+
// vertical's data (see the header).
|
|
89
|
+
...(unit.emitted ? { emitted: unit.emitted } : {}),
|
|
90
|
+
withEntities: false,
|
|
91
|
+
versionId: unit.versionId ?? null,
|
|
92
|
+
async: {
|
|
93
|
+
outcome: unit.outcome,
|
|
94
|
+
...(unit.kind === 'consumer'
|
|
95
|
+
? { eventType: unit.eventType ?? null, eventId: unit.eventId ?? null, attempt: unit.attempt ?? null }
|
|
96
|
+
: { dueAt: unit.dueAt ?? null, latenessMs: unit.latenessMs ?? null }),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Open a pass. Every write is swallowed on failure: a line that cannot be written is one
|
|
102
|
+
* missing line, never a failed delivery — the delivery has already been journaled.
|
|
103
|
+
*/
|
|
104
|
+
export function asyncLinePass(sink = consoleInvocationLineSink, cap = ASYNC_LINES_PER_PASS) {
|
|
105
|
+
let written = 0;
|
|
106
|
+
let withheld = null;
|
|
107
|
+
const safe = (line) => {
|
|
108
|
+
try {
|
|
109
|
+
sink(line());
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* see above */
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
write(unit) {
|
|
117
|
+
if (written < cap) {
|
|
118
|
+
written += 1;
|
|
119
|
+
safe(() => asyncInvocationLine(unit));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
withheld ??= { first: unit, levels: new Set(), by: {}, n: 0 };
|
|
123
|
+
const key = `${unit.kind}:${unit.outcome}`;
|
|
124
|
+
withheld.by[key] = (withheld.by[key] ?? 0) + 1;
|
|
125
|
+
withheld.levels.add(asyncLevelOf(unit.outcome, unit.error !== undefined));
|
|
126
|
+
withheld.n += 1;
|
|
127
|
+
},
|
|
128
|
+
end() {
|
|
129
|
+
if (!withheld)
|
|
130
|
+
return;
|
|
131
|
+
const w = withheld;
|
|
132
|
+
withheld = null;
|
|
133
|
+
safe(() => invocationLine({
|
|
134
|
+
kind: w.first.kind,
|
|
135
|
+
tenantId: w.first.tenantId,
|
|
136
|
+
scopeId: w.first.scopeId,
|
|
137
|
+
invocationId: w.first.invocationId,
|
|
138
|
+
threw: false,
|
|
139
|
+
durationMs: 0,
|
|
140
|
+
// The worst level withheld, so the histogram still shows an error pass as one.
|
|
141
|
+
level: w.levels.has('error') ? 'error' : w.levels.has('warn') ? 'warn' : 'info',
|
|
142
|
+
principalKind: w.first.principalKind ?? 'system',
|
|
143
|
+
versionId: w.first.versionId ?? null,
|
|
144
|
+
async: { outcome: 'suppressed', suppressed: w.n, suppressedBy: w.by },
|
|
145
|
+
}));
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=async-invocation-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-invocation-log.js","sourceRoot":"","sources":["../src/async-invocation-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
*
|
|
39
39
|
* What the kernel cannot do from inside one isolate is STOP code that does not yield. A
|
|
40
40
|
* synchronous loop holds the thread until it returns, timer or not; the abort is a request,
|
|
41
|
-
* honoured by an extractor that checks its `signal` and yields
|
|
42
|
-
* bundled parsers do, every
|
|
41
|
+
* honoured by an extractor that checks its `signal` and yields a turn at least every
|
|
42
|
+
* `EXTRACTION_STRIDE` units of work (the bundled parsers do, every native search included).
|
|
43
|
+
* So the guarantee is: a cooperative extractor stops
|
|
43
44
|
* promptly, an uncooperative one cannot get its late answer indexed, and only the runtime's
|
|
44
45
|
* own CPU limit ends it sooner. A hard deadline on uncooperative code needs process or
|
|
45
46
|
* isolate isolation — a host can provide it by running its extractors in a separate worker
|
|
@@ -48,6 +49,13 @@
|
|
|
48
49
|
* broken parser, not from a hostile one, and the bundled parsers bound their own CPU by
|
|
49
50
|
* construction (linear scans under fixed input caps — their package says how far).
|
|
50
51
|
*/
|
|
52
|
+
/**
|
|
53
|
+
* How much work a cooperative extractor does between two checks of its `signal`: 256 Ki
|
|
54
|
+
* units, a unit being a character scanned or decoded, or a byte inflated or decoded. Part of
|
|
55
|
+
* the seam's contract rather than of any one parser, because it is what "stops promptly"
|
|
56
|
+
* means above — and the one number both sides of the seam cite.
|
|
57
|
+
*/
|
|
58
|
+
export declare const EXTRACTION_STRIDE: number;
|
|
51
59
|
/**
|
|
52
60
|
* The cancellation an extractor is handed — the runtime's own `AbortSignal`, typed here by the
|
|
53
61
|
* one member the kernel promises: once `aborted` is true, nothing the extractor answers will be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-extractor.d.ts","sourceRoot":"","sources":["../src/attachment-extractor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attachment-extractor.d.ts","sourceRoot":"","sources":["../src/attachment-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
*
|
|
39
39
|
* What the kernel cannot do from inside one isolate is STOP code that does not yield. A
|
|
40
40
|
* synchronous loop holds the thread until it returns, timer or not; the abort is a request,
|
|
41
|
-
* honoured by an extractor that checks its `signal` and yields
|
|
42
|
-
* bundled parsers do, every
|
|
41
|
+
* honoured by an extractor that checks its `signal` and yields a turn at least every
|
|
42
|
+
* `EXTRACTION_STRIDE` units of work (the bundled parsers do, every native search included).
|
|
43
|
+
* So the guarantee is: a cooperative extractor stops
|
|
43
44
|
* promptly, an uncooperative one cannot get its late answer indexed, and only the runtime's
|
|
44
45
|
* own CPU limit ends it sooner. A hard deadline on uncooperative code needs process or
|
|
45
46
|
* isolate isolation — a host can provide it by running its extractors in a separate worker
|
|
@@ -48,6 +49,13 @@
|
|
|
48
49
|
* broken parser, not from a hostile one, and the bundled parsers bound their own CPU by
|
|
49
50
|
* construction (linear scans under fixed input caps — their package says how far).
|
|
50
51
|
*/
|
|
52
|
+
/**
|
|
53
|
+
* How much work a cooperative extractor does between two checks of its `signal`: 256 Ki
|
|
54
|
+
* units, a unit being a character scanned or decoded, or a byte inflated or decoded. Part of
|
|
55
|
+
* the seam's contract rather than of any one parser, because it is what "stops promptly"
|
|
56
|
+
* means above — and the one number both sides of the seam cite.
|
|
57
|
+
*/
|
|
58
|
+
export const EXTRACTION_STRIDE = 256 * 1024;
|
|
51
59
|
/**
|
|
52
60
|
* The defaults. `maxTextBytes` is 512 KiB of UTF-8 — a few hundred pages of prose, and a
|
|
53
61
|
* quarter of the ~2 MB a Durable Object row holds. 32 MiB of input is what a Worker can hold
|
|
@@ -159,7 +167,12 @@ export function normalizeExtractedText(text) {
|
|
|
159
167
|
.replace(/\n{3,}/g, '\n\n')
|
|
160
168
|
.trim();
|
|
161
169
|
}
|
|
162
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* One turn of the event loop — a macrotask, so a due timer runs before what follows. A
|
|
172
|
+
* `setTimeout` on purpose, not the cheaper `setImmediate` the parsers yield with: timers run in
|
|
173
|
+
* order of when they are due, so this one cannot run before a deadline that has already
|
|
174
|
+
* passed, whichever phase of the loop the extractor returned in. It runs once per extraction.
|
|
175
|
+
*/
|
|
163
176
|
const nextTurn = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
164
177
|
const TIMED_OUT = Symbol('timed out');
|
|
165
178
|
/** A recorded reason is short: an extractor's `failed` is cut, never trusted to be. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-extractor.js","sourceRoot":"","sources":["../src/attachment-extractor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attachment-extractor.js","sourceRoot":"","sources":["../src/attachment-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The alphabet, whole: what `toBase64url` can emit and `fromBase64url` will accept. */
|
|
2
|
+
export declare const BASE64URL: RegExp;
|
|
3
|
+
export declare function toBase64url(bytes: Uint8Array): string;
|
|
4
|
+
/** The bytes, or `undefined` for a string outside the alphabet or of an impossible length. */
|
|
5
|
+
export declare function fromBase64url(s: string): Uint8Array | undefined;
|
|
6
|
+
//# sourceMappingURL=base64url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../src/base64url.ts"],"names":[],"mappings":"AAOA,wFAAwF;AACxF,eAAO,MAAM,SAAS,QAAqB,CAAC;AAE5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIrD;AAED,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAM/D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The alphabet, whole: what `toBase64url` can emit and `fromBase64url` will accept. */
|
|
2
|
+
export const BASE64URL = /^[A-Za-z0-9_-]*$/;
|
|
3
|
+
export function toBase64url(bytes) {
|
|
4
|
+
let s = '';
|
|
5
|
+
for (const b of bytes)
|
|
6
|
+
s += String.fromCharCode(b);
|
|
7
|
+
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
8
|
+
}
|
|
9
|
+
/** The bytes, or `undefined` for a string outside the alphabet or of an impossible length. */
|
|
10
|
+
export function fromBase64url(s) {
|
|
11
|
+
if (!BASE64URL.test(s) || s.length % 4 === 1)
|
|
12
|
+
return undefined;
|
|
13
|
+
const raw = atob(s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4));
|
|
14
|
+
const out = new Uint8Array(raw.length);
|
|
15
|
+
for (let i = 0; i < raw.length; i++)
|
|
16
|
+
out[i] = raw.charCodeAt(i);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=base64url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.js","sourceRoot":"","sources":["../src/base64url.ts"],"names":[],"mappings":"AAOA,wFAAwF;AACxF,MAAM,CAAC,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAE5C,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjG,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/capability.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BecomeCapabilityInput, type CapabilityExchange, type CapabilityFilter, type CapabilityId, type CapabilityMintInput, type CapabilityRecord, type CheckSubject, type Decision, type DomainEventInput, type EntityRef, type Instant, type MintedCapability, type Node, type PermissionKey, type PlatformActorId, type PrincipalId } from '@substrat-run/contracts';
|
|
1
|
+
import { type BecomeCapabilityInput, type CapabilityExchange, type CapabilityFilter, type CapabilityId, type CapabilityMintInput, type CapabilityPage, type CapabilityRecord, type CheckSubject, type Decision, type DomainEventInput, type EntityRef, type Instant, type MintedCapability, type Node, type PermissionKey, type PlatformActorId, type PrincipalId } from '@substrat-run/contracts';
|
|
2
2
|
import { PermissionDenied } from './permission-checker.js';
|
|
3
3
|
import type { ScopedSql, SqlValue } from './scope-host.js';
|
|
4
4
|
/**
|
|
@@ -7,6 +7,8 @@ import type { ScopedSql, SqlValue } from './scope-host.js';
|
|
|
7
7
|
* self-host builds and the shape production builds cannot part company.
|
|
8
8
|
*/
|
|
9
9
|
export declare const CAPABILITY_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_capabilities (\n id TEXT PRIMARY KEY,\n -- #1672: SHA-256 hex of the secret. The secret itself is stored NOWHERE \u2014 it leaves\n -- the kernel once, in the mint's return value, and a lookup is this indexed equality.\n token_hash TEXT NOT NULL UNIQUE,\n -- 'act': the holder acts AS the capability ({ capability } on the spine), bounded by\n -- entity + permissions (+ operations). 'become': exchanging it yields `principal`.\n mode TEXT NOT NULL,\n label TEXT,\n entity_type TEXT,\n entity_id TEXT,\n -- JSON array of permission keys (act). The capability's whole authority: no\n -- `capability:` tuple is ever written, so none can be forged through the tuple table.\n permissions TEXT,\n -- JSON array of operation names, or NULL = any operation the keys allow (act).\n operations TEXT,\n principal TEXT,\n -- JSON capabilityAuthor: a principal id (a module minted it, and its authority is\n -- re-checked on every use) or {\"platform\": \u2026} (HostAdmin minted it).\n minted_by TEXT NOT NULL,\n minted_at TEXT NOT NULL,\n expires_at TEXT,\n -- NULL = unlimited. A use is an EXCHANGE of the secret, never an invocation.\n max_uses INTEGER,\n uses INTEGER NOT NULL DEFAULT 0,\n last_used_at TEXT,\n -- Tombstone, never a delete: a capability that once admitted someone is evidence\n -- of why they were admitted (K-4, K-21).\n revoked_at TEXT,\n revoked_by TEXT\n );\n CREATE INDEX IF NOT EXISTS _substrat_capabilities_entity\n ON _substrat_capabilities (entity_type, entity_id);\n -- #1672: what an exchange hands out instead of the secret \u2014 a session token, of which\n -- again only the hash is kept. Each session acts as its capability until the earlier of\n -- its own expiry and the capability's; the capability is re-read on every invoke, so\n -- revoking it ends every session at once \u2014 a revoke touches only the capability row,\n -- and it is that per-invoke read, not a cascade here, that refuses the next call.\n -- Expired rows are pruned on exchange, at most CAPABILITY_SESSION_PRUNE_BATCH at a time,\n -- on the only path that adds one \u2014 so an exchange's write is bounded however many expired.\n CREATE TABLE IF NOT EXISTS _substrat_capability_sessions (\n token_hash TEXT PRIMARY KEY,\n capability_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS _substrat_capability_sessions_expiry\n ON _substrat_capability_sessions (expires_at);\n";
|
|
10
|
+
/** The tables `CAPABILITY_DDL` builds — what a copy of a scope leaves behind (`capabilitiesForLoad`, #1686). */
|
|
11
|
+
export declare const CAPABILITY_TABLE_NAMES: readonly ['_substrat_capabilities', '_substrat_capability_sessions'];
|
|
10
12
|
/** One capability row as the spine stores it — snake_case, because that is what both adapters `SELECT`. */
|
|
11
13
|
export interface CapabilityRow {
|
|
12
14
|
id: string;
|
|
@@ -33,6 +35,39 @@ export declare function capabilityByIdQuery(id: string): {
|
|
|
33
35
|
sql: string;
|
|
34
36
|
params: SqlValue[];
|
|
35
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The SELECT behind every read of a scope's capabilities — `ctx.capabilities.list` and the
|
|
40
|
+
* operator's `HostAdmin.listCapabilities` (#1686) both build it here, so "newest first" and
|
|
41
|
+
* "live only unless asked" have one definition. Columns are `CAPABILITY_COLUMNS`, which never
|
|
42
|
+
* includes `token_hash`: no reader of this query can return a hash. The filter is re-parsed
|
|
43
|
+
* (bounded: `limit` is 1..200, default 50; a `cursor` must be a capability id) and every value
|
|
44
|
+
* is bound.
|
|
45
|
+
*
|
|
46
|
+
* Keyset-paged on `id`, the table's primary key, walked backwards: a ULID id IS creation
|
|
47
|
+
* order, and a record minted mid-walk gets a LARGER id than any cursor already handed out, so
|
|
48
|
+
* a concurrent mint can neither repeat a record nor make the walk skip one. `extra` reads that
|
|
49
|
+
* many rows beyond the page, which is how `readCapabilityPage` knows more follow.
|
|
50
|
+
*/
|
|
51
|
+
export declare function capabilityListQuery(raw?: CapabilityFilter, extra?: number): {
|
|
52
|
+
sql: string;
|
|
53
|
+
params: SqlValue[];
|
|
54
|
+
};
|
|
55
|
+
/** A read that names no `limit` gets this many records. */
|
|
56
|
+
export declare const DEFAULT_CAPABILITY_LIMIT = 50;
|
|
57
|
+
/**
|
|
58
|
+
* The directory read, decoded — the ONE function behind `ctx.capabilities.list`, and (as
|
|
59
|
+
* `readCapabilityPage`) `HostAdmin.listCapabilities` on both adapters, so "a reader returns
|
|
60
|
+
* records and never a hash" is held in one place: the query selects no `token_hash`, and the
|
|
61
|
+
* decode keeps only the record schema's fields.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readCapabilities(sql: ScopedSql, filter?: CapabilityFilter): CapabilityRecord[];
|
|
64
|
+
/**
|
|
65
|
+
* One page of the operator's read. Reads one row past the page: `nextCursor` is the last
|
|
66
|
+
* returned record's id when that extra row exists, and `null` when the walk is complete — so
|
|
67
|
+
* the caller can always tell "that is all" from "there is more", and a console never has to
|
|
68
|
+
* guess from a full page whether it truncated.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readCapabilityPage(sql: ScopedSql, filter?: CapabilityFilter): CapabilityPage;
|
|
36
71
|
/**
|
|
37
72
|
* Is this capability USABLE — not revoked, not expired? The ONE predicate for it: the
|
|
38
73
|
* permission checker, the session door and the exchange all call this, so the three can
|
package/dist/capability.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAiBL,KAAK,qBAAqB,EAE1B,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAExB,KAAK,gBAAgB,EAErB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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"}
|
package/dist/capability.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CAPABILITY_EXERCISED, CAPABILITY_MINTED, CAPABILITY_REVOKED, CAPABILITY_SECRET_PREFIX, CAPABILITY_SESSION_PREFIX, CAPABILITY_SESSION_TTL_MS, becomeCapabilityInput, capabilityAuthor, capabilityFilter, capabilityId as capabilityIdSchema, capabilityMintInput, capabilityRecord, permissionKey, principalId as principalIdSchema, substratError, entityObjectRef, } from '@substrat-run/contracts';
|
|
2
|
+
import { toBase64url } from './base64url.js';
|
|
2
3
|
import { PermissionDenied } from './permission-checker.js';
|
|
3
4
|
import { ulid } from './ulid.js';
|
|
4
5
|
/**
|
|
@@ -56,6 +57,8 @@ export const CAPABILITY_DDL = `
|
|
|
56
57
|
CREATE INDEX IF NOT EXISTS _substrat_capability_sessions_expiry
|
|
57
58
|
ON _substrat_capability_sessions (expires_at);
|
|
58
59
|
`;
|
|
60
|
+
/** The tables `CAPABILITY_DDL` builds — what a copy of a scope leaves behind (`capabilitiesForLoad`, #1686). */
|
|
61
|
+
export const CAPABILITY_TABLE_NAMES = ['_substrat_capabilities', '_substrat_capability_sessions'];
|
|
59
62
|
/** Every column a read returns — everything but `token_hash`, which no reader has a use for. */
|
|
60
63
|
export const CAPABILITY_COLUMNS = 'id, mode, label, entity_type, entity_id, permissions, operations, principal, minted_by, ' +
|
|
61
64
|
'minted_at, expires_at, max_uses, uses, last_used_at, revoked_at, revoked_by';
|
|
@@ -66,6 +69,67 @@ export function capabilityByIdQuery(id) {
|
|
|
66
69
|
params: [id],
|
|
67
70
|
};
|
|
68
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The SELECT behind every read of a scope's capabilities — `ctx.capabilities.list` and the
|
|
74
|
+
* operator's `HostAdmin.listCapabilities` (#1686) both build it here, so "newest first" and
|
|
75
|
+
* "live only unless asked" have one definition. Columns are `CAPABILITY_COLUMNS`, which never
|
|
76
|
+
* includes `token_hash`: no reader of this query can return a hash. The filter is re-parsed
|
|
77
|
+
* (bounded: `limit` is 1..200, default 50; a `cursor` must be a capability id) and every value
|
|
78
|
+
* is bound.
|
|
79
|
+
*
|
|
80
|
+
* Keyset-paged on `id`, the table's primary key, walked backwards: a ULID id IS creation
|
|
81
|
+
* order, and a record minted mid-walk gets a LARGER id than any cursor already handed out, so
|
|
82
|
+
* a concurrent mint can neither repeat a record nor make the walk skip one. `extra` reads that
|
|
83
|
+
* many rows beyond the page, which is how `readCapabilityPage` knows more follow.
|
|
84
|
+
*/
|
|
85
|
+
export function capabilityListQuery(raw, extra = 0) {
|
|
86
|
+
const filter = capabilityFilter.parse(raw ?? {});
|
|
87
|
+
const where = [];
|
|
88
|
+
const params = [];
|
|
89
|
+
if (filter.entity) {
|
|
90
|
+
where.push('entity_type = ? AND entity_id = ?');
|
|
91
|
+
params.push(filter.entity.entityType, filter.entity.entityId);
|
|
92
|
+
}
|
|
93
|
+
if (!filter.includeRevoked)
|
|
94
|
+
where.push('revoked_at IS NULL');
|
|
95
|
+
if (filter.cursor !== undefined) {
|
|
96
|
+
where.push('id < ?');
|
|
97
|
+
params.push(filter.cursor);
|
|
98
|
+
}
|
|
99
|
+
params.push((filter.limit ?? DEFAULT_CAPABILITY_LIMIT) + extra);
|
|
100
|
+
return {
|
|
101
|
+
sql: `SELECT ${CAPABILITY_COLUMNS} FROM _substrat_capabilities` +
|
|
102
|
+
`${where.length ? ` WHERE ${where.join(' AND ')}` : ''} ORDER BY id DESC LIMIT ?`,
|
|
103
|
+
params,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** A read that names no `limit` gets this many records. */
|
|
107
|
+
export const DEFAULT_CAPABILITY_LIMIT = 50;
|
|
108
|
+
/**
|
|
109
|
+
* The directory read, decoded — the ONE function behind `ctx.capabilities.list`, and (as
|
|
110
|
+
* `readCapabilityPage`) `HostAdmin.listCapabilities` on both adapters, so "a reader returns
|
|
111
|
+
* records and never a hash" is held in one place: the query selects no `token_hash`, and the
|
|
112
|
+
* decode keeps only the record schema's fields.
|
|
113
|
+
*/
|
|
114
|
+
export function readCapabilities(sql, filter) {
|
|
115
|
+
const q = capabilityListQuery(filter);
|
|
116
|
+
return sql.query(q.sql, q.params).map(capabilityRecordOf);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* One page of the operator's read. Reads one row past the page: `nextCursor` is the last
|
|
120
|
+
* returned record's id when that extra row exists, and `null` when the walk is complete — so
|
|
121
|
+
* the caller can always tell "that is all" from "there is more", and a console never has to
|
|
122
|
+
* guess from a full page whether it truncated.
|
|
123
|
+
*/
|
|
124
|
+
export function readCapabilityPage(sql, filter) {
|
|
125
|
+
const parsed = capabilityFilter.parse(filter ?? {});
|
|
126
|
+
const limit = parsed.limit ?? DEFAULT_CAPABILITY_LIMIT;
|
|
127
|
+
const q = capabilityListQuery(parsed, 1);
|
|
128
|
+
const rows = sql.query(q.sql, q.params);
|
|
129
|
+
const entries = rows.slice(0, limit).map(capabilityRecordOf);
|
|
130
|
+
const last = entries[entries.length - 1];
|
|
131
|
+
return { entries, nextCursor: rows.length > limit && last ? last.id : null };
|
|
132
|
+
}
|
|
69
133
|
/**
|
|
70
134
|
* Is this capability USABLE — not revoked, not expired? The ONE predicate for it: the
|
|
71
135
|
* permission checker, the session door and the exchange all call this, so the three can
|
|
@@ -119,19 +183,13 @@ export function capabilityRecordOf(row) {
|
|
|
119
183
|
operations: row.operations === null ? null : JSON.parse(row.operations),
|
|
120
184
|
});
|
|
121
185
|
}
|
|
122
|
-
const b64url = (bytes) => {
|
|
123
|
-
let s = '';
|
|
124
|
-
for (const b of bytes)
|
|
125
|
-
s += String.fromCharCode(b);
|
|
126
|
-
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
127
|
-
};
|
|
128
186
|
/** A fresh capability secret: 256 bits, base64url, prefixed. Only its hash is ever stored. */
|
|
129
187
|
export function mintCapabilitySecret() {
|
|
130
|
-
return CAPABILITY_SECRET_PREFIX +
|
|
188
|
+
return CAPABILITY_SECRET_PREFIX + toBase64url(crypto.getRandomValues(new Uint8Array(32)));
|
|
131
189
|
}
|
|
132
190
|
/** A fresh session token — the same construction, its own prefix. */
|
|
133
191
|
export function mintCapabilitySessionToken() {
|
|
134
|
-
return CAPABILITY_SESSION_PREFIX +
|
|
192
|
+
return CAPABILITY_SESSION_PREFIX + toBase64url(crypto.getRandomValues(new Uint8Array(32)));
|
|
135
193
|
}
|
|
136
194
|
/**
|
|
137
195
|
* SHA-256 hex of a secret or session token (Web Crypto — the same call in workerd, node and
|
|
@@ -354,22 +412,7 @@ export function createCapabilityVerbs(deps) {
|
|
|
354
412
|
payload,
|
|
355
413
|
});
|
|
356
414
|
},
|
|
357
|
-
list(raw)
|
|
358
|
-
const filter = capabilityFilter.parse(raw ?? {});
|
|
359
|
-
const where = [];
|
|
360
|
-
const params = [];
|
|
361
|
-
if (filter.entity) {
|
|
362
|
-
where.push('entity_type = ? AND entity_id = ?');
|
|
363
|
-
params.push(filter.entity.entityType, filter.entity.entityId);
|
|
364
|
-
}
|
|
365
|
-
if (!filter.includeRevoked)
|
|
366
|
-
where.push('revoked_at IS NULL');
|
|
367
|
-
params.push(filter.limit ?? 50);
|
|
368
|
-
const rows = deps.sql.query(`SELECT ${CAPABILITY_COLUMNS} FROM _substrat_capabilities
|
|
369
|
-
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
370
|
-
ORDER BY id DESC LIMIT ?`, params);
|
|
371
|
-
return rows.map(capabilityRecordOf);
|
|
372
|
-
},
|
|
415
|
+
list: (raw) => readCapabilities(deps.sql, raw),
|
|
373
416
|
};
|
|
374
417
|
}
|
|
375
418
|
// ---------------------------------------------------------------------------
|