@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.
- package/CHANGELOG.md +5321 -0
- package/README.md +110 -0
- package/THIRD-PARTY-NOTICES.md +864 -1
- package/dist/analytics.d.ts +337 -0
- package/dist/analytics.js +16 -0
- package/dist/cacheAtomicProtocol.js +247 -0
- package/dist/cacheAtomicScript.js +309 -0
- package/dist/cacheAtomicState.js +141 -0
- package/dist/connection.d.ts +990 -9
- package/dist/connection.js +330 -50
- package/dist/connectionAnalyticsConfig.js +153 -0
- package/dist/connectionConfigProvider.js +111 -0
- package/dist/connectionDefinitions.js +285 -0
- package/dist/connectionMemory.js +207 -0
- package/dist/connectionMetrics.js +79 -0
- package/dist/connectionNamespace.js +19 -0
- package/dist/connectionNats.js +148 -0
- package/dist/connectionNatsConfig.js +87 -0
- package/dist/connectionNatsSubjects.js +16 -0
- package/dist/connectionOwner.js +44 -0
- package/dist/connectionPool.js +738 -0
- package/dist/connectionPrefix.js +37 -0
- package/dist/connectionScripts.js +4 -0
- package/dist/connectionSqlConfig.js +119 -0
- package/dist/connectionStorageConfig.js +190 -0
- package/dist/connectionStorageFacade.js +78 -0
- package/dist/connectionSubscriber.js +130 -0
- package/dist/errors.js +5 -1
- package/dist/hot-contract.d.ts +297 -0
- package/dist/hot-contract.js +18 -0
- package/dist/hot-testing.d.ts +229 -0
- package/dist/hot-testing.js +1 -0
- package/dist/hot.d.ts +936 -0
- package/dist/hot.js +7 -0
- package/dist/hotMemory.js +46 -0
- package/dist/hotOperations.js +223 -0
- package/dist/hotRateLimit.js +103 -0
- package/dist/hotReadAvailability.js +19 -0
- package/dist/hotRedis.js +139 -0
- package/dist/hotRedisScript.js +4 -0
- package/dist/hotRouting.js +78 -0
- package/dist/hotState.js +182 -0
- package/dist/hotStoreConformance.js +367 -0
- package/dist/index.d.ts +113 -9
- package/dist/nativeRedisReads.js +78 -0
- package/dist/nativeRedisTopology.js +221 -0
- package/dist/redisDiagnostics.js +27 -0
- package/dist/redisReadPolicy.js +39 -0
- package/dist/sql.d.ts +77 -0
- package/dist/sql.js +0 -0
- package/dist/storage.d.ts +166 -0
- package/dist/storage.js +48 -0
- package/dist/storageAddress.js +20 -0
- 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 };
|