@volter/world-core 3.0.154 → 3.0.156

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.
Files changed (107) hide show
  1. package/dist/generated/pack-facts.json +140 -0
  2. package/dist/inject.cjs +150 -86
  3. package/dist/src/actions.d.ts +15 -0
  4. package/dist/src/actions.js +82 -21
  5. package/dist/src/ancestry.d.ts +5 -4
  6. package/dist/src/ancestry.js +53 -28
  7. package/dist/src/changeset.js +1 -1
  8. package/dist/src/clickhouse/compression.d.ts +28 -0
  9. package/dist/src/clickhouse/compression.js +281 -0
  10. package/dist/src/clickhouse/index.d.ts +1 -0
  11. package/dist/src/clickhouse/index.js +1 -0
  12. package/dist/src/client-bundle.js +15 -15
  13. package/dist/src/derived-core.d.ts +12 -1
  14. package/dist/src/derived-core.js +145 -11
  15. package/dist/src/derived.js +3 -0
  16. package/dist/src/fork.js +1 -1
  17. package/dist/src/graphql-wire.d.ts +5 -0
  18. package/dist/src/graphql-wire.js +6 -6
  19. package/dist/src/grpc-wire.js +2 -1
  20. package/dist/src/h2.js +63 -10
  21. package/dist/src/head.d.ts +3 -0
  22. package/dist/src/head.js +9 -0
  23. package/dist/src/history.d.ts +1 -1
  24. package/dist/src/history.js +7 -7
  25. package/dist/src/index.d.ts +4 -1
  26. package/dist/src/index.js +5 -1
  27. package/dist/src/log.d.ts +23 -3
  28. package/dist/src/log.js +160 -37
  29. package/dist/src/managed-original-inputs.d.ts +101 -0
  30. package/dist/src/managed-original-inputs.js +125 -0
  31. package/dist/src/mongodb/client.d.ts +8 -0
  32. package/dist/src/mongodb/client.js +27 -0
  33. package/dist/src/mongodb/index.d.ts +2 -0
  34. package/dist/src/mongodb/index.js +5 -0
  35. package/dist/src/mongodb/stream.d.ts +74 -0
  36. package/dist/src/mongodb/stream.js +206 -0
  37. package/dist/src/observe.js +1 -1
  38. package/dist/src/pack-fetch.d.ts +2 -1
  39. package/dist/src/pack-fetch.js +21 -9
  40. package/dist/src/packRegistry.d.ts +10 -14
  41. package/dist/src/packRegistry.js +2 -13
  42. package/dist/src/protocol.d.ts +13 -0
  43. package/dist/src/protocol.js +13 -0
  44. package/dist/src/redis/engine.d.ts +10 -4
  45. package/dist/src/redis/engine.js +103 -32
  46. package/dist/src/redis/member-storage.js +20 -8
  47. package/dist/src/redis/service-times.d.ts +25 -0
  48. package/dist/src/redis/service-times.js +87 -0
  49. package/dist/src/redis/stream.d.ts +2 -1
  50. package/dist/src/redis/stream.js +109 -20
  51. package/dist/src/runtime-state.d.ts +11 -0
  52. package/dist/src/runtime-state.js +13 -0
  53. package/dist/src/runtime.d.ts +4 -2
  54. package/dist/src/runtime.js +5 -2
  55. package/dist/src/serve-http.d.ts +2 -0
  56. package/dist/src/serve-http.js +21 -4
  57. package/dist/src/serve.d.ts +10 -0
  58. package/dist/src/serve.js +43 -3
  59. package/dist/src/storage.d.ts +2 -1
  60. package/dist/src/storage.js +6 -3
  61. package/dist/src/world-boot.d.ts +1 -0
  62. package/dist/src/world-boot.js +6 -0
  63. package/dist/src/world-clock.js +6 -2
  64. package/dist/src/world-store.d.ts +108 -7
  65. package/dist/src/world-store.js +883 -103
  66. package/dist/src/xml.d.ts +30 -0
  67. package/dist/src/xml.js +74 -0
  68. package/generated/pack-facts.json +140 -0
  69. package/inject.cjs +150 -86
  70. package/package.json +20 -1
  71. package/src/actions.ts +78 -21
  72. package/src/ancestry.ts +46 -24
  73. package/src/changeset.ts +1 -1
  74. package/src/clickhouse/compression.ts +172 -0
  75. package/src/clickhouse/index.ts +2 -0
  76. package/src/client-bundle.ts +13 -14
  77. package/src/derived-core.ts +142 -11
  78. package/src/derived.ts +3 -0
  79. package/src/fork.ts +1 -1
  80. package/src/graphql-wire.ts +9 -6
  81. package/src/grpc-wire.ts +2 -1
  82. package/src/h2.ts +46 -5
  83. package/src/head.ts +9 -0
  84. package/src/history.ts +7 -7
  85. package/src/index.ts +6 -1
  86. package/src/log.ts +147 -32
  87. package/src/managed-original-inputs.ts +163 -0
  88. package/src/mongodb/client.ts +27 -0
  89. package/src/mongodb/index.ts +5 -0
  90. package/src/mongodb/stream.ts +213 -0
  91. package/src/observe.ts +1 -1
  92. package/src/pack-fetch.ts +23 -11
  93. package/src/packRegistry.ts +3 -15
  94. package/src/protocol.ts +13 -0
  95. package/src/redis/engine.ts +100 -57
  96. package/src/redis/member-storage.ts +17 -9
  97. package/src/redis/service-times.ts +81 -0
  98. package/src/redis/stream.ts +66 -14
  99. package/src/runtime-state.ts +14 -0
  100. package/src/runtime.ts +6 -1
  101. package/src/serve-http.ts +23 -4
  102. package/src/serve.ts +45 -3
  103. package/src/storage.ts +5 -3
  104. package/src/world-boot.ts +6 -0
  105. package/src/world-clock.ts +6 -2
  106. package/src/world-store.ts +704 -91
  107. package/src/xml.ts +71 -0
@@ -6,8 +6,9 @@ import { parseExactJson, compareExactNumbers, exactInteger, isExactIntegerValue
6
6
  // serves CRUD generically and applies declared transitions, over the kernel's tree and write path.
7
7
  // Nothing here knows a vendor: every vendor difference is a manifest value.
8
8
 
9
- import { applyTwinWrite, applyTwinWriteAtomic, twinResources, type TwinResource } from './serve.ts';
10
- import { copyResource, ownFields, parentStamp, subjectHistory, treeChangesSince, treeStamp } from './log.ts';
9
+ import { applyTwinWrite, applyTwinWriteAtomic, applyTwinWrites, twinResources, type TwinResource } from './serve.ts';
10
+ import { copyResource, foldLocalWrite, heldSubject, ownFields, parentStamp, subjectHistory, treeChangesSince, treeResources, treeStamp, type Subject } from './log.ts';
11
+ import { performsAtHeadNow } from './head.ts';
11
12
  import { getActiveWorldStore } from './world-store.ts';
12
13
  import { getActiveBlobStore } from './blob-store.ts';
13
14
  import { worldPaths } from './storage.ts';
@@ -26,6 +27,7 @@ import { handlerCrypto, hmac, ksuidFrom, lettersFrom, sha256, uuidFrom, type Han
26
27
  import { hashFieldValue } from './hash.ts';
27
28
  import { readResourceBlob, removeResourceBlob, writeResourceBlob } from './resource-blob.ts';
28
29
  import { managedDatabase, unboundDatabase, type ManagedDatabase } from './managed-database.ts';
30
+ import { redisServiceTimes } from './redis/service-times.ts';
29
31
  import { execRedisRun, type RedisDialect, type RunItem } from './redis/engine.ts';
30
32
  import { sendMail, type Mail, type SmtpRoute } from './smtp.ts';
31
33
  import { ownField, parseBracketForm } from './form.ts';
@@ -738,13 +740,15 @@ export function resourcesOfType(service: string, type: string, root?: string): T
738
740
  for (const [k, r] of byKey) if (!delta.appended.includes(k)) (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)!).set(k, r);
739
741
  for (const k of delta.appended) { const r = byKey.get(k)!; (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)!).set(k, r); }
740
742
  held.stamp = delta.stamp;
741
- } else if (!held || held.stamp !== treeStamp(service, root)) {
742
- // the stamp is read before the fold, so a write between them can only leave a newer fold under an older stamp
743
+ } else {
744
+ // the stamp is read once, before the fold, so a write between them can only leave a newer fold under an older stamp
743
745
  const stamp = treeStamp(service, root);
744
- const byType = new Map<string, Map<string, TwinResource>>();
745
- for (const r of twinResources(service, root)) (byType.get(r.type) ?? byType.set(r.type, new Map()).get(r.type)!).set(`${r.type}:${r.id}`, r);
746
- held = { stamp, byType };
747
- indexes.set(key, held);
746
+ if (!held || held.stamp !== stamp) {
747
+ const byType = new Map<string, Map<string, TwinResource>>();
748
+ for (const r of twinResources(service, root)) (byType.get(r.type) ?? byType.set(r.type, new Map()).get(r.type)!).set(`${r.type}:${r.id}`, r);
749
+ held = { stamp, byType };
750
+ indexes.set(key, held);
751
+ }
748
752
  }
749
753
  return [...(held.byType.get(type)?.values() ?? [])].map(copyResource);
750
754
  }
@@ -1808,6 +1812,17 @@ export type SemanticsContext = {
1808
1812
  /** Decide an update from the current live stored row under the action lock; undefined leaves it unchanged. */
1809
1813
  change(resource: string, id: string, decide: (row: Record<string, unknown>) => Record<string, unknown> | undefined, operation: string): Promise<Record<string, unknown> | undefined>;
1810
1814
  write(resource: string, id: string, fields: Record<string, unknown>, operation: string): Promise<Record<string, unknown>>;
1815
+ /**
1816
+ * One command's writes as one group (S2 R17; docs/contributing/architecture.md, "A command's writes as one group").
1817
+ * Inside `fn`, `write` stages its write and answers the row as it will stand; `row` and `rowsRaw` read the tree with
1818
+ * the staged writes folded over it, as it will be once they land. When `fn` returns or throws, every staged write
1819
+ * lands, in order, as one append; then its value is answered or its error thrown. Every other way to write, or to
1820
+ * wait on a write, lands the staged writes first; any other read of the tree (`get`, `rows`, `tree`, `history`, ...)
1821
+ * while writes are staged is refused, never answered without them. Each write keeps its own action, id, ordinal,
1822
+ * moment and recorded request. Where the pack has write hooks, events, sockets or counts, a state observer is on,
1823
+ * a head performs, or ids are aliased, `fn` simply runs, each write landing as it is made.
1824
+ */
1825
+ batch<T>(fn: () => Promise<T>): Promise<T>;
1811
1826
  /** Add numeric deltas to an existing bookkeeping row under the kernel's atomic write seam. */
1812
1827
  accumulate(resource: string, id: string, deltas: Record<string, number>, operation: string): Promise<void>;
1813
1828
  /** `write`, with what a live vendor answered when the head performed it */
@@ -1917,7 +1932,7 @@ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically
1917
1932
  };
1918
1933
  /** What a write hook reads: the writing call's context without its ways to write, so a hook that renders an event
1919
1934
  * cannot write again (and run itself again). */
1920
- export type WriteHookContext = Omit<HandlerContext, 'write' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'at' | 'over'>;
1935
+ export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'at' | 'over'>;
1921
1936
  /** A derived pack's handler: one operation, by its operationId, over the contract's context. */
1922
1937
  export type Handler = (ctx: HandlerContext) => Promise<Response>;
1923
1938
 
@@ -1963,7 +1978,7 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
1963
1978
  rawResources.set(row as Record<string, unknown>, name ?? resource);
1964
1979
  return row as Record<string, unknown>;
1965
1980
  };
1966
- return {
1981
+ const context: SemanticsContext = {
1967
1982
  call,
1968
1983
  params,
1969
1984
  id: Object.values(call.params).at(-1),
@@ -2172,13 +2187,129 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2172
2187
  over: (other) => contextFor(other, { ...asAsked, request: asAsked.request.clone() }, { ...scope, clock: () => at }),
2173
2188
  core: async () => { const out = await serveCore(m, { ...call, request: pristine.clone() }, { ...scope, clock: () => at }); return 'served' in out ? out.served : undefined; },
2174
2189
  own: (row) => ownFields(row as unknown as TwinResource),
2190
+ batch: (fn) => fn(),
2191
+ };
2192
+ if (batchable(m, root)) bindBatch(m, call, context, params, root, at, rawRow);
2193
+ return context;
2194
+ }
2195
+
2196
+ /**
2197
+ * Whether a call's writes may be grouped (`ctx.batch`) with nothing about them changing: every effect a write has
2198
+ * besides its action and the tree (write hooks, events, sockets, a parent's counts, a state observer, a head that
2199
+ * performs it, an alias a read resolves through) is one the grouped path would move, so where any is present the
2200
+ * writes land one at a time, as they always have.
2201
+ */
2202
+ function batchable(m: DerivedManifest, root: string | undefined): boolean {
2203
+ return !m.onWrite && !m.events && !m.sockets?.length && !transitionObserver
2204
+ && !Object.values(m.resources).some((decl) => decl.counts && Object.keys(decl.counts).length > 0)
2205
+ && !performsAtHeadNow(m.service, root) && subjectAliases(m.service, root).size === 0;
2206
+ }
2207
+
2208
+ /** A staged write of `ctx.batch`, as `write` was asked it. */
2209
+ type StagedWrite = { resource: string; id: string; fields: Record<string, unknown>; operation: string };
2210
+
2211
+ /** The members of a context that read the tree other than `row`/`rowsRaw`: refused while writes are staged. */
2212
+ const BATCH_REFUSED_READS = ['get', 'rows', 'tree', 'history', 'find', 'list', 'expand', 'mint', 'conflict', 'ownerRow', 'render'] as const;
2213
+ /** The members that write, or wait on writes, or answer as another context: each lands the staged writes first. */
2214
+ const BATCH_LANDING_MEMBERS = ['create', 'createUnique', 'change', 'accumulate', 'writeDetailed', 'record', 'atomically', 'remove', 'issue', 'at', 'over', 'core', 'awaitWrite', 'awaitCondition',
2215
+ // a World's seed and signing keys are written when first asked (worldSeed, worldSigningKey, as a vendor's move)
2216
+ 'secret', 'signingKey'] as const;
2217
+
2218
+ /** `ctx.batch` over a context whose writes may be grouped (see `batchable`). */
2219
+ function bindBatch(m: DerivedManifest, call: DerivedCall, context: SemanticsContext, params: Record<string, unknown>, root: string | undefined, at: string,
2220
+ rawRow: (resource: string, row: TwinResource) => Record<string, unknown>): void {
2221
+ let staged: StagedWrite[] | undefined;
2222
+ // each staged subject as it will stand, by `type:id`, in the order first written
2223
+ const subjects = new Map<string, Subject>();
2224
+ const land = async (): Promise<void> => {
2225
+ if (!staged?.length) return;
2226
+ const writes = staged;
2227
+ staged = [];
2228
+ subjects.clear();
2229
+ await writeGroup(m, call, writes, params, root, at);
2230
+ };
2231
+ const ctx = context as unknown as Record<string, unknown>;
2232
+ for (const name of BATCH_LANDING_MEMBERS) {
2233
+ const plain = ctx[name];
2234
+ if (typeof plain !== 'function') continue;
2235
+ ctx[name] = async (...args: unknown[]) => { await land(); return (plain as (...a: unknown[]) => unknown)(...args); };
2236
+ }
2237
+ for (const name of BATCH_REFUSED_READS) {
2238
+ const plain = ctx[name];
2239
+ if (typeof plain !== 'function') continue;
2240
+ ctx[name] = (...args: unknown[]) => {
2241
+ if (staged?.length) throw new Error(`ctx.${name} reads the tree while ctx.batch holds staged writes: only row and rowsRaw see them`);
2242
+ return (plain as (...a: unknown[]) => unknown)(...args);
2243
+ };
2244
+ }
2245
+ const plainWrite = context.write, plainRow = context.row, plainRowsRaw = context.rowsRaw;
2246
+ context.write = async (resource, id, fields, operation) => {
2247
+ // a vendor's own move (ctx.asVendor) is the clock's write, recorded as the system's: it lands alone, in its place
2248
+ if (!staged || inVendorMove()) { await land(); return plainWrite(resource, id, fields, operation); }
2249
+ const type = storedType(m, resource), key = `${type}:${id}`;
2250
+ // the one subject, as the memoized tree holds it (readTreeMap would copy the whole tree on every staged write)
2251
+ const subject = foldLocalWrite(subjects.get(key) ?? heldSubject(m.service, type, id, root), type, id, fields as SubjectFields, at);
2252
+ subjects.set(key, subject);
2253
+ staged.push({ resource, id, fields, operation });
2254
+ return view(m, resource, treeResources(new Map([[key, subject]]))[0]!);
2255
+ };
2256
+ // the stored rows of a resource's type with the staged subjects folded over them, filtered as `stored` filters
2257
+ const overlaid = (resource: string, opts?: { withDeleted?: boolean }): TwinResource[] | undefined => {
2258
+ if (!staged?.length) return undefined;
2259
+ const type = storedType(m, resource), prefix = `${type}:`;
2260
+ const mine = [...subjects].filter(([key]) => key.startsWith(prefix));
2261
+ if (!mine.length) return undefined;
2262
+ const replaced = new Map(mine.map(([key, subject]) => [key, treeResources(new Map([[key, subject]]))[0]!]));
2263
+ const base = resourcesOfType(m.service, type, root);
2264
+ // a subject the tree holds stays where it stands; one new to it goes to the end, in the order first written
2265
+ const rows = base.map((row) => replaced.get(`${prefix}${row.id}`) ?? row);
2266
+ const held = new Set(base.map((row) => `${prefix}${row.id}`));
2267
+ for (const [key, row] of replaced) if (!held.has(key)) rows.push(row);
2268
+ const when = Object.entries(m.resources[resource]?.readableWhen ?? {});
2269
+ return rows.filter((r) => (opts?.withDeleted || r.deleted !== true) && when.every(([k, v]) => (r as Record<string, unknown>)[k] === v));
2175
2270
  };
2271
+ context.row = (resource, id, opts) => {
2272
+ const rows = overlaid(resource, opts);
2273
+ if (!rows) return plainRow(resource, id, opts);
2274
+ const row = rows.find((r) => r.id === id);
2275
+ return row ? rawRow(resource, row) : undefined;
2276
+ };
2277
+ context.rowsRaw = (resource, opts) => {
2278
+ const rows = overlaid(resource, opts);
2279
+ return rows ? rows.map((row) => rawRow(resource, row)) : plainRowsRaw(resource, opts);
2280
+ };
2281
+ context.batch = async (fn) => {
2282
+ // a batch inside a batch is the outer one's
2283
+ if (staged !== undefined) return fn();
2284
+ staged = [];
2285
+ try { return await fn(); }
2286
+ finally {
2287
+ // every write `write` answered lands, whether fn returned or threw: an ordered insert's prefix stays written
2288
+ try { await land(); } finally { staged = undefined; subjects.clear(); }
2289
+ }
2290
+ };
2291
+ }
2292
+
2293
+ /**
2294
+ * A command's staged writes landed as one group: each recorded with the request as `writeDetailed` records it (one
2295
+ * recordedInput, the same for every write of the call), the call's moment and actor, in order, under one lock and one
2296
+ * append (serve.ts applyTwinWrites); then a held read on each written type is told, as each write tells it.
2297
+ */
2298
+ async function writeGroup(m: DerivedManifest, call: DerivedCall, writes: readonly StagedWrite[], params: Record<string, unknown>,
2299
+ root: string | undefined, occurredAt: string): Promise<void> {
2300
+ const input = recordedInput(m, call, params);
2301
+ const files = input.operationId ? await keptFiles(m, call, root) : undefined;
2302
+ const recorded = files ? { ...input, files } : input;
2303
+ const actor = { kind: 'agent' as const, ...(m.identity ? { id: m.identity(call.request.headers.get('authorization'), root) } : {}) };
2304
+ await applyTwinWrites(m.service, writes.map((write) => ({ operation: write.operation, subjectType: storedType(m, write.resource),
2305
+ subjectId: write.id, fields: write.fields, input: recorded, occurredAt, actor })), root);
2306
+ for (const type of new Set(writes.map((write) => storedType(m, write.resource)))) heldReadsWrite(m.service, root, type);
2176
2307
  }
2177
2308
 
2178
2309
  /** A pack's semantics handlers as the dispatch's handlers. */
2179
2310
  export function bindSemantics(m: DerivedManifest, handlers: Record<string, Semantics>, scope: CoreScope = {}): Record<string, DerivedHandler> {
2180
2311
  prepareViewOmissions(m);
2181
- return Object.fromEntries(Object.entries(handlers).map(([id, h]) => [id, async (call: DerivedCall) => h(await contextFor(m, call, scope))]));
2312
+ return Object.fromEntries(Object.entries(handlers).map(([id, h]) => [id, async (call: DerivedCall) => { const ctx = await contextFor(m, call, scope); redisServiceTimes.lap('6c the handler\'s own context opened'); return h(ctx); }]));
2182
2313
  }
2183
2314
 
2184
2315
  /** The derived core as the dispatch's core: it owns every operation on a resource the manifest declares. */
package/src/derived.ts CHANGED
@@ -9,6 +9,7 @@ import { parseExactJson } from './exact-json.ts';
9
9
  import type { WebSocketUpgrade } from './serve-http.ts';
10
10
  import type { TwinStream } from './twin-fetch.ts';
11
11
  import { runWithRequestTrace } from './trace-context.ts';
12
+ import { redisServiceTimes } from './redis/service-times.ts';
12
13
 
13
14
  /** The slice of a generated surface the dispatch reads. */
14
15
  export type DerivedOperation = {
@@ -285,7 +286,9 @@ export function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch {
285
286
  if (!matched) return fallback(request, undefined, 'no operation at this path');
286
287
  for (const observer of dispatchObservers ?? []) observer({ surface: options.surface, operation: matched.operation, owner: handlers[matched.operation.id] ? 'handler' : options.core?.owns(matched.operation) ? 'core' : 'gap' });
287
288
  // the body read once, as text, for everything the dispatch does with it (bodyTextOf)
289
+ redisServiceTimes.lap('6a around entered, guards and scenario passed, operation matched again');
288
290
  if (request.method !== 'GET' && request.method !== 'HEAD' && request.body && !(request.headers.get('content-type') ?? '').includes('multipart')) tagBody(request, await request.clone().text());
291
+ redisServiceTimes.lap('6b the dispatch read the body as text');
289
292
  // the path prefix's named groups (pack-fetch.ts, `pathPrefix`) are the call's parameters too
290
293
  const prefixed = request.headers.get(PREFIX_PARAMS_HEADER);
291
294
  const call: DerivedCall = { request, operation: matched.operation, params: prefixed ? { ...(parseExactJson(prefixed) as Record<string, string>), ...matched.params } : matched.params };
package/src/fork.ts CHANGED
@@ -76,7 +76,7 @@ export function forkTwin(opts: {
76
76
  };
77
77
  getActiveWorldStore().write(forkMetaPath(service, toRoot), `${JSON.stringify(meta, null, 2)}\n`);
78
78
  return meta;
79
- });
79
+ }, { operation: 'pointer publication' });
80
80
  }
81
81
 
82
82
  export function readForkMeta(service: string, root: string): ForkMeta {
@@ -7,9 +7,9 @@
7
7
  // modeled; any other is answered by the manifest's `graphql.unmodeled`, naming the first field nothing models. An error
8
8
  // a resolver throws (`GraphqlError`) carries its `type`, as GitHub's GraphQL errors do.
9
9
  //
10
- // The `graphql` protocol library is loaded the first time a GraphQL request comes (installed by the pack's package.json,
11
- // as node-postgres is for the managed database): the kernel's own dependencies stay B1's, and importing the kernel
12
- // loads nothing.
10
+ // The generated pack fetch supplies a lazy pack-local loader for the `graphql` protocol library installed by the pack's
11
+ // package.json. Each endpoint caches its library on the first request; callers without a loader keep the kernel-relative
12
+ // lazy import fallback. The kernel's own dependencies stay B1's, and importing the kernel loads nothing.
13
13
 
14
14
  /** A resolver of one field, `Type.field`: its parent object, its arguments, and the request's handler context. */
15
15
  export type GraphqlResolver<C = unknown> = (source: Record<string, unknown>, args: Record<string, unknown>, ctx: C) => unknown;
@@ -32,8 +32,7 @@ export class GraphqlError extends Error {
32
32
  }
33
33
 
34
34
  type GraphqlLib = typeof import('graphql');
35
- let lib: Promise<GraphqlLib> | undefined;
36
- const graphqlLib = (): Promise<GraphqlLib> => (lib ??= import('graphql') as Promise<GraphqlLib>);
35
+ export type GraphqlLibraryLoader = () => Promise<GraphqlLib>;
37
36
 
38
37
  const snake = (s: string): string => s.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
39
38
 
@@ -59,10 +58,14 @@ function connection(items: unknown[], args: Record<string, unknown>): Record<str
59
58
  export function graphqlEndpoint<C>(options: {
60
59
  sdl: string;
61
60
  part: GraphqlPart<C>;
61
+ /** The generated pack fetch's lazy import, resolving the library from the pack that declares it. */
62
+ loadLibrary?: GraphqlLibraryLoader;
62
63
  context: (request: Request) => Promise<C>;
63
64
  unmodeled: (field: { type: string; field: string }) => Response;
64
65
  mutationRefused?: (request: Request) => Response | undefined;
65
66
  }): (request: Request) => Promise<Response> {
67
+ let lib: Promise<GraphqlLib> | undefined;
68
+ const loadLibrary = options.loadLibrary ?? (() => import('graphql'));
66
69
  let schema: import('graphql').GraphQLSchema | undefined;
67
70
  const { resolvers, aliases, stored } = options.part;
68
71
  // introspection (`__typename`, `__type`, `__schema` and the `__Type`/`__Field`/… meta types) is the vendor's schema
@@ -71,7 +74,7 @@ export function graphqlEndpoint<C>(options: {
71
74
  const modeled = (type: string, field: string): boolean =>
72
75
  field.startsWith('__') || type.startsWith('__') || `${type}.${field}` in resolvers || !!aliases?.[type]?.[field] || !!stored?.[type]?.includes(field) || !!stored?.[type]?.includes(snake(field));
73
76
  return async (request) => {
74
- const g = await graphqlLib();
77
+ const g = await (lib ??= loadLibrary());
75
78
  schema ??= g.buildSchema(options.sdl, { assumeValidSDL: true });
76
79
  const payload = (await request.clone().json().catch(() => ({}))) as { query?: string; variables?: Record<string, unknown>; operationName?: string };
77
80
  let document: import('graphql').DocumentNode;
package/src/grpc-wire.ts CHANGED
@@ -7,6 +7,7 @@ import { parseExactJson } from './exact-json.ts';
7
7
  // and what they answer is the pack's. Importing it does nothing.
8
8
  import { acceptH2Connection, type H2Handler } from './h2.ts';
9
9
  import { nodeBuiltin, WORLD_BOOT_PATH } from './serve-http.ts';
10
+ import { worldBootAnswer } from './world-boot.ts';
10
11
  import { decodeMessage, encodeMessage, type ProtoSchema } from './protobuf.ts';
11
12
 
12
13
  /** gRPC's status codes (https://grpc.github.io/grpc/core/md_doc_statuscodes.html). */
@@ -151,7 +152,7 @@ export function grpcOverFetch(fetch: (request: Request) => Promise<Response>): G
151
152
  export async function serveGrpc(options: { port?: number; fetch: (request: Request) => Promise<Response>; schema: ProtoSchema }): Promise<{ port: number; stop: () => void }> {
152
153
  const net = nodeBuiltin<typeof import('node:net')>('node:net');
153
154
  const worldBoot = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.VOLTER_WORLD_BOOT_ID;
154
- const fetch = worldBoot ? (request: Request) => (new URL(request.url).pathname === WORLD_BOOT_PATH ? Promise.resolve(Response.json({ boot: worldBoot })) : options.fetch(request)) : options.fetch;
155
+ const fetch = worldBoot ? (request: Request) => (new URL(request.url).pathname === WORLD_BOOT_PATH ? Promise.resolve(worldBootAnswer(worldBoot)) : options.fetch(request)) : options.fetch;
155
156
  const handler = grpcStreamHandler(options.schema, grpcOverFetch(fetch));
156
157
  const server = net.createServer((socket) => { acceptH2Connection(socket, handler, fetch); });
157
158
  await new Promise<void>((resolve, reject) => { server.once('error', reject); server.listen(options.port ?? 0, '127.0.0.1', () => resolve()); });
package/src/h2.ts CHANGED
@@ -25,6 +25,36 @@ const MAX_CONCURRENT_STREAMS = 1000;
25
25
  /** a request body larger than this resets its stream: a memory bound, not Temporal's message limit (the
26
26
  * SDKs' gRPC default is 4 MiB; this server does not enforce that one) */
27
27
  const MAX_REQUEST_BYTES = 64 * 1024 * 1024;
28
+
29
+ /** A chunked body (RFC 9112 §7.1) from `from`: its bytes once the last chunk and the trailer section have arrived,
30
+ * 'incomplete' before, 'bad' for a malformed size line or one past MAX_REQUEST_BYTES. Extensions and trailers are
31
+ * read past and not kept. */
32
+ function chunkedBody(buffer: Buffer, from: number): Uint8Array | 'incomplete' | 'bad' {
33
+ const parts: Buffer[] = [];
34
+ let at = from, total = 0;
35
+ for (;;) {
36
+ const line = buffer.indexOf('\r\n', at);
37
+ if (line < 0) return buffer.length - at > 4096 ? 'bad' : 'incomplete';
38
+ const size = /^([0-9A-Fa-f]{1,8})[ \t]*(?:;.*)?$/su.exec(buffer.subarray(at, line).toString('latin1'));
39
+ if (!size) return 'bad';
40
+ const length = parseInt(size[1]!, 16);
41
+ at = line + 2;
42
+ if (length === 0) break;
43
+ total += length;
44
+ if (total > MAX_REQUEST_BYTES) return 'bad';
45
+ if (buffer.length < at + length + 2) return 'incomplete';
46
+ if (buffer[at + length] !== 0x0d || buffer[at + length + 1] !== 0x0a) return 'bad';
47
+ parts.push(buffer.subarray(at, at + length));
48
+ at += length + 2;
49
+ }
50
+ // The trailer section: field lines up to an empty line.
51
+ for (;;) {
52
+ const line = buffer.indexOf('\r\n', at);
53
+ if (line < 0) return buffer.length - at > 64 * 1024 ? 'bad' : 'incomplete';
54
+ if (line === at) return Buffer.concat(parts);
55
+ at = line + 2;
56
+ }
57
+ }
28
58
  /** a header block (HEADERS + CONTINUATION) larger than this closes the connection */
29
59
  const MAX_HEADER_BLOCK = 256 * 1024;
30
60
 
@@ -293,11 +323,22 @@ class H2Connection {
293
323
  this.respondHttp1(new Response('bad header', { status: 400 }));
294
324
  return;
295
325
  }
296
- if (headers.get('transfer-encoding')) { this.respondHttp1(new Response('chunked request bodies are not accepted', { status: 411 })); return; }
297
- const length = Number(headers.get('content-length') ?? 0);
298
- if (!Number.isFinite(length) || length < 0 || length > MAX_REQUEST_BYTES) { this.respondHttp1(new Response('bad content-length', { status: 400 })); return; }
299
- if (this.buffer.length < end + 4 + length) return;
300
- const body = this.buffer.subarray(end + 4, end + 4 + length);
326
+ // A request with a Transfer-Encoding has a body framed by it, and its Content-Length is not read (RFC 9112 §6.3):
327
+ // undici sends a streamed body (a World's fetch forwarding a request's own body, duplex 'half') chunked.
328
+ const coding = headers.get('transfer-encoding');
329
+ let body: Uint8Array;
330
+ if (coding !== null) {
331
+ if (coding.split(',').map((each) => each.trim().toLowerCase()).at(-1) !== 'chunked') { this.respondHttp1(new Response('a transfer coding other than chunked is not read', { status: 400 })); return; }
332
+ const read = chunkedBody(this.buffer, end + 4);
333
+ if (read === 'bad') { this.respondHttp1(new Response('bad chunked body', { status: 400 })); return; }
334
+ if (read === 'incomplete') return;
335
+ body = read;
336
+ } else {
337
+ const length = Number(headers.get('content-length') ?? 0);
338
+ if (!Number.isFinite(length) || length < 0 || length > MAX_REQUEST_BYTES) { this.respondHttp1(new Response('bad content-length', { status: 400 })); return; }
339
+ if (this.buffer.length < end + 4 + length) return;
340
+ body = this.buffer.subarray(end + 4, end + 4 + length);
341
+ }
301
342
  this.buffer = Buffer.alloc(0);
302
343
  this.socket.removeAllListeners('data');
303
344
  let request: Request;
package/src/head.ts CHANGED
@@ -461,6 +461,15 @@ async function performNow(opts: Parameters<typeof performEntries>[0]): Promise<D
461
461
  * `auto` is performed now. Returns the vendor's id when it minted one, so the answer carries it.
462
462
  * Raises `RefusedWriteError` / `VendorWriteError` for the pack to answer in the vendor's shape.
463
463
  */
464
+ /** Whether `performAtHead` would perform an entry now (a twin whose root says `auto`, outside a pack fetch's deferred
465
+ * group): a caller appending several writes at once appends them one at a time then, each performed before the next. */
466
+ export function performsAtHeadNow(service: string, root?: string): boolean {
467
+ const request = currentCorrelationId();
468
+ if (request !== undefined && deferred.has(request)) return false;
469
+ const head = headOf(service, root);
470
+ return head.kind === 'real' && head.deploy === 'auto';
471
+ }
472
+
464
473
  export async function performAtHead(service: string, action: Pick<TwinAction, 'id'>, root?: string): Promise<{ performed: boolean; externalId?: string; data?: unknown }> {
465
474
  // a pack fetch performs its request's entries as one group after the handler (performRequestGroup)
466
475
  const request = currentCorrelationId();
package/src/history.ts CHANGED
@@ -91,14 +91,14 @@ export function saveHistoryView(directory: string, view: HistoryView): string {
91
91
  getActiveWorldStore().writeAtomic(file, JSON.stringify({ ...view, dependencies }));
92
92
  for (const dependency of dependencies) commitParentPin(dependency.directory, file);
93
93
  return id;
94
- });
94
+ }, { operation: 'view pin publication' });
95
95
  }
96
96
  /** What an inherited history's content stands on, as file facts: every segment file the view names
97
97
  * (and the view document itself) and each named directory's ancestry record (its generation). A read cache keyed by this sees an
98
98
  * ancestor's retained history truncated, replaced or its directory re-created, which the full read
99
99
  * refuses (`historyEntries`), instead of serving a tree folded from the history it replaced. */
100
- export function inheritedHistoryKey(service: string, root?: string): string {
101
- const parent = readBranchMeta(service, root)?.parent;
100
+ export function inheritedHistoryKey(service: string, root?: string, meta?: string | null): string {
101
+ const parent = readBranchMeta(service, root, meta)?.parent;
102
102
  if (!parent?.view) return '';
103
103
  const store = getActiveWorldStore();
104
104
  const fact = (path: string): string => { const st = store.stat(path); return st ? `${st.size}:${st.mtimeMs}` : '-'; };
@@ -136,7 +136,7 @@ export function captureHistory(service: string, root?: string): { view: string;
136
136
  if (entries.length) descriptor.segments.push({ directory, generation, file, start: 0, count: entries.length, digest: historyDigest(entries) });
137
137
  }
138
138
  return { view: saveHistoryView(directory, descriptor), position: historyLength(descriptor.layout), descriptor };
139
- });
139
+ }, { operation: 'capture' });
140
140
  }
141
141
  export function originHead(service: string, root?: string): { view: string; remoteView: string } | null {
142
142
  const raw = getActiveWorldStore().read(join(worldPaths(service, root).dir, 'origin-head.json'));
@@ -162,7 +162,7 @@ export function publishOriginHistory(service: string, root: string, remoteView:
162
162
  const view = saveHistoryView(directory, { version: 1, owner: { directory, generation }, layout, segments });
163
163
  store.writeAtomic(join(directory, 'origin-head.json'), JSON.stringify({ view, remoteView }));
164
164
  return { view, appended };
165
- });
165
+ }, { operation: 'origin fetch' });
166
166
  }
167
167
 
168
168
  /** A changeset binds the complete inherited view, including this root's observations. */
@@ -171,7 +171,7 @@ export function captureParentHistory(service: string, root?: string): HistoryRef
171
171
  const head = captureHistory(service, root);
172
172
  const position = head.position - head.descriptor.layout.branch;
173
173
  return { view: saveHistoryView(worldPaths(service, root).dir, historyPrefix(head.descriptor, position)), position };
174
- });
174
+ }, { operation: 'capture' });
175
175
  }
176
176
  /** Compare evaluated views, so removals, reordered rows and layout changes count as drift. */
177
177
  export function historyChanges(service: string, before: HistoryView, after: HistoryView): Entry[] {
@@ -222,7 +222,7 @@ export function historyAtInstant(service: string, instant: string, root?: string
222
222
  }
223
223
  const view = saveHistoryView(directory, { version: 1, owner: source.owner, layout, segments, ...(source.origin ? { origin: { ...source.origin, ...(keep.slice(0, source.origin.position).some(v => !v) ? { incomplete: true } : {}) } } : {}) });
224
224
  return { view, position: historyLength(layout) };
225
- });
225
+ }, { operation: 'capture' });
226
226
  }
227
227
 
228
228
  /** Replace only the tracked origin layer; inherited local layers keep their exact projection order. */
package/src/index.ts CHANGED
@@ -53,7 +53,7 @@ export { VendorUnreachableError } from './vendor-call.ts';
53
53
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.ts';
54
54
 
55
55
  // ── the serve factory ──────────────────────────────────────────────────────────────────────────
56
- export { serveHttp, serveStream, StreamListenerUnavailable, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.ts';
56
+ export { serveHttp, serveStream, nodeBuiltin, StreamListenerUnavailable, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.ts';
57
57
  export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.ts';
58
58
  export { privateTransportKey } from './private-transport.ts';
59
59
  // where a page links a hostname its twin answers, inside the World that shows it
@@ -79,6 +79,8 @@ export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatemen
79
79
  export type { Mail, SmtpRoute } from './smtp.ts';
80
80
  // S3's wire (contract "S3's wire is a kernel library too") and the git plane (contract "The git plane is a kernel library")
81
81
  export * as s3 from './s3/wire.ts';
82
+ // Generic strict XML grammar; vendor interpretation remains in its consumer.
83
+ export * as xml from './xml.ts';
82
84
  export * as clickhouse from './clickhouse/index.ts';
83
85
  export { sampleFromSchema } from './schema-sample.ts';
84
86
  export * as openaiWire from './openai-wire.ts';
@@ -91,6 +93,7 @@ export { presignSigV4, signSigV4, verifySigV4, type SigV4Verdict } from './sigv4
91
93
  export * as redis from './redis/index.ts';
92
94
  export * as mysql from './mysql/index.ts';
93
95
  export * as live from './live/index.ts';
96
+ export * as mongodb from './mongodb/index.ts';
94
97
  export * as git from './git/index.ts';
95
98
 
96
99
  // ── the scenario grammar (architecture, "Behaviour") ───────────────────────────────────────────
@@ -103,3 +106,5 @@ export { browserSessionToken, startBrowserSession, endBrowserSession } from './b
103
106
 
104
107
  export { readHostPort } from './host-port.ts';
105
108
  export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost, type BrowserCookie, type BrowserState, type BrowserStorageState, type BrowserContext, type BrowserIndexedDB } from './browser-state.ts';
109
+
110
+ export * as managedOriginalInputs from './managed-original-inputs.ts';