@voltro/kv 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +5321 -0
  2. package/README.md +110 -0
  3. package/THIRD-PARTY-NOTICES.md +864 -1
  4. package/dist/analytics.d.ts +337 -0
  5. package/dist/analytics.js +16 -0
  6. package/dist/cacheAtomicProtocol.js +247 -0
  7. package/dist/cacheAtomicScript.js +309 -0
  8. package/dist/cacheAtomicState.js +141 -0
  9. package/dist/connection.d.ts +990 -9
  10. package/dist/connection.js +330 -50
  11. package/dist/connectionAnalyticsConfig.js +153 -0
  12. package/dist/connectionConfigProvider.js +111 -0
  13. package/dist/connectionDefinitions.js +285 -0
  14. package/dist/connectionMemory.js +207 -0
  15. package/dist/connectionMetrics.js +79 -0
  16. package/dist/connectionNamespace.js +19 -0
  17. package/dist/connectionNats.js +148 -0
  18. package/dist/connectionNatsConfig.js +87 -0
  19. package/dist/connectionNatsSubjects.js +16 -0
  20. package/dist/connectionOwner.js +44 -0
  21. package/dist/connectionPool.js +738 -0
  22. package/dist/connectionPrefix.js +37 -0
  23. package/dist/connectionScripts.js +4 -0
  24. package/dist/connectionSqlConfig.js +119 -0
  25. package/dist/connectionStorageConfig.js +190 -0
  26. package/dist/connectionStorageFacade.js +78 -0
  27. package/dist/connectionSubscriber.js +130 -0
  28. package/dist/errors.js +5 -1
  29. package/dist/hot-contract.d.ts +297 -0
  30. package/dist/hot-contract.js +18 -0
  31. package/dist/hot-testing.d.ts +229 -0
  32. package/dist/hot-testing.js +1 -0
  33. package/dist/hot.d.ts +936 -0
  34. package/dist/hot.js +7 -0
  35. package/dist/hotMemory.js +46 -0
  36. package/dist/hotOperations.js +223 -0
  37. package/dist/hotRateLimit.js +103 -0
  38. package/dist/hotReadAvailability.js +19 -0
  39. package/dist/hotRedis.js +139 -0
  40. package/dist/hotRedisScript.js +4 -0
  41. package/dist/hotRouting.js +78 -0
  42. package/dist/hotState.js +182 -0
  43. package/dist/hotStoreConformance.js +367 -0
  44. package/dist/index.d.ts +113 -9
  45. package/dist/nativeRedisReads.js +78 -0
  46. package/dist/nativeRedisTopology.js +221 -0
  47. package/dist/redisDiagnostics.js +27 -0
  48. package/dist/redisReadPolicy.js +39 -0
  49. package/dist/sql.d.ts +77 -0
  50. package/dist/sql.js +0 -0
  51. package/dist/storage.d.ts +166 -0
  52. package/dist/storage.js +48 -0
  53. package/dist/storageAddress.js +20 -0
  54. package/package.json +43 -3
@@ -0,0 +1,337 @@
1
+ import { Effect } from 'effect';
2
+ import { Schema } from 'effect';
3
+ import { YieldableError } from 'effect/Cause';
4
+
5
+ export declare interface AggregateQuery {
6
+ readonly event: string;
7
+ readonly filter?: PropertyFilter;
8
+ readonly metric: AnalyticsMetric;
9
+ readonly range: TimeRange;
10
+ /**
11
+ * Restrict the read to one tenant's events. `useAnalytics()` injects the
12
+ * caller's `subject.tenantId` automatically, so handler code never sets
13
+ * this; a sink that honours it (e.g. analytics-postgres) then AND-filters
14
+ * by tenant so a query can't read across tenants. `null`/absent = unscoped
15
+ * (system/cross-tenant read).
16
+ */
17
+ readonly tenantId?: string | null;
18
+ }
19
+
20
+ export declare interface AnalyticsBucket {
21
+ readonly bucketStart: Date;
22
+ readonly value: number;
23
+ }
24
+
25
+ /**
26
+ * The sink doesn't implement this capability. PostHog returns this
27
+ * for `aggregate`/`timeseries`/`topN`. Callers typically
28
+ * `Effect.catchTag('AnalyticsCapabilityNotSupported', ...)` to fall
29
+ * back to a different sink or report the missing feature.
30
+ */
31
+ export declare class AnalyticsCapabilityNotSupported extends AnalyticsCapabilityNotSupported_base {
32
+ }
33
+
34
+ declare const AnalyticsCapabilityNotSupported_base: Schema.Class<AnalyticsCapabilityNotSupported, Schema.TaggedStruct<"AnalyticsCapabilityNotSupported", {
35
+ readonly provider: Schema.String;
36
+ readonly capability: Schema.String;
37
+ }>, YieldableError>;
38
+
39
+ declare interface AnalyticsConnectionBatch {
40
+ readonly maxSize?: number;
41
+ readonly flushIntervalMs?: number;
42
+ }
43
+
44
+ export declare type AnalyticsConnectionConfig = AnalyticsOptions & ConnectionMetadata & {
45
+ readonly source?: 'config' | 'environment' | 'override';
46
+ };
47
+
48
+ export declare type AnalyticsConnectionConnector = (config: AnalyticsConnectionConfig) => Effect.Effect<AnalyticsConnectionTransport, KvError>;
49
+
50
+ /** Physical transport returned only to the registry, never to a consumer. */
51
+ export declare interface AnalyticsConnectionTransport {
52
+ readonly sink: AnalyticsSinkImpl;
53
+ readonly probe: Effect.Effect<void, KvError>;
54
+ readonly close: Effect.Effect<void>;
55
+ }
56
+
57
+ /**
58
+ * The underlying sink raised an error. `cause` is stringified for
59
+ * round-trip safety across the rpc boundary.
60
+ */
61
+ export declare class AnalyticsError extends AnalyticsError_base {
62
+ get message(): string;
63
+ }
64
+
65
+ declare const AnalyticsError_base: Schema.Class<AnalyticsError, Schema.TaggedStruct<"AnalyticsError", {
66
+ readonly provider: Schema.String;
67
+ readonly operation: Schema.String;
68
+ readonly cause: Schema.String;
69
+ }>, YieldableError>;
70
+
71
+ /**
72
+ * One analytics event. `properties` must be JSON-serialisable —
73
+ * individual sinks validate at write time (Postgres stores as JSONB;
74
+ * ClickHouse encodes as a JSON string; PostHog forwards verbatim).
75
+ */
76
+ export declare interface AnalyticsEvent {
77
+ /** Event name (e.g. `'match_completed'`, `'page_viewed'`). */
78
+ readonly name: string;
79
+ /** Subject id (typically `ctx.request.subject.id`) — anonymous when null. */
80
+ readonly subjectId?: string | null;
81
+ /**
82
+ * Owning tenant. Normally you DON'T set this by hand — `useAnalytics()`
83
+ * stamps it from the caller's `subject.tenantId` so every event is
84
+ * tenant-attributable + every read is tenant-scoped (the multi-tenant
85
+ * isolation default). Set it explicitly only for a cross-tenant system
86
+ * write. `null` = no tenant (system/anonymous).
87
+ */
88
+ readonly tenantId?: string | null;
89
+ /** Property bag. Values must be JSON-serialisable. */
90
+ readonly properties: Readonly<Record<string, unknown>>;
91
+ /** When the event occurred. Defaults to server-now when omitted. */
92
+ readonly occurredAt?: Date;
93
+ }
94
+
95
+ export declare type AnalyticsFailure = AnalyticsError | AnalyticsCapabilityNotSupported;
96
+
97
+ /** Metric to compute. `count` = row count, `unique` = distinct subjectId,
98
+ * `sum` / `avg` = numeric aggregation on a property field. */
99
+ export declare type AnalyticsMetric = 'count' | 'unique' | {
100
+ readonly sum: string;
101
+ } | {
102
+ readonly avg: string;
103
+ };
104
+
105
+ /**
106
+ * Optional CDC-mirror capability. A sink that declares `mirror` opts in
107
+ * to receiving the main DataStore's reactive-table changes, so
108
+ * analytical queries can JOIN warehouse events against live user data
109
+ * (`SELECT … FROM voltro_events e JOIN users u ON …`).
110
+ *
111
+ * The framework subscribes to `DataStore.onChange` once at boot, filters
112
+ * to the tables the sink lists in `tables`, and routes each change here.
113
+ * Inserts/updates `upsert` by primary key; deletes `remove`. Every call
114
+ * MUST be idempotent — the same change may be re-delivered after a
115
+ * reconnect, and the upsert is keyed on `primaryKey` so re-applying a
116
+ * row is a no-op-equivalent overwrite.
117
+ *
118
+ * Idempotent is not enough on its own: re-delivery can also arrive OUT
119
+ * OF ORDER, so both methods additionally receive a {@link MirrorVersion}
120
+ * and MUST ignore a write whose version is not greater than the version
121
+ * already stored for that key (ClickHouse gets this from
122
+ * `ReplacingMergeTree(version)`; DuckDB from an `ON CONFLICT … WHERE
123
+ * excluded.version > version` guard).
124
+ */
125
+ export declare interface AnalyticsMirrorImpl {
126
+ /** Reactive tables this sink mirrors. The framework only forwards
127
+ * changes whose `table` is in this set. */
128
+ readonly tables: ReadonlyArray<string>;
129
+ /** Primary-key column the sink upserts/deletes by. Defaults to `'id'`
130
+ * when omitted. */
131
+ readonly primaryKey?: string;
132
+ /** Apply an insert/update. Idempotent upsert keyed on `primaryKey`,
133
+ * version-guarded by `write.version`. */
134
+ readonly upsert: (write: AnalyticsMirrorUpsert) => Effect.Effect<void, AnalyticsFailure>;
135
+ /** Apply a delete. Idempotent — removing an absent key is a no-op —
136
+ * and version-guarded by `write.version`. */
137
+ readonly remove: (write: AnalyticsMirrorRemove) => Effect.Effect<void, AnalyticsFailure>;
138
+ /**
139
+ * The highest version this sink has PERSISTED for one key — `null` when
140
+ * the key has never been written (a tombstoned key still reports its
141
+ * tombstone's version; both warehouse sinks keep tombstones for exactly
142
+ * this reason).
143
+ *
144
+ * This is the fleet-scope seed read: a replica that joins mid-stream has
145
+ * no per-key history of its own, so before its first write to a key it
146
+ * asks the warehouse where the fleet already is and continues from
147
+ * there. Without it a joiner would restart the key's versions low and
148
+ * every write it produced would be silently rejected by the sink's own
149
+ * `version >` guard. Called at most once per key per process (off the
150
+ * commit path, inside the per-key drain worker), never per change.
151
+ */
152
+ readonly maxVersion: (input: {
153
+ readonly table: string;
154
+ readonly primaryKeyValue: unknown;
155
+ }) => Effect.Effect<MirrorVersion | null, AnalyticsFailure>;
156
+ }
157
+
158
+ /** One delete to apply to the mirror. */
159
+ export declare interface AnalyticsMirrorRemove {
160
+ readonly table: string;
161
+ /** Primary-key value of the removed row. */
162
+ readonly primaryKeyValue: unknown;
163
+ /** See {@link MirrorVersion}. A delete is ordered against upserts of
164
+ * the same key by exactly this number. */
165
+ readonly version: MirrorVersion;
166
+ }
167
+
168
+ /** One insert/update to apply to the mirror. */
169
+ export declare interface AnalyticsMirrorUpsert {
170
+ readonly table: string;
171
+ /** Post-image of the changed row. */
172
+ readonly row: Readonly<Record<string, unknown>>;
173
+ /** See {@link MirrorVersion} — persist it, compare against it, never
174
+ * replace it with a local clock reading. */
175
+ readonly version: MirrorVersion;
176
+ }
177
+
178
+ declare type AnalyticsOptions = ({
179
+ readonly provider: 'duckdb';
180
+ readonly path?: string;
181
+ } & MirrorOptions) | ({
182
+ readonly provider: 'clickhouse';
183
+ readonly url: string;
184
+ readonly username?: string;
185
+ readonly password?: string;
186
+ readonly database?: string;
187
+ readonly table?: string;
188
+ readonly batch?: AnalyticsConnectionBatch | false;
189
+ } & MirrorOptions) | {
190
+ readonly provider: 'tinybird';
191
+ readonly token: string;
192
+ readonly apiUrl?: string;
193
+ readonly region?: string;
194
+ readonly datasource?: string;
195
+ readonly pipes?: {
196
+ readonly aggregate?: string;
197
+ readonly timeseries?: string;
198
+ readonly topN?: string;
199
+ };
200
+ } | {
201
+ readonly provider: 'posthog';
202
+ readonly apiKey: string;
203
+ readonly host?: string;
204
+ readonly batch?: AnalyticsConnectionBatch;
205
+ };
206
+
207
+ /**
208
+ * The runtime contract every analytics provider implements. Plugins
209
+ * build a value of this shape inside their factory function and the
210
+ * framework wires it under `AnalyticsSink` for handler code to consume.
211
+ */
212
+ export declare interface AnalyticsSinkImpl {
213
+ readonly info: AnalyticsSinkInfo;
214
+ readonly track: (event: AnalyticsEvent) => Effect.Effect<void, AnalyticsFailure>;
215
+ readonly aggregate: (query: AggregateQuery) => Effect.Effect<number, AnalyticsFailure>;
216
+ readonly timeseries: (query: TimeseriesQuery) => Effect.Effect<ReadonlyArray<AnalyticsBucket>, AnalyticsFailure>;
217
+ readonly topN: (query: TopNQuery) => Effect.Effect<ReadonlyArray<AnalyticsTopEntry>, AnalyticsFailure>;
218
+ /**
219
+ * Present when the sink mirrors reactive tables (opt-in via the
220
+ * plugin's `mirrorTables` config). Absent → no mirroring; the
221
+ * framework skips wiring `DataStore.onChange` for this sink.
222
+ */
223
+ readonly mirror?: AnalyticsMirrorImpl;
224
+ }
225
+
226
+ /**
227
+ * Per-sink metadata surfaced via the manifest endpoint + dashboard.
228
+ * Lets the operator see at a glance which provider is wired + what
229
+ * it supports without grepping app.config.ts.
230
+ */
231
+ export declare interface AnalyticsSinkInfo {
232
+ readonly provider: string;
233
+ readonly capabilities: {
234
+ readonly track: boolean;
235
+ readonly aggregate: boolean;
236
+ readonly timeseries: boolean;
237
+ readonly topN: boolean;
238
+ /** True when the sink mirrors reactive tables into the warehouse so
239
+ * analytical queries can JOIN events against live user data. */
240
+ readonly mirror: boolean;
241
+ };
242
+ /** Optional plugin-supplied detail (`{ ingestBufferDepth: 42 }`,
243
+ * `{ url: 'https://eu.posthog.com' }`, etc.) — surfaced verbatim. */
244
+ readonly detail?: Readonly<Record<string, unknown>>;
245
+ }
246
+
247
+ export declare interface AnalyticsTopEntry {
248
+ readonly key: string;
249
+ readonly value: number;
250
+ }
251
+
252
+ declare interface ConnectionMetadata {
253
+ readonly tls?: 'auto' | 'required';
254
+ readonly persistence?: 'required' | 'optional' | 'none';
255
+ readonly keyPrefix?: string;
256
+ /** Explicit opt-in when a production connection intentionally uses a shared namespace. */
257
+ readonly allowSharedNamespace?: boolean;
258
+ readonly region?: string;
259
+ readonly label?: string;
260
+ readonly acl?: string;
261
+ }
262
+
263
+ declare class KvError extends KvError_base {
264
+ get message(): string;
265
+ }
266
+
267
+ declare const KvError_base: Schema.Class<KvError, Schema.TaggedStruct<"KvError", {
268
+ /** Which op failed: 'get' | 'set' | 'delete' | 'has' | 'clear' |
269
+ * 'list' | 'connect' | 'serialize' | 'deserialize'. */
270
+ readonly operation: Schema.String;
271
+ /** The key (or connection name) involved; '' where not applicable. */
272
+ readonly key: Schema.String;
273
+ /** `String(originalError)` — round-trip-safe diagnostic form. */
274
+ readonly cause: Schema.String;
275
+ }>, YieldableError>;
276
+
277
+ declare interface MirrorOptions {
278
+ readonly mirrorTables?: ReadonlyArray<string>;
279
+ readonly mirrorPrimaryKey?: string;
280
+ }
281
+
282
+ /**
283
+ * Ordering token for one mirrored write.
284
+ *
285
+ * **The sink MUST persist this and MUST NOT invent its own.** It is
286
+ * assigned by the framework at the moment the change leaves the store's
287
+ * change channel — i.e. in COMMIT order — so a write with a lower
288
+ * version is, by construction, an older image of that row. Stamping a
289
+ * version at sink-call time instead (a `Date.now()` inside `upsert`)
290
+ * makes a late-arriving STALE image outrank the fresh one and win
291
+ * permanently; that is the bug this type exists to make unrepresentable.
292
+ *
293
+ * How the number is derived depends on the store's `changeScope`:
294
+ *
295
+ * - `'local'` (one process observes each change): a hybrid clock —
296
+ * wall-clock microseconds forced strictly upward, so it orders within
297
+ * a millisecond and keeps increasing across a restart.
298
+ * - `'fleet'` (every replica observes every change): derived from the
299
+ * CHANGE, not from the receiving process — the warehouse's own max
300
+ * version for the key (read once via {@link AnalyticsMirrorImpl.maxVersion})
301
+ * plus the change's per-key position in the totally-ordered fleet
302
+ * stream. N replicas mirroring the same change therefore stamp the
303
+ * SAME number, and the sink's version guard dedupes the N duplicate
304
+ * writes for free — no leader, no clock, no skew hazard.
305
+ */
306
+ export declare type MirrorVersion = number;
307
+
308
+ /** Exact-match property filter. Values are AND'd. */
309
+ export declare type PropertyFilter = Readonly<Record<string, string | number | boolean | null>>;
310
+
311
+ export declare type TimeBucket = 'minute' | 'hour' | 'day' | 'week' | 'month';
312
+
313
+ /** Time window. `to` defaults to now. */
314
+ export declare interface TimeRange {
315
+ readonly from: Date;
316
+ readonly to?: Date;
317
+ }
318
+
319
+ export declare interface TimeseriesQuery extends AggregateQuery {
320
+ readonly bucket: TimeBucket;
321
+ }
322
+
323
+ export declare interface TopNQuery {
324
+ readonly event: string;
325
+ /** Property to group by (must be a string-valued property). */
326
+ readonly groupBy: string;
327
+ readonly metric: 'count' | {
328
+ readonly sum: string;
329
+ };
330
+ readonly n: number;
331
+ readonly range: TimeRange;
332
+ readonly filter?: PropertyFilter;
333
+ /** Tenant scope — injected by `useAnalytics()`; see {@link AggregateQuery.tenantId}. */
334
+ readonly tenantId?: string | null;
335
+ }
336
+
337
+ export { }
@@ -0,0 +1,16 @@
1
+ import { Schema as e } from "effect";
2
+ //#region src/connectionAnalytics.ts
3
+ var t = class extends e.TaggedError()("AnalyticsError", {
4
+ provider: e.String,
5
+ operation: e.String,
6
+ cause: e.String
7
+ }) {
8
+ get message() {
9
+ return this.cause;
10
+ }
11
+ }, n = class extends e.TaggedError()("AnalyticsCapabilityNotSupported", {
12
+ provider: e.String,
13
+ capability: e.String
14
+ }) {};
15
+ //#endregion
16
+ export { n as AnalyticsCapabilityNotSupported, t as AnalyticsError };
@@ -0,0 +1,247 @@
1
+ //#region src/cacheAtomicProtocol.ts
2
+ var e = class extends Error {
3
+ constructor() {
4
+ super("Invalid atomic cache command or state."), this.name = "CacheAtomicProtocolError";
5
+ }
6
+ }, t = 9e5, n = () => {
7
+ throw new e();
8
+ }, r = (e, t) => {
9
+ if (typeof e != "object" || !e || Array.isArray(e) || Object.getPrototypeOf(e) !== Object.prototype && Object.getPrototypeOf(e) !== null) return n();
10
+ let r = e;
11
+ return Object.keys(r).length !== t.length || t.some((e) => !Object.hasOwn(r, e)) ? n() : r;
12
+ }, i = (e) => typeof e == "string" ? e : n(), a = (e) => {
13
+ let t = i(e);
14
+ return /^(?:[a-f0-9]{2})+$/.test(t) ? t : n();
15
+ }, o = (e) => {
16
+ let t = i(e);
17
+ return /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(t) ? t : n();
18
+ }, s = (e, t, r) => {
19
+ if (!Array.isArray(e)) return n();
20
+ let i = e.map(t);
21
+ return i.some((e, t) => t > 0 && r(i[t - 1]) >= r(e)) ? n() : i;
22
+ }, c = (e) => s(e, a, (e) => e), l = (e) => Array.isArray(e) ? e : typeof e == "object" && e && Object.keys(e).length === 0 && (Object.getPrototypeOf(e) === Object.prototype || Object.getPrototypeOf(e) === null) ? [] : n(), u = (e) => {
23
+ if (e === null) return null;
24
+ let t = r(e, ["version", "tags"]);
25
+ return {
26
+ version: o(t.version),
27
+ tags: c(l(t.tags))
28
+ };
29
+ }, d = (e) => {
30
+ let t = i(e);
31
+ return t[36] === ":" ? {
32
+ version: o(t.slice(0, 36)),
33
+ key: a(t.slice(37))
34
+ } : n();
35
+ }, f = (e) => typeof e == "string" ? Array.from(new TextEncoder().encode(JSON.stringify(e)), (e) => e.toString(16).padStart(2, "0")).join("") : n(), p = (e) => `{voltro:cache:atomic:${f(e)}}:r`, m = (e) => {
36
+ if (typeof e != "object" || !e || Array.isArray(e)) return n();
37
+ let t = e.op;
38
+ switch (t) {
39
+ case "revision": return r(e, ["op"]), { op: t };
40
+ case "fence": return r(e, ["op"]), { op: t };
41
+ case "inspect": return {
42
+ op: t,
43
+ key: a(r(e, ["op", "key"]).key)
44
+ };
45
+ case "inspect-values": return {
46
+ op: t,
47
+ keys: c(r(e, ["op", "keys"]).keys)
48
+ };
49
+ case "read": {
50
+ let n = r(e, [
51
+ "op",
52
+ "key",
53
+ "snapshot"
54
+ ]), i = u(n.snapshot);
55
+ return {
56
+ op: t,
57
+ key: a(n.key),
58
+ snapshot: i
59
+ };
60
+ }
61
+ case "tag": return {
62
+ op: t,
63
+ tag: a(r(e, ["op", "tag"]).tag)
64
+ };
65
+ case "scan": {
66
+ let a = i(r(e, ["op", "cursor"]).cursor);
67
+ return !/^(0|[1-9][0-9]{0,19})$/.test(a) || BigInt(a) > 18446744073709551615n ? n() : {
68
+ op: t,
69
+ cursor: a
70
+ };
71
+ }
72
+ case "write": {
73
+ let s = r(e, [
74
+ "op",
75
+ "key",
76
+ "snapshot",
77
+ "version",
78
+ "revision",
79
+ "tags",
80
+ "value",
81
+ "ttlMs",
82
+ "fence"
83
+ ]), l = u(s.snapshot), d = o(s.version);
84
+ if (d === l?.version) return n();
85
+ let f = s.ttlMs;
86
+ if (f !== null && (typeof f != "number" || !Number.isSafeInteger(f) || f < 0)) return n();
87
+ let p = s.fence;
88
+ return p !== null && (typeof p != "number" || !Number.isSafeInteger(p) || p < 0) ? n() : {
89
+ op: t,
90
+ key: a(s.key),
91
+ snapshot: l,
92
+ version: d,
93
+ revision: o(s.revision),
94
+ tags: c(s.tags),
95
+ value: i(s.value),
96
+ ttlMs: f,
97
+ fence: p
98
+ };
99
+ }
100
+ case "remove": {
101
+ let n = r(e, [
102
+ "op",
103
+ "key",
104
+ "snapshot",
105
+ "revision"
106
+ ]);
107
+ return {
108
+ op: t,
109
+ key: a(n.key),
110
+ snapshot: u(n.snapshot),
111
+ revision: o(n.revision)
112
+ };
113
+ }
114
+ case "invalidate": {
115
+ let c = r(e, [
116
+ "op",
117
+ "tag",
118
+ "members",
119
+ "snapshots",
120
+ "revision"
121
+ ]), l = s(c.members, (e) => (d(e), i(e)), (e) => e), f = s(c.snapshots, (e) => {
122
+ let t = r(e, ["key", "snapshot"]);
123
+ return {
124
+ key: a(t.key),
125
+ snapshot: u(t.snapshot)
126
+ };
127
+ }, (e) => e.key), p = [...new Set(l.map((e) => d(e).key))].sort();
128
+ return p.length !== f.length || p.some((e, t) => f[t].key !== e) ? n() : {
129
+ op: t,
130
+ tag: a(c.tag),
131
+ members: l,
132
+ snapshots: f,
133
+ revision: o(c.revision)
134
+ };
135
+ }
136
+ case "clear": {
137
+ let n = r(e, [
138
+ "op",
139
+ "keys",
140
+ "revision"
141
+ ]);
142
+ return {
143
+ op: t,
144
+ keys: s(n.keys, i, (e) => e),
145
+ revision: n.revision === null ? null : o(n.revision)
146
+ };
147
+ }
148
+ default: return n();
149
+ }
150
+ }, h = (e, t) => {
151
+ if (typeof e != "string" || !/\{voltro:cache:atomic:(?:[a-f0-9]{2})+\}:r$/.test(e)) return n();
152
+ let r = e.slice(0, -2), i = /* @__PURE__ */ new Set(), o = (e) => {
153
+ i.add(`${r}:v:${a(e)}`);
154
+ }, s = (e) => {
155
+ i.add(`${r}:t:${a(e)}`);
156
+ }, c = () => {
157
+ i.add(`${r}:s`);
158
+ }, l = (e) => {
159
+ i.add(`${r}:e:${a(e)}`);
160
+ };
161
+ switch (t.op) {
162
+ case "inspect":
163
+ o(t.key);
164
+ break;
165
+ case "inspect-values":
166
+ t.keys.forEach(o);
167
+ break;
168
+ case "read":
169
+ o(t.key), t.snapshot?.tags.forEach(s);
170
+ break;
171
+ case "fence":
172
+ c();
173
+ break;
174
+ case "write":
175
+ o(t.key), t.tags.forEach(s), t.snapshot?.tags.forEach(s), t.fence !== null && t.tags.forEach(l);
176
+ break;
177
+ case "remove":
178
+ o(t.key), t.snapshot?.tags.forEach(s);
179
+ break;
180
+ case "tag":
181
+ s(t.tag);
182
+ break;
183
+ case "invalidate":
184
+ s(t.tag), c(), l(t.tag), t.snapshots.forEach((e) => {
185
+ o(e.key), e.snapshot?.tags.forEach(s);
186
+ });
187
+ break;
188
+ case "clear": for (let o of t.keys) if (o !== e) {
189
+ if (!o.startsWith(`${r}:v:`) && !o.startsWith(`${r}:t:`)) return n();
190
+ a(o.slice(r.length + 3)), i.add(o);
191
+ }
192
+ }
193
+ return [e, ...[...i].sort()];
194
+ }, g = (e, t) => `${o(t)}:${a(e)}`, _ = (e) => e === void 0 ? null : !Number.isFinite(e) || !Number.isSafeInteger(Math.ceil(e)) ? n() : Math.max(0, Math.ceil(e)), v = (e) => {
195
+ if (typeof e != "object" || !e || Array.isArray(e) || Object.getPrototypeOf(e) !== Object.prototype && Object.getPrototypeOf(e) !== null) return n();
196
+ let t = e;
197
+ if (Object.keys(t).some((e) => e !== "maxTagMembers" && e !== "maxReplyBytes")) return n();
198
+ for (let [e, r] of Object.entries(t)) if (typeof r != "number" || !Number.isSafeInteger(r) || r < (e === "maxReplyBytes" ? 128 : 1)) return n();
199
+ return {
200
+ ...Object.hasOwn(t, "maxTagMembers") ? { maxTagMembers: t.maxTagMembers } : {},
201
+ ...Object.hasOwn(t, "maxReplyBytes") ? { maxReplyBytes: t.maxReplyBytes } : {}
202
+ };
203
+ }, y = (e) => {
204
+ if (typeof e != "string") return n();
205
+ let t;
206
+ try {
207
+ t = JSON.parse(e);
208
+ } catch {
209
+ return n();
210
+ }
211
+ let i = r(t, ["status", "value"]), a = i.status;
212
+ return a !== "ok" && a !== "conflict" && a !== "limit" || a !== "ok" && i.value !== null ? n() : {
213
+ status: a,
214
+ value: i.value
215
+ };
216
+ }, b = (e) => s(l(e), (e) => {
217
+ let t = r(e, ["key", "snapshot"]);
218
+ return {
219
+ key: a(t.key),
220
+ snapshot: u(t.snapshot)
221
+ };
222
+ }, (e) => e.key), x = (e, t) => {
223
+ let n = r(t, ["cursor", "keys"]), a = i(n.cursor);
224
+ m({
225
+ op: "scan",
226
+ cursor: a
227
+ });
228
+ let o = e.slice(0, -2), s = l(n.keys).map((e) => `${o}${i(e)}`).filter((e) => e !== `${o}:s` && !e.startsWith(`${o}:e:`));
229
+ return h(e, {
230
+ op: "clear",
231
+ keys: [...new Set(s)].sort(),
232
+ revision: null
233
+ }), {
234
+ cursor: a,
235
+ keys: s
236
+ };
237
+ }, S = (e) => [
238
+ "revision",
239
+ "fence",
240
+ "inspect",
241
+ "inspect-values",
242
+ "read",
243
+ "tag",
244
+ "scan"
245
+ ].includes(e.op);
246
+ //#endregion
247
+ export { t as CACHE_FENCE_RETENTION_MS, e as CacheAtomicProtocolError, l as cacheAtomicArray, f as cacheAtomicComponent, h as cacheAtomicKeys, d as cacheAtomicMember, g as cacheAtomicMembership, p as cacheAtomicRoot, x as cacheAtomicScanPage, u as cacheAtomicSnapshot, _ as cacheAtomicTtl, b as cacheAtomicValueSnapshots, m as decodeCacheAtomicCommand, v as decodeCacheAtomicPolicy, y as decodeCacheAtomicReply, S as isCacheAtomicRead };