@volter/twin-standard 1.0.83 → 1.0.85

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.
@@ -27,6 +27,7 @@ import { basename, dirname, extname, join, matchesGlob, relative, resolve } from
27
27
  import { citations, sourceHeld } from "./check-sources.js";
28
28
  import { fileURLToPath } from 'node:url';
29
29
  import { servedHttpFetch } from "./clients/harness.js";
30
+ import { servePack as serve } from "./walk/serve-pack.js";
30
31
  const pascal = (s) => s.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
31
32
  const readJson = (p) => (existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : undefined);
32
33
  /** The pack's units: the pack and each lane in it (a directory with its own src/manifest.ts and no package.json). */
@@ -268,15 +269,6 @@ async function allowance(pack, runtime) {
268
269
  }
269
270
  return out;
270
271
  }
271
- /** The pack served on its own, as a World's process or an application's test serves it: its server.ts, from a scratch
272
- * working directory (a server keeps its World under it), with the options given. */
273
- async function serve(pack, options) {
274
- const mod = (await import(__rewriteRelativeImportExtension(join(pack, 'src', 'server.ts'))));
275
- const create = Object.entries(mod).find(([k, v]) => /^create\w+TwinServer$/.test(k) && typeof v === 'function')?.[1];
276
- if (!create)
277
- throw new Error('src/server.ts exports no create<Vendor>TwinServer');
278
- return create({ port: 0, ...options });
279
- }
280
272
  const within = (ms, p) => Promise.race([p, new Promise((r) => setTimeout(() => r('timeout'), ms))]);
281
273
  /** Served by its server.ts as a World runs it (its boot id set), the pack answers HTTP and the boot probe, and every socket
282
274
  * its manifests declare takes an upgrade. Escaped twice: serveHttp never took the upgrade a pack's fetch carries, then
@@ -321,7 +313,7 @@ async function standalone(pack) {
321
313
  }
322
314
  }
323
315
  finally {
324
- server.stop();
316
+ await server.stop();
325
317
  }
326
318
  return out;
327
319
  }
@@ -383,7 +375,8 @@ async function lifeServed(pack) {
383
375
  const http = servedHttpFetch(port);
384
376
  return {
385
377
  // the vendor's URL as the application called it, sent to the served pack: its host carried in the Host header
386
- fetch: async (request) => {
378
+ fetch: Object.assign(async (request) => {
379
+ await server.managedDatabaseUrl?.();
387
380
  const abort = new AbortController();
388
381
  try {
389
382
  const answer = await within(10_000, http(request, { signal: abort.signal }));
@@ -392,7 +385,7 @@ async function lifeServed(pack) {
392
385
  finally {
393
386
  abort.abort();
394
387
  }
395
- },
388
+ }, server.managedDatabaseUrl ? { managedDatabaseUrl: server.managedDatabaseUrl } : {}),
396
389
  // a socket opened as an application opens it: a WebSocket to the served port, the vendor's host in its Host header
397
390
  socket: (url, headers) => new WebSocket(`ws://127.0.0.1:${port}${url}`, { headers }),
398
391
  // a line connection opened as an application's client opens it: TCP to the protocol listener (the primary port)
@@ -477,7 +470,7 @@ async function clientCases(pack, scratch) {
477
470
  return { failures: results.flatMap((r) => r.failures.map((f) => `${r.id} (${r.asks}): ${f}`)), note: `${results.length} case(s) through the vendor's client` };
478
471
  }
479
472
  finally {
480
- server.stop();
473
+ await server.stop();
481
474
  }
482
475
  }
483
476
  /** Every check over the pack at `dir`, in order. */
@@ -1174,11 +1174,15 @@ export async function walk(journey, root, collect, opts) {
1174
1174
  await Promise.allSettled([...pending.values()].map((request) => request.answer));
1175
1175
  setDeliveryObserver(previousObserver);
1176
1176
  setInProcessTwins(previousTwins);
1177
- await served?.stop();
1178
- if (sqlSession)
1179
- await sqlSession.then((session) => session.end(), () => { });
1180
- // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
1181
- await twin.stopDatabase?.();
1177
+ try {
1178
+ if (sqlSession)
1179
+ await sqlSession.then((session) => session.end(), () => { });
1180
+ }
1181
+ finally {
1182
+ await served?.stop();
1183
+ // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
1184
+ await twin.stopDatabase?.();
1185
+ }
1182
1186
  if (clockFile && startedAt !== undefined)
1183
1187
  store.write(clockFile, startedAt);
1184
1188
  }
@@ -0,0 +1,7 @@
1
+ export type ServedPack = {
2
+ port: number;
3
+ inspectPort?: number;
4
+ stop: () => void | Promise<void>;
5
+ managedDatabaseUrl?: () => Promise<string>;
6
+ };
7
+ export declare function servePack(pack: string, options: Record<string, unknown>): Promise<ServedPack>;
@@ -0,0 +1,39 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ // The standard serves a pack with its declared backing, as the runtime binds it in a World.
10
+ import { join } from 'node:path';
11
+ import { worldNow } from '@volter/world-core/runtime';
12
+ import { startPgliteHost } from "./walk-database.js";
13
+ export async function servePack(pack, options) {
14
+ const mod = await import(__rewriteRelativeImportExtension(join(pack, 'src', 'server.ts')));
15
+ const create = Object.entries(mod).find(([name, value]) => /^create\w+TwinServer$/.test(name) && typeof value === 'function')?.[1];
16
+ if (!create)
17
+ throw new Error('src/server.ts exports no create<Vendor>TwinServer');
18
+ const descriptor = (await import(__rewriteRelativeImportExtension(join(pack, 'src', 'index.ts')))).pack;
19
+ let database;
20
+ if (descriptor?.managedDatabase?.kind === 'postgres' && options.database === undefined)
21
+ database = await startPgliteHost();
22
+ let server;
23
+ try {
24
+ server = await create({ port: 0, ...options, ...(database ? { database: database.url } : {}) });
25
+ }
26
+ catch (error) {
27
+ await database?.stop();
28
+ throw error;
29
+ }
30
+ if (!database)
31
+ return server;
32
+ const backing = database;
33
+ let stopping;
34
+ return {
35
+ ...server,
36
+ managedDatabaseUrl: async () => { backing.clock(worldNow()); return backing.url; },
37
+ stop: () => stopping ??= (async () => { await server.stop(); await backing.stop(); })(),
38
+ };
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.91",
78
+ "@volter/world-core": "3.0.93",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
@@ -20,6 +20,7 @@ import type { TwinStream } from '@volter/world-core';
20
20
  import { citations, sourceHeld, type SourceRecord } from './check-sources.ts';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { servedHttpFetch } from './clients/harness.ts';
23
+ import { servePack as serve, type ServedPack as Served } from './walk/serve-pack.ts';
23
24
 
24
25
  export type ConformanceResult = { id: string; asks: string; failures: string[]; note?: string };
25
26
 
@@ -247,16 +248,6 @@ type Runtime = {
247
248
  getPack: (vendor: string) => { stateSystem?: { perform?: unknown; refresh?: unknown } } | undefined;
248
249
  DEFAULT_RATE_BUDGET: NonNullable<Manifest['rateBudget']>;
249
250
  };
250
- type Served = { port: number; inspectPort?: number; stop: () => void };
251
-
252
- /** The pack served on its own, as a World's process or an application's test serves it: its server.ts, from a scratch
253
- * working directory (a server keeps its World under it), with the options given. */
254
- async function serve(pack: string, options: Record<string, unknown>): Promise<Served> {
255
- const mod = (await import(join(pack, 'src', 'server.ts'))) as Record<string, unknown>;
256
- const create = Object.entries(mod).find(([k, v]) => /^create\w+TwinServer$/.test(k) && typeof v === 'function')?.[1] as ((o: Record<string, unknown>) => Promise<Served>) | undefined;
257
- if (!create) throw new Error('src/server.ts exports no create<Vendor>TwinServer');
258
- return create({ port: 0, ...options });
259
- }
260
251
  const within = <T>(ms: number, p: Promise<T>): Promise<T | 'timeout'> => Promise.race([p, new Promise<'timeout'>((r) => setTimeout(() => r('timeout'), ms))]);
261
252
 
262
253
  /** Served by its server.ts as a World runs it (its boot id set), the pack answers HTTP and the boot probe, and every socket
@@ -287,7 +278,7 @@ async function standalone(pack: string): Promise<string[]> {
287
278
  });
288
279
  if (upgraded) out.push(`${s.unit}: the socket at ${s.host ?? ''}${s.path}: ${upgraded}`);
289
280
  }
290
- } finally { server.stop(); }
281
+ } finally { await server.stop(); }
291
282
  return out;
292
283
  }
293
284
 
@@ -338,13 +329,14 @@ async function lifeServed(pack: string): Promise<string[]> {
338
329
  const http = servedHttpFetch(port);
339
330
  return {
340
331
  // the vendor's URL as the application called it, sent to the served pack: its host carried in the Host header
341
- fetch: async (request: Request): Promise<Response> => {
332
+ fetch: Object.assign(async (request: Request): Promise<Response> => {
333
+ await server.managedDatabaseUrl?.();
342
334
  const abort = new AbortController();
343
335
  try {
344
336
  const answer = await within(10_000, http(request, { signal: abort.signal }));
345
337
  return answer === 'timeout' ? new Response(`no answer in 10 s from the served pack (${request.method} ${new URL(request.url).pathname})`, { status: 599 }) : answer;
346
338
  } finally { abort.abort(); }
347
- },
339
+ }, server.managedDatabaseUrl ? { managedDatabaseUrl: server.managedDatabaseUrl } : {}),
348
340
  // a socket opened as an application opens it: a WebSocket to the served port, the vendor's host in its Host header
349
341
  socket: (url: string, headers: Record<string, string>) => new WebSocket(`ws://127.0.0.1:${port}${url}`, { headers } as unknown as string[]),
350
342
  // a line connection opened as an application's client opens it: TCP to the protocol listener (the primary port)
@@ -413,7 +405,7 @@ async function clientCases(pack: string, scratch: string): Promise<{ failures: s
413
405
  });
414
406
  const results = out.split('\n').filter((l) => l.startsWith('{')).map((l) => JSON.parse(l) as { id: string; asks: string; failures: string[] });
415
407
  return { failures: results.flatMap((r) => r.failures.map((f) => `${r.id} (${r.asks}): ${f}`)), note: `${results.length} case(s) through the vendor's client` };
416
- } finally { server.stop(); }
408
+ } finally { await server.stop(); }
417
409
  }
418
410
 
419
411
  /** Every check over the pack at `dir`, in order. */
@@ -819,10 +819,13 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
819
819
  await Promise.allSettled([...pending.values()].map((request) => request.answer));
820
820
  setDeliveryObserver(previousObserver);
821
821
  setInProcessTwins(previousTwins);
822
- await served?.stop();
823
- if (sqlSession) await sqlSession.then((session) => session.end(), () => {});
824
- // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
825
- await (twin as { stopDatabase?: () => Promise<void> }).stopDatabase?.();
822
+ try {
823
+ if (sqlSession) await sqlSession.then((session) => session.end(), () => {});
824
+ } finally {
825
+ await served?.stop();
826
+ // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
827
+ await (twin as { stopDatabase?: () => Promise<void> }).stopDatabase?.();
828
+ }
826
829
  if (clockFile && startedAt !== undefined) store.write(clockFile, startedAt);
827
830
  }
828
831
  }
@@ -0,0 +1,31 @@
1
+ // The standard serves a pack with its declared backing, as the runtime binds it in a World.
2
+ import { join } from 'node:path';
3
+ import { worldNow } from '@volter/world-core/runtime';
4
+ import { startPgliteHost, type PgliteHost } from './walk-database.ts';
5
+
6
+ export type ServedPack = {
7
+ port: number;
8
+ inspectPort?: number;
9
+ stop: () => void | Promise<void>;
10
+ managedDatabaseUrl?: () => Promise<string>;
11
+ };
12
+
13
+ export async function servePack(pack: string, options: Record<string, unknown>): Promise<ServedPack> {
14
+ const mod = await import(join(pack, 'src', 'server.ts')) as Record<string, unknown>;
15
+ const create = Object.entries(mod).find(([name, value]) => /^create\w+TwinServer$/.test(name) && typeof value === 'function')?.[1] as ((options: Record<string, unknown>) => Promise<ServedPack>) | undefined;
16
+ if (!create) throw new Error('src/server.ts exports no create<Vendor>TwinServer');
17
+ const descriptor = (await import(join(pack, 'src', 'index.ts'))).pack as { managedDatabase?: { kind: string } } | undefined;
18
+ let database: PgliteHost | undefined;
19
+ if (descriptor?.managedDatabase?.kind === 'postgres' && options.database === undefined) database = await startPgliteHost();
20
+ let server: ServedPack;
21
+ try { server = await create({ port: 0, ...options, ...(database ? { database: database.url } : {}) }); }
22
+ catch (error) { await database?.stop(); throw error; }
23
+ if (!database) return server;
24
+ const backing = database;
25
+ let stopping: Promise<void> | undefined;
26
+ return {
27
+ ...server,
28
+ managedDatabaseUrl: async () => { backing.clock(worldNow()); return backing.url; },
29
+ stop: () => stopping ??= (async () => { await server.stop(); await backing.stop(); })(),
30
+ };
31
+ }