@rotorsoft/act-tck 1.38.20 → 1.38.21

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
@@ -135,9 +135,9 @@ var runCacheDifferentialTck = (options) => {
135
135
 
136
136
  // src/cache-tck.ts
137
137
  import { afterEach, beforeEach as beforeEach2, describe as describe2, expect as expect2, it as it2 } from "vitest";
138
- var entry = (event_id, state = {}, stream = `s-${event_id}`) => ({
138
+ var entry = (event_id, state2 = {}, stream = `s-${event_id}`) => ({
139
139
  stream,
140
- state,
140
+ state: state2,
141
141
  version: event_id,
142
142
  event_id,
143
143
  patches: 1,
@@ -363,7 +363,7 @@ async function load_surface(entry2) {
363
363
  if (visited.has(abs)) return;
364
364
  const content = await fs.readFile(abs, "utf8");
365
365
  visited.set(abs, content);
366
- const re_export = /^\s*(?:import|export)\b[^"';]*\bfrom\s+["'](\.[^"']+)["']/gm;
366
+ const re_export = /^\s*export\b[^"';]*\bfrom\s+["'](\.[^"']+)["']/gm;
367
367
  for (const m of content.matchAll(re_export)) {
368
368
  const rel = m[1].replace(/\.js$/, ".ts");
369
369
  const target = path.resolve(path.dirname(abs), rel);
@@ -1113,10 +1113,12 @@ import {
1113
1113
  InMemoryCache,
1114
1114
  SNAP_EVENT as SNAP_EVENT2,
1115
1115
  sleep,
1116
+ state,
1116
1117
  TOMBSTONE_EVENT,
1117
1118
  ValidationError
1118
1119
  } from "@rotorsoft/act";
1119
1120
  import { afterAll as afterAll4, beforeAll as beforeAll4, describe as describe8, expect as expect8, it as it7, vi } from "vitest";
1121
+ import { z as z2 } from "zod";
1120
1122
  var runStoreTck = (options) => {
1121
1123
  describe8(`TCK / Store / ${options.name}`, () => {
1122
1124
  let store;
@@ -3218,6 +3220,106 @@ var runStoreTck = (options) => {
3218
3220
  expect8(got1.priority).toBe(3);
3219
3221
  expect8(got2.priority).toBe(5);
3220
3222
  });
3223
+ it7("matches a stream pattern", async () => {
3224
+ const tag = uid();
3225
+ await store.subscribe([
3226
+ { stream: `pri-${tag}-proj-a` },
3227
+ { stream: `pri-${tag}-proj-b` },
3228
+ { stream: `pri-${tag}-audit-x` }
3229
+ ]);
3230
+ expect8(
3231
+ await store.prioritize({ stream: `^pri-${tag}-proj-.*$` }, 3)
3232
+ ).toBe(2);
3233
+ });
3234
+ it7("matches by source, skipping rows that have none", async () => {
3235
+ const tag = uid();
3236
+ await store.subscribe([
3237
+ { stream: `pri-${tag}-t1`, source: `pri-src-${tag}-users` },
3238
+ { stream: `pri-${tag}-t2`, source: `pri-src-${tag}-audit` },
3239
+ { stream: `pri-${tag}-t3` }
3240
+ ]);
3241
+ expect8(
3242
+ await store.prioritize(
3243
+ { source: `pri-src-${tag}-users`, source_exact: true },
3244
+ 4
3245
+ )
3246
+ ).toBe(1);
3247
+ expect8(
3248
+ await store.prioritize({ source: `^pri-src-${tag}-.*$` }, 6)
3249
+ ).toBe(2);
3250
+ });
3251
+ it7("matches by blocked state, both ways", async () => {
3252
+ const fresh = await options.factory();
3253
+ try {
3254
+ await fresh.drop();
3255
+ await fresh.seed();
3256
+ const tag = uid();
3257
+ const src = `pri-src-${tag}`;
3258
+ const ok = `pri-${tag}-ok`;
3259
+ const bad = `pri-${tag}-bad`;
3260
+ await fresh.commit(
3261
+ src,
3262
+ [inc(1)],
3263
+ make_meta({ stream: src })
3264
+ );
3265
+ await fresh.subscribe([
3266
+ { stream: ok, source: src },
3267
+ { stream: bad, source: src }
3268
+ ]);
3269
+ await correlate(fresh);
3270
+ const leases = await fresh.claim(2, 0, `w-${uid()}`, 5e3);
3271
+ const lease = leases.find((l) => l.stream === bad);
3272
+ await fresh.block([{ ...lease, error: "boom" }]);
3273
+ const scope = `^pri-${tag}-.*$`;
3274
+ expect8(
3275
+ await fresh.prioritize({ stream: scope, blocked: true }, 9)
3276
+ ).toBe(1);
3277
+ expect8(
3278
+ await fresh.prioritize({ stream: scope, blocked: false }, 9)
3279
+ ).toBe(1);
3280
+ } finally {
3281
+ await fresh.dispose();
3282
+ }
3283
+ });
3284
+ it7("an empty filter updates every row, and skips rows already at the value", async () => {
3285
+ const fresh = await options.factory();
3286
+ try {
3287
+ await fresh.drop();
3288
+ await fresh.seed();
3289
+ const tag = uid();
3290
+ await fresh.subscribe([
3291
+ { stream: `pri-${tag}-a` },
3292
+ { stream: `pri-${tag}-b`, priority: 5 }
3293
+ ]);
3294
+ expect8(await fresh.prioritize({}, 5)).toBe(1);
3295
+ expect8(await fresh.prioritize({}, 5)).toBe(0);
3296
+ } finally {
3297
+ await fresh.dispose();
3298
+ }
3299
+ });
3300
+ it7("claim returns higher-priority streams first under tied watermarks", async () => {
3301
+ const fresh = await options.factory();
3302
+ try {
3303
+ await fresh.drop();
3304
+ await fresh.seed();
3305
+ const tag = uid();
3306
+ const src = `pri-src-${tag}`;
3307
+ await fresh.commit(
3308
+ src,
3309
+ [inc(1), inc(2)],
3310
+ make_meta({ stream: src })
3311
+ );
3312
+ await fresh.subscribe([
3313
+ { stream: `pri-${tag}-low`, source: src, priority: 0 },
3314
+ { stream: `pri-${tag}-high`, source: src, priority: 5 }
3315
+ ]);
3316
+ await correlate(fresh);
3317
+ const leases = await fresh.claim(1, 0, `w-${uid()}`, 5e3);
3318
+ expect8(leases.map((l) => l.stream)).toEqual([`pri-${tag}-high`]);
3319
+ } finally {
3320
+ await fresh.dispose();
3321
+ }
3322
+ });
3221
3323
  });
3222
3324
  describe8("lanes", () => {
3223
3325
  it7("subscribe defaults lane to 'default' when omitted", async () => {
@@ -4258,6 +4360,43 @@ var runStoreTck = (options) => {
4258
4360
  });
4259
4361
  expect8([...all.keys()].sort()).toEqual([...streams].sort());
4260
4362
  });
4363
+ it7("pages the count and tail paths by limit + after too", async () => {
4364
+ const tag = uid();
4365
+ for (const s of [`qsc-${tag}-c`, `qsc-${tag}-a`, `qsc-${tag}-b`])
4366
+ await store.commit(
4367
+ s,
4368
+ [inc(1), inc(2)],
4369
+ make_meta({ stream: s })
4370
+ );
4371
+ const pattern = { stream: `qsc-${tag}-.*` };
4372
+ const counted = await store.query_stats(pattern, {
4373
+ count: true,
4374
+ names: true,
4375
+ limit: 2
4376
+ });
4377
+ expect8([...counted.keys()]).toEqual([`qsc-${tag}-a`, `qsc-${tag}-b`]);
4378
+ expect8(counted.get(`qsc-${tag}-a`)?.count).toBe(2);
4379
+ expect8(counted.get(`qsc-${tag}-a`)?.names).toEqual({ Incremented: 2 });
4380
+ const counted2 = await store.query_stats(pattern, {
4381
+ count: true,
4382
+ limit: 2,
4383
+ after: `qsc-${tag}-b`
4384
+ });
4385
+ expect8([...counted2.keys()]).toEqual([`qsc-${tag}-c`]);
4386
+ const tailed = await store.query_stats(pattern, {
4387
+ tail: true,
4388
+ limit: 2
4389
+ });
4390
+ expect8([...tailed.keys()]).toEqual([`qsc-${tag}-a`, `qsc-${tag}-b`]);
4391
+ expect8(tailed.get(`qsc-${tag}-a`)?.head.version).toBe(1);
4392
+ expect8(tailed.get(`qsc-${tag}-a`)?.tail?.version).toBe(0);
4393
+ const tailed2 = await store.query_stats(pattern, {
4394
+ tail: true,
4395
+ limit: 2,
4396
+ after: `qsc-${tag}-b`
4397
+ });
4398
+ expect8([...tailed2.keys()]).toEqual([`qsc-${tag}-c`]);
4399
+ });
4261
4400
  });
4262
4401
  describe8.skipIf(!caps.source_matches)(
4263
4402
  "query_streams source_matches (capability)",
@@ -4388,6 +4527,52 @@ var runStoreTck = (options) => {
4388
4527
  expect8(positions).toEqual([s]);
4389
4528
  });
4390
4529
  });
4530
+ describe8("autoclose end-to-end", () => {
4531
+ const Ticket = state({ TckTicket: z2.object({ open: z2.boolean() }) }).init(() => ({ open: false })).emits({
4532
+ TckTicketOpened: z2.object({ title: z2.string() }),
4533
+ TckTicketResolved: z2.object({})
4534
+ }).patch({
4535
+ TckTicketOpened: () => ({ open: true }),
4536
+ TckTicketResolved: () => ({ open: false })
4537
+ }).on({ OpenTckTicket: z2.object({ title: z2.string() }) }).emit((a) => ["TckTicketOpened", { title: a.title }]).on({ ResolveTckTicket: z2.object({}) }).emit(() => ["TckTicketResolved", {}]).autocloses({ is: "TckTicketResolved" }).build();
4538
+ const actor2 = { id: "tck", name: "tck" };
4539
+ const run = async (resolve) => {
4540
+ const fresh = await options.factory();
4541
+ await fresh.drop();
4542
+ await fresh.seed();
4543
+ const cache = new InMemoryCache();
4544
+ const app = act().withState(Ticket).build({ scoped: { store: fresh, cache } });
4545
+ const truncated = [];
4546
+ app.on("closed", (r) => truncated.push(...r.truncated.keys()));
4547
+ const stream = `ac-${uid()}`;
4548
+ try {
4549
+ await app.do("OpenTckTicket", { stream, actor: actor2 }, { title: "a" });
4550
+ if (resolve) await app.do("ResolveTckTicket", { stream, actor: actor2 }, {});
4551
+ await app.correlate();
4552
+ await app.drain();
4553
+ const surviving = [];
4554
+ await fresh.query((e) => surviving.push(String(e.name)), {
4555
+ stream,
4556
+ stream_exact: true
4557
+ });
4558
+ return { stream, truncated, surviving };
4559
+ } finally {
4560
+ await app.shutdown();
4561
+ await cache.dispose();
4562
+ await fresh.dispose();
4563
+ }
4564
+ };
4565
+ it7("closes a stream whose head is the terminal event, leaving a tombstone", async () => {
4566
+ const { stream, truncated, surviving } = await run(true);
4567
+ expect8(truncated).toContain(stream);
4568
+ expect8(surviving).toEqual([TOMBSTONE_EVENT]);
4569
+ });
4570
+ it7("leaves a stream whose head is not the terminal event", async () => {
4571
+ const { stream, truncated, surviving } = await run(false);
4572
+ expect8(truncated).not.toContain(stream);
4573
+ expect8(surviving).toEqual(["TckTicketOpened"]);
4574
+ });
4575
+ });
4391
4576
  describe8("seed_stream helper coverage", () => {
4392
4577
  it7("commits N events with monotonically increasing ids", async () => {
4393
4578
  const s = `seed-${uid()}`;