@volter/world-core 2.0.13 → 2.0.15

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.
@@ -296,6 +296,9 @@ export type DerivedManifest = {
296
296
  root?: string;
297
297
  occurredAt: string;
298
298
  request: Request;
299
+ /** the writing call's context, narrow as a handler's, at the write's moment: what the hook reads to render the event
300
+ * it sends (until `declared-semantics` makes events data the kernel renders) */
301
+ context(): Promise<WriteHookContext>;
299
302
  }) => Promise<void>;
300
303
  };
301
304
  /** A form body's bracket notation (`a[b][0][c]=v`, `expand[]=x`) as nested objects and arrays. A form carries only
@@ -428,12 +431,31 @@ export type SemanticsContext = {
428
431
  expand(resource: string, body: Record<string, unknown>): Record<string, unknown>;
429
432
  /** This context at another moment of the World clock (ISO): a move time makes is written at the moment it fell due. */
430
433
  at(when: string): Promise<SemanticsContext>;
434
+ /** This call's context over another manifest of the same service, at the same moment: a lane over its vendor's
435
+ * state reads and writes the vendor's resources through the vendor's manifest ("Pack layout"). */
436
+ over(other: DerivedManifest): Promise<SemanticsContext>;
431
437
  /** The generic core's answer to this call, or undefined where the core does not serve it. */
432
438
  core(): Promise<Response | undefined>;
433
439
  /** A stored row's own fields, as the vendor's object carries them (an `id`, `type` or `updatedAt` of its own restored). */
434
440
  own(row: Record<string, unknown>): Record<string, unknown>;
435
441
  };
436
442
  export type Semantics = (ctx: SemanticsContext) => Promise<Response>;
443
+ /** The context a derived pack's handler is given ("What an author writes, and how"): the kernel's own members
444
+ * (`now`, `core`, `atomically`, `tree`, `writeDetailed`) and the tree's location (`root`) are not on it, so a handler
445
+ * typed `Handler` cannot reach them. Its reads are those the contract lists (`row`, `rowsRaw`, `rows`, `get`,
446
+ * `history`, `resolve`, `expand`); `at` and `over` give the same narrow context at another moment or over the
447
+ * vendor's manifest. */
448
+ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically' | 'tree' | 'writeDetailed' | 'root' | 'at' | 'over'> & {
449
+ /** This context at another moment of the World clock (ISO): a move time makes is written at the moment it fell due. */
450
+ at(when: string): Promise<HandlerContext>;
451
+ /** This call's context over the manifest of the vendor whose state a lane shares, at the same moment. */
452
+ over(other: DerivedManifest): Promise<HandlerContext>;
453
+ };
454
+ /** What a write hook reads: the writing call's context without its ways to write, so a hook that renders an event
455
+ * cannot write again (and run itself again). */
456
+ export type WriteHookContext = Omit<HandlerContext, 'write' | 'record' | 'at' | 'over'>;
457
+ /** A derived pack's handler: one operation, by its operationId, over the contract's context. */
458
+ export type Handler = (ctx: HandlerContext) => Promise<Response>;
437
459
  /** A pack's semantics handlers as the dispatch's handlers. */
438
460
  export declare function bindSemantics(m: DerivedManifest, handlers: Record<string, Semantics>, scope?: CoreScope): Record<string, DerivedHandler>;
439
461
  /** The derived core as the dispatch's core: it owns every operation on a resource the manifest declares. */
@@ -311,7 +311,7 @@ async function writeDetailed(m, call, resource, id, fields, operation, params, r
311
311
  { operation, subjectType: storedType(m, resource), subjectId: id, fields, input: { operationId: call.operation.id, params: call.params, body: params }, occurredAt, actor: { kind: 'agent', ...(m.identity ? { id: m.identity(call.request.headers.get('authorization'), root) } : {}) } }, root);
312
312
  const body = view(m, resource, row);
313
313
  if (m.onWrite)
314
- await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: call.request });
314
+ await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: call.request, context: () => contextFor(m, { ...call, request: call.request.clone() }, { ...(root !== undefined ? { root } : {}), clock: () => occurredAt }) });
315
315
  // the subject's id as it stands after the write: the vendor's, when a live head minted one
316
316
  return { body, id: result.externalId ?? row.id, ...(result.vendorData !== undefined ? { vendorData: result.vendorData } : {}) };
317
317
  }
@@ -428,7 +428,8 @@ export async function serveCore(m, call, scope = {}) {
428
428
  if (!resource || !decl)
429
429
  return { unmodeled: `no manifest entry for resource ${resource ?? '(none)'}` };
430
430
  call = adoptedPath(m, call, root);
431
- const params = await boundaryParams(m, call, root);
431
+ // the body is read from a copy, so the call's own request is still unread for the write hook's context
432
+ const params = await boundaryParams(m, { ...call, request: call.request.clone() }, root);
432
433
  const idParam = subjectOf(m, resource, call);
433
434
  const orphan = missingParent(m, resource, call, root);
434
435
  if (orphan)
@@ -601,6 +602,7 @@ export function sse(m, events, operationId) {
601
602
  }
602
603
  async function contextFor(m, call, scope) {
603
604
  const root = scope.root;
605
+ const asAsked = call;
604
606
  call = adoptedPath(m, call, root);
605
607
  const at = (scope.clock ?? worldNow)();
606
608
  const text = await call.request.clone().text().catch(() => '');
@@ -680,6 +682,7 @@ async function contextFor(m, call, scope) {
680
682
  sse: (events) => sse(m, events, call.operation.id),
681
683
  expand: (resource, body) => (paths.length ? expand(m, resource, body, paths, root) : body),
682
684
  at: (when) => contextFor(m, { ...call, request: new Request(call.request.url) }, { ...scope, clock: () => when }),
685
+ over: (other) => contextFor(other, { ...asAsked, request: asAsked.request.clone() }, { ...scope, clock: () => at }),
683
686
  core: async () => { const out = await serveCore(m, { ...call, request: pristine.clone() }, { ...scope, clock: () => at }); return 'served' in out ? out.served : undefined; },
684
687
  own: (row) => ownFields(row),
685
688
  };
@@ -10,7 +10,7 @@ export { currentTraceparent, deliveryTraceHeaders, parseTraceparent, runWithRequ
10
10
  export type { Traceparent } from './trace-context.js';
11
11
  export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface } from './derived.js';
12
12
  export { bindSemantics, coreFor, crossCutting, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.js';
13
- export type { Actor, CoreScope, DerivedManifest, ErrorSpec, FieldRule, ResourceDecl, ScreenDecl, Semantics, SemanticsContext, StateField, Transition, TransitionObserver } from './derived-core.js';
13
+ export type { Actor, CoreScope, DerivedManifest, ErrorSpec, FieldRule, Handler, HandlerContext, ResourceDecl, WriteHookContext, ScreenDecl, Semantics, SemanticsContext, StateField, Transition, TransitionObserver } from './derived-core.js';
14
14
  export type { RemoteExecute, RemoteExecuteRequest, RemoteExecuteResponse } from './remote-execute.js';
15
15
  export type { TwinFetchAdapterConfig, TwinFetchHandlerRequest, TwinFetchHandlerResult, TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.js';
16
16
  export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioExtractorSpec, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord, ScenarioStatus } from './scenario.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
@@ -48,7 +48,7 @@
48
48
  "url": "git+https://github.com/volter-ai/twin.git",
49
49
  "directory": "packages/world-core"
50
50
  },
51
- "homepage": "https://github.com/volter-ai/twin/tree/main/packages/world-core#readme",
51
+ "homepage": "https://world.volter.ai/docs/README",
52
52
  "type": "module",
53
53
  "exports": {
54
54
  ".": {
@@ -226,7 +226,12 @@ export type DerivedManifest = {
226
226
  /** the vendor's screens this pack serves or owes (demand decides which exist) */
227
227
  screens?: ScreenDecl[];
228
228
  /** called after every stored write with the rendered resource, its stored type and the kernel operation name */
229
- onWrite?: (write: { operation: string; storedType: string; body: Record<string, unknown>; root?: string; occurredAt: string; request: Request }) => Promise<void>;
229
+ onWrite?: (write: {
230
+ operation: string; storedType: string; body: Record<string, unknown>; root?: string; occurredAt: string; request: Request;
231
+ /** the writing call's context, narrow as a handler's, at the write's moment: what the hook reads to render the event
232
+ * it sends (until `declared-semantics` makes events data the kernel renders) */
233
+ context(): Promise<WriteHookContext>;
234
+ }) => Promise<void>;
230
235
  };
231
236
 
232
237
  // ── request parsing ──────────────────────────────────────────────────────────────────────────
@@ -504,7 +509,7 @@ async function writeDetailed(m: DerivedManifest, call: DerivedCall, resource: st
504
509
  root,
505
510
  );
506
511
  const body = view(m, resource, row);
507
- if (m.onWrite) await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: call.request });
512
+ if (m.onWrite) await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: call.request, context: () => contextFor(m, { ...call, request: call.request.clone() }, { ...(root !== undefined ? { root } : {}), clock: () => occurredAt }) });
508
513
  // the subject's id as it stands after the write: the vendor's, when a live head minted one
509
514
  return { body, id: result.externalId ?? row.id, ...(result.vendorData !== undefined ? { vendorData: result.vendorData } : {}) };
510
515
  }
@@ -620,7 +625,8 @@ export async function serveCore(m: DerivedManifest, call: DerivedCall, scope: Co
620
625
  const decl = resource ? m.resources[resource] : undefined;
621
626
  if (!resource || !decl) return { unmodeled: `no manifest entry for resource ${resource ?? '(none)'}` };
622
627
  call = adoptedPath(m, call, root);
623
- const params = await boundaryParams(m, call, root);
628
+ // the body is read from a copy, so the call's own request is still unread for the write hook's context
629
+ const params = await boundaryParams(m, { ...call, request: call.request.clone() }, root);
624
630
  const idParam = subjectOf(m, resource, call);
625
631
  const orphan = missingParent(m, resource, call, root);
626
632
  if (orphan) return { served: orphan };
@@ -834,6 +840,9 @@ export type SemanticsContext = {
834
840
  expand(resource: string, body: Record<string, unknown>): Record<string, unknown>;
835
841
  /** This context at another moment of the World clock (ISO): a move time makes is written at the moment it fell due. */
836
842
  at(when: string): Promise<SemanticsContext>;
843
+ /** This call's context over another manifest of the same service, at the same moment: a lane over its vendor's
844
+ * state reads and writes the vendor's resources through the vendor's manifest ("Pack layout"). */
845
+ over(other: DerivedManifest): Promise<SemanticsContext>;
837
846
  /** The generic core's answer to this call, or undefined where the core does not serve it. */
838
847
  core(): Promise<Response | undefined>;
839
848
  /** A stored row's own fields, as the vendor's object carries them (an `id`, `type` or `updatedAt` of its own restored). */
@@ -841,8 +850,26 @@ export type SemanticsContext = {
841
850
  };
842
851
  export type Semantics = (ctx: SemanticsContext) => Promise<Response>;
843
852
 
853
+ /** The context a derived pack's handler is given ("What an author writes, and how"): the kernel's own members
854
+ * (`now`, `core`, `atomically`, `tree`, `writeDetailed`) and the tree's location (`root`) are not on it, so a handler
855
+ * typed `Handler` cannot reach them. Its reads are those the contract lists (`row`, `rowsRaw`, `rows`, `get`,
856
+ * `history`, `resolve`, `expand`); `at` and `over` give the same narrow context at another moment or over the
857
+ * vendor's manifest. */
858
+ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically' | 'tree' | 'writeDetailed' | 'root' | 'at' | 'over'> & {
859
+ /** This context at another moment of the World clock (ISO): a move time makes is written at the moment it fell due. */
860
+ at(when: string): Promise<HandlerContext>;
861
+ /** This call's context over the manifest of the vendor whose state a lane shares, at the same moment. */
862
+ over(other: DerivedManifest): Promise<HandlerContext>;
863
+ };
864
+ /** What a write hook reads: the writing call's context without its ways to write, so a hook that renders an event
865
+ * cannot write again (and run itself again). */
866
+ export type WriteHookContext = Omit<HandlerContext, 'write' | 'record' | 'at' | 'over'>;
867
+ /** A derived pack's handler: one operation, by its operationId, over the contract's context. */
868
+ export type Handler = (ctx: HandlerContext) => Promise<Response>;
869
+
844
870
  async function contextFor(m: DerivedManifest, call: DerivedCall, scope: CoreScope): Promise<SemanticsContext> {
845
871
  const root = scope.root;
872
+ const asAsked = call;
846
873
  call = adoptedPath(m, call, root);
847
874
  const at = (scope.clock ?? worldNow)();
848
875
  const text = await call.request.clone().text().catch(() => '');
@@ -915,6 +942,7 @@ async function contextFor(m: DerivedManifest, call: DerivedCall, scope: CoreScop
915
942
  sse: (events) => sse(m, events, call.operation.id),
916
943
  expand: (resource, body) => (paths.length ? expand(m, resource, body, paths, root) : body),
917
944
  at: (when) => contextFor(m, { ...call, request: new Request(call.request.url) }, { ...scope, clock: () => when }),
945
+ over: (other) => contextFor(other, { ...asAsked, request: asAsked.request.clone() }, { ...scope, clock: () => at }),
918
946
  core: async () => { const out = await serveCore(m, { ...call, request: pristine.clone() }, { ...scope, clock: () => at }); return 'served' in out ? out.served : undefined; },
919
947
  own: (row) => ownFields(row as unknown as TwinResource),
920
948
  };
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export { currentTraceparent, deliveryTraceHeaders, parseTraceparent, runWithRequ
22
22
  export type { Traceparent } from './trace-context.ts';
23
23
  export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface } from './derived.ts';
24
24
  export { bindSemantics, coreFor, crossCutting, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.ts';
25
- export type { Actor, CoreScope, DerivedManifest, ErrorSpec, FieldRule, ResourceDecl, ScreenDecl, Semantics, SemanticsContext, StateField, Transition, TransitionObserver } from './derived-core.ts';
25
+ export type { Actor, CoreScope, DerivedManifest, ErrorSpec, FieldRule, Handler, HandlerContext, ResourceDecl, WriteHookContext, ScreenDecl, Semantics, SemanticsContext, StateField, Transition, TransitionObserver } from './derived-core.ts';
26
26
  export type { RemoteExecute, RemoteExecuteRequest, RemoteExecuteResponse } from './remote-execute.ts';
27
27
  export type { TwinFetchAdapterConfig, TwinFetchHandlerRequest, TwinFetchHandlerResult, TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.ts';
28
28
  export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioExtractorSpec, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord, ScenarioStatus } from './scenario.ts';