@voltro/testing 0.86.0 → 0.87.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.
@@ -0,0 +1,73 @@
1
+ import { declaredEncryptedColumns as e, declaredRenames as t, getFieldCipher as n, isFieldKeyring as r, makeFieldKeyring as i, openCell as a, setFieldCipher as o } from "@voltro/runtime";
2
+ //#region src/fieldKeys.ts
3
+ var s = /* @__PURE__ */ new WeakMap(), c = (e) => {
4
+ let t = i(e);
5
+ return s.set(t, e), o(t), t;
6
+ }, l = (e, t = e.active) => {
7
+ let n = e.keys.find((e) => e.id === t);
8
+ if (n === void 0) throw Error(`the keyring does not hold its active key "${t}"`);
9
+ return i({
10
+ active: n.id,
11
+ keys: [n]
12
+ });
13
+ }, u = (e = (() => {
14
+ let e = n();
15
+ return r(e) ? e : void 0;
16
+ })(), t) => {
17
+ if (t !== void 0) return l(t);
18
+ if (e === void 0) throw Error("no field keyring is registered — pass `fieldKeys` to makeTestContext, or call registerTestFieldKeys");
19
+ let i = s.get(e);
20
+ if (i === void 0) throw Error("the registered field keyring was not built by the test helpers, so its keys are unknown here — register it with `fieldKeys` / registerTestFieldKeys");
21
+ return l(i, e.activeKeyId);
22
+ }, d = /* @__PURE__ */ new WeakMap(), f = (e, t) => {
23
+ d.set(e, t);
24
+ }, p = (e) => typeof e == "string" ? e : e?.tableName, m = async (n, r) => {
25
+ let i = d.get(n) ?? n;
26
+ if (typeof i.query != "function") throw Error("pass a test context (makeTestContext) or a DataStore");
27
+ let o = [...r.tables], s = e(o), c = t(o), l = u(r.keyring), f = 0, m = [];
28
+ for (let e of o) {
29
+ let t = p(e);
30
+ if (t === void 0) continue;
31
+ let n = await i.query({
32
+ table: t,
33
+ predicate: void 0,
34
+ order: [],
35
+ take: void 0,
36
+ skip: void 0,
37
+ projection: void 0
38
+ });
39
+ for (let e of n) {
40
+ let { id: n, ...r } = e;
41
+ for (let [i, o] of Object.entries(e)) {
42
+ if (o == null) continue;
43
+ let e = typeof o == "string" ? o : JSON.stringify(o);
44
+ if (!e.includes("enc:v")) continue;
45
+ let u = a(e, l, {
46
+ table: t,
47
+ column: i,
48
+ rowId: n,
49
+ row: r,
50
+ declared: s,
51
+ renames: c
52
+ });
53
+ f += u.opened, u.unopened > 0 && m.push({
54
+ table: t,
55
+ column: i,
56
+ rowId: n,
57
+ count: u.unopened
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return {
63
+ activeKeyId: l.activeKeyId,
64
+ opened: f,
65
+ unopened: m
66
+ };
67
+ }, h = (e) => `${e.unopened.reduce((e, t) => e + t.count, 0)} sealed value(s) do not open with the active key ${e.activeKeyId} alone:\n` + e.unopened.slice(0, 20).map((e) => ` ${e.table}.${e.column}${e.rowId === void 0 ? "" : ` (row ${String(e.rowId)})`}: ${e.count}`).join("\n") + (e.unopened.length > 20 ? `\n … and ${e.unopened.length - 20} more place(s)` : ""), g = async (e, t) => {
68
+ let n = await m(e, t);
69
+ if (n.unopened.length > 0) throw Error(h(n));
70
+ return n;
71
+ };
72
+ //#endregion
73
+ export { u as activeKeyAlone, h as describeActiveKeyAloneFailures, g as expectSealedWithActiveKeyAlone, f as recordTestRawStore, c as registerTestFieldKeys, m as sealedValuesWithActiveKeyAlone };
@@ -0,0 +1,14 @@
1
+ import { activeKeyAlone as e } from "./fieldKeys.js";
2
+ import { Effect as t } from "effect";
3
+ import { verifySealedValues as n } from "@voltro/runtime";
4
+ //#region src/fieldKeysSql.ts
5
+ var r = async (r) => {
6
+ let i = e(r.keyring, r.fieldKeys), a = await t.runPromise(t.scoped(n(r.dialect, i, r.tables ?? [])).pipe(t.provide(r.layer))), o = {
7
+ activeKeyId: i.activeKeyId,
8
+ ...a
9
+ };
10
+ if (o.unopened.length > 0 || o.unreadable.length > 0) throw Error(`${o.unopened.reduce((e, t) => e + t.count, 0)} sealed value(s) do not open with the active key ${o.activeKeyId} alone` + (o.unopened.length === 0 ? "" : `:\n${o.unopened.map((e) => ` ${e.table}.${e.column}: ${e.count}`).join("\n")}`) + (o.unreadable.length === 0 ? "" : `\n${o.unreadable.length} column(s) could not be read:\n${o.unreadable.map((e) => ` ${e.table}.${e.column}: ${e.reason}`).join("\n")}`));
11
+ return o;
12
+ };
13
+ //#endregion
14
+ export { r as expectSealedWithActiveKeyAloneInSql };
@@ -0,0 +1,132 @@
1
+ import { eq as e, queryFor as t, registerTable as n } from "@voltro/database";
2
+ import { Effect as r, Layer as i, Option as a, Result as o, Stream as s } from "effect";
3
+ import { describe as c, expect as l, it as u } from "vitest";
4
+ import { randomUUID as d } from "node:crypto";
5
+ import { HotStore as f } from "@voltro/kv/hot/contract";
6
+ import { hotStoreConformance as p } from "@voltro/kv/hot/testing";
7
+ import { dataStoreHotStore as m, hotDataStoreTables as h, hotKvTable as g, hotZTable as _ } from "@voltro/runtime/hot";
8
+ //#region src/hotStoreSqlParity.ts
9
+ var v = (v) => {
10
+ h.forEach(n), p(v.name, () => {
11
+ let e = 0;
12
+ return {
13
+ layer: i.effect(f, r.acquireRelease(r.promise(v.make), (e) => r.promise(e.dispose)).pipe(r.map((t) => m(t.store, {
14
+ dialect: v.dialect,
15
+ namespace: d(),
16
+ now: () => e
17
+ })))),
18
+ advance: (t) => r.sync(() => {
19
+ e += t;
20
+ }),
21
+ capabilities: {
22
+ atomicMulti: !0,
23
+ crossNode: !0,
24
+ persistent: !0
25
+ }
26
+ };
27
+ });
28
+ let y = (e, t) => u(e, async () => {
29
+ let e = await v.make();
30
+ try {
31
+ await t(e);
32
+ } finally {
33
+ await e.dispose();
34
+ }
35
+ }, 6e4);
36
+ c(`HotStore SQL parity — ${v.name}`, () => {
37
+ y("idempotent table DDL preserves exact normalized scores and physical deletion", async (e) => {
38
+ let n = m(e.store, {
39
+ dialect: v.dialect,
40
+ namespace: "sql"
41
+ }), i = 1.2345678901234567;
42
+ await r.runPromise(n.zadd("player:subject", [{
43
+ member: "member",
44
+ score: i
45
+ }])), await e.migrate(), l(await e.store.query(t(_).descriptor)).toEqual([l.objectContaining({
46
+ member: "member",
47
+ score: i
48
+ })]), await r.runPromise(n.delete("player:subject")), l(await e.store.query(t(_).descriptor)).toEqual([]), l(await e.store.query(t(g).descriptor)).toEqual([l.objectContaining({
49
+ keyHash: "lock",
50
+ namespace: null,
51
+ key: null,
52
+ payload: null,
53
+ expiresAtMs: null
54
+ })]);
55
+ }), y("wrong-type batches roll back every preceding SQL write", async (n) => {
56
+ let i = m(n.store, {
57
+ dialect: v.dialect,
58
+ namespace: "atomic"
59
+ });
60
+ await r.runPromise(i.zadd("board", [{
61
+ member: "m",
62
+ score: 1
63
+ }]));
64
+ let s = await r.runPromise(i.multi([{
65
+ op: "incrBy",
66
+ key: "other",
67
+ delta: 1
68
+ }, {
69
+ op: "incrBy",
70
+ key: "board",
71
+ delta: 1
72
+ }]).pipe(r.result));
73
+ l(o.isFailure(s)).toBe(!0), l(await n.store.query(t(g).where(e("key", "other")).descriptor)).toEqual([]), l(a.getOrNull(await r.runPromise(i.zscore("board", "m")))).toBe(1);
74
+ }), y("namespace/key boundaries, case, wildcard prefixes and long Unicode values remain exact", async (e) => {
75
+ let t = m(e.store, {
76
+ dialect: v.dialect,
77
+ namespace: "n"
78
+ }), n = m(e.store, {
79
+ dialect: v.dialect,
80
+ namespace: "n:tenant"
81
+ }), i = m(e.store, {
82
+ dialect: v.dialect,
83
+ namespace: "N"
84
+ });
85
+ await r.runPromise(t.incrBy("tenant:key", 4)), await r.runPromise(n.incrBy("key", 2)), await r.runPromise(i.incrBy("tenant:key", 9)), await r.runPromise(t.incrBy("%_:key", 1)), await r.runPromise(t.incrBy("wildcard-key", 1));
86
+ let o = "🎮".repeat(2e3);
87
+ await r.runPromise(t.zadd(o, [{
88
+ member: o,
89
+ score: -1.5
90
+ }])), l(await r.runPromise(t.incrBy("tenant:key", 0))).toBe(4), l(await r.runPromise(n.incrBy("key", 0))).toBe(2), l(await r.runPromise(i.incrBy("tenant:key", 0))).toBe(9), l(await r.runPromise(s.runCollect(t.scanPrefix("%_:")))).toEqual(["%_:key"]), l(await r.runPromise(s.runCollect(i.scanPrefix("")))).toEqual(["tenant:key"]), l(a.getOrNull(await r.runPromise(t.zscore(o, o)))).toBe(-1.5);
91
+ }), y("independent connections retain every cold-key increment and have one lease winner", async (e) => {
92
+ let t = [e.store];
93
+ for (let n = 0; n < 3; n++) t.push(await e.connect());
94
+ let n = t.map((e) => m(e, {
95
+ dialect: v.dialect,
96
+ namespace: "connections"
97
+ })), i = await Promise.all(Array.from({ length: 32 }, (e, t) => r.runPromise(n[t % n.length].incrBy("counter", 1))));
98
+ l(new Set(i).size).toBe(32), l(await r.runPromise(n[0].incrBy("counter", 0))).toBe(32);
99
+ let a = await Promise.all(Array.from({ length: 32 }, (e, t) => r.runPromise(n[t % n.length].setNx("seat", String(t), 6e4))));
100
+ l(a.filter(Boolean)).toHaveLength(1);
101
+ }), y("concurrent first-use ports admit coordination before application writes", async (e) => {
102
+ let t = [e.store];
103
+ for (let n = 0; n < 3; n++) t.push(await e.connect());
104
+ let n = Array.from({ length: 32 }, (e, n) => m(t[n % t.length], {
105
+ dialect: v.dialect,
106
+ namespace: "first-use"
107
+ })), i = (await Promise.allSettled(n.map((e) => r.runPromise(e.incrBy("counter", 1))))).map((e) => {
108
+ if (e.status === "rejected") throw e.reason;
109
+ return e.value;
110
+ });
111
+ l(new Set(i).size).toBe(32), l(await r.runPromise(n[0].incrBy("counter", 0))).toBe(32);
112
+ }), y("new connections rebuild the port from persisted SQL and apply stored expiry", async (e) => {
113
+ let t = 0, n = m(e.store, {
114
+ dialect: v.dialect,
115
+ namespace: "reconnect",
116
+ now: () => t
117
+ });
118
+ await r.runPromise(n.incrBy("counter", 7)), await r.runPromise(n.zadd("board", [{
119
+ member: "𐀀",
120
+ score: 1.2345678901234567
121
+ }])), await r.runPromise(n.setNx("seat", "holder", 100));
122
+ let i = m(await e.connect(), {
123
+ dialect: v.dialect,
124
+ namespace: "reconnect",
125
+ now: () => t
126
+ });
127
+ l(await r.runPromise(i.incrBy("counter", 0))).toBe(7), l(a.getOrNull(await r.runPromise(i.zscore("board", "𐀀")))).toBe(1.2345678901234567), l(await r.runPromise(i.setNx("seat", "replacement", 100))).toBe(!1), t = 100, l(await r.runPromise(i.setNx("seat", "replacement", 100))).toBe(!0);
128
+ });
129
+ });
130
+ };
131
+ //#endregion
132
+ export { v as runHotStoreSqlParity };
package/dist/index.d.ts CHANGED
@@ -6,22 +6,33 @@ import { ChangeEvent } from '@voltro/database';
6
6
  import { changeInsert } from '@voltro/database';
7
7
  import { changeSoftDelete } from '@voltro/database';
8
8
  import { changeUpdate } from '@voltro/database';
9
+ import { ComputedQueryCacheDeps } from '@voltro/runtime';
10
+ import { ConnectionRef } from '@voltro/kv/connection';
11
+ import { ConnectionRegistry } from '@voltro/kv/connection';
12
+ import { ConnectionRegistryOptions } from '@voltro/kv/connection';
13
+ import { ConnectionsConfig } from '@voltro/kv/connection';
14
+ import { DialectId } from '@voltro/database';
9
15
  import { Effect } from 'effect';
10
16
  import { EventBus } from '@voltro/runtime';
11
17
  import { EventDescriptor } from '@voltro/protocol';
12
18
  import { EventPublisher } from '@voltro/runtime';
19
+ import { FieldKeyring } from '@voltro/runtime';
20
+ import { FieldKeyringConfig } from '@voltro/runtime';
13
21
  import { InspectedStep } from '@voltro/workflow';
14
22
  import { InspectedWorkflow } from '@voltro/workflow';
15
23
  import { Layer } from 'effect';
24
+ import { MemoryRedisConnection } from '@voltro/kv/connection';
16
25
  import { ProcedureDescriptor } from '@voltro/protocol';
17
26
  import { PublicApiDescriptor } from '@voltro/protocol/rest';
18
27
  import { PublicApiProfileConfig } from '@voltro/protocol';
19
28
  import { PublicApiSpec } from '@voltro/protocol';
29
+ import { RedisConnections } from '@voltro/kv';
20
30
  import { RelationsSpec } from '@voltro/database';
21
31
  import { RestRouteDescriptor } from '@voltro/protocol/rest';
22
32
  import { RestServeBindings } from '@voltro/protocol/rest';
23
33
  import { Row } from '@voltro/database';
24
34
  import { RowFilter } from '@voltro/runtime';
35
+ import { RowFilterScope } from '@voltro/runtime';
25
36
  import { RpcConnection } from '@voltro/runtime';
26
37
  import { RpcInterceptor } from '@voltro/protocol';
27
38
  import { RpcKind } from '@voltro/protocol';
@@ -29,12 +40,46 @@ import { Schema } from 'effect';
29
40
  import { SchemaError } from 'effect/Schema';
30
41
  import { Scope } from 'effect';
31
42
  import { ServiceApi } from '@voltro/protocol';
43
+ import { SqlClient } from 'effect/sql';
32
44
  import { Subject } from '@voltro/protocol';
33
45
  import { SubscribeContext } from '@voltro/runtime';
34
46
  import { SyncLogger } from '@voltro/logger';
35
47
  import { TableLike } from '@voltro/database';
36
48
  import { VoltroPlugin } from '@voltro/protocol';
37
49
 
50
+ /**
51
+ * The keyring reduced to its active key — `fieldKeys` when given (a
52
+ * configuration, e.g. the one a rotation printed), else the registered one.
53
+ */
54
+ export declare const activeKeyAlone: (keyring?: FieldKeyring | undefined, fieldKeys?: FieldKeyringConfig) => FieldKeyring;
55
+
56
+ /** Values the active key alone does not open, per place. */
57
+ export declare interface ActiveKeyAloneReport {
58
+ readonly activeKeyId: string;
59
+ readonly opened: number;
60
+ readonly unopened: ReadonlyArray<{
61
+ readonly table: string;
62
+ readonly column: string;
63
+ readonly rowId: unknown;
64
+ readonly count: number;
65
+ }>;
66
+ }
67
+
68
+ export declare interface ActiveKeyAloneSqlReport {
69
+ readonly activeKeyId: string;
70
+ readonly opened: number;
71
+ readonly unopened: ReadonlyArray<{
72
+ readonly table: string;
73
+ readonly column: string;
74
+ readonly count: number;
75
+ }>;
76
+ readonly unreadable: ReadonlyArray<{
77
+ readonly table: string;
78
+ readonly column: string;
79
+ readonly reason: string;
80
+ }>;
81
+ }
82
+
38
83
  /** An unauthenticated caller, optionally carrying a tenant (what the
39
84
  * `x-tenant` header resolves to when no auth strategy matches). Delegates to
40
85
  * the protocol's own constructor so the harness cannot drift from it. */
@@ -142,6 +187,28 @@ export declare interface EmittedWebhook {
142
187
  * ergonomic without an `any` in the public type. */
143
188
  declare type ErasedWorkflowEffect = Effect.Effect<unknown, unknown, never>;
144
189
 
190
+ /** Assert that after a rotation every sealed value opens with the active key alone. Returns the report. */
191
+ export declare const expectSealedWithActiveKeyAlone: (source: object, options: {
192
+ readonly tables: Iterable<unknown>;
193
+ readonly keyring?: FieldKeyring;
194
+ }) => Promise<ActiveKeyAloneReport>;
195
+
196
+ /**
197
+ * Assert that every sealed value in the database `layer` connects to opens with
198
+ * the active key alone. `tables` are the app's declared tables (their
199
+ * `.encrypted()` columns are always checked); `keyring` defaults to the one
200
+ * registered with `fieldKeys` / `registerTestFieldKeys`. Columns that cannot be
201
+ * read fail the assertion too — a check that skipped them would pass vacuously.
202
+ */
203
+ export declare const expectSealedWithActiveKeyAloneInSql: (options: {
204
+ readonly dialect: DialectId;
205
+ readonly layer: Layer.Layer<SqlClient.SqlClient, unknown, never>;
206
+ readonly tables?: Iterable<unknown>;
207
+ readonly keyring?: FieldKeyring;
208
+ /** The keyring as configured (ids and keys, the active one named) — instead of a registered keyring. */
209
+ readonly fieldKeys?: FieldKeyringConfig;
210
+ }) => Promise<ActiveKeyAloneSqlReport>;
211
+
145
212
  export declare interface Factory {
146
213
  /** The table this factory builds rows for. */
147
214
  readonly table: TableLike;
@@ -482,6 +549,14 @@ export declare interface MakeTestContextOptions<RowFilterCtx = unknown> {
482
549
  * gate, and the gate is what turns the variable into a cipher.
483
550
  */
484
551
  readonly fieldEncryptionKey?: string;
552
+ /**
553
+ * A keyring instead of one key — several ids, one active — registered the way
554
+ * the boot registers `VOLTRO_FIELD_ENCRYPTION_KEYS` (`registerTestFieldKeys`).
555
+ * With it, `expectSealedWithActiveKeyAlone(ctx, { tables })` checks what a
556
+ * rotation ends with: every sealed value opens with the active key alone.
557
+ * Not together with `fieldEncryptionKey`.
558
+ */
559
+ readonly fieldKeys?: FieldKeyringConfig;
485
560
  /**
486
561
  * Plugins whose rpc interceptors (`interceptMutation` / `interceptQuery` /
487
562
  * `interceptAction`) wrap every `invoke` on this context — the same list an
@@ -528,7 +603,7 @@ export declare interface MakeTestContextOptions<RowFilterCtx = unknown> {
528
603
  * Merged LAST, so a user layer overrides a plugin layer declaring the same
529
604
  * Tag — the ordering `makeHandlerServiceLayer` uses on both boot paths.
530
605
  */
531
- readonly layers?: ReadonlyArray<Layer.Layer<unknown, never, never>>;
606
+ readonly layers?: ReadonlyArray<Layer.Layer<never, never, never>>;
532
607
  /**
533
608
  * Row-level security for this context, WITHOUT touching the process global.
534
609
  *
@@ -706,6 +781,8 @@ export declare interface PublicApiBinding {
706
781
  readonly handler: (input: never, ctx: TestContext) => unknown;
707
782
  }
708
783
 
784
+ export declare const queryCacheOf: (ctx: TestContext) => TestQueryCache | undefined;
785
+
709
786
  export declare interface ReachableTarget {
710
787
  readonly host: string;
711
788
  readonly port: number;
@@ -721,6 +798,14 @@ export declare interface RecordedDelivery<P = unknown> {
721
798
  readonly n: number;
722
799
  }
723
800
 
801
+ /**
802
+ * Register a keyring as this process's field cipher — what the boot builds from
803
+ * `VOLTRO_FIELD_ENCRYPTION_KEYS` — and remember its keys, so the active-key-alone
804
+ * check can open values with the active key and nothing else. Call it again with
805
+ * the next configuration to rotate inside a test.
806
+ */
807
+ export declare const registerTestFieldKeys: (config: FieldKeyringConfig) => FieldKeyring;
808
+
724
809
  /**
725
810
  * The engine BUILD behind a target, printed once per file.
726
811
  *
@@ -809,6 +894,16 @@ export declare const runInStoreTransaction: <T>(ctx: TestContext, work: (txCtx:
809
894
 
810
895
  export { SchemaError }
811
896
 
897
+ /**
898
+ * Every sealed value in `tables` — the app's declared tables, or names — opened
899
+ * with the active key alone; the values it does not open are listed. Reads the
900
+ * RAW rows (envelopes as stored), not through the store's decryption.
901
+ */
902
+ export declare const sealedValuesWithActiveKeyAlone: (source: object, options: {
903
+ readonly tables: Iterable<unknown>;
904
+ readonly keyring?: FieldKeyring;
905
+ }) => Promise<ActiveKeyAloneReport>;
906
+
812
907
  /** A machine subject scoped to ONE tenant (a per-tenant integration, a
813
908
  * `storeForTenant` view). NOT the cross-tenant `system()` below. */
814
909
  export declare const serviceAccount: (id: string, options?: TestSubjectOptions) => Subject;
@@ -1071,6 +1166,22 @@ export declare interface TestEventBusOptions {
1071
1166
 
1072
1167
  export declare const TESTING_PRESET_VERSION: 1;
1073
1168
 
1169
+ /**
1170
+ * The query cache a context's `invoke` serves `cache:` queries through — the
1171
+ * runtime's own `cachedComputedCall` over a real `@voltro/cache` `Cache` (memory
1172
+ * backend, ttl on `ctx.clock`). A write through any context of the tree drops
1173
+ * the entries tagged with its table, synchronously, so a test can assert
1174
+ * "write X invalidates query Y" and "a revoked grant refuses a hit" without a
1175
+ * redis and without waiting.
1176
+ */
1177
+ export declare interface TestQueryCache {
1178
+ readonly deps: ComputedQueryCacheDeps;
1179
+ /** This context's resolved row-filter visibility (`undefined`: no filter). */
1180
+ readonly rowFilter: () => Promise<RowFilterScope | undefined>;
1181
+ /** Results a cached compute did not keep, and why — the dev log's lines. */
1182
+ readonly notices: ReadonlyArray<string>;
1183
+ }
1184
+
1074
1185
  export declare interface TestRequestOptions {
1075
1186
  /** Extra headers for this call, merged over the app's (`actingAs` /
1076
1187
  * `withHeaders`) headers. Names are lowercased, as the transport delivers
@@ -1138,6 +1249,13 @@ export declare interface TestSubscriber<P = unknown> {
1138
1249
  */
1139
1250
  export declare const user: (id: string, options?: TestSubjectOptions) => Subject;
1140
1251
 
1252
+ /** Replace named app entries with scoped local transports, without changing a
1253
+ * handler's references. Provide this layer last in a test context's layers. */
1254
+ export declare const withConnections: (overrides: Readonly<Record<ConnectionRef<"redis">, MemoryRedisConnection>>, options?: {
1255
+ readonly connections?: ConnectionsConfig;
1256
+ readonly connectAnalytics?: ConnectionRegistryOptions["connectAnalytics"];
1257
+ }) => Layer.Layer< ConnectionRegistry | RedisConnections, never, never>;
1258
+
1141
1259
  /**
1142
1260
  * Run `body` with global time frozen at `at`, then restore — whether the body
1143
1261
  * returns, throws, resolves or rejects.
package/dist/index.js CHANGED
@@ -2,19 +2,22 @@ import { fixtureRow as e, nextSequence as t } from "./fixtureRow.js";
2
2
  import { hydrateFixtureRows as n } from "./hydrateFixtureRows.js";
3
3
  import { defineFactory as r } from "./factory.js";
4
4
  import { MockClock as i, frozenTime as a, withFrozenTime as o } from "./mockClock.js";
5
- import { MockWebhooks as s } from "./mockWebhooks.js";
6
- import { testEventBus as c } from "./testEventBus.js";
7
- import { MockLLM as l } from "./mockLLM.js";
8
- import { makeTestContext as u, markRestInvocation as d, mockStore as f, outboxNudgesOf as p, pluginsOf as m, resetAfterCommit as h, restInvocationOf as g, rpcInterceptorFor as _, runAfterCommit as v, runInStoreTransaction as y, serviceLayerFor as b, withRpcConnection as x } from "./testContext.js";
9
- import { invoke as S } from "./invoke.js";
10
- import { describeIfAvailable as C, describeIfReachable as w, isTcpReachable as T, reportEngineVersion as E } from "./reachable.js";
11
- import { TEST_TENANT_ID as D, anonymous as O, apiKey as k, serviceAccount as A, system as j, user as M } from "./subject.js";
12
- import { makeTestApp as N } from "./testRequest.js";
13
- import { changeDelete as P, changeInsert as F, changeSoftDelete as I, changeUpdate as L, makeSubscribeContext as R } from "./subscribeContext.js";
14
- import { makeWorkflowRunner as z } from "./workflowRunner.js";
15
- import { makeServiceApiMock as B } from "./serviceApiMock.js";
16
- import { startTestApps as V } from "./testApps.js";
5
+ import { activeKeyAlone as s, expectSealedWithActiveKeyAlone as c, registerTestFieldKeys as l, sealedValuesWithActiveKeyAlone as u } from "./fieldKeys.js";
6
+ import { MockWebhooks as d } from "./mockWebhooks.js";
7
+ import { testEventBus as f } from "./testEventBus.js";
8
+ import { MockLLM as p } from "./mockLLM.js";
9
+ import { makeTestContext as m, markRestInvocation as h, mockStore as g, outboxNudgesOf as _, pluginsOf as v, queryCacheOf as y, resetAfterCommit as b, restInvocationOf as x, rpcInterceptorFor as S, runAfterCommit as C, runInStoreTransaction as w, serviceLayerFor as T, withRpcConnection as E } from "./testContext.js";
10
+ import { invoke as D } from "./invoke.js";
11
+ import { describeIfAvailable as O, describeIfReachable as k, isTcpReachable as A, reportEngineVersion as j } from "./reachable.js";
12
+ import { TEST_TENANT_ID as M, anonymous as N, apiKey as P, serviceAccount as F, system as I, user as L } from "./subject.js";
13
+ import { makeTestApp as R } from "./testRequest.js";
14
+ import { expectSealedWithActiveKeyAloneInSql as z } from "./fieldKeysSql.js";
15
+ import { changeDelete as B, changeInsert as V, changeSoftDelete as H, changeUpdate as U, makeSubscribeContext as W } from "./subscribeContext.js";
16
+ import { makeWorkflowRunner as G } from "./workflowRunner.js";
17
+ import { makeServiceApiMock as K } from "./serviceApiMock.js";
18
+ import { startTestApps as q } from "./testApps.js";
19
+ import { withConnections as J } from "./withConnections.js";
17
20
  //#region src/index.ts
18
- var H = 1;
21
+ var Y = 1;
19
22
  //#endregion
20
- export { i as MockClock, l as MockLLM, s as MockWebhooks, H as TESTING_PRESET_VERSION, D as TEST_TENANT_ID, O as anonymous, k as apiKey, P as changeDelete, F as changeInsert, I as changeSoftDelete, L as changeUpdate, r as defineFactory, C as describeIfAvailable, w as describeIfReachable, e as fixtureRow, a as frozenTime, n as hydrateFixtureRows, S as invoke, T as isTcpReachable, B as makeServiceApiMock, R as makeSubscribeContext, N as makeTestApp, u as makeTestContext, z as makeWorkflowRunner, d as markRestInvocation, f as mockStore, t as nextSequence, p as outboxNudgesOf, m as pluginsOf, E as reportEngineVersion, h as resetAfterCommit, g as restInvocationOf, _ as rpcInterceptorFor, v as runAfterCommit, y as runInStoreTransaction, A as serviceAccount, b as serviceLayerFor, V as startTestApps, j as system, c as testEventBus, M as user, o as withFrozenTime, x as withRpcConnection };
23
+ export { i as MockClock, p as MockLLM, d as MockWebhooks, Y as TESTING_PRESET_VERSION, M as TEST_TENANT_ID, s as activeKeyAlone, N as anonymous, P as apiKey, B as changeDelete, V as changeInsert, H as changeSoftDelete, U as changeUpdate, r as defineFactory, O as describeIfAvailable, k as describeIfReachable, c as expectSealedWithActiveKeyAlone, z as expectSealedWithActiveKeyAloneInSql, e as fixtureRow, a as frozenTime, n as hydrateFixtureRows, D as invoke, A as isTcpReachable, K as makeServiceApiMock, W as makeSubscribeContext, R as makeTestApp, m as makeTestContext, G as makeWorkflowRunner, h as markRestInvocation, g as mockStore, t as nextSequence, _ as outboxNudgesOf, v as pluginsOf, y as queryCacheOf, l as registerTestFieldKeys, j as reportEngineVersion, b as resetAfterCommit, x as restInvocationOf, S as rpcInterceptorFor, C as runAfterCommit, w as runInStoreTransaction, u as sealedValuesWithActiveKeyAlone, F as serviceAccount, T as serviceLayerFor, q as startTestApps, I as system, f as testEventBus, L as user, J as withConnections, o as withFrozenTime, E as withRpcConnection };
package/dist/invoke.js CHANGED
@@ -1,44 +1,82 @@
1
- import { resetAfterCommit as e, restInvocationOf as t, rpcInterceptorFor as n, runAfterCommit as r, runInStoreTransaction as i, serviceLayerFor as a } from "./testContext.js";
2
- import { Cause as o, Effect as s, Exit as c, Schema as l } from "effect";
3
- import { SubjectService as u, checkGuardsEffect as d } from "@voltro/protocol";
4
- import { makeEffectStoreLayer as f, runProvidedEffect as p, runWithDeadlockRetry as m } from "@voltro/runtime";
1
+ import { queryCacheOf as e, resetAfterCommit as t, restInvocationOf as n, rpcInterceptorFor as r, runAfterCommit as i, runInStoreTransaction as a, serviceLayerFor as o } from "./testContext.js";
2
+ import { Cause as s, Effect as c, Exit as l, Schema as u } from "effect";
3
+ import { SubjectService as d, checkGuardsEffect as f } from "@voltro/protocol";
4
+ import { cachedComputedCall as p, computedCacheKey as m, makeEffectStoreLayer as h, runProvidedEffect as g, runWithDeadlockRetry as _ } from "@voltro/runtime";
5
5
  //#region src/invoke.ts
6
- var h = (e, t) => {
7
- let n = a(t);
8
- return (n === void 0 ? e : s.provide(e, n)).pipe(s.provide(f(t.store)), s.provideService(u, t.request.subject));
9
- }, g = async (a, u, f, g) => {
10
- let _ = a.input, v = await l.decodeUnknownPromise(_)(f), y = a.kind, b = async (e) => {
11
- let t = u(v, e);
12
- return s.isEffect(t) ? p(h(t, e)) : t;
13
- }, x = async () => {
14
- let t = a.guards;
15
- if (t !== void 0 && t.length > 0) {
16
- let e = await s.runPromise(d(g.request.subject, t, v));
17
- if (e !== null) throw e;
6
+ var v = (e, t) => {
7
+ let n = o(t);
8
+ return (n === void 0 ? e : c.provide(e, n)).pipe(c.provide(h(t.store)), c.provideService(d, t.request.subject));
9
+ }, y = async (o, h, y, x) => {
10
+ let S = o.input, C = await u.decodeUnknownPromise(S)(y), w = o.kind, T = async (e) => {
11
+ let t = h(C, e);
12
+ return c.isEffect(t) ? g(v(t, e)) : t;
13
+ }, E = async () => {
14
+ let t = e(x), n = o.cache;
15
+ if (t === void 0 || n === void 0) return b;
16
+ let r = t.deps.policyFor(n);
17
+ if (r === void 0) return b;
18
+ let i = o, a = i.name ?? "", s = i.source === void 0 ? [] : typeof i.source == "string" ? [i.source] : [...i.source], l = await t.rowFilter(), u = m({
19
+ name: a,
20
+ codeIdentity: t.deps.codeIdentity,
21
+ output: i.output,
22
+ input: i.input,
23
+ decodedInput: C,
24
+ scope: r.scope,
25
+ subject: x.request.subject,
26
+ rowFilter: l,
27
+ declaredSources: s
28
+ });
29
+ return "bypass" in u ? b : g(p({
30
+ cache: t.deps,
31
+ policy: r,
32
+ key: u.key,
33
+ name: a,
34
+ sources: s,
35
+ output: i.output,
36
+ subject: x.request.subject,
37
+ rowFilter: l,
38
+ ctx: x,
39
+ execute: async (e, t) => {
40
+ let n = h(C, e);
41
+ return c.isEffect(n) ? g(v(n.pipe(c.provideService(d, t)), x)) : n;
42
+ },
43
+ onDescriptor: () => {},
44
+ isDescriptor: () => !1,
45
+ unwrap: (e) => e
46
+ }).pipe(c.map(({ value: e }) => e)));
47
+ }, D = async () => {
48
+ let e = o.guards;
49
+ if (e !== void 0 && e.length > 0) {
50
+ let t = await c.runPromise(f(x.request.subject, e, C));
51
+ if (t !== null) throw t;
18
52
  }
19
- if (y !== "mutation") return b(g);
20
- let n = await m(async () => i(g, async (t) => (e(g), b(t))), { delay: () => Promise.resolve() });
21
- return await r(g), n;
22
- }, S = y === "mutation" || y === "query" || y === "action" ? y : void 0, C = S === void 0 ? void 0 : n(g, S);
23
- if (C === void 0 || S === void 0) return await x();
24
- let w = s.tryPromise({
25
- try: () => x(),
53
+ if (w === "query") {
54
+ let e = await E();
55
+ if (e !== b) return e;
56
+ }
57
+ if (w !== "mutation") return T(x);
58
+ let n = await _(async () => a(x, async (e) => (t(x), T(e))), { delay: () => Promise.resolve() });
59
+ return await i(x), n;
60
+ }, O = w === "mutation" || w === "query" || w === "action" ? w : void 0, k = O === void 0 ? void 0 : r(x, O);
61
+ if (k === void 0 || O === void 0) return await D();
62
+ let A = c.tryPromise({
63
+ try: () => D(),
26
64
  catch: (e) => e
27
- }), T = t(g), E = C(w, {
28
- tag: a.name ?? "",
29
- kind: S,
30
- input: v,
31
- subject: g.request.subject,
32
- traceId: g.request.traceId,
33
- ...g.request.spanId === void 0 ? {} : { spanId: g.request.spanId },
34
- ...T === void 0 ? {} : {
65
+ }), j = n(x), M = k(A, {
66
+ tag: o.name ?? "",
67
+ kind: O,
68
+ input: C,
69
+ subject: x.request.subject,
70
+ traceId: x.request.traceId,
71
+ ...x.request.spanId === void 0 ? {} : { spanId: x.request.spanId },
72
+ ...j === void 0 ? {} : {
35
73
  surface: "rest",
36
- publicApi: T.publicApi
74
+ publicApi: j.publicApi
37
75
  },
38
- ...T?.remoteAddr === void 0 ? {} : { remoteAddr: T.remoteAddr }
39
- }), D = await s.runPromiseExit(E);
40
- if (c.isSuccess(D)) return D.value;
41
- throw o.squash(D.cause);
42
- };
76
+ ...j?.remoteAddr === void 0 ? {} : { remoteAddr: j.remoteAddr }
77
+ }), N = await c.runPromiseExit(M);
78
+ if (l.isSuccess(N)) return N.value;
79
+ throw s.squash(N.cause);
80
+ }, b = Symbol("voltro:testing:not-cached");
43
81
  //#endregion
44
- export { g as invoke };
82
+ export { y as invoke };