@rotorsoft/act-tck 1.40.0 → 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
@@ -1099,6 +1099,7 @@ import {
1099
1099
  ConcurrencyError,
1100
1100
  InMemoryCache,
1101
1101
  SNAP_EVENT as SNAP_EVENT2,
1102
+ sensitive,
1102
1103
  sleep,
1103
1104
  state,
1104
1105
  TOMBSTONE_EVENT,
@@ -4546,6 +4547,191 @@ var runStoreTck = (options) => {
4546
4547
  expect8(positions).toEqual([s]);
4547
4548
  });
4548
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
+ });
4549
4735
  describe8("autoclose end-to-end", () => {
4550
4736
  const Ticket = state({ TckTicket: z2.object({ open: z2.boolean() }) }).init(() => ({ open: false })).emits({
4551
4737
  TckTicketOpened: z2.object({ title: z2.string() }),