@rotorsoft/act-sqlite 0.5.0 → 0.5.2
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/README.md +2 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/@types/index.d.ts +7 -0
- package/dist/@types/index.d.ts.map +1 -0
- package/dist/@types/sqlite-store.d.ts +81 -0
- package/dist/@types/sqlite-store.d.ts.map +1 -0
- package/dist/index.cjs +533 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +495 -0
- package/dist/index.js.map +1 -0
- package/package.json +6 -2
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
SqliteStore: () => SqliteStore,
|
|
34
|
+
streamPatternToLike: () => streamPatternToLike
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/sqlite-store.ts
|
|
39
|
+
var import_client = require("@libsql/client");
|
|
40
|
+
var DEFAULT_CONFIG = {
|
|
41
|
+
url: "file::memory:"
|
|
42
|
+
};
|
|
43
|
+
function streamPatternToLike(input) {
|
|
44
|
+
let s = input;
|
|
45
|
+
const start = s.startsWith("^");
|
|
46
|
+
const end = s.endsWith("$");
|
|
47
|
+
if (start) s = s.slice(1);
|
|
48
|
+
if (end) s = s.slice(0, -1);
|
|
49
|
+
s = s.replace(/\.\*/g, "%").replace(/\./g, "_");
|
|
50
|
+
const out = (start ? "" : "%") + s + (end ? "" : "%");
|
|
51
|
+
return out.replace(/%+/g, "%");
|
|
52
|
+
}
|
|
53
|
+
var SqliteStore = class {
|
|
54
|
+
client;
|
|
55
|
+
constructor(config = {}) {
|
|
56
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
57
|
+
this.client = (0, import_client.createClient)({
|
|
58
|
+
url: cfg.url,
|
|
59
|
+
authToken: cfg.authToken
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async seed() {
|
|
63
|
+
await this.client.execute("PRAGMA journal_mode=WAL");
|
|
64
|
+
await this.client.execute(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
66
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
|
+
stream TEXT NOT NULL,
|
|
68
|
+
version INTEGER NOT NULL,
|
|
69
|
+
name TEXT NOT NULL,
|
|
70
|
+
data TEXT NOT NULL,
|
|
71
|
+
meta TEXT NOT NULL,
|
|
72
|
+
created TEXT NOT NULL,
|
|
73
|
+
UNIQUE(stream, version)
|
|
74
|
+
)
|
|
75
|
+
`);
|
|
76
|
+
await this.client.execute(
|
|
77
|
+
"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)"
|
|
78
|
+
);
|
|
79
|
+
await this.client.execute(
|
|
80
|
+
"CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)"
|
|
81
|
+
);
|
|
82
|
+
await this.client.execute(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS streams (
|
|
84
|
+
stream TEXT PRIMARY KEY,
|
|
85
|
+
source TEXT,
|
|
86
|
+
at INTEGER NOT NULL DEFAULT -1,
|
|
87
|
+
retry INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
blocked INTEGER NOT NULL DEFAULT 0,
|
|
89
|
+
error TEXT NOT NULL DEFAULT '',
|
|
90
|
+
leased_by TEXT,
|
|
91
|
+
leased_until TEXT,
|
|
92
|
+
priority INTEGER NOT NULL DEFAULT 0
|
|
93
|
+
)
|
|
94
|
+
`);
|
|
95
|
+
try {
|
|
96
|
+
await this.client.execute(
|
|
97
|
+
"ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0"
|
|
98
|
+
);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
await this.client.execute(
|
|
102
|
+
"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
async drop() {
|
|
106
|
+
await this.client.execute("DROP TABLE IF EXISTS events");
|
|
107
|
+
await this.client.execute("DROP TABLE IF EXISTS streams");
|
|
108
|
+
}
|
|
109
|
+
async dispose() {
|
|
110
|
+
await Promise.resolve();
|
|
111
|
+
this.client.close();
|
|
112
|
+
}
|
|
113
|
+
// --- commit: transaction + optimistic concurrency ---
|
|
114
|
+
async commit(stream, msgs, meta, expectedVersion) {
|
|
115
|
+
const tx = await this.client.transaction("write");
|
|
116
|
+
try {
|
|
117
|
+
const versionRow = await tx.execute({
|
|
118
|
+
sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
|
|
119
|
+
args: [stream]
|
|
120
|
+
});
|
|
121
|
+
const currentVersion = Number(versionRow.rows[0].v);
|
|
122
|
+
if (typeof expectedVersion === "number" && currentVersion !== expectedVersion) {
|
|
123
|
+
const { ConcurrencyError } = await import("@rotorsoft/act");
|
|
124
|
+
throw new ConcurrencyError(
|
|
125
|
+
stream,
|
|
126
|
+
currentVersion,
|
|
127
|
+
msgs,
|
|
128
|
+
expectedVersion
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
132
|
+
const committed = [];
|
|
133
|
+
let version = currentVersion + 1;
|
|
134
|
+
for (const { name, data } of msgs) {
|
|
135
|
+
const result = await tx.execute({
|
|
136
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)",
|
|
137
|
+
args: [
|
|
138
|
+
stream,
|
|
139
|
+
version,
|
|
140
|
+
name,
|
|
141
|
+
JSON.stringify(data),
|
|
142
|
+
JSON.stringify(meta),
|
|
143
|
+
now
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
committed.push({
|
|
147
|
+
id: Number(result.lastInsertRowid),
|
|
148
|
+
stream,
|
|
149
|
+
version,
|
|
150
|
+
created: new Date(now),
|
|
151
|
+
name,
|
|
152
|
+
data,
|
|
153
|
+
meta
|
|
154
|
+
});
|
|
155
|
+
version++;
|
|
156
|
+
}
|
|
157
|
+
await tx.commit();
|
|
158
|
+
return committed;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
await tx.rollback();
|
|
161
|
+
throw e;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// --- query: read-only, no transaction needed ---
|
|
165
|
+
async query(callback, query) {
|
|
166
|
+
let sql = "SELECT * FROM events WHERE 1=1";
|
|
167
|
+
const args = [];
|
|
168
|
+
if (query?.stream) {
|
|
169
|
+
if (query.stream_exact) {
|
|
170
|
+
sql += " AND stream = ?";
|
|
171
|
+
args.push(query.stream);
|
|
172
|
+
} else {
|
|
173
|
+
sql += " AND stream LIKE ?";
|
|
174
|
+
args.push(streamPatternToLike(query.stream));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (query?.names) {
|
|
178
|
+
sql += ` AND name IN (${query.names.map(() => "?").join(",")})`;
|
|
179
|
+
args.push(...query.names);
|
|
180
|
+
}
|
|
181
|
+
if (query?.correlation) {
|
|
182
|
+
sql += " AND json_extract(meta, '$.correlation') = ?";
|
|
183
|
+
args.push(query.correlation);
|
|
184
|
+
}
|
|
185
|
+
if (query?.after !== void 0) {
|
|
186
|
+
sql += " AND id > ?";
|
|
187
|
+
args.push(query.after);
|
|
188
|
+
}
|
|
189
|
+
if (query?.before !== void 0) {
|
|
190
|
+
sql += " AND id < ?";
|
|
191
|
+
args.push(query.before);
|
|
192
|
+
}
|
|
193
|
+
if (query?.created_after) {
|
|
194
|
+
sql += " AND created > ?";
|
|
195
|
+
args.push(query.created_after.toISOString());
|
|
196
|
+
}
|
|
197
|
+
if (query?.created_before) {
|
|
198
|
+
sql += " AND created < ?";
|
|
199
|
+
args.push(query.created_before.toISOString());
|
|
200
|
+
}
|
|
201
|
+
if (!query?.with_snaps) {
|
|
202
|
+
sql += " AND name != '__snapshot__'";
|
|
203
|
+
}
|
|
204
|
+
sql += query?.backward ? " ORDER BY id DESC" : " ORDER BY id ASC";
|
|
205
|
+
if (query?.limit) {
|
|
206
|
+
sql += " LIMIT ?";
|
|
207
|
+
args.push(query.limit);
|
|
208
|
+
}
|
|
209
|
+
const result = await this.client.execute({ sql, args });
|
|
210
|
+
let count = 0;
|
|
211
|
+
for (const row of result.rows) {
|
|
212
|
+
callback({
|
|
213
|
+
id: Number(row.id),
|
|
214
|
+
stream: row.stream,
|
|
215
|
+
version: Number(row.version),
|
|
216
|
+
created: new Date(row.created),
|
|
217
|
+
name: row.name,
|
|
218
|
+
data: JSON.parse(row.data),
|
|
219
|
+
meta: JSON.parse(row.meta)
|
|
220
|
+
});
|
|
221
|
+
count++;
|
|
222
|
+
}
|
|
223
|
+
return count;
|
|
224
|
+
}
|
|
225
|
+
// --- subscribe: idempotent INSERT OR IGNORE (= PG ON CONFLICT DO NOTHING)
|
|
226
|
+
// plus a UPDATE pass to keep the *max* priority across reactions
|
|
227
|
+
// targeting the same stream (ACT-102). Operator overrides go
|
|
228
|
+
// through `prioritize()` instead.
|
|
229
|
+
async subscribe(streams) {
|
|
230
|
+
const tx = await this.client.transaction("write");
|
|
231
|
+
try {
|
|
232
|
+
let subscribed = 0;
|
|
233
|
+
for (const { stream, source, priority = 0 } of streams) {
|
|
234
|
+
const inserted = await tx.execute({
|
|
235
|
+
sql: "INSERT OR IGNORE INTO streams (stream, source, priority) VALUES (?, ?, ?)",
|
|
236
|
+
args: [stream, source ?? null, priority]
|
|
237
|
+
});
|
|
238
|
+
if (inserted.rowsAffected > 0) {
|
|
239
|
+
subscribed++;
|
|
240
|
+
} else if (priority > 0) {
|
|
241
|
+
await tx.execute({
|
|
242
|
+
sql: "UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?",
|
|
243
|
+
args: [priority, stream, priority]
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const wm = await tx.execute(
|
|
248
|
+
"SELECT COALESCE(MAX(at), -1) as w FROM streams"
|
|
249
|
+
);
|
|
250
|
+
await tx.commit();
|
|
251
|
+
return { subscribed, watermark: Number(wm.rows[0].w) };
|
|
252
|
+
} catch (e) {
|
|
253
|
+
await tx.rollback();
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// --- claim: write transaction (SQLite serializes writes = equivalent
|
|
258
|
+
// to PG FOR UPDATE SKIP LOCKED for single-server) ---
|
|
259
|
+
async claim(lagging, leading, by, millis) {
|
|
260
|
+
const tx = await this.client.transaction("write");
|
|
261
|
+
try {
|
|
262
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
263
|
+
const result = await tx.execute({
|
|
264
|
+
sql: `SELECT stream, source, at, priority FROM streams
|
|
265
|
+
WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)
|
|
266
|
+
ORDER BY priority DESC, at ASC`,
|
|
267
|
+
args: [now]
|
|
268
|
+
});
|
|
269
|
+
const candidates = [];
|
|
270
|
+
for (const row of result.rows) {
|
|
271
|
+
const stream = row.stream;
|
|
272
|
+
const source = row.source;
|
|
273
|
+
const at = Number(row.at);
|
|
274
|
+
let hasEvents;
|
|
275
|
+
if (source) {
|
|
276
|
+
const check = await tx.execute({
|
|
277
|
+
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,
|
|
278
|
+
args: [at, streamPatternToLike(source)]
|
|
279
|
+
});
|
|
280
|
+
hasEvents = check.rows.length > 0;
|
|
281
|
+
} else {
|
|
282
|
+
const check = await tx.execute({
|
|
283
|
+
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,
|
|
284
|
+
args: [at]
|
|
285
|
+
});
|
|
286
|
+
hasEvents = check.rows.length > 0;
|
|
287
|
+
}
|
|
288
|
+
if (hasEvents) {
|
|
289
|
+
candidates.push({
|
|
290
|
+
stream,
|
|
291
|
+
source: source ?? void 0,
|
|
292
|
+
at,
|
|
293
|
+
priority: Number(row.priority)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const lag = candidates.slice(0, lagging);
|
|
298
|
+
const lead = [...candidates].sort((a, b) => b.at - a.at).slice(0, leading);
|
|
299
|
+
const seen = /* @__PURE__ */ new Set();
|
|
300
|
+
const combined = [...lag, ...lead].filter((p) => {
|
|
301
|
+
if (seen.has(p.stream)) return false;
|
|
302
|
+
seen.add(p.stream);
|
|
303
|
+
return true;
|
|
304
|
+
});
|
|
305
|
+
const leases = [];
|
|
306
|
+
const until = new Date(Date.now() + millis).toISOString();
|
|
307
|
+
for (const row of combined) {
|
|
308
|
+
await tx.execute({
|
|
309
|
+
sql: "UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ?",
|
|
310
|
+
args: [by, until, row.stream]
|
|
311
|
+
});
|
|
312
|
+
leases.push({
|
|
313
|
+
stream: row.stream,
|
|
314
|
+
source: row.source,
|
|
315
|
+
at: row.at,
|
|
316
|
+
by,
|
|
317
|
+
retry: 0,
|
|
318
|
+
lagging: row.at < 0
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
await tx.commit();
|
|
322
|
+
return leases;
|
|
323
|
+
} catch (e) {
|
|
324
|
+
await tx.rollback();
|
|
325
|
+
throw e;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// --- ack: transaction + ownership check (= PG WHERE leased_by) ---
|
|
329
|
+
async ack(leases) {
|
|
330
|
+
const tx = await this.client.transaction("write");
|
|
331
|
+
try {
|
|
332
|
+
const result = [];
|
|
333
|
+
for (const l of leases) {
|
|
334
|
+
const r = await tx.execute({
|
|
335
|
+
sql: `UPDATE streams SET at = ?, leased_by = NULL, leased_until = NULL, retry = -1
|
|
336
|
+
WHERE stream = ? AND leased_by = ?`,
|
|
337
|
+
args: [l.at, l.stream, l.by]
|
|
338
|
+
});
|
|
339
|
+
if (r.rowsAffected > 0) result.push(l);
|
|
340
|
+
}
|
|
341
|
+
await tx.commit();
|
|
342
|
+
return result;
|
|
343
|
+
} catch (e) {
|
|
344
|
+
await tx.rollback();
|
|
345
|
+
throw e;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// --- block: transaction + ownership + idempotent (= PG) ---
|
|
349
|
+
async block(leases) {
|
|
350
|
+
const tx = await this.client.transaction("write");
|
|
351
|
+
try {
|
|
352
|
+
const result = [];
|
|
353
|
+
for (const l of leases) {
|
|
354
|
+
const r = await tx.execute({
|
|
355
|
+
sql: `UPDATE streams SET blocked = 1, error = ?
|
|
356
|
+
WHERE stream = ? AND leased_by = ? AND blocked = 0`,
|
|
357
|
+
args: [l.error, l.stream, l.by]
|
|
358
|
+
});
|
|
359
|
+
if (r.rowsAffected > 0) result.push(l);
|
|
360
|
+
}
|
|
361
|
+
await tx.commit();
|
|
362
|
+
return result;
|
|
363
|
+
} catch (e) {
|
|
364
|
+
await tx.rollback();
|
|
365
|
+
throw e;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// --- reset: transactional ---
|
|
369
|
+
async reset(streams) {
|
|
370
|
+
const tx = await this.client.transaction("write");
|
|
371
|
+
try {
|
|
372
|
+
let count = 0;
|
|
373
|
+
for (const stream of streams) {
|
|
374
|
+
const r = await tx.execute({
|
|
375
|
+
sql: `UPDATE streams SET at = -1, retry = 0, blocked = 0, error = '',
|
|
376
|
+
leased_by = NULL, leased_until = NULL WHERE stream = ?`,
|
|
377
|
+
args: [stream]
|
|
378
|
+
});
|
|
379
|
+
count += r.rowsAffected;
|
|
380
|
+
}
|
|
381
|
+
await tx.commit();
|
|
382
|
+
return count;
|
|
383
|
+
} catch (e) {
|
|
384
|
+
await tx.rollback();
|
|
385
|
+
throw e;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// --- query_streams: read-only introspection with filters ---
|
|
389
|
+
async query_streams(callback, query) {
|
|
390
|
+
const limit = query?.limit ?? 100;
|
|
391
|
+
let sql = "SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority FROM streams WHERE 1=1";
|
|
392
|
+
const args = [];
|
|
393
|
+
if (query?.stream !== void 0) {
|
|
394
|
+
if (query.stream_exact) {
|
|
395
|
+
sql += " AND stream = ?";
|
|
396
|
+
args.push(query.stream);
|
|
397
|
+
} else {
|
|
398
|
+
sql += " AND stream LIKE ?";
|
|
399
|
+
args.push(streamPatternToLike(query.stream));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (query?.source !== void 0) {
|
|
403
|
+
sql += " AND source IS NOT NULL";
|
|
404
|
+
if (query.source_exact) {
|
|
405
|
+
sql += " AND source = ?";
|
|
406
|
+
args.push(query.source);
|
|
407
|
+
} else {
|
|
408
|
+
sql += " AND source LIKE ?";
|
|
409
|
+
args.push(streamPatternToLike(query.source));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (query?.blocked !== void 0) {
|
|
413
|
+
sql += " AND blocked = ?";
|
|
414
|
+
args.push(query.blocked ? 1 : 0);
|
|
415
|
+
}
|
|
416
|
+
if (query?.after !== void 0) {
|
|
417
|
+
sql += " AND stream > ?";
|
|
418
|
+
args.push(query.after);
|
|
419
|
+
}
|
|
420
|
+
sql += " ORDER BY stream LIMIT ?";
|
|
421
|
+
args.push(limit);
|
|
422
|
+
const [streamsResult, maxResult] = await Promise.all([
|
|
423
|
+
this.client.execute({ sql, args }),
|
|
424
|
+
this.client.execute("SELECT COALESCE(MAX(id), -1) AS m FROM events")
|
|
425
|
+
]);
|
|
426
|
+
let count = 0;
|
|
427
|
+
for (const row of streamsResult.rows) {
|
|
428
|
+
const leased_until = row.leased_until;
|
|
429
|
+
callback({
|
|
430
|
+
stream: row.stream,
|
|
431
|
+
source: row.source ?? void 0,
|
|
432
|
+
at: Number(row.at),
|
|
433
|
+
retry: Number(row.retry),
|
|
434
|
+
blocked: Number(row.blocked) === 1,
|
|
435
|
+
error: row.error,
|
|
436
|
+
priority: Number(row.priority),
|
|
437
|
+
leased_by: row.leased_by ?? void 0,
|
|
438
|
+
leased_until: leased_until ? new Date(leased_until) : void 0
|
|
439
|
+
});
|
|
440
|
+
count++;
|
|
441
|
+
}
|
|
442
|
+
return { maxEventId: Number(maxResult.rows[0].m), count };
|
|
443
|
+
}
|
|
444
|
+
// --- prioritize: bulk priority update with filter (ACT-102) ---
|
|
445
|
+
async prioritize(filter, priority) {
|
|
446
|
+
const args = [priority, priority];
|
|
447
|
+
const conditions = ["priority <> ?"];
|
|
448
|
+
if (filter.stream !== void 0) {
|
|
449
|
+
if (filter.stream_exact) {
|
|
450
|
+
conditions.push("stream = ?");
|
|
451
|
+
args.push(filter.stream);
|
|
452
|
+
} else {
|
|
453
|
+
conditions.push("stream LIKE ?");
|
|
454
|
+
args.push(streamPatternToLike(filter.stream));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (filter.source !== void 0) {
|
|
458
|
+
conditions.push("source IS NOT NULL");
|
|
459
|
+
if (filter.source_exact) {
|
|
460
|
+
conditions.push("source = ?");
|
|
461
|
+
args.push(filter.source);
|
|
462
|
+
} else {
|
|
463
|
+
conditions.push("source LIKE ?");
|
|
464
|
+
args.push(streamPatternToLike(filter.source));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (filter.blocked !== void 0) {
|
|
468
|
+
conditions.push("blocked = ?");
|
|
469
|
+
args.push(filter.blocked ? 1 : 0);
|
|
470
|
+
}
|
|
471
|
+
const sql = `UPDATE streams SET priority = ? WHERE ${conditions.join(" AND ")}`;
|
|
472
|
+
const result = await this.client.execute({ sql, args });
|
|
473
|
+
return result.rowsAffected;
|
|
474
|
+
}
|
|
475
|
+
// --- truncate: transactional delete + seed ---
|
|
476
|
+
async truncate(targets) {
|
|
477
|
+
const result = /* @__PURE__ */ new Map();
|
|
478
|
+
const tx = await this.client.transaction("write");
|
|
479
|
+
try {
|
|
480
|
+
for (const { stream, snapshot, meta } of targets) {
|
|
481
|
+
const countRow = await tx.execute({
|
|
482
|
+
sql: "SELECT COUNT(*) as c FROM events WHERE stream = ?",
|
|
483
|
+
args: [stream]
|
|
484
|
+
});
|
|
485
|
+
const deleted = Number(countRow.rows[0].c);
|
|
486
|
+
await tx.execute({
|
|
487
|
+
sql: "DELETE FROM events WHERE stream = ?",
|
|
488
|
+
args: [stream]
|
|
489
|
+
});
|
|
490
|
+
await tx.execute({
|
|
491
|
+
sql: "DELETE FROM streams WHERE stream = ?",
|
|
492
|
+
args: [stream]
|
|
493
|
+
});
|
|
494
|
+
const eventName = snapshot !== void 0 ? "__snapshot__" : "__tombstone__";
|
|
495
|
+
const eventMeta = meta ?? { correlation: "", causation: {} };
|
|
496
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
497
|
+
const ins = await tx.execute({
|
|
498
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)",
|
|
499
|
+
args: [
|
|
500
|
+
stream,
|
|
501
|
+
eventName,
|
|
502
|
+
JSON.stringify(snapshot ?? {}),
|
|
503
|
+
JSON.stringify(eventMeta),
|
|
504
|
+
now
|
|
505
|
+
]
|
|
506
|
+
});
|
|
507
|
+
result.set(stream, {
|
|
508
|
+
deleted,
|
|
509
|
+
committed: {
|
|
510
|
+
id: Number(ins.lastInsertRowid),
|
|
511
|
+
stream,
|
|
512
|
+
version: 0,
|
|
513
|
+
created: new Date(now),
|
|
514
|
+
name: eventName,
|
|
515
|
+
data: snapshot ?? {},
|
|
516
|
+
meta: eventMeta
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
await tx.commit();
|
|
521
|
+
} catch (e) {
|
|
522
|
+
await tx.rollback();
|
|
523
|
+
throw e;
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
529
|
+
0 && (module.exports = {
|
|
530
|
+
SqliteStore,
|
|
531
|
+
streamPatternToLike
|
|
532
|
+
});
|
|
533
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sqlite-store.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-sqlite\n * Main entry point for the Act-SQLite adapter. Re-exports all core APIs\n */\nexport * from \"./sqlite-store.js\";\n","import { type Client, createClient } from \"@libsql/client\";\nimport type {\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Message,\n PrioritizeFilter,\n Query,\n QueryStreams,\n QueryStreamsResult,\n Schemas,\n Store,\n StreamPosition,\n} from \"@rotorsoft/act\";\n\n/**\n * SQLite store configuration\n */\nexport interface SqliteConfig {\n /** Path to the SQLite database file (default: \":memory:\") */\n url: string;\n /** Auth token for libSQL server connections (optional) */\n authToken?: string;\n}\n\nconst DEFAULT_CONFIG: SqliteConfig = {\n url: \"file::memory:\",\n};\n\n/** Translate a stream filter (regex-shaped or plain substring) into a\n * SQL LIKE pattern. Honors `^` / `$` anchors and converts `.*` → `%`,\n * `.` → `_`. Unanchored input gets `%` wildcards on both sides.\n *\n * Examples:\n * - `^abc$` → `abc` (exact)\n * - `^abc.*` → `abc%` (starts-with)\n * - `.*abc$` → `%abc` (ends-with)\n * - `abc` → `%abc%` (contains)\n * - `a.c` → `%a_c%` (single-char wildcard, contains)\n *\n * @internal exported for testing\n */\nexport function streamPatternToLike(input: string): string {\n let s = input;\n const start = s.startsWith(\"^\");\n const end = s.endsWith(\"$\");\n if (start) s = s.slice(1);\n if (end) s = s.slice(0, -1);\n s = s.replace(/\\.\\*/g, \"%\").replace(/\\./g, \"_\");\n const out = (start ? \"\" : \"%\") + s + (end ? \"\" : \"%\");\n // Collapse adjacent `%` — e.g. `^a.*` would otherwise yield `a%%`.\n // Same matching semantics, cleaner output.\n return out.replace(/%+/g, \"%\");\n}\n\n/**\n * SQLite event store adapter for [@rotorsoft/act](https://www.npmjs.com/package/@rotorsoft/act).\n *\n * Provides persistent event storage using SQLite via `@libsql/client`.\n * All write operations use transactions for ACID guarantees.\n * Since SQLite serializes writes at the database level, the concurrency\n * model is equivalent to PostgreSQL's `FOR UPDATE SKIP LOCKED` for\n * single-server deployments.\n *\n * **`Store.notify` is intentionally not implemented.** The notify hook is\n * a cross-process wake-up signal that lets a horizontally-scaled Act\n * deployment wake `settle()` immediately on remote commits. SQLite is\n * single-node by design — there is no remote writer to be notified of —\n * so the {@link Act} orchestrator falls back to the existing\n * debounce/poll path, which is correct for this topology.\n *\n * @example\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { SqliteStore } from \"@rotorsoft/act-sqlite\";\n *\n * store(new SqliteStore({ url: \"file:myapp.db\" }));\n * await store().seed();\n * ```\n */\nexport class SqliteStore implements Store {\n private client: Client;\n\n constructor(config: Partial<SqliteConfig> = {}) {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n this.client = createClient({\n url: cfg.url,\n authToken: cfg.authToken,\n });\n }\n\n async seed() {\n await this.client.execute(\"PRAGMA journal_mode=WAL\");\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream TEXT NOT NULL,\n version INTEGER NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL,\n meta TEXT NOT NULL,\n created TEXT NOT NULL,\n UNIQUE(stream, version)\n )\n `);\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)\"\n );\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)\"\n );\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS streams (\n stream TEXT PRIMARY KEY,\n source TEXT,\n at INTEGER NOT NULL DEFAULT -1,\n retry INTEGER NOT NULL DEFAULT 0,\n blocked INTEGER NOT NULL DEFAULT 0,\n error TEXT NOT NULL DEFAULT '',\n leased_by TEXT,\n leased_until TEXT,\n priority INTEGER NOT NULL DEFAULT 0\n )\n `);\n // Migration for tables created before priority lanes (ACT-102).\n // libSQL surfaces \"duplicate column\" as an error, hence the\n // try/swallow — this mirrors PG's `ADD COLUMN IF NOT EXISTS`.\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0\"\n );\n } catch {\n // already present\n }\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)\"\n );\n }\n\n async drop() {\n await this.client.execute(\"DROP TABLE IF EXISTS events\");\n await this.client.execute(\"DROP TABLE IF EXISTS streams\");\n }\n\n async dispose() {\n await Promise.resolve();\n this.client.close();\n }\n\n // --- commit: transaction + optimistic concurrency ---\n async commit<E extends Schemas>(\n stream: string,\n msgs: Message<E, keyof E>[],\n meta: EventMeta,\n expectedVersion?: number\n ): Promise<Committed<E, keyof E>[]> {\n const tx = await this.client.transaction(\"write\");\n try {\n const versionRow = await tx.execute({\n sql: \"SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?\",\n args: [stream],\n });\n const currentVersion = Number(versionRow.rows[0].v);\n\n if (\n typeof expectedVersion === \"number\" &&\n currentVersion !== expectedVersion\n ) {\n const { ConcurrencyError } = await import(\"@rotorsoft/act\");\n throw new ConcurrencyError(\n stream,\n currentVersion,\n msgs as Message<Schemas, keyof Schemas>[],\n expectedVersion\n );\n }\n\n const now = new Date().toISOString();\n const committed: Committed<E, keyof E>[] = [];\n let version = currentVersion + 1;\n\n for (const { name, data } of msgs) {\n const result = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)\",\n args: [\n stream,\n version,\n name as string,\n JSON.stringify(data),\n JSON.stringify(meta),\n now,\n ],\n });\n committed.push({\n id: Number(result.lastInsertRowid),\n stream,\n version,\n created: new Date(now),\n name,\n data,\n meta,\n });\n version++;\n }\n\n await tx.commit();\n return committed;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query: read-only, no transaction needed ---\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ): Promise<number> {\n let sql = \"SELECT * FROM events WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.names) {\n sql += ` AND name IN (${query.names.map(() => \"?\").join(\",\")})`;\n args.push(...query.names);\n }\n if ((query as any)?.correlation) {\n sql += \" AND json_extract(meta, '$.correlation') = ?\";\n args.push((query as any).correlation);\n }\n if (query?.after !== undefined) {\n sql += \" AND id > ?\";\n args.push(query.after);\n }\n if (query?.before !== undefined) {\n sql += \" AND id < ?\";\n args.push(query.before);\n }\n if (query?.created_after) {\n sql += \" AND created > ?\";\n args.push(query.created_after.toISOString());\n }\n if (query?.created_before) {\n sql += \" AND created < ?\";\n args.push(query.created_before.toISOString());\n }\n if (!query?.with_snaps) {\n sql += \" AND name != '__snapshot__'\";\n }\n\n sql += query?.backward ? \" ORDER BY id DESC\" : \" ORDER BY id ASC\";\n\n if (query?.limit) {\n sql += \" LIMIT ?\";\n args.push(query.limit);\n }\n\n const result = await this.client.execute({ sql, args: args as any[] });\n let count = 0;\n\n for (const row of result.rows) {\n callback({\n id: Number(row.id),\n stream: row.stream as string,\n version: Number(row.version),\n created: new Date(row.created as string),\n name: row.name as string,\n data: JSON.parse(row.data as string),\n meta: JSON.parse(row.meta as string),\n });\n count++;\n }\n\n return count;\n }\n\n // --- subscribe: idempotent INSERT OR IGNORE (= PG ON CONFLICT DO NOTHING)\n // plus a UPDATE pass to keep the *max* priority across reactions\n // targeting the same stream (ACT-102). Operator overrides go\n // through `prioritize()` instead.\n async subscribe(\n streams: Array<{ stream: string; source?: string; priority?: number }>\n ) {\n const tx = await this.client.transaction(\"write\");\n try {\n let subscribed = 0;\n for (const { stream, source, priority = 0 } of streams) {\n const inserted = await tx.execute({\n sql: \"INSERT OR IGNORE INTO streams (stream, source, priority) VALUES (?, ?, ?)\",\n args: [stream, source ?? null, priority],\n });\n if (inserted.rowsAffected > 0) {\n subscribed++;\n } else if (priority > 0) {\n await tx.execute({\n sql: \"UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?\",\n args: [priority, stream, priority],\n });\n }\n }\n const wm = await tx.execute(\n \"SELECT COALESCE(MAX(at), -1) as w FROM streams\"\n );\n await tx.commit();\n return { subscribed, watermark: Number(wm.rows[0].w) };\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- claim: write transaction (SQLite serializes writes = equivalent\n // to PG FOR UPDATE SKIP LOCKED for single-server) ---\n async claim(lagging: number, leading: number, by: string, millis: number) {\n const tx = await this.client.transaction(\"write\");\n try {\n const now = new Date().toISOString();\n\n const result = await tx.execute({\n sql: `SELECT stream, source, at, priority FROM streams\n WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)\n ORDER BY priority DESC, at ASC`,\n args: [now],\n });\n\n const candidates: {\n stream: string;\n source: string | undefined;\n at: number;\n priority: number;\n }[] = [];\n for (const row of result.rows) {\n const stream = row.stream as string;\n const source = row.source as string | null;\n const at = Number(row.at);\n\n let hasEvents: boolean;\n if (source) {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,\n args: [at, streamPatternToLike(source)],\n });\n hasEvents = check.rows.length > 0;\n } else {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,\n args: [at],\n });\n hasEvents = check.rows.length > 0;\n }\n\n if (hasEvents) {\n candidates.push({\n stream,\n source: source ?? undefined,\n at,\n priority: Number(row.priority),\n });\n }\n }\n\n // Dual frontier: lagging (priority DESC, watermark ASC — ACT-102)\n // + leading (newest first). The candidates list arrives sorted\n // by `priority DESC, at ASC` from the SELECT above, so the\n // `slice(0, lagging)` already does the right thing.\n const lag = candidates.slice(0, lagging);\n const lead = [...candidates]\n .sort((a, b) => b.at - a.at)\n .slice(0, leading);\n const seen = new Set<string>();\n const combined = [...lag, ...lead].filter((p) => {\n if (seen.has(p.stream)) return false;\n seen.add(p.stream);\n return true;\n });\n\n const leases: Lease[] = [];\n const until = new Date(Date.now() + millis).toISOString();\n for (const row of combined) {\n await tx.execute({\n sql: \"UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ?\",\n args: [by, until, row.stream],\n });\n leases.push({\n stream: row.stream,\n source: row.source,\n at: row.at,\n by,\n retry: 0,\n lagging: row.at < 0,\n });\n }\n\n await tx.commit();\n return leases;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- ack: transaction + ownership check (= PG WHERE leased_by) ---\n async ack(leases: Lease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: Lease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET at = ?, leased_by = NULL, leased_until = NULL, retry = -1\n WHERE stream = ? AND leased_by = ?`,\n args: [l.at, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- block: transaction + ownership + idempotent (= PG) ---\n async block(leases: BlockedLease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: BlockedLease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET blocked = 1, error = ?\n WHERE stream = ? AND leased_by = ? AND blocked = 0`,\n args: [l.error, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- reset: transactional ---\n async reset(streams: string[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n for (const stream of streams) {\n const r = await tx.execute({\n sql: `UPDATE streams SET at = -1, retry = 0, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL WHERE stream = ?`,\n args: [stream],\n });\n count += r.rowsAffected;\n }\n await tx.commit();\n return count;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query_streams: read-only introspection with filters ---\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n const limit = query?.limit ?? 100;\n let sql =\n \"SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority FROM streams WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream !== undefined) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.source !== undefined) {\n sql += \" AND source IS NOT NULL\";\n if (query.source_exact) {\n sql += \" AND source = ?\";\n args.push(query.source);\n } else {\n sql += \" AND source LIKE ?\";\n args.push(streamPatternToLike(query.source));\n }\n }\n if (query?.blocked !== undefined) {\n sql += \" AND blocked = ?\";\n args.push(query.blocked ? 1 : 0);\n }\n if (query?.after !== undefined) {\n sql += \" AND stream > ?\";\n args.push(query.after);\n }\n sql += \" ORDER BY stream LIMIT ?\";\n args.push(limit);\n\n const [streamsResult, maxResult] = await Promise.all([\n this.client.execute({ sql, args: args as any[] }),\n this.client.execute(\"SELECT COALESCE(MAX(id), -1) AS m FROM events\"),\n ]);\n\n let count = 0;\n for (const row of streamsResult.rows) {\n const leased_until = row.leased_until as string | null;\n callback({\n stream: row.stream as string,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n retry: Number(row.retry),\n blocked: Number(row.blocked) === 1,\n error: row.error as string,\n priority: Number(row.priority),\n leased_by: (row.leased_by as string | null) ?? undefined,\n leased_until: leased_until ? new Date(leased_until) : undefined,\n });\n count++;\n }\n\n return { maxEventId: Number(maxResult.rows[0].m), count };\n }\n\n // --- prioritize: bulk priority update with filter (ACT-102) ---\n async prioritize(\n filter: PrioritizeFilter,\n priority: number\n ): Promise<number> {\n // libSQL `?` placeholders are positional and NOT reusable, so we\n // bind `priority` twice: once for SET, once for the no-op skip\n // in WHERE.\n const args: unknown[] = [priority, priority];\n const conditions: string[] = [\"priority <> ?\"];\n\n if (filter.stream !== undefined) {\n if (filter.stream_exact) {\n conditions.push(\"stream = ?\");\n args.push(filter.stream);\n } else {\n conditions.push(\"stream LIKE ?\");\n args.push(streamPatternToLike(filter.stream));\n }\n }\n if (filter.source !== undefined) {\n conditions.push(\"source IS NOT NULL\");\n if (filter.source_exact) {\n conditions.push(\"source = ?\");\n args.push(filter.source);\n } else {\n conditions.push(\"source LIKE ?\");\n args.push(streamPatternToLike(filter.source));\n }\n }\n if (filter.blocked !== undefined) {\n conditions.push(\"blocked = ?\");\n args.push(filter.blocked ? 1 : 0);\n }\n const sql = `UPDATE streams SET priority = ? WHERE ${conditions.join(\" AND \")}`;\n const result = await this.client.execute({ sql, args: args as any[] });\n return result.rowsAffected;\n }\n\n // --- truncate: transactional delete + seed ---\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Record<string, unknown>;\n meta?: EventMeta;\n }>\n ) {\n const result = new Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >();\n\n const tx = await this.client.transaction(\"write\");\n try {\n for (const { stream, snapshot, meta } of targets) {\n const countRow = await tx.execute({\n sql: \"SELECT COUNT(*) as c FROM events WHERE stream = ?\",\n args: [stream],\n });\n const deleted = Number(countRow.rows[0].c);\n await tx.execute({\n sql: \"DELETE FROM events WHERE stream = ?\",\n args: [stream],\n });\n await tx.execute({\n sql: \"DELETE FROM streams WHERE stream = ?\",\n args: [stream],\n });\n\n const eventName =\n snapshot !== undefined ? \"__snapshot__\" : \"__tombstone__\";\n const eventMeta = meta ?? { correlation: \"\", causation: {} };\n const now = new Date().toISOString();\n const ins = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)\",\n args: [\n stream,\n eventName,\n JSON.stringify(snapshot ?? {}),\n JSON.stringify(eventMeta),\n now,\n ],\n });\n\n result.set(stream, {\n deleted,\n committed: {\n id: Number(ins.lastInsertRowid),\n stream,\n version: 0,\n created: new Date(now),\n name: eventName,\n data: snapshot ?? {},\n meta: eventMeta,\n },\n });\n }\n await tx.commit();\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n\n return result;\n }\n}\n"],"mappings":"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","names":[]}
|