@volter/world-core 3.0.162 → 3.0.164
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/src/browser-session.d.ts +7 -1
- package/dist/src/browser-session.js +21 -10
- package/dist/src/derived-core.d.ts +36 -3
- package/dist/src/derived-core.js +121 -13
- package/dist/src/derived-real.js +15 -3
- package/dist/src/events.d.ts +6 -3
- package/dist/src/events.js +52 -7
- package/dist/src/file-response.d.ts +6 -2
- package/dist/src/file-response.js +29 -5
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -1
- package/dist/src/managed-database.d.ts +4 -2
- package/dist/src/managed-database.js +1 -0
- package/dist/src/media-header.d.ts +34 -0
- package/dist/src/media-header.js +129 -0
- package/dist/src/pack-fetch.d.ts +24 -3
- package/dist/src/pack-fetch.js +114 -94
- package/dist/src/packRegistry.d.ts +12 -0
- package/dist/src/request-body.d.ts +2 -3
- package/dist/src/request-body.js +2 -3
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +2 -1
- package/dist/src/serve-http.d.ts +1 -1
- package/dist/src/serve-http.js +93 -17
- package/package.json +1 -1
- package/src/browser-session.ts +17 -5
- package/src/derived-core.ts +135 -17
- package/src/derived-real.ts +10 -2
- package/src/events.ts +54 -9
- package/src/file-response.ts +28 -6
- package/src/index.ts +2 -1
- package/src/managed-database.ts +4 -3
- package/src/media-header.ts +108 -0
- package/src/pack-fetch.ts +107 -85
- package/src/packRegistry.ts +7 -1
- package/src/request-body.ts +2 -3
- package/src/runtime.ts +2 -1
- package/src/serve-http.ts +70 -16
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { HandlerContext } from './derived-core.js';
|
|
2
|
+
import { type Person } from './people.js';
|
|
2
3
|
type Writer = Pick<HandlerContext, 'record' | 'secret' | 'occurredAt'>;
|
|
3
|
-
/** The
|
|
4
|
+
/** The value of the cookie `name` the request carries, as a browser sends it back. */
|
|
5
|
+
export declare function cookieOf(request: Request, name: string): string | undefined;
|
|
6
|
+
/** The named session cookie; malformed encodings do not name a session. */
|
|
4
7
|
export declare function browserSessionToken(request: Request, name: string): string | undefined;
|
|
8
|
+
/** The person the request's session cookie names, or undefined when nobody is signed in; with `scope`, only a session
|
|
9
|
+
* begun in it (a vendor's sandbox dashboard and its production one sign a person in apart). */
|
|
10
|
+
export declare function signedIn(ctx: HandlerContext, cookie: string, scope?: string): Person | undefined;
|
|
5
11
|
/** A new, independent sign-in, returning the cookie the vendor places on its response. */
|
|
6
12
|
export declare function startBrowserSession(ctx: Writer, email: string, cookie: string, scope?: string): Promise<string>;
|
|
7
13
|
/** End the session the request holds, then clear its cookie. Unknown cookies create no rows. */
|
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { personOf } from "./people.js";
|
|
2
|
+
/** The value of the cookie `name` the request carries, as a browser sends it back. */
|
|
3
|
+
export function cookieOf(request, name) {
|
|
3
4
|
for (const part of (request.headers.get('cookie') ?? '').split(';')) {
|
|
4
5
|
const at = part.indexOf('=');
|
|
5
|
-
if (at > 0 && part.slice(0, at).trim() === name)
|
|
6
|
-
|
|
7
|
-
return decodeURIComponent(part.slice(at + 1).trim());
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
6
|
+
if (at > 0 && part.slice(0, at).trim() === name)
|
|
7
|
+
return decodeURIComponent(part.slice(at + 1).trim());
|
|
13
8
|
}
|
|
14
9
|
return undefined;
|
|
15
10
|
}
|
|
11
|
+
/** The named session cookie; malformed encodings do not name a session. */
|
|
12
|
+
export function browserSessionToken(request, name) {
|
|
13
|
+
try {
|
|
14
|
+
return cookieOf(request, name);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** The person the request's session cookie names, or undefined when nobody is signed in; with `scope`, only a session
|
|
21
|
+
* begun in it (a vendor's sandbox dashboard and its production one sign a person in apart). */
|
|
22
|
+
export function signedIn(ctx, cookie, scope) {
|
|
23
|
+
const token = cookieOf(ctx.call.request, cookie);
|
|
24
|
+
const session = token ? ctx.rowsRaw('_web_session').find((s) => s.token === token && (scope === undefined || s.scope === scope)) : undefined;
|
|
25
|
+
return session ? personOf(ctx, String(session.email)) : undefined;
|
|
26
|
+
}
|
|
16
27
|
/** A new, independent sign-in, returning the cookie the vendor places on its response. */
|
|
17
28
|
export async function startBrowserSession(ctx, email, cookie, scope) {
|
|
18
29
|
const issued = await ctx.record('_web_session_issue', { email, ...(scope !== undefined ? { scope } : {}) });
|
|
@@ -15,7 +15,7 @@ import { type MultipartPart } from './multipart.js';
|
|
|
15
15
|
import type { CorsDecl } from './cors.js';
|
|
16
16
|
import { type TwinAction } from './actions.js';
|
|
17
17
|
import type { DerivedCall, DerivedCoreOutcome, DerivedHandler, DerivedOperation } from './derived.js';
|
|
18
|
-
import type
|
|
18
|
+
import { type PackDescriptor } from './packRegistry.js';
|
|
19
19
|
import type { ScenarioDecision, ScenarioHandler } from './scenario.js';
|
|
20
20
|
/** How a stored field gets its value when the server assigns it. `now` is the world clock in the
|
|
21
21
|
* manifest's time format; `id` the subject's id; a `value` is stored as given. */
|
|
@@ -914,13 +914,25 @@ export type CoreScope = {
|
|
|
914
914
|
readOnly?: boolean;
|
|
915
915
|
/** `semantics/tenant.ts`: the tenant the caller acts in (the manifest's `tenant`), or undefined for none */
|
|
916
916
|
tenant?: (ctx: HandlerContext) => string | undefined | Promise<string | undefined>;
|
|
917
|
-
/** the application a walk stands in for:
|
|
917
|
+
/** the application a walk stands in for: vendor requests and declared events are answered by it (events.ts) */
|
|
918
918
|
application?: ApplicationStandIn;
|
|
919
919
|
};
|
|
920
920
|
/** World-owned generated values use the same private seed and log as pack secrets. */
|
|
921
921
|
export declare function worldSecret(service: string, root: string, label: string, at?: string): Promise<string>;
|
|
922
922
|
/** The same retained account primitive for pack doors and private native twin fronts. */
|
|
923
923
|
export declare function worldAccount(service: string, root: string | undefined, id?: unknown, at?: string, provisioning?: boolean): Promise<string>;
|
|
924
|
+
type WorldOrganization = {
|
|
925
|
+
id: string;
|
|
926
|
+
label: string;
|
|
927
|
+
members: string[];
|
|
928
|
+
};
|
|
929
|
+
type VendorNameRule = {
|
|
930
|
+
max: number;
|
|
931
|
+
lower?: boolean;
|
|
932
|
+
allowed: RegExp;
|
|
933
|
+
separator?: string;
|
|
934
|
+
first?: RegExp;
|
|
935
|
+
};
|
|
924
936
|
/** The lane a vendor's router sent a request to (pack-fetch's laneRouter sets it). */
|
|
925
937
|
export declare const LANE_HEADER = "x-volter-lane";
|
|
926
938
|
/** What a coverage measurement sees of a pack's declared state logic: each move a request made and each refusal a
|
|
@@ -1018,6 +1030,27 @@ export type SemanticsContext = {
|
|
|
1018
1030
|
secret(label: string): Promise<string>;
|
|
1019
1031
|
/** Select an account at a pack's credential door. Packs retain their own vendor account/key semantics. */
|
|
1020
1032
|
account(field?: string): Promise<string>;
|
|
1033
|
+
/** The selected organization's label and people from the World's organization registry. */
|
|
1034
|
+
organization(): Promise<WorldOrganization>;
|
|
1035
|
+
/** The selected deployment, or an empty string for the organization's own level. */
|
|
1036
|
+
deployment(): string;
|
|
1037
|
+
/** A vendor-safe label; retained links keep identity independent of names. */
|
|
1038
|
+
vendorName(label: string, rule: VendorNameRule, options?: {
|
|
1039
|
+
suffixed?: boolean;
|
|
1040
|
+
identity?: string;
|
|
1041
|
+
}): string;
|
|
1042
|
+
/** Retain the native customer tenant for the selected organization. */
|
|
1043
|
+
tenant(resource: string, make: (organization: WorldOrganization) => Promise<string>): Promise<Record<string, unknown>>;
|
|
1044
|
+
/** Retain one native resource of this kind for the selected organization and deployment. */
|
|
1045
|
+
deploymentObject(kind: string, resource: string, make: (deployment: string) => Promise<string>): Promise<Record<string, unknown>>;
|
|
1046
|
+
/** Reconstruct a live native key, replacing its link when the vendor's reader refuses it. */
|
|
1047
|
+
accountKey(purpose: string, resource: string, key: {
|
|
1048
|
+
make: () => Promise<string>;
|
|
1049
|
+
read: (row: Record<string, unknown>) => Promise<string | undefined>;
|
|
1050
|
+
}): Promise<{
|
|
1051
|
+
id: string;
|
|
1052
|
+
secret: string;
|
|
1053
|
+
}>;
|
|
1021
1054
|
/** The World's machine pool (machines.ts), as the pack's `machine-pool` door enrolled it; `none` when none was. */
|
|
1022
1055
|
machines: MachinePool;
|
|
1023
1056
|
/** A signing key pair for `label` (an issuer's ID tokens, a provider's JWTs, a CA's or a log's key), PEM: RSA 2048
|
|
@@ -1219,7 +1252,7 @@ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically
|
|
|
1219
1252
|
};
|
|
1220
1253
|
/** What a write hook reads: the writing call's context without its ways to write, so a hook that renders an event
|
|
1221
1254
|
* cannot write again (and run itself again). */
|
|
1222
|
-
export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'at' | 'over'>;
|
|
1255
|
+
export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'tenant' | 'deploymentObject' | 'accountKey' | 'at' | 'over'>;
|
|
1223
1256
|
/** A derived pack's handler: one operation, by its operationId, over the contract's context. */
|
|
1224
1257
|
export type Handler = (ctx: HandlerContext) => Promise<Response>;
|
|
1225
1258
|
export declare function contextFor(m: DerivedManifest, call: DerivedCall, scope: CoreScope): Promise<SemanticsContext>;
|
package/dist/src/derived-core.js
CHANGED
|
@@ -37,6 +37,7 @@ import { vendorFetch } from "./vendor-call.js";
|
|
|
37
37
|
import { packReferences } from "./references.js";
|
|
38
38
|
import { twinPublicBase, withRequestScopes } from "./twin-fetch.js";
|
|
39
39
|
import { inVendorMove, isReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
|
|
40
|
+
import { getPack } from "./packRegistry.js";
|
|
40
41
|
import { bodyTextOf, checkJsonBody, takeParsedBody } from "./derived.js";
|
|
41
42
|
/** What a move to `to` stores in the field: a boolean field's value is a boolean, and a derived field's
|
|
42
43
|
* value is written by the move's effects (a state name is not a timestamp), so it stores nothing itself. */
|
|
@@ -473,6 +474,57 @@ export async function worldAccount(service, root, id = 'default', at = worldNow(
|
|
|
473
474
|
}, root);
|
|
474
475
|
return id;
|
|
475
476
|
}
|
|
477
|
+
function nativeAccountLink(row, field) {
|
|
478
|
+
if (!row || row.deleted === true)
|
|
479
|
+
return undefined;
|
|
480
|
+
const fields = ownFields(row);
|
|
481
|
+
const value = field === undefined ? fields : fields[field];
|
|
482
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
483
|
+
return undefined;
|
|
484
|
+
const link = value;
|
|
485
|
+
return typeof link.resource === 'string' && typeof link.id === 'string' ? { resource: link.resource, id: link.id } : undefined;
|
|
486
|
+
}
|
|
487
|
+
const sameAccountLink = (a, b) => a?.resource === b?.resource && a?.id === b?.id;
|
|
488
|
+
/** Native creation and reconstruction run outside the action lock; only the retained link is compared and written. */
|
|
489
|
+
async function retainNativeAccountLink(service, root, at, call, type, id, field, resource, make, read) {
|
|
490
|
+
for (;;) {
|
|
491
|
+
const before = nativeAccountLink(resourcesOfType(service, type, root).find((row) => row.id === id), field);
|
|
492
|
+
const held = before && await read(before);
|
|
493
|
+
if (held !== undefined)
|
|
494
|
+
return held;
|
|
495
|
+
const made = { resource, id: await make() };
|
|
496
|
+
if (await read(made) === undefined)
|
|
497
|
+
throw new Error(`Credential ${type}/${id}: native ${resource}/${made.id} is unavailable after creation`);
|
|
498
|
+
const { value } = await applyTwinWriteAtomic(service, (snapshot) => {
|
|
499
|
+
const current = nativeAccountLink(snapshot.find((row) => row.type === type && row.id === id), field);
|
|
500
|
+
if (!sameAccountLink(current, before))
|
|
501
|
+
return { kind: 'skip', value: current };
|
|
502
|
+
return {
|
|
503
|
+
kind: 'write', value: made,
|
|
504
|
+
write: {
|
|
505
|
+
operation: `${type.slice(1)}.link`, subjectType: type, subjectId: id,
|
|
506
|
+
fields: field === undefined ? made : { name: id, [field]: made },
|
|
507
|
+
...provisioningInput(call), occurredAt: at, actor: { kind: 'system' },
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}, root);
|
|
511
|
+
const linked = value && await read(value);
|
|
512
|
+
if (linked !== undefined)
|
|
513
|
+
return linked;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function vendorName(label, rule, { suffixed = false, identity = label } = {}) {
|
|
517
|
+
const allowed = new RegExp(rule.allowed.source, rule.allowed.flags.replace(/[gy]/g, ''));
|
|
518
|
+
const separator = rule.separator ?? '-';
|
|
519
|
+
if ([...separator].length !== 1 || !allowed.test(separator))
|
|
520
|
+
throw new Error('Credential name separator must be one allowed character');
|
|
521
|
+
const first = rule.first && new RegExp(rule.first.source, rule.first.flags.replace(/[gy]/g, ''));
|
|
522
|
+
let name = [...(rule.lower ? label.toLowerCase() : label)].map((letter) => allowed.test(letter) ? letter : separator).join('');
|
|
523
|
+
name = name.replace(new RegExp(`${escapeRe(separator)}+`, 'g'), separator);
|
|
524
|
+
if (first && !first.test(name[0] ?? ''))
|
|
525
|
+
name = `w${separator}${name}`;
|
|
526
|
+
return name !== label || name.length > rule.max || suffixed ? `${name.slice(0, Math.max(0, rule.max - 9))}${separator}${sha256(identity).slice(0, 8)}` : name;
|
|
527
|
+
}
|
|
476
528
|
/** A World's signing key for a label (ctx.signingKey): made once from the platform's random source, then read; the
|
|
477
529
|
* making is one per service store and label in the process, as the seed's is. */
|
|
478
530
|
const SIGNING_KEY = '_world_signing_key';
|
|
@@ -571,9 +623,9 @@ function mintId(m, resource, root, at, snapshot) {
|
|
|
571
623
|
* increment. One space over every snowflake resource of the vendor, as its one generator's: the instant's first, or one
|
|
572
624
|
* past the greatest the World holds when that is later (a frozen clock, or several ids in one millisecond), so ids
|
|
573
625
|
* rise with time and never repeat. */
|
|
574
|
-
/** The last snowflake minted per vendor and World
|
|
626
|
+
/** The last snowflake minted per store, vendor and World: two mints before the first is written (a message
|
|
575
627
|
* and its attachment) must not meet. */
|
|
576
|
-
const lastSnowflake = new
|
|
628
|
+
const lastSnowflake = new WeakMap();
|
|
577
629
|
function mintSnowflake(m, epoch, root, at, snapshot) {
|
|
578
630
|
const ms = BigInt(Date.parse(at) || 0);
|
|
579
631
|
const base = ms > epoch ? (ms - epoch) << 22n : 0n;
|
|
@@ -588,11 +640,13 @@ function mintSnowflake(m, epoch, root, at, snapshot) {
|
|
|
588
640
|
max = v;
|
|
589
641
|
}
|
|
590
642
|
}
|
|
643
|
+
const store = getActiveWorldStore();
|
|
644
|
+
const reservations = lastSnowflake.get(store) ?? lastSnowflake.set(store, new Map()).get(store);
|
|
591
645
|
const key = `${m.service}\0${root ?? ''}`;
|
|
592
|
-
const last =
|
|
646
|
+
const last = reservations.get(key) ?? -1n;
|
|
593
647
|
const floor = max > last ? max : last;
|
|
594
648
|
const id = base > floor ? base : floor + 1n;
|
|
595
|
-
|
|
649
|
+
reservations.set(key, id);
|
|
596
650
|
return String(id);
|
|
597
651
|
}
|
|
598
652
|
/** The next KSUID for a resource (Clerk's and Svix's ids, `ksuidFrom`): the World's instant, then the type's mint
|
|
@@ -895,7 +949,7 @@ async function finishWrite(m, call, resource, row, result, operation, params, ro
|
|
|
895
949
|
const endpoint = (kind) => ({ ...kind, storedAs: storedType(m, kind.storedAs), ...(kind.joins ? { joins: Object.fromEntries(Object.entries(kind.joins).map(([name, join]) => [name, { ...join, storedAs: storedType(m, join.storedAs) }])) } : {}) });
|
|
896
950
|
const events = { ...m.events, ...(m.events.store ? { store: storedType(m, m.events.store) } : {}), endpoints: 'storedAs' in m.events.endpoints ? endpoint(m.events.endpoints) : m.events.endpoints.map(endpoint) };
|
|
897
951
|
const write = { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
|
|
898
|
-
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined);
|
|
952
|
+
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined, scopeOfCall.get(call)?.application);
|
|
899
953
|
}
|
|
900
954
|
if (m.sockets?.length)
|
|
901
955
|
socketWrite(m.service, root, { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
|
|
@@ -1489,6 +1543,8 @@ export async function contextFor(m, call, scope) {
|
|
|
1489
1543
|
});
|
|
1490
1544
|
const paths = expandPaths(m, params);
|
|
1491
1545
|
const stateChecks = new Map();
|
|
1546
|
+
const credentialDoor = m.descriptor?.credentialDoor ?? getPack(m.vendor)?.credentialDoor;
|
|
1547
|
+
const credentialFields = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
1492
1548
|
const rawResources = new WeakMap();
|
|
1493
1549
|
const rawRow = (resource, row) => {
|
|
1494
1550
|
const name = m.resources[resource] ? resource : Object.keys(m.resources).find((name) => storedType(m, name) === row.type);
|
|
@@ -1502,6 +1558,10 @@ export async function contextFor(m, call, scope) {
|
|
|
1502
1558
|
rawResources.set(row, name ?? resource);
|
|
1503
1559
|
return row;
|
|
1504
1560
|
};
|
|
1561
|
+
const liveNativeRow = (link) => {
|
|
1562
|
+
const row = context.row(link.resource, link.id);
|
|
1563
|
+
return row && row.deleted !== true ? row : undefined;
|
|
1564
|
+
};
|
|
1505
1565
|
const context = {
|
|
1506
1566
|
call,
|
|
1507
1567
|
params,
|
|
@@ -1558,9 +1618,38 @@ export async function contextFor(m, call, scope) {
|
|
|
1558
1618
|
},
|
|
1559
1619
|
occurredAt: at,
|
|
1560
1620
|
secret: async (label) => hmac(await worldSeed(m.service, root, at), scopeLabel(m, label), 'base64url'),
|
|
1561
|
-
account: async (field = 'account') => {
|
|
1562
|
-
|
|
1563
|
-
|
|
1621
|
+
account: async (field = credentialDoor?.account?.field ?? 'account') => {
|
|
1622
|
+
return worldAccount(m.service, root, credentialFields[field] ?? 'default', at, call.request.headers.get('x-volter-world-activity') === 'provisioning');
|
|
1623
|
+
},
|
|
1624
|
+
organization: async () => {
|
|
1625
|
+
const id = await context.account();
|
|
1626
|
+
const row = projectOwnerResources('_world', root, { type: '_world_organization', id }).find((row) => row.type === '_world_organization' && row.id === id && row.deleted !== true);
|
|
1627
|
+
return row ? { id, label: row.label, members: [...row.members] } : { id, label: id, members: ['owner@world.test'] };
|
|
1628
|
+
},
|
|
1629
|
+
deployment: () => {
|
|
1630
|
+
const id = credentialFields[credentialDoor?.deployment?.field ?? 'deployment'] ?? '';
|
|
1631
|
+
if (id !== '' && !isWorldAccountName(id))
|
|
1632
|
+
throw new Error('Credential deployment must be a name of at most 128 letters, digits, dots, underscores or hyphens');
|
|
1633
|
+
return id;
|
|
1634
|
+
},
|
|
1635
|
+
vendorName,
|
|
1636
|
+
tenant: async (resource, make) => {
|
|
1637
|
+
const id = await context.account();
|
|
1638
|
+
return retainNativeAccountLink(m.service, root, at, call, '_world_account', id, 'tenant', resource, async () => make(await context.organization()), async (link) => liveNativeRow(link));
|
|
1639
|
+
},
|
|
1640
|
+
deploymentObject: async (kind, resource, make) => {
|
|
1641
|
+
const organization = await context.account(), deployment = context.deployment();
|
|
1642
|
+
return retainNativeAccountLink(m.service, root, at, call, '_world_deployment_object', `${organization}/${deployment}/${kind}`, undefined, resource, () => make(deployment), async (link) => liveNativeRow(link));
|
|
1643
|
+
},
|
|
1644
|
+
accountKey: async (purpose, resource, key) => {
|
|
1645
|
+
const organization = await context.account(), deployment = context.deployment();
|
|
1646
|
+
return retainNativeAccountLink(m.service, root, at, call, '_world_account_key', `${organization}/${deployment}/${purpose}`, undefined, resource, () => key.make(), async (link) => {
|
|
1647
|
+
const row = liveNativeRow(link);
|
|
1648
|
+
if (!row)
|
|
1649
|
+
return undefined;
|
|
1650
|
+
const secret = await key.read(row);
|
|
1651
|
+
return secret === undefined ? undefined : { id: link.id, secret };
|
|
1652
|
+
});
|
|
1564
1653
|
},
|
|
1565
1654
|
record: async (type, fields, id, operation) => {
|
|
1566
1655
|
if (!type.startsWith('_'))
|
|
@@ -1621,7 +1710,12 @@ export async function contextFor(m, call, scope) {
|
|
|
1621
1710
|
const row = await atomicWrite(m, call, resource, (snapshot) => {
|
|
1622
1711
|
const id = options?.id ?? mintId(m, resource, root, at, snapshot);
|
|
1623
1712
|
const selected = typeof fields === 'function' ? fields(id) : fields;
|
|
1624
|
-
const hit = snapshot.find((r) =>
|
|
1713
|
+
const hit = snapshot.find((r) => {
|
|
1714
|
+
if (r.type !== storedType(m, resource) || r.deleted === true)
|
|
1715
|
+
return false;
|
|
1716
|
+
const fields = { [m.resources[resource]?.idAs ?? 'id']: r.id, ...ownFields(r) };
|
|
1717
|
+
return rules.some((rule) => rule.fields.every((field) => selected[field] !== undefined && fields[field] === selected[field]));
|
|
1718
|
+
});
|
|
1625
1719
|
if (hit) {
|
|
1626
1720
|
existing = view(m, resource, hit);
|
|
1627
1721
|
return undefined;
|
|
@@ -1661,7 +1755,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1661
1755
|
},
|
|
1662
1756
|
writeDetailed: (resource, id, fields, operation) => writeDetailed(m, call, resource, id, fields, operation, params, root, at, stateChecks),
|
|
1663
1757
|
resolve: (resource, id) => resolveSubjectId(m.service, storedType(m, resource), id, root),
|
|
1664
|
-
storedType: (resource) =>
|
|
1758
|
+
storedType: (resource) => m.resources[resource]?.storedAs ?? resource,
|
|
1665
1759
|
ok: (fields) => Response.json({ ...(m.success ?? {}), ...fields }),
|
|
1666
1760
|
row: (resource, id, opts) => { const row = stored(m, resource, root, opts).find((r) => r.id === id); return row ? rawRow(resource, row) : undefined; },
|
|
1667
1761
|
rowsRaw: (resource, opts) => stored(m, resource, root, opts).map((row) => rawRow(resource, row)),
|
|
@@ -1782,10 +1876,12 @@ const BATCH_REFUSED_READS = ['get', 'rows', 'tree', 'history', 'find', 'list', '
|
|
|
1782
1876
|
/** The members that write, or wait on writes, or answer as another context: each lands the staged writes first. */
|
|
1783
1877
|
const BATCH_LANDING_MEMBERS = ['create', 'createUnique', 'change', 'accumulate', 'writeDetailed', 'record', 'atomically', 'remove', 'issue', 'at', 'over', 'core', 'awaitWrite', 'awaitCondition',
|
|
1784
1878
|
// a World's seed and signing keys are written when first asked (worldSeed, worldSigningKey, as a vendor's move)
|
|
1785
|
-
'secret', 'signingKey'];
|
|
1879
|
+
'secret', 'signingKey', 'account', 'organization', 'tenant', 'deploymentObject', 'accountKey'];
|
|
1786
1880
|
/** `ctx.batch` over a context whose writes may be grouped (see `batchable`). */
|
|
1787
1881
|
function bindBatch(m, call, context, params, root, at, rawRow) {
|
|
1788
1882
|
let staged;
|
|
1883
|
+
// A callback's native object must land before the helper writes its retained link.
|
|
1884
|
+
let nativeAccountWrites = 0;
|
|
1789
1885
|
// each staged subject as it will stand, by `type:id`, in the order first written
|
|
1790
1886
|
const subjects = new Map();
|
|
1791
1887
|
const land = async () => {
|
|
@@ -1801,7 +1897,19 @@ function bindBatch(m, call, context, params, root, at, rawRow) {
|
|
|
1801
1897
|
const plain = ctx[name];
|
|
1802
1898
|
if (typeof plain !== 'function')
|
|
1803
1899
|
continue;
|
|
1804
|
-
ctx[name] = async (...args) => {
|
|
1900
|
+
ctx[name] = async (...args) => {
|
|
1901
|
+
await land();
|
|
1902
|
+
const native = name === 'tenant' || name === 'deploymentObject' || name === 'accountKey';
|
|
1903
|
+
if (native)
|
|
1904
|
+
nativeAccountWrites++;
|
|
1905
|
+
try {
|
|
1906
|
+
return await plain(...args);
|
|
1907
|
+
}
|
|
1908
|
+
finally {
|
|
1909
|
+
if (native)
|
|
1910
|
+
nativeAccountWrites--;
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1805
1913
|
}
|
|
1806
1914
|
for (const name of BATCH_REFUSED_READS) {
|
|
1807
1915
|
const plain = ctx[name];
|
|
@@ -1816,7 +1924,7 @@ function bindBatch(m, call, context, params, root, at, rawRow) {
|
|
|
1816
1924
|
const plainWrite = context.write, plainRow = context.row, plainRowsRaw = context.rowsRaw;
|
|
1817
1925
|
context.write = async (resource, id, fields, operation) => {
|
|
1818
1926
|
// a vendor's own move (ctx.asVendor) is the clock's write, recorded as the system's: it lands alone, in its place
|
|
1819
|
-
if (!staged || inVendorMove()) {
|
|
1927
|
+
if (!staged || nativeAccountWrites > 0 || inVendorMove()) {
|
|
1820
1928
|
await land();
|
|
1821
1929
|
return plainWrite(resource, id, fields, operation);
|
|
1822
1930
|
}
|
package/dist/src/derived-real.js
CHANGED
|
@@ -682,10 +682,10 @@ function refreshOf(units, service) {
|
|
|
682
682
|
const sent = asked.length ? `${target}${target.includes('?') ? '&' : '?'}${new URLSearchParams(asked)}` : target;
|
|
683
683
|
const answer = await execute({ method: op.method.toUpperCase(), path: sentTo(m, sent, subject), ...onLane, headers: fixedHeaders(op) });
|
|
684
684
|
// a door's subject the vendor does not show the root (another bot's private chat) stays as the World holds it
|
|
685
|
-
if (!rooted.has(key) && answer.status >= 400 && answer.status !== 429)
|
|
686
|
-
continue;
|
|
687
685
|
if (answer.status === 401)
|
|
688
686
|
throw new Error(`401: the vendor refused the root's credential (${op.method.toUpperCase()} ${path}); nothing folded`);
|
|
687
|
+
if (!rooted.has(key) && answer.status >= 400 && answer.status !== 429)
|
|
688
|
+
continue;
|
|
689
689
|
if (answer.status === 403) {
|
|
690
690
|
unread.push({ type, path, status: 403 });
|
|
691
691
|
refused = true;
|
|
@@ -700,7 +700,19 @@ function refreshOf(units, service) {
|
|
|
700
700
|
continue;
|
|
701
701
|
if (answer.status >= 400 || refusedIn(m, parse(answer)))
|
|
702
702
|
throw new Error(`${m.vendor}: ${resource} refresh ${op.id} (${op.method.toUpperCase()} ${op.path}): ${answer.status}: ${refusalWords(m, parse(answer))}`);
|
|
703
|
-
const
|
|
703
|
+
const parsed = parse(answer);
|
|
704
|
+
let full;
|
|
705
|
+
if (op.answers?.list) {
|
|
706
|
+
const items = itemsOf(op, parsed);
|
|
707
|
+
const matching = items.find((item) => (scopeIdOf(scope, item) ?? storedIdOf(decl, item)) === vendorId);
|
|
708
|
+
if (!matching && items.length)
|
|
709
|
+
throw new Error(`${m.vendor}: ${resource} refresh ${op.id} omitted the requested subject; nothing folded`);
|
|
710
|
+
if (!matching)
|
|
711
|
+
continue;
|
|
712
|
+
full = matching;
|
|
713
|
+
}
|
|
714
|
+
else
|
|
715
|
+
full = object(op.answers?.key ? at(parsed, op.answers.key) : parsed) ?? {};
|
|
704
716
|
const local = m.worldFields?.[resource] ?? {};
|
|
705
717
|
const fields = Object.fromEntries(Object.entries(full).filter(([k]) => !(k in local)));
|
|
706
718
|
if (decl.parent) {
|
package/dist/src/events.d.ts
CHANGED
|
@@ -164,6 +164,8 @@ export declare function eventTypeOf(decl: Pick<EventsDecl, 'types' | 'known'>, o
|
|
|
164
164
|
/** Whether an endpoint's list of event types takes a type: every type when the list is empty, `*` or `<family>.*`
|
|
165
165
|
* its patterns, else the type itself. */
|
|
166
166
|
export declare function takesEvent(list: unknown, type: string): boolean;
|
|
167
|
+
/** Await already-scheduled first dispatches and consume their retained persistence failures. */
|
|
168
|
+
export declare function settleEventDeliveries(root: string): Promise<void>;
|
|
167
169
|
/** Who watches signed packets before dispatch (a walk reading each as its receiver would). This is not evidence of
|
|
168
170
|
* transport or acceptance. Set and restored by the walk, as its World store is; unset, nothing watches. */
|
|
169
171
|
export type DeliveryObserver = (delivery: EventDelivery & {
|
|
@@ -179,13 +181,14 @@ export type ApplicationAnswer = {
|
|
|
179
181
|
body: string;
|
|
180
182
|
missed?: 'timeout' | 'unreachable';
|
|
181
183
|
};
|
|
182
|
-
/** Who answers
|
|
184
|
+
/** Who answers a vendor's requests to the application before the World's application route does, given to a
|
|
183
185
|
* pack's fetch (`PackFetchOptions.application`) and belonging to that fetch alone: an answer it gives is the
|
|
184
186
|
* application's; `'unreachable'` is a question nobody answers (a walk's life, a sealed World, takes no other); undefined
|
|
185
187
|
* is a question it does not take, which goes on as any other does: the application route, the World's egress rule, the
|
|
186
188
|
* network. A walk's stand-in answers every question (its life's answers, else unreachable); a hosted World's router
|
|
187
189
|
* answers only what it can answer truthfully (WorldDoors.askInWorld) and leaves the rest. `bodyError` models a
|
|
188
|
-
* disconnect after the response headers; a caller needing the complete body receives no complete answer
|
|
190
|
+
* disconnect after the response headers; a caller needing the complete body receives no complete answer, while an
|
|
191
|
+
* event dispatch acknowledges the response status without reading its body. */
|
|
189
192
|
export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
190
193
|
status: number;
|
|
191
194
|
headers?: Record<string, string>;
|
|
@@ -198,4 +201,4 @@ export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
|
198
201
|
export declare function askApplication(url: string, init: RequestInit, within: number, standIn?: ApplicationStandIn): Promise<ApplicationAnswer>;
|
|
199
202
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
200
203
|
* that takes it, and recorded. */
|
|
201
|
-
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown
|
|
204
|
+
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown>>, application?: ApplicationStandIn): Promise<EventDelivery[]>;
|
package/dist/src/events.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Imports stay free of side effects: the delivery's transport (Node's http, the World's egress policy) is loaded when a
|
|
8
8
|
// delivery is made, never when a browser client imports the kernel.
|
|
9
9
|
import { createHash, createHmac } from 'node:crypto';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
10
11
|
import { applyTwinWrite, applyTwinWriteAtomic, twinResources } from "./serve.js";
|
|
11
12
|
import { equalSecrets as same, ksuidFrom } from "./signing.js";
|
|
12
13
|
import { deliveryTraceHeaders } from "./trace-context.js";
|
|
@@ -223,6 +224,28 @@ const worldTransport = async (url, body, headers) => {
|
|
|
223
224
|
return 0;
|
|
224
225
|
}
|
|
225
226
|
};
|
|
227
|
+
const pendingDeliveries = new WeakMap();
|
|
228
|
+
function forgetDeliveries(store, root, deliveries) {
|
|
229
|
+
const roots = pendingDeliveries.get(store), pending = roots?.get(root);
|
|
230
|
+
if (!pending)
|
|
231
|
+
return;
|
|
232
|
+
for (const delivery of deliveries)
|
|
233
|
+
pending.delete(delivery);
|
|
234
|
+
if (!pending.size)
|
|
235
|
+
roots.delete(root);
|
|
236
|
+
if (!roots.size)
|
|
237
|
+
pendingDeliveries.delete(store);
|
|
238
|
+
}
|
|
239
|
+
/** Await already-scheduled first dispatches and consume their retained persistence failures. */
|
|
240
|
+
export async function settleEventDeliveries(root) {
|
|
241
|
+
const store = getActiveWorldStore(), path = resolve(root);
|
|
242
|
+
const deliveries = [...(pendingDeliveries.get(store)?.get(path) ?? [])];
|
|
243
|
+
const outcomes = await Promise.allSettled(deliveries);
|
|
244
|
+
forgetDeliveries(store, path, deliveries);
|
|
245
|
+
const failures = outcomes.flatMap((outcome) => outcome.status === 'rejected' ? [outcome.reason] : []);
|
|
246
|
+
if (failures.length)
|
|
247
|
+
throw new AggregateError(failures, 'Declared-event dispatch failed');
|
|
248
|
+
}
|
|
226
249
|
let deliveryObserver;
|
|
227
250
|
export function setDeliveryObserver(observer) {
|
|
228
251
|
const previous = deliveryObserver;
|
|
@@ -266,12 +289,16 @@ export async function askApplication(url, init, within, standIn) {
|
|
|
266
289
|
}
|
|
267
290
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
268
291
|
* that takes it, and recorded. */
|
|
269
|
-
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values) {
|
|
292
|
+
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values, application) {
|
|
270
293
|
if (write.storedType === decl.store)
|
|
271
294
|
return [];
|
|
295
|
+
const dispatch = application ? async (url, body, headers) => {
|
|
296
|
+
const answer = await application(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
|
|
297
|
+
return answer === undefined ? transport(url, body, headers) : answer === 'unreachable' ? 0 : answer.status;
|
|
298
|
+
} : transport;
|
|
272
299
|
const sent = [];
|
|
273
300
|
for (const type of eventTypesOf(decl, write.operation))
|
|
274
|
-
sent.push(...(await deliverEventType(service, decl, write, data,
|
|
301
|
+
sent.push(...(await deliverEventType(service, decl, write, data, dispatch, values, type)));
|
|
275
302
|
return sent;
|
|
276
303
|
}
|
|
277
304
|
/** One event type a write sends: to the live endpoints that take it, stored when the vendor keeps its events. */
|
|
@@ -364,11 +391,29 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
364
391
|
const store = getActiveWorldStore(), deliveryRoot = worldPaths(service, write.root).root;
|
|
365
392
|
for (const d of sent)
|
|
366
393
|
deliveryObserver?.({ ...d, service });
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
394
|
+
const roots = pendingDeliveries.get(store) ?? new Map();
|
|
395
|
+
pendingDeliveries.set(store, roots);
|
|
396
|
+
const pending = roots.get(deliveryRoot) ?? new Set();
|
|
397
|
+
roots.set(deliveryRoot, pending);
|
|
398
|
+
for (const packet of recorded) {
|
|
399
|
+
let finish;
|
|
400
|
+
let fail;
|
|
401
|
+
const dispatched = new Promise((done, reject) => { finish = done; fail = reject; });
|
|
402
|
+
// Live writes return before dispatch; an explicit settlement reports persistence failures.
|
|
403
|
+
void dispatched.catch(() => { });
|
|
404
|
+
pending.add(dispatched);
|
|
405
|
+
setTimeout(async () => {
|
|
406
|
+
try {
|
|
407
|
+
await withWorldStore(store, () => sendRecordedEvent(service, packet.type, packet.id, deliveryRoot, transport, trace));
|
|
408
|
+
finish();
|
|
409
|
+
forgetDeliveries(store, deliveryRoot, [dispatched]);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
// Keep the rejection until explicit settlement consumes it. The queued/sending claim remains for
|
|
413
|
+
// native recovery; this must not fail the already-committed vendor write or cause a blind resend.
|
|
414
|
+
fail(error);
|
|
415
|
+
}
|
|
372
416
|
}, 0);
|
|
417
|
+
}
|
|
373
418
|
return sent;
|
|
374
419
|
}
|
|
@@ -5,6 +5,10 @@ export declare function fileResponse(path: string, init?: ResponseInit): Respons
|
|
|
5
5
|
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it: every satisfiable range is a
|
|
6
6
|
* 206 with its Content-Range, `bytes=0-` (the whole body) included; a range that does not parse, a first-byte-pos past
|
|
7
7
|
* its last-byte-pos (`bytes=5-2`) included, is ignored and the whole body is a 200; a well-formed range that starts past
|
|
8
|
-
* the end (any range of an empty body) is a 416.
|
|
8
|
+
* the end (any range of an empty body) is a 416. Lazy sources supply inclusive ranges and stream in bounded reads.
|
|
9
|
+
* `nosniff` stops a browser second-guessing the content type the vendor gave.
|
|
9
10
|
*/
|
|
10
|
-
export declare function bytesResponse(request: Request, bytes: Uint8Array
|
|
11
|
+
export declare function bytesResponse(request: Request, bytes: Uint8Array | {
|
|
12
|
+
size: number;
|
|
13
|
+
range(start: number, end: number): Promise<Uint8Array | null>;
|
|
14
|
+
}, contentType: string, cacheControl?: string): Response;
|
|
@@ -24,10 +24,11 @@ export function fileResponse(path, init) {
|
|
|
24
24
|
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it: every satisfiable range is a
|
|
25
25
|
* 206 with its Content-Range, `bytes=0-` (the whole body) included; a range that does not parse, a first-byte-pos past
|
|
26
26
|
* its last-byte-pos (`bytes=5-2`) included, is ignored and the whole body is a 200; a well-formed range that starts past
|
|
27
|
-
* the end (any range of an empty body) is a 416.
|
|
27
|
+
* the end (any range of an empty body) is a 416. Lazy sources supply inclusive ranges and stream in bounded reads.
|
|
28
|
+
* `nosniff` stops a browser second-guessing the content type the vendor gave.
|
|
28
29
|
*/
|
|
29
30
|
export function bytesResponse(request, bytes, contentType, cacheControl = 'public, max-age=604800, immutable') {
|
|
30
|
-
const total = bytes.length;
|
|
31
|
+
const total = bytes instanceof Uint8Array ? bytes.length : bytes.size;
|
|
31
32
|
const base = { 'content-type': contentType, 'x-content-type-options': 'nosniff', 'accept-ranges': 'bytes', 'cache-control': cacheControl };
|
|
32
33
|
const range = /^bytes=(\d*)-(\d*)$/.exec((request.headers.get('range') ?? '').trim());
|
|
33
34
|
let start = 0;
|
|
@@ -47,9 +48,32 @@ export function bytesResponse(request, bytes, contentType, cacheControl = 'publi
|
|
|
47
48
|
// every satisfiable range is answered as one, the whole body's `bytes=0-` included: a media player (Safari's) reads a
|
|
48
49
|
// 206 with its Content-Range as the server taking ranges, and a 200 as one it cannot seek in
|
|
49
50
|
const partial = valid;
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
let body = null;
|
|
52
|
+
if (request.method !== 'HEAD') {
|
|
53
|
+
if (bytes instanceof Uint8Array)
|
|
54
|
+
body = new Blob([bytes.slice(start, end + 1)]);
|
|
55
|
+
else {
|
|
56
|
+
let at = start;
|
|
57
|
+
body = new ReadableStream({
|
|
58
|
+
async pull(controller) {
|
|
59
|
+
if (at > end) {
|
|
60
|
+
controller.close();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const last = Math.min(at + 8 * 1024 * 1024 - 1, end);
|
|
64
|
+
const chunk = await bytes.range(at, last);
|
|
65
|
+
if (!chunk) {
|
|
66
|
+
controller.error(new Error('stored bytes are gone'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
controller.enqueue(chunk);
|
|
70
|
+
at = last + 1;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return new Response(body, {
|
|
52
76
|
status: partial ? 206 : 200,
|
|
53
|
-
headers: { ...base, 'content-length': String(
|
|
77
|
+
headers: { ...base, 'content-length': String(end + 1 - start), ...(partial ? { 'content-range': `bytes ${start}-${end}/${total}` } : {}) },
|
|
54
78
|
});
|
|
55
79
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ export * as protobuf from './protobuf.js';
|
|
|
67
67
|
export * as grpc from './grpc-wire.js';
|
|
68
68
|
export { acceptH2Connection, type H2Handler, type Http1Handler } from './h2.js';
|
|
69
69
|
export { placeholderPng } from './placeholder-image.js';
|
|
70
|
+
export { imageDimensions, readImageHeader, readMp4Header } from './media-header.js';
|
|
70
71
|
export { presignSigV4, signSigV4, verifySigV4, type SigV4Verdict } from './sigv4.js';
|
|
71
72
|
export * as redis from './redis/index.js';
|
|
72
73
|
export * as mysql from './mysql/index.js';
|
|
@@ -76,7 +77,7 @@ export * as git from './git/index.js';
|
|
|
76
77
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError } from './scenario.js';
|
|
77
78
|
export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioExtractorSpec, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord, ScenarioStatus } from './scenario.js';
|
|
78
79
|
export { verifyAttestationBundle } from './attestation.js';
|
|
79
|
-
export { browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.js';
|
|
80
|
+
export { cookieOf, signedIn, browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.js';
|
|
80
81
|
export { readHostPort } from './host-port.js';
|
|
81
82
|
export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost, type BrowserCookie, type BrowserState, type BrowserStorageState, type BrowserContext, type BrowserIndexedDB } from './browser-state.js';
|
|
82
83
|
export * as managedOriginalInputs from './managed-original-inputs.js';
|
package/dist/src/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export * as protobuf from "./protobuf.js";
|
|
|
59
59
|
export * as grpc from "./grpc-wire.js";
|
|
60
60
|
export { acceptH2Connection } from "./h2.js";
|
|
61
61
|
export { placeholderPng } from "./placeholder-image.js";
|
|
62
|
+
export { imageDimensions, readImageHeader, readMp4Header } from "./media-header.js";
|
|
62
63
|
export { presignSigV4, signSigV4, verifySigV4 } from "./sigv4.js";
|
|
63
64
|
export * as redis from "./redis/index.js";
|
|
64
65
|
export * as mysql from "./mysql/index.js";
|
|
@@ -68,7 +69,7 @@ export * as git from "./git/index.js";
|
|
|
68
69
|
// ── the scenario grammar (architecture, "Behaviour") ───────────────────────────────────────────
|
|
69
70
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError } from "./scenario.js";
|
|
70
71
|
export { verifyAttestationBundle } from "./attestation.js";
|
|
71
|
-
export { browserSessionToken, startBrowserSession, endBrowserSession } from "./browser-session.js";
|
|
72
|
+
export { cookieOf, signedIn, browserSessionToken, startBrowserSession, endBrowserSession } from "./browser-session.js";
|
|
72
73
|
export { readHostPort } from "./host-port.js";
|
|
73
74
|
export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost } from "./browser-state.js";
|
|
74
75
|
export * as managedOriginalInputs from "./managed-original-inputs.js";
|