@volter/world-core 2.0.37 → 3.0.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/README.md +4 -5
- package/app-route.cjs +12 -6
- package/app-route.d.cts +1 -1
- package/dist/app-route.cjs +12 -6
- package/dist/app-route.d.cts +1 -1
- package/dist/generated/pack-facts.json +1410 -3069
- package/dist/inject.cjs +64 -9
- package/dist/pack-facts.cjs +44 -0
- package/dist/src/actions.d.ts +3 -3
- package/dist/src/actions.js +22 -16
- package/dist/src/ancestry.d.ts +14 -2
- package/dist/src/ancestry.js +92 -2
- package/dist/src/anthropic-wire.d.ts +39 -0
- package/dist/src/anthropic-wire.js +136 -0
- package/dist/src/bytes.d.ts +7 -0
- package/dist/src/bytes.js +35 -0
- package/dist/src/changeset.d.ts +1 -1
- package/dist/src/changeset.js +0 -0
- package/dist/src/clickhouse/index.d.ts +3 -0
- package/dist/src/clickhouse/index.js +6 -0
- package/dist/src/clickhouse/sql.d.ts +233 -0
- package/dist/src/clickhouse/sql.js +4329 -0
- package/dist/src/clickhouse/types.d.ts +18 -0
- package/dist/src/clickhouse/types.js +47 -0
- package/dist/src/clickhouse/values.d.ts +146 -0
- package/dist/src/clickhouse/values.js +858 -0
- package/dist/src/client-bundle.js +2 -3
- package/dist/src/cors.d.ts +15 -0
- package/dist/src/cors.js +31 -0
- package/dist/src/derived-core.d.ts +487 -24
- package/dist/src/derived-core.js +788 -144
- package/dist/src/derived-real.d.ts +13 -0
- package/dist/src/derived-real.js +518 -0
- package/dist/src/derived.d.ts +35 -1
- package/dist/src/derived.js +61 -9
- package/dist/src/emit.js +1 -2
- package/dist/src/events.d.ts +206 -0
- package/dist/src/events.js +341 -0
- package/dist/src/executor.d.ts +3 -0
- package/dist/src/executor.js +19 -2
- package/dist/src/file-response.d.ts +6 -0
- package/dist/src/file-response.js +30 -0
- package/dist/src/fork.js +3 -2
- package/dist/src/git/history.d.ts +7 -0
- package/dist/src/git/history.js +24 -0
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +1 -0
- package/dist/src/git/lfs.d.ts +28 -0
- package/dist/src/git/lfs.js +66 -0
- package/dist/src/git/objects.js +3 -8
- package/dist/src/git/smart-http.d.ts +3 -1
- package/dist/src/git/smart-http.js +67 -6
- package/dist/src/graphql-wire.d.ts +29 -0
- package/dist/src/graphql-wire.js +101 -0
- package/dist/src/grpc-wire.d.ts +67 -0
- package/dist/src/grpc-wire.js +170 -0
- package/dist/src/h2.d.ts +40 -0
- package/dist/src/h2.js +656 -0
- package/dist/src/head.d.ts +32 -3
- package/dist/src/head.js +161 -40
- package/dist/src/history.d.ts +1 -1
- package/dist/src/history.js +6 -6
- package/dist/src/hpack.json +1 -0
- package/dist/src/index.d.ts +64 -75
- package/dist/src/index.js +58 -101
- package/dist/src/log.js +28 -19
- package/dist/src/machines.d.ts +50 -0
- package/dist/src/machines.js +151 -0
- package/dist/src/managed-database.d.ts +86 -0
- package/dist/src/managed-database.js +283 -0
- package/dist/src/multipart.d.ts +11 -0
- package/dist/src/multipart.js +51 -0
- package/dist/src/observe.d.ts +15 -5
- package/dist/src/observe.js +23 -9
- package/dist/src/openai-wire.d.ts +108 -0
- package/dist/src/openai-wire.js +337 -0
- package/dist/src/pack-assets.d.ts +3 -4
- package/dist/src/pack-assets.js +15 -10
- package/dist/src/pack-fetch.d.ts +77 -0
- package/dist/src/pack-fetch.js +449 -0
- package/dist/src/pack-paths.d.ts +12 -0
- package/dist/src/pack-paths.js +86 -0
- package/dist/src/packRegistry.d.ts +69 -162
- package/dist/src/packRegistry.js +55 -20
- package/dist/src/people.d.ts +13 -0
- package/dist/src/people.js +18 -0
- package/dist/src/placeholder-image.d.ts +5 -0
- package/dist/src/placeholder-image.js +114 -0
- package/dist/src/protobuf.d.ts +28 -0
- package/dist/src/protobuf.js +332 -0
- package/dist/src/redis/engine.js +1 -1
- package/dist/src/request-scope.d.ts +1 -1
- package/dist/src/request-scope.js +6 -4
- package/dist/src/resource-blob.d.ts +5 -0
- package/dist/src/resource-blob.js +11 -0
- package/dist/src/runtime.d.ts +85 -0
- package/dist/src/runtime.js +104 -0
- package/dist/src/s3/wire.d.ts +60 -0
- package/dist/src/s3/wire.js +157 -0
- package/dist/src/scenario.d.ts +3 -0
- package/dist/src/scenario.js +2 -0
- package/dist/src/schema-sample.d.ts +1 -0
- package/dist/src/schema-sample.js +21 -0
- package/dist/src/sealed-box.d.ts +14 -0
- package/dist/src/sealed-box.js +225 -0
- package/dist/src/serve-http.d.ts +14 -0
- package/dist/src/serve-http.js +27 -3
- package/dist/src/serve.d.ts +6 -0
- package/dist/src/serve.js +69 -14
- package/dist/src/signing.d.ts +135 -0
- package/dist/src/signing.js +222 -0
- package/dist/src/sigv4.d.ts +48 -0
- package/dist/src/sigv4.js +167 -0
- package/dist/src/smtp.d.ts +16 -0
- package/dist/src/smtp.js +72 -0
- package/dist/src/sockets.d.ts +51 -0
- package/dist/src/sockets.js +90 -0
- package/dist/src/state-system.d.ts +1 -0
- package/dist/src/state-system.js +1 -1
- package/dist/src/storage.d.ts +1 -1
- package/dist/src/storage.js +3 -3
- package/dist/src/trace-context.js +1 -1
- package/dist/src/twin-fetch.d.ts +0 -7
- package/dist/src/twin-fetch.js +0 -14
- package/dist/src/vendor-call.d.ts +6 -0
- package/dist/src/vendor-call.js +41 -0
- package/dist/src/world-store.js +1 -1
- package/dist/vendor-hosts.cjs +36 -125
- package/dist/vendor-hosts.d.cts +8 -0
- package/generated/pack-facts.json +1410 -3069
- package/inject.cjs +64 -9
- package/pack-facts.cjs +44 -0
- package/package.json +17 -3
- package/src/actions.ts +23 -16
- package/src/ancestry.ts +74 -2
- package/src/anthropic-wire.ts +137 -0
- package/src/bytes.ts +42 -0
- package/src/changeset.ts +5 -5
- package/src/clickhouse/index.ts +6 -0
- package/src/clickhouse/sql.ts +3059 -0
- package/src/clickhouse/types.ts +44 -0
- package/src/clickhouse/values.ts +697 -0
- package/src/client-bundle.ts +2 -3
- package/src/cors.ts +34 -0
- package/src/derived-core.ts +1013 -146
- package/src/derived-real.ts +434 -0
- package/src/derived.ts +73 -3
- package/src/emit.ts +1 -2
- package/src/events.ts +449 -0
- package/src/executor.ts +24 -2
- package/src/file-response.ts +27 -0
- package/src/fork.ts +3 -2
- package/src/git/history.ts +19 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +67 -0
- package/src/git/objects.ts +3 -5
- package/src/git/smart-http.ts +56 -6
- package/src/graphql-wire.ts +106 -0
- package/src/grpc-wire.ts +159 -0
- package/src/h2.ts +627 -0
- package/src/head.ts +132 -41
- package/src/history.ts +6 -6
- package/src/hpack.json +1 -0
- package/src/index.ts +82 -329
- package/src/log.ts +27 -18
- package/src/machines.ts +151 -0
- package/src/managed-database.ts +299 -0
- package/src/multipart.ts +51 -0
- package/src/observe.ts +31 -15
- package/src/openai-wire.ts +371 -0
- package/src/pack-assets.ts +15 -11
- package/src/pack-fetch.ts +458 -0
- package/src/pack-paths.ts +72 -0
- package/src/packRegistry.ts +79 -167
- package/src/people.ts +31 -0
- package/src/placeholder-image.ts +88 -0
- package/src/protobuf.ts +251 -0
- package/src/redis/engine.ts +1 -1
- package/src/request-scope.ts +8 -4
- package/src/resource-blob.ts +13 -0
- package/src/runtime.ts +344 -0
- package/src/s3/wire.ts +172 -0
- package/src/scenario.ts +4 -0
- package/src/schema-sample.ts +24 -0
- package/src/sealed-box.ts +182 -0
- package/src/serve-http.ts +31 -3
- package/src/serve.ts +58 -14
- package/src/signing.ts +231 -0
- package/src/sigv4.ts +158 -0
- package/src/smtp.ts +76 -0
- package/src/sockets.ts +140 -0
- package/src/state-system.ts +2 -2
- package/src/storage.ts +3 -3
- package/src/trace-context.ts +1 -1
- package/src/twin-fetch.ts +0 -20
- package/src/vendor-call.ts +41 -0
- package/src/world-store.ts +1 -1
- package/vendor-hosts.cjs +36 -125
- package/vendor-hosts.d.cts +8 -0
- package/dist/src/mirror-shell.d.ts +0 -2
- package/dist/src/mirror-shell.js +0 -13
- package/dist/src/v1-removed.d.ts +0 -159
- package/dist/src/v1-removed.js +0 -124
- package/src/mirror-shell.ts +0 -15
- package/src/v1-removed.ts +0 -172
package/src/head.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
12
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
13
|
-
import { confirmAction, isTwinBookkeeping, resolveSubjectId, revertAction, type TwinAction } from './actions.ts';
|
|
13
|
+
import { confirmAction, currentCorrelationId, isTwinBookkeeping, resolveSubjectId, revertAction, type TwinAction } from './actions.ts';
|
|
14
14
|
import { keySealer, type CredentialPayload, type SealedCredential, type Sealer } from './credential.ts';
|
|
15
15
|
import { buildRemoteExecute, validateRemoteOrigin, type CredentialCustody } from './executor.ts';
|
|
16
16
|
import { getPack } from './packRegistry.ts';
|
|
@@ -26,7 +26,13 @@ import { getActiveWorldStore } from './world-store.ts';
|
|
|
26
26
|
|
|
27
27
|
/** What a perform answers: the id the vendor minted (the local id when it kept ours), its URL, and
|
|
28
28
|
* the vendor's answer as fields for the landed copy. */
|
|
29
|
-
export type PushOutcome = {
|
|
29
|
+
export type PushOutcome = {
|
|
30
|
+
externalId: string; url?: string; data?: Record<string, unknown>;
|
|
31
|
+
/** the group's entry the id is for, when it is not the group's first (a cascade writes children first) */
|
|
32
|
+
actionId?: string;
|
|
33
|
+
/** the group's other entries the answer named an id for (a resource's `companions`) */
|
|
34
|
+
also?: Array<{ actionId: string; externalId: string }>;
|
|
35
|
+
};
|
|
30
36
|
/** What a perform is handed beside the executor: a resolver from a local id to the vendor's (an
|
|
31
37
|
* entry authored against `twin-1` crosses against `REAL-42` once that subject's landing adopted it). */
|
|
32
38
|
/** `credential`: the sealed credential's keyed fingerprint (credential.ts — an HMAC under the sealing key, or the
|
|
@@ -34,7 +40,8 @@ export type PushOutcome = { externalId: string; url?: string; data?: Record<stri
|
|
|
34
40
|
* never the secret and no offline oracle for it), so a pack keys its budget ledger per real credential:
|
|
35
41
|
* every World and branch performing with one vendor key then shares that key's one allowance. */
|
|
36
42
|
export type PerformContext = { resolve: (type: string, localId: string) => string; service?: string; root?: string; credential?: string };
|
|
37
|
-
|
|
43
|
+
/** One request's entries (a group: they share its `correlationId`), performed as the one call that made them. */
|
|
44
|
+
export type PerformAction = (execute: RemoteExecute, group: TwinAction[], ctx: PerformContext) => Promise<PushOutcome>;
|
|
38
45
|
export function performContext(service: string, root?: string, credential?: string): PerformContext {
|
|
39
46
|
return { resolve: (type, localId) => resolveSubjectId(service, type, localId, root), service, ...(root !== undefined ? { root } : {}), ...(credential !== undefined ? { credential } : {}) };
|
|
40
47
|
}
|
|
@@ -255,6 +262,8 @@ export type DeployReport = {
|
|
|
255
262
|
skipped: string[];
|
|
256
263
|
/** entries whose landed copy already said `deployed` — not performed again */
|
|
257
264
|
replayed: string[];
|
|
265
|
+
/** entries the World's own doors made (a door, a screen, the gap, the clock, a seed): never offered to the vendor */
|
|
266
|
+
worldOnly: string[];
|
|
258
267
|
};
|
|
259
268
|
|
|
260
269
|
/** The receipt a branch entry's latest landed copy carries, if any. */
|
|
@@ -264,18 +273,45 @@ function receiptsByEntry(parent: Entry[]): Map<string, Receipt> {
|
|
|
264
273
|
return out;
|
|
265
274
|
}
|
|
266
275
|
|
|
267
|
-
/**
|
|
268
|
-
*
|
|
269
|
-
|
|
276
|
+
/** Whether an entry records a call of the vendor's surface (an operation or a GraphQL mutation): what a perform can send
|
|
277
|
+
* again (the real-system adapters, "What the derived core records with a write"). */
|
|
278
|
+
function surfaceWrite(e: { input?: Record<string, unknown> }): boolean {
|
|
279
|
+
return typeof e.input?.operationId === 'string' || (e.input?.graphql !== undefined && e.input.graphql !== null);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The branch entries a root twin still has to settle: `set` rows, not reverted, whose landed copy is absent or says
|
|
283
|
+
* `failed`/`skipped` (a `deployed` or `refused` copy settles the entry), split into what is offered to the vendor (a
|
|
284
|
+
* surface write on a subject the vendor can have) and what is the World's own: a write a door, a screen, the gap, the
|
|
285
|
+
* clock or a seed made, and any write on a subject only the World's own writes made. */
|
|
286
|
+
export function settlingEntries(service: string, root?: string): { offered: TwinAction[]; worldOnly: TwinAction[] } {
|
|
270
287
|
const parent = parentEntries(service, root);
|
|
271
288
|
const receipts = receiptsByEntry(parent);
|
|
272
289
|
const branch = branchEntries(service, root);
|
|
273
290
|
const reverted = new Set(branch.filter((e) => e.op === 'revert' && e.revertsActionId).map((e) => e.revertsActionId!));
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
291
|
+
const live = branch.filter((e) => e.op === 'set' && !reverted.has(e.id) && !isTwinBookkeeping(e as TwinAction)) as TwinAction[];
|
|
292
|
+
// a subject some surface write made (or the vendor holds: the parent log knows it) is the vendor's; one only the
|
|
293
|
+
// World's own writes made is not, and nothing written to it is sent
|
|
294
|
+
const key = (e: TwinAction): string => `${e.subject.type}\u0000${e.subject.id}`;
|
|
295
|
+
const first = new Map<string, TwinAction>();
|
|
296
|
+
for (const e of live) if (!first.has(key(e))) first.set(key(e), e);
|
|
297
|
+
const vendors = new Set([...[...first].filter(([, e]) => surfaceWrite(e)).map(([k]) => k), ...(parent as Array<TwinAction & { landsId?: string }>).filter((e) => !e.landsId).map(key)]);
|
|
298
|
+
const pending = live.filter((e) => { const r = receipts.get(e.id); return r === undefined || r.status === 'failed' || r.status === 'skipped'; });
|
|
299
|
+
return {
|
|
300
|
+
offered: pending.filter((e) => surfaceWrite(e) && vendors.has(key(e))),
|
|
301
|
+
worldOnly: pending.filter((e) => !surfaceWrite(e) || !vendors.has(key(e))),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The entries a root twin offers the vendor (settlingEntries' `offered`). */
|
|
306
|
+
export function deployableEntries(service: string, root?: string): TwinAction[] {
|
|
307
|
+
return settlingEntries(service, root).offered;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Entries in groups of the request that made them (their `correlationId`), each group in log order. */
|
|
311
|
+
function groupsOf(entries: TwinAction[]): TwinAction[][] {
|
|
312
|
+
const groups = new Map<string, TwinAction[]>();
|
|
313
|
+
for (const e of entries) { const k = e.correlationId ?? e.id; groups.set(k, [...(groups.get(k) ?? []), e]); }
|
|
314
|
+
return [...groups.values()];
|
|
279
315
|
}
|
|
280
316
|
|
|
281
317
|
const queues = new Map<string, Promise<unknown>>(); // one performer per twin: wire writes perform in append order
|
|
@@ -316,57 +352,75 @@ async function performNow(opts: Parameters<typeof performEntries>[0]): Promise<D
|
|
|
316
352
|
const bound = performRoot(service, root, opts.execute !== undefined);
|
|
317
353
|
const vendor = bound.vendor;
|
|
318
354
|
const adapters = opts.adapters ?? stateSystemFor(vendor) ?? stateSystemFor(service);
|
|
319
|
-
if (!adapters?.perform) throw new Error(`the ${vendor} twin has no perform adapter — a
|
|
355
|
+
if (!adapters?.perform) throw new Error(`the ${vendor} twin has no perform adapter — a pack names one in its descriptor and registers itself (registerPack)`);
|
|
320
356
|
// the caller's executor, or the one built over the sealed credential; an unsealed root has no other
|
|
321
|
-
const execute = opts.execute ?? buildRemoteExecute(validateRemoteOrigin(bound.sealed!.url, { allowInsecureLoopback: true }), await openRootCredential(bound.sealed!, vendor), authStrategyFor(vendor), getPack(vendor)?.hosts, { custody: rootCustody(bound.sealed!, vendor) });
|
|
357
|
+
const execute = opts.execute ?? buildRemoteExecute(validateRemoteOrigin(bound.sealed!.url, { allowInsecureLoopback: true }), await openRootCredential(bound.sealed!, vendor), authStrategyFor(vendor), getPack(vendor)?.hosts, { custody: rootCustody(bound.sealed!, vendor), vendor });
|
|
322
358
|
// an unsealed root's World is where its state resolves (the root given, PROJECT_ROOT, or the working directory)
|
|
323
359
|
const checks = opts.checks ?? (await loadChecks(bound.sealed ? worldRootOf(bound.sealed) : worldPaths(service, root).root));
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const report: DeployReport = { pushed: 0, deployed: [], skipped: [], replayed: [] };
|
|
360
|
+
const settling = settlingEntries(service, root);
|
|
361
|
+
const pick = (list: TwinAction[]): TwinAction[] => (opts.actionIds === undefined ? list : opts.actionIds.map((id) => list.find((a) => a.id === id)).filter((a): a is TwinAction => a !== undefined));
|
|
362
|
+
const report: DeployReport = { pushed: 0, deployed: [], skipped: [], replayed: [], worldOnly: pick(settling.worldOnly).map((e) => e.id) };
|
|
327
363
|
if (opts.actionIds) {
|
|
328
364
|
const settled = receiptsByEntry(parentEntries(service, root));
|
|
329
365
|
for (const id of opts.actionIds) if (settled.get(id)?.status === 'deployed') report.replayed.push(id);
|
|
330
366
|
}
|
|
367
|
+
const settle = (action: TwinAction, at: string, receipt: Partial<Receipt>, fields: Record<string, unknown> = action.fields ?? {}, vendorSubjectId?: string): void => {
|
|
368
|
+
confirmAction({ service, actionId: action.id, subject: action.subject, fields, occurredAt: at, ...(vendorSubjectId ? { vendorSubjectId } : {}), receipt, ...(root !== undefined ? { root } : {}) });
|
|
369
|
+
};
|
|
370
|
+
// the subjects the vendor refused and never took: a request naming one is the World's alone, never sent (the
|
|
371
|
+
// real-system adapters, "Perform" 3), settled as it landed here
|
|
372
|
+
const parent = parentEntries(service, root);
|
|
373
|
+
const taken = new Set<string>(); const refusedIds = new Set<string>();
|
|
374
|
+
for (const e of parent) { if (e.receipt?.status === 'deployed') taken.add(e.aliasOf ?? e.subject.id); else if (e.receipt?.status === 'refused') refusedIds.add(e.subject.id); }
|
|
375
|
+
for (const id of taken) refusedIds.delete(id);
|
|
376
|
+
const named = (action: TwinAction): string[] => { const out: string[] = []; const walk = (v: unknown): void => { if (typeof v === 'string') out.push(v); else if (v && typeof v === 'object') for (const x of Object.values(v)) walk(x); }; const i = (action.input ?? {}) as Record<string, unknown>; walk(i.path); walk(i.query); walk(i.body); return out; };
|
|
331
377
|
let stopped = false;
|
|
332
|
-
for (const
|
|
333
|
-
if (stopped) { report.skipped.push(
|
|
378
|
+
for (const group of groupsOf(pick(settling.offered))) {
|
|
379
|
+
if (stopped) { report.skipped.push(...group.map((a) => a.id)); continue; }
|
|
380
|
+
if (named(group[0]!).some((v) => refusedIds.has(v))) {
|
|
381
|
+
const at = opts.at ?? worldNow();
|
|
382
|
+
for (const action of group) { settle(action, at, { status: 'landed', reason: 'names a subject the vendor refused: the World\'s alone' }); refusedIds.add(action.subject.id); }
|
|
383
|
+
report.worldOnly.push(...group.map((a) => a.id));
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
334
386
|
const at = opts.at ?? worldNow();
|
|
335
|
-
|
|
387
|
+
// checks in front of every entry: one refused refuses the group, which is one call
|
|
388
|
+
let verdict: Awaited<ReturnType<typeof runChecks>> = null;
|
|
389
|
+
for (const action of group) { verdict = await runChecks(checks, action, readTree(service, root)); if (verdict) break; }
|
|
336
390
|
if (verdict) {
|
|
337
|
-
|
|
338
|
-
report.refused = { actionId:
|
|
391
|
+
for (const action of group) settle(action, at, { status: 'refused', reason: `${verdict.name}: ${verdict.reason}` });
|
|
392
|
+
report.refused = { actionId: group[0]!.id, check: verdict.name, reason: verdict.reason };
|
|
339
393
|
stopped = true;
|
|
340
394
|
continue;
|
|
341
395
|
}
|
|
396
|
+
// a declared reference to an adopted id is resolved before the pack sees the entry: the vendor is
|
|
397
|
+
// asked about #7, never the local #3 (docs/contributing/architecture.md#alias-aware-lookup-at-the-request-boundary)
|
|
398
|
+
const aliases = aliasesFrom(parentEntries(service, root));
|
|
399
|
+
const resolved = group.map((action) => { const fields = resolveReferences(service, action.subject.type, action.fields ?? {}, aliases); return fields === action.fields ? action : { ...action, fields }; });
|
|
342
400
|
try {
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
const fields = resolveReferences(service, action.subject.type, action.fields ?? {}, aliasesFrom(parentEntries(service, root)));
|
|
346
|
-
const outcome = await adapters.perform(execute, fields === action.fields ? action : { ...action, fields }, performContext(service, root, bound.sealed ? rootCredentialFingerprint(bound.sealed) : undefined));
|
|
347
|
-
// the vendor's answer, when the adapter returned one as fields, is what the landed copy holds
|
|
401
|
+
const outcome = await adapters.perform(execute, resolved, performContext(service, root, bound.sealed ? rootCredentialFingerprint(bound.sealed) : undefined));
|
|
402
|
+
// the vendor's answer, when the adapter returned one as fields, is what the primary entry's landed copy holds
|
|
348
403
|
const answered = outcome.data !== null && typeof outcome.data === 'object' && !Array.isArray(outcome.data) ? (outcome.data as Record<string, unknown>) : {};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
...(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
404
|
+
const primary = resolved.find((a) => a.id === outcome.actionId) ?? resolved[0]!;
|
|
405
|
+
const also = new Map((outcome.also ?? []).map((a) => [a.actionId, a.externalId]));
|
|
406
|
+
for (const action of resolved) {
|
|
407
|
+
const externalId = action === primary ? outcome.externalId : also.get(action.id);
|
|
408
|
+
settle(action, at, { status: 'deployed', ...(action === primary && outcome.url ? { url: outcome.url } : {}) }, action === primary ? { ...action.fields, ...answered } : action.fields ?? {}, externalId || undefined);
|
|
409
|
+
report.pushed += 1;
|
|
410
|
+
report.deployed.push({ actionId: action.id, at, ...(externalId ? { externalId } : {}), ...(action === primary && outcome.data !== undefined ? { data: outcome.data } : {}) });
|
|
411
|
+
}
|
|
357
412
|
} catch (error) {
|
|
358
|
-
// a write the
|
|
359
|
-
//
|
|
360
|
-
// anything else failed and stays deployable for a retry
|
|
413
|
+
// a write the vendor refuses (a 4xx: it can never be sent as it is) settles the group refused, as a check's
|
|
414
|
+
// refusal does, so the next deploy moves on to the group after it; anything else failed and stays offered
|
|
361
415
|
if (error instanceof RefusedWriteError) {
|
|
362
|
-
|
|
363
|
-
report.refused = { actionId:
|
|
416
|
+
for (const action of group) { settle(action, at, { status: 'refused', reason: error.message }); refusedIds.add(action.subject.id); }
|
|
417
|
+
report.refused = { actionId: group[0]!.id, check: error.check, reason: error.reason };
|
|
364
418
|
stopped = true;
|
|
365
419
|
continue;
|
|
366
420
|
}
|
|
367
421
|
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
-
|
|
369
|
-
report.failed = { actionId:
|
|
422
|
+
for (const action of group) settle(action, at, { status: 'failed', reason: message });
|
|
423
|
+
report.failed = { actionId: group[0]!.id, error: message };
|
|
370
424
|
stopped = true;
|
|
371
425
|
}
|
|
372
426
|
}
|
|
@@ -379,6 +433,10 @@ async function performNow(opts: Parameters<typeof performEntries>[0]): Promise<D
|
|
|
379
433
|
* Raises `RefusedWriteError` / `VendorWriteError` for the pack to answer in the vendor's shape.
|
|
380
434
|
*/
|
|
381
435
|
export async function performAtHead(service: string, action: Pick<TwinAction, 'id'>, root?: string): Promise<{ performed: boolean; externalId?: string; data?: unknown }> {
|
|
436
|
+
// a pack fetch performs its request's entries as one group after the handler (performRequestGroup)
|
|
437
|
+
const request = currentCorrelationId();
|
|
438
|
+
const mark = request !== undefined ? deferred.get(request) : undefined;
|
|
439
|
+
if (mark) { mark.appended = true; return { performed: false }; }
|
|
382
440
|
const head = headOf(service, root);
|
|
383
441
|
if (head.kind !== 'real' || head.deploy !== 'auto') return { performed: false };
|
|
384
442
|
let report: DeployReport;
|
|
@@ -394,6 +452,39 @@ export async function performAtHead(service: string, action: Pick<TwinAction, 'i
|
|
|
394
452
|
return { performed: done !== undefined, ...(done?.externalId ? { externalId: done.externalId } : {}), ...(done?.data !== undefined ? { data: done.data } : {}) };
|
|
395
453
|
}
|
|
396
454
|
|
|
455
|
+
/** The requests a pack fetch is answering, whose entries it performs as one group when the handler returns, and
|
|
456
|
+
* whether each has appended any. */
|
|
457
|
+
const deferred = new Map<string, { appended: boolean }>();
|
|
458
|
+
/** Run a pack fetch's handling of one request with its writes' perform deferred to the group (`performRequestGroup`):
|
|
459
|
+
* its answer, and whether it appended (a request that wrote nothing has no group, and consults no head). */
|
|
460
|
+
export async function deferPerform<T>(request: string, fn: () => Promise<T>): Promise<{ value: T; appended: boolean }> {
|
|
461
|
+
const mark = { appended: false };
|
|
462
|
+
deferred.set(request, mark);
|
|
463
|
+
try { return { value: await fn(), appended: mark.appended }; } finally { deferred.delete(request); }
|
|
464
|
+
}
|
|
465
|
+
/** Under `auto`, perform the entries one request made as one call (the real-system adapters, "Perform"): the ids the
|
|
466
|
+
* vendor gave them by local id, or the vendor's refusal or failure, the request's entries reverted as performAtHead
|
|
467
|
+
* reverts one. Nothing when the head is not real and auto, or the request wrote nothing the vendor is offered. */
|
|
468
|
+
export async function performRequestGroup(service: string, request: string, root?: string): Promise<{ ids: Map<string, string> } | RefusedWriteError | VendorWriteError> {
|
|
469
|
+
const head = headOf(service, root);
|
|
470
|
+
const ids = new Map<string, string>();
|
|
471
|
+
if (head.kind !== 'real' || head.deploy !== 'auto') return { ids };
|
|
472
|
+
const group = deployableEntries(service, root).filter((e) => e.correlationId === request);
|
|
473
|
+
if (!group.length) return { ids };
|
|
474
|
+
let report: DeployReport;
|
|
475
|
+
try { report = await performEntries({ service, actionIds: group.map((e) => e.id), ...(root !== undefined ? { root } : {}) }); }
|
|
476
|
+
catch (error) { throw error instanceof HeadError ? error : new HeadError(error instanceof Error ? error.message : String(error)); }
|
|
477
|
+
if (report.refused || report.failed) {
|
|
478
|
+
for (const e of [...group].reverse()) revertAction({ service, actionId: e.id, occurredAt: worldNow(), ...(root !== undefined ? { root } : {}) });
|
|
479
|
+
return report.refused ? new RefusedWriteError(report.refused.check, report.refused.reason, report.refused.actionId) : new VendorWriteError(report.failed!.error, report.failed!.actionId);
|
|
480
|
+
}
|
|
481
|
+
for (const d of report.deployed) {
|
|
482
|
+
const e = group.find((g) => g.id === d.actionId);
|
|
483
|
+
if (e && d.externalId && d.externalId !== e.subject.id) ids.set(e.subject.id, d.externalId);
|
|
484
|
+
}
|
|
485
|
+
return { ids };
|
|
486
|
+
}
|
|
487
|
+
|
|
397
488
|
/** Wrap a pack's fetch: a `RefusedWriteError` or `VendorWriteError` the head raised becomes the
|
|
398
489
|
* vendor's own error body (the pack's `shape`), so an app under live use sees what the vendor
|
|
399
490
|
* would have said. Anything else propagates. */
|
package/src/history.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { resolveReferences } from './references.ts';
|
|
5
5
|
import { canonicalJson } from './hash.ts';
|
|
6
|
-
import { canonicalStatePath, checkParent, commitParentPin, pinParent, stateGeneration, withAncestryLock } from './ancestry.ts';
|
|
6
|
+
import { canonicalStatePath, checkParent, commitParentPin, pinParent, stateGeneration, withAncestryLock, withHistoryLock } from './ancestry.ts';
|
|
7
7
|
import { getActiveWorldStore } from './world-store.ts';
|
|
8
8
|
import { worldPaths } from './storage.ts';
|
|
9
9
|
import { aliasesFrom, foldHistory, branchEntries, isUrlParent, readBranchMeta, toEntry, type Entry } from './log.ts';
|
|
@@ -114,9 +114,9 @@ export function inheritedHistory(service: string, root?: string): HistoryView |
|
|
|
114
114
|
if (!isUrlParent(parent.at)) checkParent(worldPaths(service, parent.at).dir, parent.generation);
|
|
115
115
|
return historyPrefix(readHistoryView(directory, parent.view), parent.position);
|
|
116
116
|
}
|
|
117
|
-
/** Capture under the
|
|
117
|
+
/** Capture under the directory's history lock, as appends and complete observation batches are made. */
|
|
118
118
|
export function captureHistory(service: string, root?: string): { view: string; position: number; descriptor: HistoryView } {
|
|
119
|
-
return
|
|
119
|
+
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
120
120
|
const directory = canonicalStatePath(worldPaths(service, root).dir);
|
|
121
121
|
getActiveWorldStore().mkdir(directory);
|
|
122
122
|
if (!getActiveWorldStore().exists(join(directory, 'history-format.json'))) getActiveWorldStore().writeAtomic(join(directory, 'history-format.json'), '{"version":1}');
|
|
@@ -137,7 +137,7 @@ export function originHead(service: string, root?: string): { view: string; remo
|
|
|
137
137
|
}
|
|
138
138
|
/** A complete remote view shares cached rows by content, never by mutable offsets or entry ID. */
|
|
139
139
|
export function publishOriginHistory(service: string, root: string, remoteView: string, layout: HistoryLayout, entries: Entry[]): { view: string; appended: number } {
|
|
140
|
-
return
|
|
140
|
+
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
141
141
|
if (entries.length !== historyLength(layout)) throw new Error('Incomplete origin history view');
|
|
142
142
|
const directory = canonicalStatePath(worldPaths(service, root).dir); const store = getActiveWorldStore(); store.mkdir(directory);
|
|
143
143
|
if (!store.exists(join(directory, 'history-format.json'))) store.writeAtomic(join(directory, 'history-format.json'), '{"version":1}');
|
|
@@ -160,7 +160,7 @@ export function publishOriginHistory(service: string, root: string, remoteView:
|
|
|
160
160
|
|
|
161
161
|
/** A changeset binds the complete inherited view, including this root's observations. */
|
|
162
162
|
export function captureParentHistory(service: string, root?: string): HistoryReference {
|
|
163
|
-
return
|
|
163
|
+
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
164
164
|
const head = captureHistory(service, root);
|
|
165
165
|
const position = head.position - head.descriptor.layout.branch;
|
|
166
166
|
return { view: saveHistoryView(worldPaths(service, root).dir, historyPrefix(head.descriptor, position)), position };
|
|
@@ -186,7 +186,7 @@ export function historyChanges(service: string, before: HistoryView, after: Hist
|
|
|
186
186
|
|
|
187
187
|
/** A timestamp can select across layers; represent it as a view, never a misleading offset. */
|
|
188
188
|
export function historyAtInstant(service: string, instant: string, root?: string, selectedView?: string): HistoryReference {
|
|
189
|
-
return
|
|
189
|
+
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
190
190
|
const stamp = Date.parse(instant); if (!Number.isFinite(stamp)) throw new Error('Invalid history instant');
|
|
191
191
|
const directory = worldPaths(service, root).dir;
|
|
192
192
|
const source = selectedView ? readHistoryView(directory, selectedView) : captureHistory(service, root).descriptor;
|
package/src/hpack.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"source":"RFC 7541 Appendix A and B","staticTable":[[":authority",""],[":method","GET"],[":method","POST"],[":path","/"],[":path","/index.html"],[":scheme","http"],[":scheme","https"],[":status","200"],[":status","204"],[":status","206"],[":status","304"],[":status","400"],[":status","404"],[":status","500"],["accept-charset",""],["accept-encoding","gzip, deflate"],["accept-language",""],["accept-ranges",""],["accept",""],["access-control-allow-origin",""],["age",""],["allow",""],["authorization",""],["cache-control",""],["content-disposition",""],["content-encoding",""],["content-language",""],["content-length",""],["content-location",""],["content-range",""],["content-type",""],["cookie",""],["date",""],["etag",""],["expect",""],["expires",""],["from",""],["host",""],["if-match",""],["if-modified-since",""],["if-none-match",""],["if-range",""],["if-unmodified-since",""],["last-modified",""],["link",""],["location",""],["max-forwards",""],["proxy-authenticate",""],["proxy-authorization",""],["range",""],["referer",""],["refresh",""],["retry-after",""],["server",""],["set-cookie",""],["strict-transport-security",""],["transfer-encoding",""],["user-agent",""],["vary",""],["via",""],["www-authenticate",""]],"huffman":[[8184,13],[8388568,23],[268435426,28],[268435427,28],[268435428,28],[268435429,28],[268435430,28],[268435431,28],[268435432,28],[16777194,24],[1073741820,30],[268435433,28],[268435434,28],[1073741821,30],[268435435,28],[268435436,28],[268435437,28],[268435438,28],[268435439,28],[268435440,28],[268435441,28],[268435442,28],[1073741822,30],[268435443,28],[268435444,28],[268435445,28],[268435446,28],[268435447,28],[268435448,28],[268435449,28],[268435450,28],[268435451,28],[20,6],[1016,10],[1017,10],[4090,12],[8185,13],[21,6],[248,8],[2042,11],[1018,10],[1019,10],[249,8],[2043,11],[250,8],[22,6],[23,6],[24,6],[0,5],[1,5],[2,5],[25,6],[26,6],[27,6],[28,6],[29,6],[30,6],[31,6],[92,7],[251,8],[32764,15],[32,6],[4091,12],[1020,10],[8186,13],[33,6],[93,7],[94,7],[95,7],[96,7],[97,7],[98,7],[99,7],[100,7],[101,7],[102,7],[103,7],[104,7],[105,7],[106,7],[107,7],[108,7],[109,7],[110,7],[111,7],[112,7],[113,7],[114,7],[252,8],[115,7],[253,8],[8187,13],[524272,19],[8188,13],[16380,14],[34,6],[32765,15],[3,5],[35,6],[4,5],[36,6],[5,5],[37,6],[38,6],[39,6],[6,5],[116,7],[117,7],[40,6],[41,6],[42,6],[7,5],[43,6],[118,7],[44,6],[8,5],[9,5],[45,6],[119,7],[120,7],[121,7],[122,7],[123,7],[32766,15],[2044,11],[16381,14],[8189,13],[268435452,28],[1048550,20],[4194258,22],[1048551,20],[1048552,20],[4194259,22],[4194260,22],[4194261,22],[8388569,23],[4194262,22],[8388570,23],[8388571,23],[8388572,23],[8388573,23],[8388574,23],[16777195,24],[8388575,23],[16777196,24],[16777197,24],[4194263,22],[8388576,23],[16777198,24],[8388577,23],[8388578,23],[8388579,23],[8388580,23],[2097116,21],[4194264,22],[8388581,23],[4194265,22],[8388582,23],[8388583,23],[16777199,24],[4194266,22],[2097117,21],[1048553,20],[4194267,22],[4194268,22],[8388584,23],[8388585,23],[2097118,21],[8388586,23],[4194269,22],[4194270,22],[16777200,24],[2097119,21],[4194271,22],[8388587,23],[8388588,23],[2097120,21],[2097121,21],[4194272,22],[2097122,21],[8388589,23],[4194273,22],[8388590,23],[8388591,23],[1048554,20],[4194274,22],[4194275,22],[4194276,22],[8388592,23],[4194277,22],[4194278,22],[8388593,23],[67108832,26],[67108833,26],[1048555,20],[524273,19],[4194279,22],[8388594,23],[4194280,22],[33554412,25],[67108834,26],[67108835,26],[67108836,26],[134217694,27],[134217695,27],[67108837,26],[16777201,24],[33554413,25],[524274,19],[2097123,21],[67108838,26],[134217696,27],[134217697,27],[67108839,26],[134217698,27],[16777202,24],[2097124,21],[2097125,21],[67108840,26],[67108841,26],[268435453,28],[134217699,27],[134217700,27],[134217701,27],[1048556,20],[16777203,24],[1048557,20],[2097126,21],[4194281,22],[2097127,21],[2097128,21],[8388595,23],[4194282,22],[4194283,22],[33554414,25],[33554415,25],[16777204,24],[16777205,24],[67108842,26],[8388596,23],[67108843,26],[134217702,27],[67108844,26],[67108845,26],[134217703,27],[134217704,27],[134217705,27],[134217706,27],[134217707,27],[268435454,28],[134217708,27],[134217709,27],[134217710,27],[134217711,27],[134217712,27],[67108846,26],[1073741823,30]]}
|