@rotorsoft/act-tck 1.39.2 → 1.41.0

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.
@@ -4,7 +4,8 @@
4
4
  * @property name - Display name for the snapshot suite (e.g., the package
5
5
  * name). Used as the `describe` block label so failing snapshots are
6
6
  * attributable at a glance.
7
- * @property entryPoints - Map of subpath → entry source file. The TCK
7
+ * @property entryPoints - Map of subpath → entry file, either source
8
+ * (`.ts`) or a published declaration (`.d.ts`). The TCK
8
9
  * reads each entry, follows its relative re-exports recursively, and
9
10
  * snapshots the resulting surface text. Empty-string subpaths label
10
11
  * the root entry; everything else is rendered with its key (e.g.
@@ -15,8 +16,8 @@ export type StabilityTckOptions = {
15
16
  readonly entryPoints: Readonly<Record<string, string>>;
16
17
  };
17
18
  /**
18
- * Snapshot-based public-API stability check. Walks each entry's source
19
- * file plus everything it re-exports through relative imports, and
19
+ * Snapshot-based public-API stability check. Walks each entry file plus
20
+ * everything it re-exports through relative imports, and
20
21
  * compares the concatenated text against a committed Vitest snapshot.
21
22
  *
22
23
  * Catches accidental rename / removal / signature drift on the public
@@ -24,9 +25,13 @@ export type StabilityTckOptions = {
24
25
  * shows up as a snapshot diff in the PR; reviewers either accept the
25
26
  * change (update the snapshot deliberately) or push back.
26
27
  *
27
- * The TCK reads source (`.ts`) rather than built declarations so it
28
- * doesn't need a prior `pnpm build`. It follows `from "./relative.js"`
29
- * re-exports transitively; non-relative paths (other packages, `node:`)
28
+ * Point it at the built declarations (`.d.ts`) to record the surface
29
+ * alone: a change to implementation then leaves the snapshot alone, and
30
+ * doc comments still show. That needs a build first. Point it at source
31
+ * (`.ts`) to skip the build, at the cost of a snapshot that also moves
32
+ * with every implementation change. It follows `from "./relative.js"`
33
+ * re-exports transitively, to `.d.ts` from a declaration and to `.ts`
34
+ * from source; non-relative paths (other packages, `node:`)
30
35
  * stop the walk — drift in another package's surface is that package's
31
36
  * stability TCK to catch.
32
37
  *
@@ -1 +1 @@
1
- {"version":3,"file":"stability-tck.d.ts","sourceRoot":"","sources":["../../src/stability-tck.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAUlE"}
1
+ {"version":3,"file":"stability-tck.d.ts","sourceRoot":"","sources":["../../src/stability-tck.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAUlE"}
@@ -1 +1 @@
1
- {"version":3,"file":"store-tck.d.ts","sourceRoot":"","sources":["../../src/store-tck.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAQV,KAAK,EAIN,MAAM,sBAAsB,CAAC;AAe9B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,OAAO,CAAC;IACpD;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACxD;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,WAAW,YAAa,eAAe,KAAG,IAmiLtD,CAAC"}
1
+ {"version":3,"file":"store-tck.d.ts","sourceRoot":"","sources":["../../src/store-tck.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAUV,KAAK,EAIN,MAAM,sBAAsB,CAAC;AAe9B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,OAAO,CAAC;IACpD;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACxD;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,WAAW,YAAa,eAAe,KAAG,IAiwLtD,CAAC"}
package/dist/index.cjs CHANGED
@@ -418,8 +418,9 @@ async function load_surface(entry2) {
418
418
  const content = await import_node_fs.promises.readFile(abs, "utf8");
419
419
  visited.set(abs, content);
420
420
  const re_export = /^\s*export\b[^"';]*\bfrom\s+["'](\.[^"']+)["']/gm;
421
+ const ext = abs.endsWith(".d.ts") ? ".d.ts" : ".ts";
421
422
  for (const m of content.matchAll(re_export)) {
422
- const rel = m[1].replace(/\.js$/, ".ts");
423
+ const rel = m[1].replace(/\.js$/, ext);
423
424
  const target = import_node_path.default.resolve(import_node_path.default.dirname(abs), rel);
424
425
  await walk(target);
425
426
  }
@@ -4590,6 +4591,191 @@ var runStoreTck = (options) => {
4590
4591
  (0, import_vitest9.expect)(positions).toEqual([s]);
4591
4592
  });
4592
4593
  });
4594
+ (0, import_vitest9.describe)("schema-driven dates on read", () => {
4595
+ const actor2 = { id: "reader", name: "Reader" };
4596
+ const reader = (shape) => {
4597
+ const Happened = import_zod2.z.object(shape);
4598
+ return (0, import_act2.state)({ Reader: import_zod2.z.object({ n: import_zod2.z.number() }) }).init(() => ({ n: 0 })).emits({ Happened }).patch({ Happened: (_, s) => ({ n: s.n + 1 }) }).on({ Do: Happened }).emit((p) => ["Happened", p]).build();
4599
+ };
4600
+ const over = async (S) => {
4601
+ const fresh = await options.factory();
4602
+ await fresh.seed();
4603
+ const cache = new import_act2.InMemoryCache();
4604
+ const app = (0, import_act2.act)().withState(S).build({ scoped: { store: fresh, cache } });
4605
+ const stream = `dt-${uid()}`;
4606
+ const read = async () => (await app.query_array({ stream, stream_exact: true }))[0].data;
4607
+ const done = async () => {
4608
+ await app.shutdown();
4609
+ await cache.dispose();
4610
+ await fresh.dispose();
4611
+ };
4612
+ return { app, fresh, cache, stream, read, done };
4613
+ };
4614
+ const raw = { correlation: "c", causation: {} };
4615
+ (0, import_vitest9.it)("types z.date() and leaves an ISO-shaped z.string() alone, warm and cold", async () => {
4616
+ const at = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
4617
+ const label = "2026-06-06T00:00:00.000Z";
4618
+ const S = reader({ at: import_zod2.z.date(), label: import_zod2.z.string() });
4619
+ const t = await over(S);
4620
+ try {
4621
+ await t.app.do("Do", { stream: t.stream, actor: actor2 }, { at, label });
4622
+ const warm = await t.read();
4623
+ await t.cache.clear();
4624
+ const cold = await t.read();
4625
+ for (const d of [warm, cold]) {
4626
+ (0, import_vitest9.expect)(d.at).toBeInstanceOf(Date);
4627
+ (0, import_vitest9.expect)(d.at.getTime()).toBe(at.getTime());
4628
+ (0, import_vitest9.expect)(d.label).toBe(label);
4629
+ }
4630
+ } finally {
4631
+ await t.done();
4632
+ }
4633
+ });
4634
+ (0, import_vitest9.it)("revives a date through every wrapper the rebuild knows", async () => {
4635
+ const S = reader({
4636
+ a: import_zod2.z.date(),
4637
+ b: import_zod2.z.date().default(/* @__PURE__ */ new Date(0)),
4638
+ t: import_zod2.z.tuple([import_zod2.z.date(), import_zod2.z.string()]),
4639
+ td: import_zod2.z.tuple([import_zod2.z.string(), import_zod2.z.string()]),
4640
+ r: import_zod2.z.date().readonly(),
4641
+ c: import_zod2.z.date().catch(/* @__PURE__ */ new Date(0)),
4642
+ n: import_zod2.z.date().optional().nonoptional(),
4643
+ p: import_zod2.z.date().prefault(/* @__PURE__ */ new Date(0)),
4644
+ u: import_zod2.z.union([import_zod2.z.date(), import_zod2.z.string()]),
4645
+ l: import_zod2.z.array(import_zod2.z.date()),
4646
+ m: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.date()),
4647
+ o: import_zod2.z.object({ deep: import_zod2.z.date() }),
4648
+ x: import_zod2.z.date().nullable()
4649
+ });
4650
+ const t = await over(S);
4651
+ try {
4652
+ const when = /* @__PURE__ */ new Date("2022-01-01");
4653
+ await t.app.do(
4654
+ "Do",
4655
+ { stream: t.stream, actor: actor2 },
4656
+ {
4657
+ a: /* @__PURE__ */ new Date("2020-01-01"),
4658
+ b: /* @__PURE__ */ new Date("2021-01-01"),
4659
+ t: [when, "x"],
4660
+ td: ["no", "dates"],
4661
+ r: when,
4662
+ c: when,
4663
+ n: when,
4664
+ p: when,
4665
+ u: when,
4666
+ l: [when],
4667
+ m: { k: when },
4668
+ o: { deep: when },
4669
+ x: null
4670
+ }
4671
+ );
4672
+ const d = await t.read();
4673
+ for (const key of ["a", "b", "r", "c", "n", "p", "u"])
4674
+ (0, import_vitest9.expect)(d[key], key).toBeInstanceOf(Date);
4675
+ (0, import_vitest9.expect)(d.t[0]).toBeInstanceOf(Date);
4676
+ (0, import_vitest9.expect)(d.t[1]).toBe("x");
4677
+ (0, import_vitest9.expect)(d.td).toEqual(["no", "dates"]);
4678
+ (0, import_vitest9.expect)(d.l[0]).toBeInstanceOf(Date);
4679
+ (0, import_vitest9.expect)(d.m.k).toBeInstanceOf(Date);
4680
+ (0, import_vitest9.expect)(d.o.deep).toBeInstanceOf(Date);
4681
+ (0, import_vitest9.expect)(d.x).toBeNull();
4682
+ } finally {
4683
+ await t.done();
4684
+ }
4685
+ });
4686
+ (0, import_vitest9.it)("revives dates in a payload that predates the declaration", async () => {
4687
+ const S = reader({ at: import_zod2.z.date(), label: import_zod2.z.string() });
4688
+ const t = await over(S);
4689
+ try {
4690
+ await t.fresh.commit(
4691
+ t.stream,
4692
+ [{ name: "Happened", data: { at: /* @__PURE__ */ new Date("2020-01-01") } }],
4693
+ raw,
4694
+ -1
4695
+ );
4696
+ const d = await t.read();
4697
+ (0, import_vitest9.expect)(d.label).toBeUndefined();
4698
+ (0, import_vitest9.expect)(d.at).toBeInstanceOf(Date);
4699
+ } finally {
4700
+ await t.done();
4701
+ }
4702
+ });
4703
+ (0, import_vitest9.it)("hands back what is stored when a date field holds something else", async () => {
4704
+ const S = reader({ at: import_zod2.z.date() });
4705
+ const t = await over(S);
4706
+ try {
4707
+ await t.fresh.commit(
4708
+ t.stream,
4709
+ [{ name: "Happened", data: { at: "not a date" } }],
4710
+ raw,
4711
+ -1
4712
+ );
4713
+ (0, import_vitest9.expect)((await t.read()).at).toBe("not a date");
4714
+ } finally {
4715
+ await t.done();
4716
+ }
4717
+ });
4718
+ import_vitest9.describe.skipIf(!caps.pii_isolation)("with sensitive fields", () => {
4719
+ (0, import_vitest9.it)("reads a redacted payload whose required sensitive field lives in pii", async () => {
4720
+ const S = reader({ at: import_zod2.z.date(), email: (0, import_act2.sensitive)(import_zod2.z.string()) });
4721
+ const t = await over(S);
4722
+ try {
4723
+ const target = { stream: t.stream, actor: actor2 };
4724
+ await t.app.do("Do", target, { at: /* @__PURE__ */ new Date(0), email: "u@x.io" });
4725
+ const d = await t.read();
4726
+ (0, import_vitest9.expect)(d.at).toBeInstanceOf(Date);
4727
+ (0, import_vitest9.expect)(d.email).toBe("[REDACTED]");
4728
+ (0, import_vitest9.expect)((await t.app.load(S, t.stream)).state.n).toBe(1);
4729
+ await t.app.do("Do", target, { at: /* @__PURE__ */ new Date(1), email: "v@x.io" });
4730
+ } finally {
4731
+ await t.done();
4732
+ }
4733
+ });
4734
+ (0, import_vitest9.it)("keeps a redacted sensitive date's sentinel", async () => {
4735
+ const S = reader({ at: import_zod2.z.date(), born: (0, import_act2.sensitive)(import_zod2.z.date()) });
4736
+ const t = await over(S);
4737
+ try {
4738
+ await t.app.do(
4739
+ "Do",
4740
+ { stream: t.stream, actor: actor2 },
4741
+ { at: /* @__PURE__ */ new Date(0), born: /* @__PURE__ */ new Date(1) }
4742
+ );
4743
+ const d = await t.read();
4744
+ (0, import_vitest9.expect)(d.at).toBeInstanceOf(Date);
4745
+ (0, import_vitest9.expect)(d.born).toBe("[REDACTED]");
4746
+ } finally {
4747
+ await t.done();
4748
+ }
4749
+ });
4750
+ (0, import_vitest9.it)("hands a disclosed sensitive date to the reducer as a Date", async () => {
4751
+ const seen = {};
4752
+ const Happened = import_zod2.z.object({
4753
+ at: import_zod2.z.date(),
4754
+ born: (0, import_act2.sensitive)(import_zod2.z.date())
4755
+ });
4756
+ const S = (0, import_act2.state)({ Disclosed: import_zod2.z.object({ n: import_zod2.z.number() }) }).init(() => ({ n: 0 })).emits({ Happened }).patch({
4757
+ Happened: ({ data }, s) => {
4758
+ seen.at = data.at instanceof Date;
4759
+ seen.born = data.born instanceof Date;
4760
+ return { n: s.n + 1 };
4761
+ }
4762
+ }).on({ Do: Happened }).emit((p) => ["Happened", p]).discloses(() => true).build();
4763
+ const t = await over(S);
4764
+ try {
4765
+ await t.app.do(
4766
+ "Do",
4767
+ { stream: t.stream, actor: actor2 },
4768
+ { at: /* @__PURE__ */ new Date(0), born: /* @__PURE__ */ new Date(1) }
4769
+ );
4770
+ await t.cache.clear();
4771
+ await t.app.load(S, { stream: t.stream, actor: actor2 });
4772
+ (0, import_vitest9.expect)(seen).toEqual({ at: true, born: true });
4773
+ } finally {
4774
+ await t.done();
4775
+ }
4776
+ });
4777
+ });
4778
+ });
4593
4779
  (0, import_vitest9.describe)("autoclose end-to-end", () => {
4594
4780
  const Ticket = (0, import_act2.state)({ TckTicket: import_zod2.z.object({ open: import_zod2.z.boolean() }) }).init(() => ({ open: false })).emits({
4595
4781
  TckTicketOpened: import_zod2.z.object({ title: import_zod2.z.string() }),