@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.
package/dist/index.js CHANGED
@@ -364,8 +364,9 @@ async function load_surface(entry2) {
364
364
  const content = await fs.readFile(abs, "utf8");
365
365
  visited.set(abs, content);
366
366
  const re_export = /^\s*export\b[^"';]*\bfrom\s+["'](\.[^"']+)["']/gm;
367
+ const ext = abs.endsWith(".d.ts") ? ".d.ts" : ".ts";
367
368
  for (const m of content.matchAll(re_export)) {
368
- const rel = m[1].replace(/\.js$/, ".ts");
369
+ const rel = m[1].replace(/\.js$/, ext);
369
370
  const target = path.resolve(path.dirname(abs), rel);
370
371
  await walk(target);
371
372
  }
@@ -1098,6 +1099,7 @@ import {
1098
1099
  ConcurrencyError,
1099
1100
  InMemoryCache,
1100
1101
  SNAP_EVENT as SNAP_EVENT2,
1102
+ sensitive,
1101
1103
  sleep,
1102
1104
  state,
1103
1105
  TOMBSTONE_EVENT,
@@ -4545,6 +4547,191 @@ var runStoreTck = (options) => {
4545
4547
  expect8(positions).toEqual([s]);
4546
4548
  });
4547
4549
  });
4550
+ describe8("schema-driven dates on read", () => {
4551
+ const actor2 = { id: "reader", name: "Reader" };
4552
+ const reader = (shape) => {
4553
+ const Happened = z2.object(shape);
4554
+ return state({ Reader: z2.object({ n: z2.number() }) }).init(() => ({ n: 0 })).emits({ Happened }).patch({ Happened: (_, s) => ({ n: s.n + 1 }) }).on({ Do: Happened }).emit((p) => ["Happened", p]).build();
4555
+ };
4556
+ const over = async (S) => {
4557
+ const fresh = await options.factory();
4558
+ await fresh.seed();
4559
+ const cache = new InMemoryCache();
4560
+ const app = act().withState(S).build({ scoped: { store: fresh, cache } });
4561
+ const stream = `dt-${uid()}`;
4562
+ const read = async () => (await app.query_array({ stream, stream_exact: true }))[0].data;
4563
+ const done = async () => {
4564
+ await app.shutdown();
4565
+ await cache.dispose();
4566
+ await fresh.dispose();
4567
+ };
4568
+ return { app, fresh, cache, stream, read, done };
4569
+ };
4570
+ const raw = { correlation: "c", causation: {} };
4571
+ it7("types z.date() and leaves an ISO-shaped z.string() alone, warm and cold", async () => {
4572
+ const at = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
4573
+ const label = "2026-06-06T00:00:00.000Z";
4574
+ const S = reader({ at: z2.date(), label: z2.string() });
4575
+ const t = await over(S);
4576
+ try {
4577
+ await t.app.do("Do", { stream: t.stream, actor: actor2 }, { at, label });
4578
+ const warm = await t.read();
4579
+ await t.cache.clear();
4580
+ const cold = await t.read();
4581
+ for (const d of [warm, cold]) {
4582
+ expect8(d.at).toBeInstanceOf(Date);
4583
+ expect8(d.at.getTime()).toBe(at.getTime());
4584
+ expect8(d.label).toBe(label);
4585
+ }
4586
+ } finally {
4587
+ await t.done();
4588
+ }
4589
+ });
4590
+ it7("revives a date through every wrapper the rebuild knows", async () => {
4591
+ const S = reader({
4592
+ a: z2.date(),
4593
+ b: z2.date().default(/* @__PURE__ */ new Date(0)),
4594
+ t: z2.tuple([z2.date(), z2.string()]),
4595
+ td: z2.tuple([z2.string(), z2.string()]),
4596
+ r: z2.date().readonly(),
4597
+ c: z2.date().catch(/* @__PURE__ */ new Date(0)),
4598
+ n: z2.date().optional().nonoptional(),
4599
+ p: z2.date().prefault(/* @__PURE__ */ new Date(0)),
4600
+ u: z2.union([z2.date(), z2.string()]),
4601
+ l: z2.array(z2.date()),
4602
+ m: z2.record(z2.string(), z2.date()),
4603
+ o: z2.object({ deep: z2.date() }),
4604
+ x: z2.date().nullable()
4605
+ });
4606
+ const t = await over(S);
4607
+ try {
4608
+ const when = /* @__PURE__ */ new Date("2022-01-01");
4609
+ await t.app.do(
4610
+ "Do",
4611
+ { stream: t.stream, actor: actor2 },
4612
+ {
4613
+ a: /* @__PURE__ */ new Date("2020-01-01"),
4614
+ b: /* @__PURE__ */ new Date("2021-01-01"),
4615
+ t: [when, "x"],
4616
+ td: ["no", "dates"],
4617
+ r: when,
4618
+ c: when,
4619
+ n: when,
4620
+ p: when,
4621
+ u: when,
4622
+ l: [when],
4623
+ m: { k: when },
4624
+ o: { deep: when },
4625
+ x: null
4626
+ }
4627
+ );
4628
+ const d = await t.read();
4629
+ for (const key of ["a", "b", "r", "c", "n", "p", "u"])
4630
+ expect8(d[key], key).toBeInstanceOf(Date);
4631
+ expect8(d.t[0]).toBeInstanceOf(Date);
4632
+ expect8(d.t[1]).toBe("x");
4633
+ expect8(d.td).toEqual(["no", "dates"]);
4634
+ expect8(d.l[0]).toBeInstanceOf(Date);
4635
+ expect8(d.m.k).toBeInstanceOf(Date);
4636
+ expect8(d.o.deep).toBeInstanceOf(Date);
4637
+ expect8(d.x).toBeNull();
4638
+ } finally {
4639
+ await t.done();
4640
+ }
4641
+ });
4642
+ it7("revives dates in a payload that predates the declaration", async () => {
4643
+ const S = reader({ at: z2.date(), label: z2.string() });
4644
+ const t = await over(S);
4645
+ try {
4646
+ await t.fresh.commit(
4647
+ t.stream,
4648
+ [{ name: "Happened", data: { at: /* @__PURE__ */ new Date("2020-01-01") } }],
4649
+ raw,
4650
+ -1
4651
+ );
4652
+ const d = await t.read();
4653
+ expect8(d.label).toBeUndefined();
4654
+ expect8(d.at).toBeInstanceOf(Date);
4655
+ } finally {
4656
+ await t.done();
4657
+ }
4658
+ });
4659
+ it7("hands back what is stored when a date field holds something else", async () => {
4660
+ const S = reader({ at: z2.date() });
4661
+ const t = await over(S);
4662
+ try {
4663
+ await t.fresh.commit(
4664
+ t.stream,
4665
+ [{ name: "Happened", data: { at: "not a date" } }],
4666
+ raw,
4667
+ -1
4668
+ );
4669
+ expect8((await t.read()).at).toBe("not a date");
4670
+ } finally {
4671
+ await t.done();
4672
+ }
4673
+ });
4674
+ describe8.skipIf(!caps.pii_isolation)("with sensitive fields", () => {
4675
+ it7("reads a redacted payload whose required sensitive field lives in pii", async () => {
4676
+ const S = reader({ at: z2.date(), email: sensitive(z2.string()) });
4677
+ const t = await over(S);
4678
+ try {
4679
+ const target = { stream: t.stream, actor: actor2 };
4680
+ await t.app.do("Do", target, { at: /* @__PURE__ */ new Date(0), email: "u@x.io" });
4681
+ const d = await t.read();
4682
+ expect8(d.at).toBeInstanceOf(Date);
4683
+ expect8(d.email).toBe("[REDACTED]");
4684
+ expect8((await t.app.load(S, t.stream)).state.n).toBe(1);
4685
+ await t.app.do("Do", target, { at: /* @__PURE__ */ new Date(1), email: "v@x.io" });
4686
+ } finally {
4687
+ await t.done();
4688
+ }
4689
+ });
4690
+ it7("keeps a redacted sensitive date's sentinel", async () => {
4691
+ const S = reader({ at: z2.date(), born: sensitive(z2.date()) });
4692
+ const t = await over(S);
4693
+ try {
4694
+ await t.app.do(
4695
+ "Do",
4696
+ { stream: t.stream, actor: actor2 },
4697
+ { at: /* @__PURE__ */ new Date(0), born: /* @__PURE__ */ new Date(1) }
4698
+ );
4699
+ const d = await t.read();
4700
+ expect8(d.at).toBeInstanceOf(Date);
4701
+ expect8(d.born).toBe("[REDACTED]");
4702
+ } finally {
4703
+ await t.done();
4704
+ }
4705
+ });
4706
+ it7("hands a disclosed sensitive date to the reducer as a Date", async () => {
4707
+ const seen = {};
4708
+ const Happened = z2.object({
4709
+ at: z2.date(),
4710
+ born: sensitive(z2.date())
4711
+ });
4712
+ const S = state({ Disclosed: z2.object({ n: z2.number() }) }).init(() => ({ n: 0 })).emits({ Happened }).patch({
4713
+ Happened: ({ data }, s) => {
4714
+ seen.at = data.at instanceof Date;
4715
+ seen.born = data.born instanceof Date;
4716
+ return { n: s.n + 1 };
4717
+ }
4718
+ }).on({ Do: Happened }).emit((p) => ["Happened", p]).discloses(() => true).build();
4719
+ const t = await over(S);
4720
+ try {
4721
+ await t.app.do(
4722
+ "Do",
4723
+ { stream: t.stream, actor: actor2 },
4724
+ { at: /* @__PURE__ */ new Date(0), born: /* @__PURE__ */ new Date(1) }
4725
+ );
4726
+ await t.cache.clear();
4727
+ await t.app.load(S, { stream: t.stream, actor: actor2 });
4728
+ expect8(seen).toEqual({ at: true, born: true });
4729
+ } finally {
4730
+ await t.done();
4731
+ }
4732
+ });
4733
+ });
4734
+ });
4548
4735
  describe8("autoclose end-to-end", () => {
4549
4736
  const Ticket = state({ TckTicket: z2.object({ open: z2.boolean() }) }).init(() => ({ open: false })).emits({
4550
4737
  TckTicketOpened: z2.object({ title: z2.string() }),