@voltro/kv 0.86.0 → 0.88.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 (61) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +110 -0
  3. package/THIRD-PARTY-NOTICES.md +2 -482
  4. package/dist/Kv.js +1 -2
  5. package/dist/KvStore.js +1 -2
  6. package/dist/analytics.d.ts +337 -0
  7. package/dist/analytics.js +15 -0
  8. package/dist/backendMemory.js +1 -2
  9. package/dist/backendRedis.js +1 -2
  10. package/dist/cacheAtomicProtocol.js +257 -0
  11. package/dist/cacheAtomicScript.js +326 -0
  12. package/dist/cacheAtomicState.js +140 -0
  13. package/dist/connection.d.ts +990 -9
  14. package/dist/connection.js +336 -52
  15. package/dist/connectionAnalyticsConfig.js +152 -0
  16. package/dist/connectionConfigError.js +10 -0
  17. package/dist/connectionConfigProvider.js +110 -0
  18. package/dist/connectionDefinitions.js +284 -0
  19. package/dist/connectionMemory.js +206 -0
  20. package/dist/connectionMetrics.js +78 -0
  21. package/dist/connectionNamespace.js +18 -0
  22. package/dist/connectionNats.js +148 -0
  23. package/dist/connectionNatsConfig.js +86 -0
  24. package/dist/connectionNatsSubjects.js +15 -0
  25. package/dist/connectionOwner.js +43 -0
  26. package/dist/connectionPool.js +738 -0
  27. package/dist/connectionPrefix.js +36 -0
  28. package/dist/connectionScripts.js +3 -0
  29. package/dist/connectionSqlConfig.js +118 -0
  30. package/dist/connectionStorageConfig.js +189 -0
  31. package/dist/connectionStorageFacade.js +77 -0
  32. package/dist/connectionSubscriber.js +130 -0
  33. package/dist/driverModule.js +9 -0
  34. package/dist/errors.js +6 -3
  35. package/dist/expirySweep.js +1 -2
  36. package/dist/hot-contract.d.ts +297 -0
  37. package/dist/hot-contract.js +17 -0
  38. package/dist/hot-testing.d.ts +242 -0
  39. package/dist/hot-testing.js +1 -0
  40. package/dist/hot.d.ts +936 -0
  41. package/dist/hot.js +7 -0
  42. package/dist/hotMemory.js +45 -0
  43. package/dist/hotOperations.js +222 -0
  44. package/dist/hotRateLimit.js +102 -0
  45. package/dist/hotReadAvailability.js +18 -0
  46. package/dist/hotRedis.js +138 -0
  47. package/dist/hotRedisScript.js +3 -0
  48. package/dist/hotRouting.js +77 -0
  49. package/dist/hotState.js +181 -0
  50. package/dist/hotStoreConformance.js +365 -0
  51. package/dist/index.d.ts +113 -9
  52. package/dist/nativeRedisReads.js +77 -0
  53. package/dist/nativeRedisTopology.js +222 -0
  54. package/dist/redisDiagnostics.js +26 -0
  55. package/dist/redisReadPolicy.js +38 -0
  56. package/dist/sql.d.ts +77 -0
  57. package/dist/sql.js +0 -0
  58. package/dist/storage.d.ts +166 -0
  59. package/dist/storage.js +47 -0
  60. package/dist/storageAddress.js +19 -0
  61. package/package.json +49 -6
@@ -0,0 +1,326 @@
1
+ import { CACHE_FENCE_RETENTION_MS as e } from "./cacheAtomicProtocol.js";
2
+ // src/cacheAtomicScript.ts
3
+ var t = String.raw`
4
+ local c = cjson.decode(ARGV[1])
5
+ local policy = ARGV[2] and cjson.decode(ARGV[2]) or {}
6
+ local function invalid() error('Invalid atomic cache command or state.') end
7
+ local function null(x) return x == nil or x == cjson.null end
8
+ local function reply(status, value)
9
+ local encoded = cjson.encode({ status = status, value = value == nil and cjson.null or value })
10
+ if policy.maxReplyBytes and string.len(encoded) > policy.maxReplyBytes then
11
+ return cjson.encode({ status = 'limit', value = cjson.null })
12
+ end
13
+ return encoded
14
+ end
15
+ local function id(s)
16
+ if type(s) ~= 'string' or #s == 0 or #s % 2 ~= 0 or not string.match(s, '^[a-f0-9]+$') then invalid() end
17
+ return s
18
+ end
19
+ local function version(s)
20
+ if type(s) ~= 'string' or #s ~= 36 or string.sub(s, 9, 9) ~= '-' or string.sub(s, 14, 14) ~= '-'
21
+ or string.sub(s, 19, 19) ~= '-' or string.sub(s, 24, 24) ~= '-' then invalid() end
22
+ local hex = string.gsub(s, '-', '')
23
+ if #hex ~= 32 or not string.match(hex, '^[a-f0-9]+$') then invalid() end
24
+ return s
25
+ end
26
+ local function array(a, check)
27
+ if type(a) ~= 'table' then invalid() end
28
+ for k, v in pairs(a) do
29
+ if type(k) ~= 'number' or k % 1 ~= 0 or k < 1 or k > #a then invalid() end
30
+ check(v)
31
+ end
32
+ return a
33
+ end
34
+ local function ids(a)
35
+ array(a, id)
36
+ for i = 2, #a do if a[i - 1] >= a[i] then invalid() end end
37
+ return a
38
+ end
39
+ local function snapshot(s)
40
+ if null(s) then return nil end
41
+ if type(s) ~= 'table' then invalid() end
42
+ version(s.version); ids(s.tags)
43
+ return s
44
+ end
45
+ local function member(s)
46
+ if type(s) ~= 'string' or string.sub(s, 37, 37) ~= ':' then invalid() end
47
+ return version(string.sub(s, 1, 36)), id(string.sub(s, 38))
48
+ end
49
+ local function equalTags(a, b)
50
+ if #a ~= #b then return false end
51
+ for i = 1, #a do if a[i] ~= b[i] then return false end end
52
+ return true
53
+ end
54
+ local function same(a, b)
55
+ if null(a) or null(b) then return null(a) and null(b) end
56
+ return a.version == b.version and equalTags(a.tags, b.tags)
57
+ end
58
+ local function contains(a, wanted)
59
+ for _, v in ipairs(a) do if v == wanted then return true end end
60
+ return false
61
+ end
62
+ if #KEYS < 1 or type(c) ~= 'table' or type(c.op) ~= 'string' then invalid() end
63
+ local function fields(value, names)
64
+ if type(value) ~= 'table' then invalid() end
65
+ local allowed = {}
66
+ for _, name in ipairs(names) do allowed[name] = true; if value[name] == nil then invalid() end end
67
+ for name, _ in pairs(value) do if not allowed[name] then invalid() end end
68
+ end
69
+ local layouts = {
70
+ revision = { 'op' }, fence = { 'op' }, inspect = { 'op', 'key' }, ['inspect-values'] = { 'op', 'keys' },
71
+ read = { 'op', 'key', 'snapshot' }, tag = { 'op', 'tag' }, scan = { 'op', 'cursor' },
72
+ write = { 'op', 'key', 'snapshot', 'version', 'revision', 'tags', 'value', 'ttlMs', 'fence' },
73
+ remove = { 'op', 'key', 'snapshot', 'revision' },
74
+ invalidate = { 'op', 'tag', 'members', 'snapshots', 'revision' },
75
+ clear = { 'op', 'keys', 'revision' },
76
+ }
77
+ if not layouts[c.op] then invalid() end
78
+ fields(c, layouts[c.op])
79
+ if type(policy) ~= 'table' then invalid() end
80
+ for name, value in pairs(policy) do
81
+ if name ~= 'maxReplyBytes' and name ~= 'maxTagMembers' then invalid() end
82
+ local minimum = name == 'maxReplyBytes' and 128 or 1
83
+ if type(value) ~= 'number' or value < minimum or value % 1 ~= 0 or value > 9007199254740991 then invalid() end
84
+ end
85
+ local root = KEYS[1]
86
+ if string.sub(root, -2) ~= ':r' then invalid() end
87
+ local space = string.sub(root, 1, #root - 2)
88
+ local declared = {}
89
+ for _, key in ipairs(KEYS) do if declared[key] then invalid() end; declared[key] = key end
90
+ local needed = { [root] = true }
91
+ local function needValue(k) needed[space .. ':v:' .. id(k)] = true end
92
+ local function needTag(t) needed[space .. ':t:' .. id(t)] = true end
93
+ local function needEpoch(t) needed[space .. ':e:' .. id(t)] = true end
94
+ local function counter(n)
95
+ if type(n) ~= 'number' or n < 0 or n % 1 ~= 0 or n > 9007199254740991 then invalid() end
96
+ return n
97
+ end
98
+ local function needSnapshot(s)
99
+ if not null(s) then fields(s, { 'version', 'tags' }) end
100
+ local value = snapshot(s)
101
+ if value then for _, tag in ipairs(value.tags) do needTag(tag) end end
102
+ end
103
+ if c.op == 'fence' then needed[space .. ':s'] = true
104
+ elseif c.op == 'inspect' then needValue(c.key)
105
+ elseif c.op == 'inspect-values' then ids(c.keys); for _, key in ipairs(c.keys) do needValue(key) end
106
+ elseif c.op == 'read' then needValue(c.key); needSnapshot(c.snapshot)
107
+ elseif c.op == 'write' then
108
+ needValue(c.key); needSnapshot(c.snapshot); ids(c.tags)
109
+ for _, tag in ipairs(c.tags) do needTag(tag) end
110
+ version(c.version); version(c.revision)
111
+ if not null(c.fence) then counter(c.fence); for _, tag in ipairs(c.tags) do needEpoch(tag) end end
112
+ if not null(c.snapshot) and c.snapshot.version == c.version then invalid() end
113
+ if type(c.value) ~= 'string' or not null(c.ttlMs) and (type(c.ttlMs) ~= 'number' or c.ttlMs < 0 or c.ttlMs % 1 ~= 0 or c.ttlMs > 9007199254740991) then invalid() end
114
+ elseif c.op == 'remove' then needValue(c.key); needSnapshot(c.snapshot); version(c.revision)
115
+ elseif c.op == 'tag' then needTag(c.tag)
116
+ elseif c.op == 'invalidate' then
117
+ needTag(c.tag); version(c.revision); needed[space .. ':s'] = true; needEpoch(c.tag)
118
+ local memberKeys = {}
119
+ array(c.members, function(m) local _, key = member(m); memberKeys[key] = true end)
120
+ for i = 2, #c.members do if c.members[i - 1] >= c.members[i] then invalid() end end
121
+ array(c.snapshots, function(s)
122
+ fields(s, { 'key', 'snapshot' }); needValue(s.key); needSnapshot(s.snapshot)
123
+ if not memberKeys[s.key] then invalid() end; memberKeys[s.key] = nil
124
+ end)
125
+ for _ in pairs(memberKeys) do invalid() end
126
+ for i = 2, #c.snapshots do if c.snapshots[i - 1].key >= c.snapshots[i].key then invalid() end end
127
+ elseif c.op == 'clear' then
128
+ if not null(c.revision) then version(c.revision) end
129
+ -- KEYS may carry a registry prefix; ARGV retains caller-logical keys.
130
+ -- The canonical cache token binds those two domains exactly, without
131
+ -- accepting a foreign connection prefix or another cache namespace.
132
+ local logicalRoot = string.match(root, '({voltro:cache:atomic:[a-f0-9]+}:r)$')
133
+ if not logicalRoot then invalid() end
134
+ id(string.match(logicalRoot, '^{voltro:cache:atomic:([a-f0-9]+)}:r$'))
135
+ local logicalSpace = string.sub(logicalRoot, 1, #logicalRoot - 2)
136
+ local selected = {}
137
+ array(c.keys, function(key)
138
+ if type(key) ~= 'string' then invalid() end
139
+ local kind = string.sub(key, 1, #space + 3)
140
+ if key ~= root and kind ~= space .. ':v:' and kind ~= space .. ':t:' then
141
+ local logicalKind = string.sub(key, 1, #logicalSpace + 3)
142
+ if key == logicalRoot then key = root
143
+ elseif logicalKind == logicalSpace .. ':v:' or logicalKind == logicalSpace .. ':t:' then
144
+ key = space .. string.sub(key, #logicalSpace + 1)
145
+ else invalid() end
146
+ end
147
+ if selected[key] then invalid() end
148
+ selected[key] = true
149
+ if key ~= root then
150
+ local prefix = string.sub(key, 1, #space + 3)
151
+ if prefix ~= space .. ':v:' and prefix ~= space .. ':t:' then invalid() end
152
+ id(string.sub(key, #space + 4)); needed[key] = true
153
+ end
154
+ end)
155
+ for i = 2, #c.keys do if c.keys[i - 1] >= c.keys[i] then invalid() end end
156
+ elseif c.op == 'scan' then
157
+ if type(c.cursor) ~= 'string' or not string.match(c.cursor, '^%d+$') or #c.cursor > 20
158
+ or #c.cursor > 1 and string.sub(c.cursor, 1, 1) == '0'
159
+ or #c.cursor == 20 and c.cursor > '18446744073709551615' then invalid() end
160
+ elseif c.op ~= 'revision' then invalid() end
161
+ for key, _ in pairs(needed) do if not declared[key] then invalid() end end
162
+ for key, _ in pairs(declared) do if not needed[key] then invalid() end end
163
+ local function valueKey(k) local key = declared[space .. ':v:' .. k]; if not key then invalid() end; return key end
164
+ local function tagKey(t) local key = declared[space .. ':t:' .. t]; if not key then invalid() end; return key end
165
+ local function epochKey(t) local key = declared[space .. ':e:' .. t]; if not key then invalid() end; return key end
166
+ local function sequence()
167
+ local key = declared[space .. ':s']; if not key then invalid() end
168
+ local raw = redis.call('GET', key)
169
+ if not raw then return 0, key end
170
+ return counter(tonumber(raw)), key
171
+ end
172
+ local RETENTION = ${e}
173
+ local function keyType(k)
174
+ local t = redis.call('TYPE', k)
175
+ return type(t) == 'table' and t.ok or t
176
+ end
177
+ for _, key in ipairs(KEYS) do
178
+ if c.op ~= 'clear' then
179
+ local t = keyType(key)
180
+ local kind = string.sub(key, #space + 1, #space + 3)
181
+ local wanted = key == root or key == space .. ':s' or kind == ':v:' or kind == ':e:'
182
+ if t ~= 'none' and (wanted and t ~= 'string' or not wanted and t ~= 'zset') then invalid() end
183
+ end
184
+ end
185
+ local function entry(k)
186
+ local raw = redis.call('GET', valueKey(k))
187
+ if not raw then return nil end
188
+ local e = cjson.decode(raw)
189
+ fields(e, { 'version', 'tags', 'value' })
190
+ snapshot(e)
191
+ if type(e.value) ~= 'string' then invalid() end
192
+ return e
193
+ end
194
+ local function meta(e) return e and { version = e.version, tags = e.tags } or cjson.null end
195
+ local tm = redis.call('TIME')
196
+ local now = tonumber(tm[1]) * 1000 + math.floor(tonumber(tm[2]) / 1000)
197
+ local NOEXP = 9000000000000000
198
+ if c.op == 'write' and not null(c.ttlMs) and c.ttlMs > NOEXP - now - 1 then invalid() end
199
+ local function touch(tag)
200
+ local key = tagKey(tag)
201
+ redis.call('ZREMRANGEBYSCORE', key, '-inf', string.format('%.0f', now))
202
+ local top = redis.call('ZREVRANGE', key, 0, 0, 'WITHSCORES')
203
+ if #top == 0 then redis.call('DEL', key)
204
+ elseif tonumber(top[2]) >= NOEXP then redis.call('PERSIST', key)
205
+ else redis.call('PEXPIRE', key, math.max(1, math.ceil(tonumber(top[2]) - now))) end
206
+ end
207
+ local function detach(k, e)
208
+ local m = e.version .. ':' .. k
209
+ for _, tag in ipairs(e.tags) do redis.call('ZREM', tagKey(tag), m); touch(tag) end
210
+ end
211
+ if c.op == 'revision' then
212
+ return reply('ok', redis.call('GET', root) or cjson.null)
213
+ elseif c.op == 'fence' then
214
+ local n = sequence()
215
+ return reply('ok', n)
216
+ elseif c.op == 'inspect' then return reply('ok', meta(entry(c.key)))
217
+ elseif c.op == 'inspect-values' then
218
+ local out = {}
219
+ for _, key in ipairs(c.keys) do table.insert(out, { key = key, snapshot = meta(entry(key)) }) end
220
+ return reply('ok', out)
221
+ elseif c.op == 'tag' then return reply('ok', redis.call('ZRANGE', tagKey(c.tag), 0, -1))
222
+ elseif c.op == 'scan' then
223
+ local escaped = {}
224
+ for i = 1, #space do
225
+ local char = string.sub(space, i, i)
226
+ if char == '\\' or char == '*' or char == '?' or char == '[' or char == ']' then table.insert(escaped, '\\') end
227
+ table.insert(escaped, char)
228
+ end
229
+ local page = redis.call('SCAN', c.cursor, 'MATCH', table.concat(escaped) .. ':*', 'COUNT', 200)
230
+ local relative = {}
231
+ for _, key in ipairs(page[2]) do table.insert(relative, string.sub(key, #space + 1)) end
232
+ return reply('ok', { cursor = page[1], keys = relative })
233
+ elseif c.op == 'read' then
234
+ local e = entry(c.key)
235
+ if not e then return reply('ok') end
236
+ if not same(e, c.snapshot) then return reply('conflict') end
237
+ local m = e.version .. ':' .. c.key
238
+ for _, tag in ipairs(e.tags) do
239
+ local score = redis.call('ZSCORE', tagKey(tag), m)
240
+ if not score or tonumber(score) <= now then return reply('ok') end
241
+ end
242
+ return reply('ok', e.value)
243
+ elseif c.op == 'write' or c.op == 'remove' then
244
+ -- A compute fenced before an invalidation of one of its tags is void: its
245
+ -- value may predate the write that invalidated it. Nothing changes.
246
+ if c.op == 'write' and not null(c.fence) then
247
+ for _, tag in ipairs(c.tags) do
248
+ local raw = redis.call('GET', epochKey(tag))
249
+ if raw and counter(tonumber(raw)) > c.fence then return reply('ok', 0) end
250
+ end
251
+ end
252
+ local old = entry(c.key)
253
+ if not same(old, c.snapshot) then return reply('conflict') end
254
+ if c.op == 'remove' or c.ttlMs == 0 then
255
+ if not old then return reply('ok', 0) end
256
+ redis.call('SET', root, c.revision)
257
+ local removed = redis.call('DEL', valueKey(c.key))
258
+ detach(c.key, old)
259
+ return reply('ok', removed)
260
+ end
261
+ if policy.maxTagMembers then
262
+ local oldMember = old and old.version .. ':' .. c.key or nil
263
+ local nextMember = c.version .. ':' .. c.key
264
+ for _, tag in ipairs(c.tags) do
265
+ local key = tagKey(tag)
266
+ local count = redis.call('ZCOUNT', key, '(' .. string.format('%.0f', now), '+inf')
267
+ local prior = oldMember and redis.call('ZSCORE', key, oldMember) or nil
268
+ local next = redis.call('ZSCORE', key, nextMember)
269
+ if prior and tonumber(prior) > now then count = count - 1 end
270
+ if not next or tonumber(next) <= now then count = count + 1 end
271
+ if count > policy.maxTagMembers then return reply('limit') end
272
+ end
273
+ end
274
+ -- Revise first: even an interrupted/erroring writer invalidates clear snapshots.
275
+ redis.call('SET', root, c.revision)
276
+ local expires = null(c.ttlMs) and NOEXP or now + c.ttlMs
277
+ local m = c.version .. ':' .. c.key
278
+ -- Prepare independent new memberships. No old live membership is removed yet.
279
+ for _, tag in ipairs(c.tags) do redis.call('ZADD', tagKey(tag), expires, m); touch(tag) end
280
+ local raw = cjson.encode({ version = c.version, tags = c.tags, value = c.value })
281
+ if null(c.ttlMs) then redis.call('SET', valueKey(c.key), raw)
282
+ else redis.call('PSETEX', valueKey(c.key), c.ttlMs, raw) end
283
+ if old then detach(c.key, old) end
284
+ return reply('ok', 1)
285
+ elseif c.op == 'invalidate' then
286
+ local tag = tagKey(c.tag)
287
+ if redis.call('ZCARD', tag) ~= #c.members then return reply('conflict') end
288
+ for _, m in ipairs(c.members) do if not redis.call('ZSCORE', tag, m) then return reply('conflict') end end
289
+ local expected = {}
290
+ for _, s in ipairs(c.snapshots) do expected[s.key] = s.snapshot end
291
+ local selected = {}
292
+ for _, m in ipairs(c.members) do
293
+ local v, k = member(m)
294
+ if not selected[k] then
295
+ local e = entry(k)
296
+ if e and e.version == v and contains(e.tags, c.tag) then
297
+ if not same(e, expected[k]) then return reply('conflict') end
298
+ selected[k] = e
299
+ end
300
+ end
301
+ end
302
+ redis.call('SET', root, c.revision)
303
+ local n, sequenceKey = sequence()
304
+ redis.call('SET', sequenceKey, n + 1)
305
+ redis.call('SET', epochKey(c.tag), n + 1, 'PX', RETENTION)
306
+ local removed = 0
307
+ for k, e in pairs(selected) do
308
+ removed = removed + redis.call('DEL', valueKey(k))
309
+ detach(k, e)
310
+ end
311
+ redis.call('DEL', tag)
312
+ return reply('ok', removed)
313
+ elseif c.op == 'clear' then
314
+ local observed = redis.call('GET', root) or cjson.null
315
+ if observed ~= c.revision then return reply('conflict') end
316
+ -- DEL is bounded per native command; all keys were declared before execution.
317
+ for first = 1, #KEYS, 500 do
318
+ local batch = {}
319
+ for i = first, math.min(first + 499, #KEYS) do table.insert(batch, KEYS[i]) end
320
+ redis.call('DEL', unpack(batch))
321
+ end
322
+ return reply('ok')
323
+ end
324
+ invalid()
325
+ `;
326
+ export { t as CACHE_ATOMIC_SCRIPT };
@@ -0,0 +1,140 @@
1
+ import { CACHE_FENCE_RETENTION_MS as e, CacheAtomicProtocolError as t, cacheAtomicKeys as n, cacheAtomicMember as r, cacheAtomicMembership as i, cacheAtomicSnapshot as a, decodeCacheAtomicCommand as o, decodeCacheAtomicPolicy as s } from "./cacheAtomicProtocol.js";
2
+ // src/cacheAtomicState.ts
3
+ var c = 9e15, l = () => {
4
+ throw new t();
5
+ }, u = (t, u, d, f = {}) => {
6
+ let p = o(d), m = s(f);
7
+ if (!Number.isSafeInteger(t.now) || t.now < 0 || t.now >= c) return l();
8
+ let h = u[0];
9
+ if (h === void 0) return l();
10
+ let g = n(h, p), _ = new Set(u);
11
+ if (u.length !== g.length || _.size !== u.length || g.some((e) => !_.has(e))) return l();
12
+ let v = h.slice(0, -2), y = (e) => `${v}:v:${e}`, b = (e) => `${v}:t:${e}`, x = `${v}:s`, S = (e) => `${v}:e:${e}`, C = (e) => e === h || e === x || e.startsWith(`${v}:v:`) || e.startsWith(`${v}:e:`);
13
+ if (p.op !== "clear") for (let e of u) {
14
+ let n = t.kind(e);
15
+ if (n !== "none" && n !== (C(e) ? "string" : "zset")) return l();
16
+ }
17
+ let w = (e) => {
18
+ if (e === null) return 0;
19
+ let t = Number(e);
20
+ return Number.isSafeInteger(t) && t >= 0 ? t : l();
21
+ }, T = (e) => {
22
+ let n = t.get(y(e));
23
+ if (n === null) return null;
24
+ let r;
25
+ try {
26
+ r = JSON.parse(n);
27
+ } catch {
28
+ return l();
29
+ }
30
+ if (typeof r != "object" || !r || Array.isArray(r)) return l();
31
+ let i = r;
32
+ if (Object.keys(i).length !== 3 || typeof i.value != "string") return l();
33
+ let o = a({
34
+ version: i.version,
35
+ tags: i.tags
36
+ });
37
+ return o === null ? l() : {
38
+ ...o,
39
+ value: i.value
40
+ };
41
+ }, E = (e, t) => e === null || t === null ? e === t : e.version === t.version && e.tags.length === t.tags.length && e.tags.every((e, n) => e === t.tags[n]), D = (e) => e === null ? null : {
42
+ version: e.version,
43
+ tags: e.tags
44
+ }, O = (e, t = null) => {
45
+ let n = JSON.stringify({
46
+ status: e,
47
+ value: t
48
+ });
49
+ return m.maxReplyBytes !== void 0 && new TextEncoder().encode(n).length > m.maxReplyBytes ? JSON.stringify({
50
+ status: "limit",
51
+ value: null
52
+ }) : n;
53
+ }, k = (e) => {
54
+ let n = b(e);
55
+ for (let e of t.members(n)) e.score <= t.now && t.removeMember(n, e.member);
56
+ let r = t.members(n);
57
+ if (r.length === 0) {
58
+ t.remove(n);
59
+ return;
60
+ }
61
+ let i = -Infinity;
62
+ for (let e of r) i = Math.max(i, e.score);
63
+ t.expire(n, i >= c ? null : Math.max(1, Math.ceil(i - t.now)));
64
+ }, A = (e, n) => {
65
+ for (let r of n.tags) t.removeMember(b(r), i(e, n.version)), k(r);
66
+ }, j = (e) => t.set(h, e.revision);
67
+ switch (p.op) {
68
+ case "revision": return O("ok", t.get(h));
69
+ case "fence": return O("ok", w(t.get(x)));
70
+ case "inspect": return O("ok", D(T(p.key)));
71
+ case "inspect-values": return O("ok", p.keys.map((e) => ({
72
+ key: e,
73
+ snapshot: D(T(e))
74
+ })));
75
+ case "tag": return O("ok", t.members(b(p.tag)).slice().sort((e, t) => e.score - t.score || (e.member < t.member ? -1 : +(e.member > t.member))).map((e) => e.member));
76
+ case "scan": return p.cursor === "0" ? O("ok", {
77
+ cursor: "0",
78
+ keys: t.keys().filter((e) => e.startsWith(`${v}:`)).map((e) => e.slice(v.length))
79
+ }) : l();
80
+ case "read": {
81
+ let e = T(p.key);
82
+ if (e === null) return O("ok");
83
+ if (!E(e, p.snapshot)) return O("conflict");
84
+ let n = i(p.key, e.version);
85
+ return e.tags.some((e) => !t.members(b(e)).some((e) => e.member === n && e.score > t.now)) ? O("ok") : O("ok", e.value);
86
+ }
87
+ case "write":
88
+ case "remove": {
89
+ if (p.op === "write" && p.fence !== null) {
90
+ let e = p.fence;
91
+ if (p.tags.some((n) => w(t.get(S(n))) > e)) return O("ok", 0);
92
+ }
93
+ let e = T(p.key);
94
+ if (!E(e, p.snapshot)) return O("conflict");
95
+ if (p.op === "remove" || p.ttlMs === 0) {
96
+ if (e === null) return O("ok", 0);
97
+ j(p);
98
+ let n = t.remove(y(p.key));
99
+ return A(p.key, e), O("ok", n);
100
+ }
101
+ if (p.ttlMs !== null && p.ttlMs > c - t.now - 1) return l();
102
+ if (m.maxTagMembers !== void 0) for (let n of p.tags) {
103
+ let r = t.members(b(n)).filter((e) => e.score > t.now), a = e === null ? null : i(p.key, e.version), o = i(p.key, p.version);
104
+ if (r.length - +!!r.some((e) => e.member === a) + +!r.some((e) => e.member === o) > m.maxTagMembers) return O("limit");
105
+ }
106
+ j(p);
107
+ for (let e of p.tags) t.addMember(b(e), i(p.key, p.version), p.ttlMs === null ? c : t.now + p.ttlMs), k(e);
108
+ return t.set(y(p.key), JSON.stringify({
109
+ version: p.version,
110
+ tags: p.tags,
111
+ value: p.value
112
+ }), p.ttlMs ?? void 0), e !== null && A(p.key, e), O("ok", 1);
113
+ }
114
+ case "invalidate": {
115
+ let n = t.members(b(p.tag));
116
+ if (n.length !== p.members.length || p.members.some((e) => !n.some((t) => t.member === e))) return O("conflict");
117
+ let i = /* @__PURE__ */ new Map(), a = new Map(p.snapshots.map((e) => [e.key, e.snapshot]));
118
+ for (let e of p.members) {
119
+ let t = r(e);
120
+ if (i.has(t.key)) continue;
121
+ let n = T(t.key);
122
+ if (n !== null && n.version === t.version && n.tags.includes(p.tag)) {
123
+ if (!E(n, a.get(t.key) ?? null)) return O("conflict");
124
+ i.set(t.key, n);
125
+ }
126
+ }
127
+ j(p);
128
+ let o = w(t.get(x)) + 1;
129
+ t.set(x, String(o)), t.set(S(p.tag), String(o), e);
130
+ let s = 0;
131
+ for (let [e, n] of i) s += t.remove(y(e)), A(e, n);
132
+ return t.remove(b(p.tag)), O("ok", s);
133
+ }
134
+ case "clear":
135
+ if (t.get(h) !== p.revision) return O("conflict");
136
+ for (let e of u) t.remove(e);
137
+ return O("ok");
138
+ }
139
+ };
140
+ export { u as applyCacheAtomicCommand };