@cnstra/persist-postgres 0.1.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/README.md +67 -0
- package/dist/chunk-U4IKAHS4.mjs +97 -0
- package/dist/chunk-U4IKAHS4.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +120 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +38 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +435 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +326 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
CNSPostgresStimulationRepository: () => CNSPostgresStimulationRepository,
|
|
26
|
+
DEFAULT_PREFIX: () => DEFAULT_PREFIX,
|
|
27
|
+
assertSafePrefix: () => assertSafePrefix,
|
|
28
|
+
ensureSchema: () => ensureSchema,
|
|
29
|
+
persistPostgresSchemaSql: () => persistPostgresSchemaSql
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/CNSPostgresStimulationRepository.ts
|
|
34
|
+
var import_pg = require("pg");
|
|
35
|
+
|
|
36
|
+
// src/migrate.ts
|
|
37
|
+
var DEFAULT_PREFIX = "cns_";
|
|
38
|
+
function assertSafePrefix(prefix) {
|
|
39
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {
|
|
40
|
+
throw new Error(`[@cnstra/persist-postgres] unsafe table prefix: ${JSON.stringify(prefix)}`);
|
|
41
|
+
}
|
|
42
|
+
return prefix;
|
|
43
|
+
}
|
|
44
|
+
__name(assertSafePrefix, "assertSafePrefix");
|
|
45
|
+
function persistPostgresSchemaSql(prefix = DEFAULT_PREFIX) {
|
|
46
|
+
const p = assertSafePrefix(prefix);
|
|
47
|
+
return `
|
|
48
|
+
CREATE TABLE IF NOT EXISTS ${p}stimulation (
|
|
49
|
+
stimulation_id text PRIMARY KEY,
|
|
50
|
+
entry jsonb NOT NULL,
|
|
51
|
+
status text NOT NULL,
|
|
52
|
+
scope_name text,
|
|
53
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
54
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
55
|
+
);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS ${p}stimulation_updated_idx ON ${p}stimulation (updated_at DESC);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS ${p}stimulation_scope_idx ON ${p}stimulation (scope_name);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS ${p}stimulation_frontier (
|
|
60
|
+
stimulation_id text NOT NULL REFERENCES ${p}stimulation(stimulation_id) ON DELETE CASCADE,
|
|
61
|
+
ord int NOT NULL,
|
|
62
|
+
neuron_name text NOT NULL,
|
|
63
|
+
dendrite_collateral_name text NOT NULL,
|
|
64
|
+
input jsonb,
|
|
65
|
+
PRIMARY KEY (stimulation_id, ord)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS ${p}stimulation_context (
|
|
69
|
+
stimulation_id text NOT NULL REFERENCES ${p}stimulation(stimulation_id) ON DELETE CASCADE,
|
|
70
|
+
neuron_name text NOT NULL,
|
|
71
|
+
value jsonb NOT NULL,
|
|
72
|
+
PRIMARY KEY (stimulation_id, neuron_name)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
-- NOTE: no FK on stimulation_id \u2014 over the devtools wire the persistor's
|
|
76
|
+
-- saveStimulation and saveAttempt ride SEPARATE async batches processed
|
|
77
|
+
-- concurrently by the server, so an attempt insert can reach Postgres before its
|
|
78
|
+
-- stimulation row's transaction commits; a hard FK would reject it (23503). Same
|
|
79
|
+
-- exposure as ${p}task; delete() removes attempts explicitly instead of cascading.
|
|
80
|
+
CREATE TABLE IF NOT EXISTS ${p}stimulation_attempt (
|
|
81
|
+
stimulation_attempt_id text PRIMARY KEY,
|
|
82
|
+
stimulation_id text NOT NULL,
|
|
83
|
+
attempt_number int NOT NULL,
|
|
84
|
+
status text NOT NULL,
|
|
85
|
+
started_at bigint NOT NULL,
|
|
86
|
+
completed_at bigint,
|
|
87
|
+
hop_count int NOT NULL,
|
|
88
|
+
has_error boolean NOT NULL,
|
|
89
|
+
replay_of text,
|
|
90
|
+
entry jsonb NOT NULL,
|
|
91
|
+
UNIQUE (stimulation_id, attempt_number)
|
|
92
|
+
);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_idx ON ${p}stimulation_attempt (stimulation_id, attempt_number);
|
|
94
|
+
|
|
95
|
+
-- NOTE: no FK on stimulation_attempt_id \u2014 the persistor streams settled tasks
|
|
96
|
+
-- (appendTask) as hops complete, BEFORE the attempt marker is flushed (saveAttempt),
|
|
97
|
+
-- so a hard FK would reject the insert. delete() removes tasks explicitly instead.
|
|
98
|
+
CREATE TABLE IF NOT EXISTS ${p}task (
|
|
99
|
+
stimulation_attempt_id text NOT NULL,
|
|
100
|
+
"index" int NOT NULL,
|
|
101
|
+
neuron_name text NOT NULL,
|
|
102
|
+
dendrite_collateral_name text NOT NULL,
|
|
103
|
+
input_index int NOT NULL,
|
|
104
|
+
output jsonb,
|
|
105
|
+
status text NOT NULL,
|
|
106
|
+
error text,
|
|
107
|
+
started_at bigint NOT NULL,
|
|
108
|
+
duration int,
|
|
109
|
+
PRIMARY KEY (stimulation_attempt_id, "index")
|
|
110
|
+
);
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
__name(persistPostgresSchemaSql, "persistPostgresSchemaSql");
|
|
114
|
+
async function ensureSchema(db, prefix = DEFAULT_PREFIX) {
|
|
115
|
+
await db.query(persistPostgresSchemaSql(prefix));
|
|
116
|
+
}
|
|
117
|
+
__name(ensureSchema, "ensureSchema");
|
|
118
|
+
|
|
119
|
+
// src/CNSPostgresStimulationRepository.ts
|
|
120
|
+
var j = /* @__PURE__ */ __name((v) => JSON.stringify(v != null ? v : null), "j");
|
|
121
|
+
var num = /* @__PURE__ */ __name((v) => Number(v), "num");
|
|
122
|
+
var numOrNull = /* @__PURE__ */ __name((v) => v == null ? null : Number(v), "numOrNull");
|
|
123
|
+
var _CNSPostgresStimulationRepository = class _CNSPostgresStimulationRepository {
|
|
124
|
+
constructor(opts) {
|
|
125
|
+
__publicField(this, "db");
|
|
126
|
+
__publicField(this, "pool");
|
|
127
|
+
__publicField(this, "ownsPool");
|
|
128
|
+
__publicField(this, "p");
|
|
129
|
+
__publicField(this, "autoMigrate");
|
|
130
|
+
__publicField(this, "ready");
|
|
131
|
+
var _a, _b;
|
|
132
|
+
if (opts.pool) {
|
|
133
|
+
this.pool = opts.pool;
|
|
134
|
+
this.db = opts.pool;
|
|
135
|
+
this.ownsPool = false;
|
|
136
|
+
} else if (opts.connectionString) {
|
|
137
|
+
this.pool = new import_pg.Pool({
|
|
138
|
+
connectionString: opts.connectionString
|
|
139
|
+
});
|
|
140
|
+
this.db = this.pool;
|
|
141
|
+
this.ownsPool = true;
|
|
142
|
+
} else if (opts.queryable) {
|
|
143
|
+
this.db = opts.queryable;
|
|
144
|
+
this.ownsPool = false;
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error("[@cnstra/persist-postgres] needs `connectionString`, `pool`, or `queryable`");
|
|
147
|
+
}
|
|
148
|
+
this.p = assertSafePrefix((_a = opts.tablePrefix) != null ? _a : DEFAULT_PREFIX);
|
|
149
|
+
this.autoMigrate = (_b = opts.autoMigrate) != null ? _b : true;
|
|
150
|
+
}
|
|
151
|
+
ensure() {
|
|
152
|
+
var _a;
|
|
153
|
+
if (!this.autoMigrate) return Promise.resolve();
|
|
154
|
+
return (_a = this.ready) != null ? _a : this.ready = ensureSchema(this.db, this.p);
|
|
155
|
+
}
|
|
156
|
+
/** Run `fn` in a transaction when a Pool is available; else best-effort inline. */
|
|
157
|
+
async tx(fn) {
|
|
158
|
+
if (!this.pool) return fn(this.db);
|
|
159
|
+
const client = await this.pool.connect();
|
|
160
|
+
try {
|
|
161
|
+
await client.query("BEGIN");
|
|
162
|
+
const out = await fn(client);
|
|
163
|
+
await client.query("COMMIT");
|
|
164
|
+
return out;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
await client.query("ROLLBACK");
|
|
167
|
+
throw e;
|
|
168
|
+
} finally {
|
|
169
|
+
client.release();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// ── write ──
|
|
173
|
+
async saveStimulation(s) {
|
|
174
|
+
await this.ensure();
|
|
175
|
+
const { p } = this;
|
|
176
|
+
await this.tx(async (db) => {
|
|
177
|
+
var _a;
|
|
178
|
+
await db.query(`INSERT INTO ${p}stimulation (stimulation_id, entry, status, scope_name, updated_at)
|
|
179
|
+
VALUES ($1, $2::jsonb, $3, $4, now())
|
|
180
|
+
ON CONFLICT (stimulation_id)
|
|
181
|
+
DO UPDATE SET entry = EXCLUDED.entry, status = EXCLUDED.status,
|
|
182
|
+
scope_name = EXCLUDED.scope_name, updated_at = now()`, [
|
|
183
|
+
s.stimulationId,
|
|
184
|
+
j(s.entry),
|
|
185
|
+
s.status,
|
|
186
|
+
(_a = s.scopeName) != null ? _a : null
|
|
187
|
+
]);
|
|
188
|
+
await db.query(`DELETE FROM ${p}stimulation_frontier WHERE stimulation_id = $1`, [
|
|
189
|
+
s.stimulationId
|
|
190
|
+
]);
|
|
191
|
+
for (let i = 0; i < s.progress.tasks.length; i++) {
|
|
192
|
+
const t = s.progress.tasks[i];
|
|
193
|
+
await db.query(`INSERT INTO ${p}stimulation_frontier
|
|
194
|
+
(stimulation_id, ord, neuron_name, dendrite_collateral_name, input)
|
|
195
|
+
VALUES ($1, $2, $3, $4, $5::jsonb)`, [
|
|
196
|
+
s.stimulationId,
|
|
197
|
+
i,
|
|
198
|
+
t.neuronName,
|
|
199
|
+
t.dendriteCollateralName,
|
|
200
|
+
t.input ? j(t.input) : null
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
await db.query(`DELETE FROM ${p}stimulation_context WHERE stimulation_id = $1`, [
|
|
204
|
+
s.stimulationId
|
|
205
|
+
]);
|
|
206
|
+
for (const [neuronName, value] of Object.entries(s.progress.context)) {
|
|
207
|
+
await db.query(`INSERT INTO ${p}stimulation_context (stimulation_id, neuron_name, value)
|
|
208
|
+
VALUES ($1, $2, $3::jsonb)`, [
|
|
209
|
+
s.stimulationId,
|
|
210
|
+
neuronName,
|
|
211
|
+
j(value)
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async saveAttempt(a) {
|
|
217
|
+
await this.ensure();
|
|
218
|
+
const { p } = this;
|
|
219
|
+
await this.db.query(`INSERT INTO ${p}stimulation_attempt
|
|
220
|
+
(stimulation_attempt_id, stimulation_id, attempt_number, status, started_at,
|
|
221
|
+
completed_at, hop_count, has_error, replay_of, entry)
|
|
222
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb)
|
|
223
|
+
ON CONFLICT (stimulation_attempt_id) DO UPDATE SET
|
|
224
|
+
status = EXCLUDED.status, completed_at = EXCLUDED.completed_at,
|
|
225
|
+
hop_count = EXCLUDED.hop_count, has_error = EXCLUDED.has_error,
|
|
226
|
+
replay_of = EXCLUDED.replay_of, entry = EXCLUDED.entry`, [
|
|
227
|
+
a.stimulationAttemptId,
|
|
228
|
+
a.stimulationId,
|
|
229
|
+
a.attemptNumber,
|
|
230
|
+
a.status,
|
|
231
|
+
a.startedAt,
|
|
232
|
+
a.completedAt,
|
|
233
|
+
a.hopCount,
|
|
234
|
+
a.hasError,
|
|
235
|
+
a.replayOf,
|
|
236
|
+
j(a.entry)
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
async appendTask(t) {
|
|
240
|
+
await this.ensure();
|
|
241
|
+
const { p } = this;
|
|
242
|
+
await this.db.query(`INSERT INTO ${p}task
|
|
243
|
+
(stimulation_attempt_id, "index", neuron_name, dendrite_collateral_name,
|
|
244
|
+
input_index, output, status, error, started_at, duration)
|
|
245
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10)
|
|
246
|
+
ON CONFLICT (stimulation_attempt_id, "index") DO NOTHING`, [
|
|
247
|
+
t.stimulationAttemptId,
|
|
248
|
+
t.index,
|
|
249
|
+
t.neuronName,
|
|
250
|
+
t.dendriteCollateralName,
|
|
251
|
+
t.inputIndex,
|
|
252
|
+
t.output ? j(t.output) : null,
|
|
253
|
+
t.status,
|
|
254
|
+
t.error,
|
|
255
|
+
t.startedAt,
|
|
256
|
+
t.duration
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
// ── read ──
|
|
260
|
+
async getStimulation(stimulationId) {
|
|
261
|
+
await this.ensure();
|
|
262
|
+
const { p } = this;
|
|
263
|
+
const stimRes = await this.db.query(`SELECT entry, status, scope_name FROM ${p}stimulation WHERE stimulation_id = $1`, [
|
|
264
|
+
stimulationId
|
|
265
|
+
]);
|
|
266
|
+
const row = stimRes.rows[0];
|
|
267
|
+
if (!row) return void 0;
|
|
268
|
+
const [frontier, context] = await Promise.all([
|
|
269
|
+
this.db.query(`SELECT ord, neuron_name, dendrite_collateral_name, input
|
|
270
|
+
FROM ${p}stimulation_frontier WHERE stimulation_id = $1 ORDER BY ord`, [
|
|
271
|
+
stimulationId
|
|
272
|
+
]),
|
|
273
|
+
this.db.query(`SELECT neuron_name, value FROM ${p}stimulation_context WHERE stimulation_id = $1`, [
|
|
274
|
+
stimulationId
|
|
275
|
+
])
|
|
276
|
+
]);
|
|
277
|
+
return {
|
|
278
|
+
stimulationId,
|
|
279
|
+
entry: row.entry,
|
|
280
|
+
status: row.status,
|
|
281
|
+
progress: this.buildProgress(frontier.rows, context.rows),
|
|
282
|
+
...row.scope_name != null ? {
|
|
283
|
+
scopeName: row.scope_name
|
|
284
|
+
} : {}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async listStimulations(filter) {
|
|
288
|
+
var _a;
|
|
289
|
+
await this.ensure();
|
|
290
|
+
const { p } = this;
|
|
291
|
+
const scope = (_a = filter == null ? void 0 : filter.scopeName) != null ? _a : null;
|
|
292
|
+
const stimsRes = await this.db.query(`SELECT stimulation_id, entry, status, scope_name FROM ${p}stimulation
|
|
293
|
+
WHERE ($1::text IS NULL OR scope_name = $1)
|
|
294
|
+
ORDER BY updated_at DESC`, [
|
|
295
|
+
scope
|
|
296
|
+
]);
|
|
297
|
+
const stims = stimsRes.rows;
|
|
298
|
+
if (stims.length === 0) return [];
|
|
299
|
+
const ids = stims.map((r) => r.stimulation_id);
|
|
300
|
+
const [frontierRes, contextRes] = await Promise.all([
|
|
301
|
+
this.db.query(`SELECT stimulation_id, ord, neuron_name, dendrite_collateral_name, input
|
|
302
|
+
FROM ${p}stimulation_frontier WHERE stimulation_id = ANY($1) ORDER BY stimulation_id, ord`, [
|
|
303
|
+
ids
|
|
304
|
+
]),
|
|
305
|
+
this.db.query(`SELECT stimulation_id, neuron_name, value
|
|
306
|
+
FROM ${p}stimulation_context WHERE stimulation_id = ANY($1)`, [
|
|
307
|
+
ids
|
|
308
|
+
])
|
|
309
|
+
]);
|
|
310
|
+
const frontierBy = groupBy(frontierRes.rows, (r) => r.stimulation_id);
|
|
311
|
+
const contextBy = groupBy(contextRes.rows, (r) => r.stimulation_id);
|
|
312
|
+
return stims.map((r) => {
|
|
313
|
+
var _a2, _b;
|
|
314
|
+
return {
|
|
315
|
+
stimulationId: r.stimulation_id,
|
|
316
|
+
entry: r.entry,
|
|
317
|
+
status: r.status,
|
|
318
|
+
progress: this.buildProgress((_a2 = frontierBy.get(r.stimulation_id)) != null ? _a2 : [], (_b = contextBy.get(r.stimulation_id)) != null ? _b : []),
|
|
319
|
+
...r.scope_name != null ? {
|
|
320
|
+
scopeName: r.scope_name
|
|
321
|
+
} : {}
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async getAttempts(stimulationId) {
|
|
326
|
+
await this.ensure();
|
|
327
|
+
const { p } = this;
|
|
328
|
+
const res = await this.db.query(`SELECT * FROM ${p}stimulation_attempt WHERE stimulation_id = $1 ORDER BY attempt_number`, [
|
|
329
|
+
stimulationId
|
|
330
|
+
]);
|
|
331
|
+
return res.rows.map(rowToAttempt);
|
|
332
|
+
}
|
|
333
|
+
async getTasks(stimulationAttemptId) {
|
|
334
|
+
await this.ensure();
|
|
335
|
+
const { p } = this;
|
|
336
|
+
const res = await this.db.query(`SELECT * FROM ${p}task WHERE stimulation_attempt_id = $1 ORDER BY "index"`, [
|
|
337
|
+
stimulationAttemptId
|
|
338
|
+
]);
|
|
339
|
+
return res.rows.map(rowToTask);
|
|
340
|
+
}
|
|
341
|
+
// ── lifecycle ──
|
|
342
|
+
async delete(stimulationId) {
|
|
343
|
+
await this.ensure();
|
|
344
|
+
const { p } = this;
|
|
345
|
+
await this.tx(async (db) => {
|
|
346
|
+
await db.query(`DELETE FROM ${p}task WHERE stimulation_attempt_id IN
|
|
347
|
+
(SELECT stimulation_attempt_id FROM ${p}stimulation_attempt WHERE stimulation_id = $1)`, [
|
|
348
|
+
stimulationId
|
|
349
|
+
]);
|
|
350
|
+
await db.query(`DELETE FROM ${p}stimulation_attempt WHERE stimulation_id = $1`, [
|
|
351
|
+
stimulationId
|
|
352
|
+
]);
|
|
353
|
+
await db.query(`DELETE FROM ${p}stimulation WHERE stimulation_id = $1`, [
|
|
354
|
+
stimulationId
|
|
355
|
+
]);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async close() {
|
|
359
|
+
if (this.ownsPool && this.pool) await this.pool.end();
|
|
360
|
+
}
|
|
361
|
+
// ── helpers ──
|
|
362
|
+
buildProgress(frontier, context) {
|
|
363
|
+
const tasks = [
|
|
364
|
+
...frontier
|
|
365
|
+
].sort((a, b) => a.ord - b.ord).map((f) => ({
|
|
366
|
+
neuronName: f.neuron_name,
|
|
367
|
+
dendriteCollateralName: f.dendrite_collateral_name,
|
|
368
|
+
...f.input != null ? {
|
|
369
|
+
input: f.input
|
|
370
|
+
} : {}
|
|
371
|
+
}));
|
|
372
|
+
const ctx = {};
|
|
373
|
+
for (const c of context) ctx[c.neuron_name] = c.value;
|
|
374
|
+
return {
|
|
375
|
+
tasks,
|
|
376
|
+
context: ctx
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
__name(_CNSPostgresStimulationRepository, "CNSPostgresStimulationRepository");
|
|
381
|
+
var CNSPostgresStimulationRepository = _CNSPostgresStimulationRepository;
|
|
382
|
+
function rowToAttempt(a) {
|
|
383
|
+
var _a;
|
|
384
|
+
return {
|
|
385
|
+
stimulationAttemptId: a.stimulation_attempt_id,
|
|
386
|
+
stimulationId: a.stimulation_id,
|
|
387
|
+
attemptNumber: num(a.attempt_number),
|
|
388
|
+
status: a.status,
|
|
389
|
+
startedAt: num(a.started_at),
|
|
390
|
+
completedAt: numOrNull(a.completed_at),
|
|
391
|
+
hopCount: num(a.hop_count),
|
|
392
|
+
hasError: Boolean(a.has_error),
|
|
393
|
+
replayOf: (_a = a.replay_of) != null ? _a : null,
|
|
394
|
+
entry: a.entry
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
__name(rowToAttempt, "rowToAttempt");
|
|
398
|
+
function rowToTask(t) {
|
|
399
|
+
var _a, _b;
|
|
400
|
+
return {
|
|
401
|
+
stimulationAttemptId: t.stimulation_attempt_id,
|
|
402
|
+
index: num(t.index),
|
|
403
|
+
neuronName: t.neuron_name,
|
|
404
|
+
dendriteCollateralName: t.dendrite_collateral_name,
|
|
405
|
+
inputIndex: num(t.input_index),
|
|
406
|
+
output: (_a = t.output) != null ? _a : null,
|
|
407
|
+
status: t.status,
|
|
408
|
+
error: (_b = t.error) != null ? _b : null,
|
|
409
|
+
startedAt: num(t.started_at),
|
|
410
|
+
duration: numOrNull(t.duration)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
__name(rowToTask, "rowToTask");
|
|
414
|
+
function groupBy(rows, key) {
|
|
415
|
+
const m = /* @__PURE__ */ new Map();
|
|
416
|
+
for (const r of rows) {
|
|
417
|
+
const k = key(r);
|
|
418
|
+
const b = m.get(k);
|
|
419
|
+
if (b) b.push(r);
|
|
420
|
+
else m.set(k, [
|
|
421
|
+
r
|
|
422
|
+
]);
|
|
423
|
+
}
|
|
424
|
+
return m;
|
|
425
|
+
}
|
|
426
|
+
__name(groupBy, "groupBy");
|
|
427
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
428
|
+
0 && (module.exports = {
|
|
429
|
+
CNSPostgresStimulationRepository,
|
|
430
|
+
DEFAULT_PREFIX,
|
|
431
|
+
assertSafePrefix,
|
|
432
|
+
ensureSchema,
|
|
433
|
+
persistPostgresSchemaSql
|
|
434
|
+
});
|
|
435
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/CNSPostgresStimulationRepository.ts","../src/migrate.ts"],"sourcesContent":["export {\n CNSPostgresStimulationRepository,\n type TCNSPostgresStimulationRepositoryOptions,\n} from './CNSPostgresStimulationRepository';\nexport {\n ensureSchema,\n persistPostgresSchemaSql,\n assertSafePrefix,\n DEFAULT_PREFIX,\n type IPgQueryable,\n} from './migrate';\n","/**\n * Postgres relational store for the CNStra stimulation/attempt/task durable model\n * (`ICNSStimulationRepository`). The stimulation's `progress` is NORMALISED: the\n * outstanding frontier into `<prefix>stimulation_frontier` (one row per task) and\n * per-neuron context into `<prefix>stimulation_context` — rebuilt into `TCNSProgress`\n * on read.\n *\n * Reuses the `IPgQueryable` seam so `pg` is an optional peer: inject any pg\n * client/pool, or pass a `connectionString` for the repo to own a Pool. Behaviourally\n * matches `CNSInMemoryStimulationRepository` (listStimulations newest-first, getAttempts\n * by attemptNumber, getTasks by index, delete cascades).\n */\nimport { Pool } from 'pg';\nimport type { ICNSStimulationRepository } from '@cnstra/persist';\nimport type {\n TCNSStimulationPersisted,\n TCNSStimulationAttemptPersisted,\n TCNSStimulationTaskPersisted,\n TCNSProgress,\n TCNSSerializedTask,\n TCNSSignalRef,\n TCNSStimulationStatus,\n TCNSStimulationTaskStatus,\n} from '@cnstra/persist-dto';\nimport {\n DEFAULT_PREFIX,\n assertSafePrefix,\n ensureSchema,\n type IPgQueryable,\n} from './migrate';\n\nexport interface TCNSPostgresStimulationRepositoryOptions {\n connectionString?: string;\n /** Inject an existing pg Pool (transactions supported; not closed by `close()`). */\n pool?: Pool;\n /** Inject any queryable (reads + best-effort non-transactional writes). */\n queryable?: IPgQueryable;\n /** Table prefix (default `cns_`). */\n tablePrefix?: string;\n /** Lazily ensure the schema on first use (default true). */\n autoMigrate?: boolean;\n}\n\nconst j = (v: unknown): string => JSON.stringify(v ?? null);\nconst num = (v: unknown): number => Number(v);\nconst numOrNull = (v: unknown): number | null => (v == null ? null : Number(v));\n\nexport class CNSPostgresStimulationRepository\n implements ICNSStimulationRepository\n{\n private readonly db: IPgQueryable;\n private readonly pool?: Pool;\n private readonly ownsPool: boolean;\n private readonly p: string;\n private readonly autoMigrate: boolean;\n private ready: Promise<void> | undefined;\n\n constructor(opts: TCNSPostgresStimulationRepositoryOptions) {\n if (opts.pool) {\n this.pool = opts.pool;\n this.db = opts.pool;\n this.ownsPool = false;\n } else if (opts.connectionString) {\n this.pool = new Pool({ connectionString: opts.connectionString });\n this.db = this.pool;\n this.ownsPool = true;\n } else if (opts.queryable) {\n this.db = opts.queryable;\n this.ownsPool = false;\n } else {\n throw new Error(\n '[@cnstra/persist-postgres] needs `connectionString`, `pool`, or `queryable`'\n );\n }\n this.p = assertSafePrefix(opts.tablePrefix ?? DEFAULT_PREFIX);\n this.autoMigrate = opts.autoMigrate ?? true;\n }\n\n private ensure(): Promise<void> {\n if (!this.autoMigrate) return Promise.resolve();\n return (this.ready ??= ensureSchema(this.db, this.p));\n }\n\n /** Run `fn` in a transaction when a Pool is available; else best-effort inline. */\n private async tx<T>(fn: (db: IPgQueryable) => Promise<T>): Promise<T> {\n if (!this.pool) return fn(this.db);\n const client = await this.pool.connect();\n try {\n await client.query('BEGIN');\n const out = await fn(client as unknown as IPgQueryable);\n await client.query('COMMIT');\n return out;\n } catch (e) {\n await client.query('ROLLBACK');\n throw e;\n } finally {\n client.release();\n }\n }\n\n // ── write ──\n\n async saveStimulation(s: TCNSStimulationPersisted): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.tx(async db => {\n await db.query(\n `INSERT INTO ${p}stimulation (stimulation_id, entry, status, scope_name, updated_at)\n VALUES ($1, $2::jsonb, $3, $4, now())\n ON CONFLICT (stimulation_id)\n DO UPDATE SET entry = EXCLUDED.entry, status = EXCLUDED.status,\n scope_name = EXCLUDED.scope_name, updated_at = now()`,\n [s.stimulationId, j(s.entry), s.status, s.scopeName ?? null]\n );\n // Replace the normalised frontier.\n await db.query(\n `DELETE FROM ${p}stimulation_frontier WHERE stimulation_id = $1`,\n [s.stimulationId]\n );\n for (let i = 0; i < s.progress.tasks.length; i++) {\n const t = s.progress.tasks[i];\n await db.query(\n `INSERT INTO ${p}stimulation_frontier\n (stimulation_id, ord, neuron_name, dendrite_collateral_name, input)\n VALUES ($1, $2, $3, $4, $5::jsonb)`,\n [\n s.stimulationId,\n i,\n t.neuronName,\n t.dendriteCollateralName,\n t.input ? j(t.input) : null,\n ]\n );\n }\n // Replace the normalised context.\n await db.query(\n `DELETE FROM ${p}stimulation_context WHERE stimulation_id = $1`,\n [s.stimulationId]\n );\n for (const [neuronName, value] of Object.entries(\n s.progress.context\n )) {\n await db.query(\n `INSERT INTO ${p}stimulation_context (stimulation_id, neuron_name, value)\n VALUES ($1, $2, $3::jsonb)`,\n [s.stimulationId, neuronName, j(value)]\n );\n }\n });\n }\n\n async saveAttempt(a: TCNSStimulationAttemptPersisted): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.db.query(\n `INSERT INTO ${p}stimulation_attempt\n (stimulation_attempt_id, stimulation_id, attempt_number, status, started_at,\n completed_at, hop_count, has_error, replay_of, entry)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb)\n ON CONFLICT (stimulation_attempt_id) DO UPDATE SET\n status = EXCLUDED.status, completed_at = EXCLUDED.completed_at,\n hop_count = EXCLUDED.hop_count, has_error = EXCLUDED.has_error,\n replay_of = EXCLUDED.replay_of, entry = EXCLUDED.entry`,\n [\n a.stimulationAttemptId,\n a.stimulationId,\n a.attemptNumber,\n a.status,\n a.startedAt,\n a.completedAt,\n a.hopCount,\n a.hasError,\n a.replayOf,\n j(a.entry),\n ]\n );\n }\n\n async appendTask(t: TCNSStimulationTaskPersisted): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.db.query(\n `INSERT INTO ${p}task\n (stimulation_attempt_id, \"index\", neuron_name, dendrite_collateral_name,\n input_index, output, status, error, started_at, duration)\n VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10)\n ON CONFLICT (stimulation_attempt_id, \"index\") DO NOTHING`,\n [\n t.stimulationAttemptId,\n t.index,\n t.neuronName,\n t.dendriteCollateralName,\n t.inputIndex,\n t.output ? j(t.output) : null,\n t.status,\n t.error,\n t.startedAt,\n t.duration,\n ]\n );\n }\n\n // ── read ──\n\n async getStimulation(\n stimulationId: string\n ): Promise<TCNSStimulationPersisted | undefined> {\n await this.ensure();\n const { p } = this;\n const stimRes = await this.db.query(\n `SELECT entry, status, scope_name FROM ${p}stimulation WHERE stimulation_id = $1`,\n [stimulationId]\n );\n const row = stimRes.rows[0] as\n | {\n entry: TCNSSignalRef;\n status: TCNSStimulationStatus;\n scope_name: string | null;\n }\n | undefined;\n if (!row) return undefined;\n const [frontier, context] = await Promise.all([\n this.db.query(\n `SELECT ord, neuron_name, dendrite_collateral_name, input\n FROM ${p}stimulation_frontier WHERE stimulation_id = $1 ORDER BY ord`,\n [stimulationId]\n ),\n this.db.query(\n `SELECT neuron_name, value FROM ${p}stimulation_context WHERE stimulation_id = $1`,\n [stimulationId]\n ),\n ]);\n return {\n stimulationId,\n entry: row.entry,\n status: row.status,\n progress: this.buildProgress(\n frontier.rows as TFrontierRow[],\n context.rows as TContextRow[]\n ),\n ...(row.scope_name != null ? { scopeName: row.scope_name } : {}),\n };\n }\n\n async listStimulations(filter?: {\n scopeName?: string;\n }): Promise<TCNSStimulationPersisted[]> {\n await this.ensure();\n const { p } = this;\n const scope = filter?.scopeName ?? null;\n const stimsRes = await this.db.query(\n `SELECT stimulation_id, entry, status, scope_name FROM ${p}stimulation\n WHERE ($1::text IS NULL OR scope_name = $1)\n ORDER BY updated_at DESC`,\n [scope]\n );\n const stims = stimsRes.rows as TStimulationRow[];\n if (stims.length === 0) return [];\n const ids = stims.map(r => r.stimulation_id);\n const [frontierRes, contextRes] = await Promise.all([\n this.db.query(\n `SELECT stimulation_id, ord, neuron_name, dendrite_collateral_name, input\n FROM ${p}stimulation_frontier WHERE stimulation_id = ANY($1) ORDER BY stimulation_id, ord`,\n [ids]\n ),\n this.db.query(\n `SELECT stimulation_id, neuron_name, value\n FROM ${p}stimulation_context WHERE stimulation_id = ANY($1)`,\n [ids]\n ),\n ]);\n const frontierBy = groupBy(\n frontierRes.rows as TFrontierRow[],\n r => r.stimulation_id\n );\n const contextBy = groupBy(\n contextRes.rows as TContextRow[],\n r => r.stimulation_id\n );\n return stims.map(r => ({\n stimulationId: r.stimulation_id,\n entry: r.entry,\n status: r.status,\n progress: this.buildProgress(\n frontierBy.get(r.stimulation_id) ?? [],\n contextBy.get(r.stimulation_id) ?? []\n ),\n ...(r.scope_name != null ? { scopeName: r.scope_name } : {}),\n }));\n }\n\n async getAttempts(\n stimulationId: string\n ): Promise<TCNSStimulationAttemptPersisted[]> {\n await this.ensure();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_attempt WHERE stimulation_id = $1 ORDER BY attempt_number`,\n [stimulationId]\n );\n return (res.rows as TAttemptRow[]).map(rowToAttempt);\n }\n\n async getTasks(\n stimulationAttemptId: string\n ): Promise<TCNSStimulationTaskPersisted[]> {\n await this.ensure();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}task WHERE stimulation_attempt_id = $1 ORDER BY \"index\"`,\n [stimulationAttemptId]\n );\n return (res.rows as TTaskRow[]).map(rowToTask);\n }\n\n // ── lifecycle ──\n\n async delete(stimulationId: string): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.tx(async db => {\n // tasks + attempts have no FK (see migrate.ts) → delete them explicitly,\n // tasks first (they reference attempt ids), then the attempts.\n await db.query(\n `DELETE FROM ${p}task WHERE stimulation_attempt_id IN\n (SELECT stimulation_attempt_id FROM ${p}stimulation_attempt WHERE stimulation_id = $1)`,\n [stimulationId]\n );\n await db.query(\n `DELETE FROM ${p}stimulation_attempt WHERE stimulation_id = $1`,\n [stimulationId]\n );\n // FK ON DELETE CASCADE removes frontier/context.\n await db.query(\n `DELETE FROM ${p}stimulation WHERE stimulation_id = $1`,\n [stimulationId]\n );\n });\n }\n\n async close(): Promise<void> {\n if (this.ownsPool && this.pool) await this.pool.end();\n }\n\n // ── helpers ──\n\n private buildProgress(\n frontier: TFrontierRow[],\n context: TContextRow[]\n ): TCNSProgress {\n const tasks: TCNSSerializedTask[] = [...frontier]\n .sort((a, b) => a.ord - b.ord)\n .map(f => ({\n neuronName: f.neuron_name,\n dendriteCollateralName: f.dendrite_collateral_name,\n ...(f.input != null ? { input: f.input } : {}),\n }));\n const ctx: Record<string, unknown> = {};\n for (const c of context) ctx[c.neuron_name] = c.value;\n return { tasks, context: ctx };\n }\n}\n\n// ── row shapes (jsonb columns come back already parsed by node-pg) ──\ntype TStimulationRow = {\n stimulation_id: string;\n entry: TCNSSignalRef;\n status: TCNSStimulationStatus;\n scope_name: string | null;\n};\ntype TFrontierRow = {\n stimulation_id: string;\n ord: number;\n neuron_name: string;\n dendrite_collateral_name: string;\n input: { collateralName: string; payload?: unknown } | null;\n};\ntype TContextRow = {\n stimulation_id: string;\n neuron_name: string;\n value: unknown;\n};\ntype TAttemptRow = {\n stimulation_attempt_id: string;\n stimulation_id: string;\n attempt_number: number;\n status: TCNSStimulationStatus;\n started_at: string | number;\n completed_at: string | number | null;\n hop_count: number;\n has_error: boolean;\n replay_of: string | null;\n entry: TCNSSignalRef[];\n};\ntype TTaskRow = {\n stimulation_attempt_id: string;\n index: number;\n neuron_name: string;\n dendrite_collateral_name: string;\n input_index: number;\n output: TCNSSignalRef | null;\n status: TCNSStimulationTaskStatus;\n error: string | null;\n started_at: string | number;\n duration: number | null;\n};\n\nfunction rowToAttempt(a: TAttemptRow): TCNSStimulationAttemptPersisted {\n return {\n stimulationAttemptId: a.stimulation_attempt_id,\n stimulationId: a.stimulation_id,\n attemptNumber: num(a.attempt_number),\n status: a.status,\n startedAt: num(a.started_at),\n completedAt: numOrNull(a.completed_at),\n hopCount: num(a.hop_count),\n hasError: Boolean(a.has_error),\n replayOf: a.replay_of ?? null,\n entry: a.entry,\n };\n}\n\nfunction rowToTask(t: TTaskRow): TCNSStimulationTaskPersisted {\n return {\n stimulationAttemptId: t.stimulation_attempt_id,\n index: num(t.index),\n neuronName: t.neuron_name,\n dendriteCollateralName: t.dendrite_collateral_name,\n inputIndex: num(t.input_index),\n output: t.output ?? null,\n status: t.status,\n error: t.error ?? null,\n startedAt: num(t.started_at),\n duration: numOrNull(t.duration),\n };\n}\n\nfunction groupBy<T, K>(rows: T[], key: (r: T) => K): Map<K, T[]> {\n const m = new Map<K, T[]>();\n for (const r of rows) {\n const k = key(r);\n const b = m.get(k);\n if (b) b.push(r);\n else m.set(k, [r]);\n }\n return m;\n}\n","/**\n * Schema / migrations for the CNStra stimulation/attempt/task relational store.\n *\n * The package OWNS this schema — run it explicitly via `cnstra-persist-postgres migrate`\n * or `ensureSchema(db)`, or let the repository auto-create it on first use. Five tables\n * under a configurable prefix (default `cns_`); the stimulation's `progress` is\n * NORMALISED into `stimulation_frontier` (one row per outstanding task) +\n * `stimulation_context` (one row per neuron).\n */\n\n/** Minimal pg client surface (a `pg` Pool or Client satisfies it). */\nexport interface IPgQueryable {\n query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }>;\n}\n\nexport const DEFAULT_PREFIX = 'cns_';\n\n/** Guard the table prefix (it is interpolated into DDL/DML, not parameterised). */\nexport function assertSafePrefix(prefix: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {\n throw new Error(\n `[@cnstra/persist-postgres] unsafe table prefix: ${JSON.stringify(prefix)}`\n );\n }\n return prefix;\n}\n\n/** The full CREATE TABLE + CREATE INDEX DDL (idempotent). */\nexport function persistPostgresSchemaSql(prefix: string = DEFAULT_PREFIX): string {\n const p = assertSafePrefix(prefix);\n return `\nCREATE TABLE IF NOT EXISTS ${p}stimulation (\n stimulation_id text PRIMARY KEY,\n entry jsonb NOT NULL,\n status text NOT NULL,\n scope_name text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_updated_idx ON ${p}stimulation (updated_at DESC);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_scope_idx ON ${p}stimulation (scope_name);\n\nCREATE TABLE IF NOT EXISTS ${p}stimulation_frontier (\n stimulation_id text NOT NULL REFERENCES ${p}stimulation(stimulation_id) ON DELETE CASCADE,\n ord int NOT NULL,\n neuron_name text NOT NULL,\n dendrite_collateral_name text NOT NULL,\n input jsonb,\n PRIMARY KEY (stimulation_id, ord)\n);\n\nCREATE TABLE IF NOT EXISTS ${p}stimulation_context (\n stimulation_id text NOT NULL REFERENCES ${p}stimulation(stimulation_id) ON DELETE CASCADE,\n neuron_name text NOT NULL,\n value jsonb NOT NULL,\n PRIMARY KEY (stimulation_id, neuron_name)\n);\n\n-- NOTE: no FK on stimulation_id — over the devtools wire the persistor's\n-- saveStimulation and saveAttempt ride SEPARATE async batches processed\n-- concurrently by the server, so an attempt insert can reach Postgres before its\n-- stimulation row's transaction commits; a hard FK would reject it (23503). Same\n-- exposure as ${p}task; delete() removes attempts explicitly instead of cascading.\nCREATE TABLE IF NOT EXISTS ${p}stimulation_attempt (\n stimulation_attempt_id text PRIMARY KEY,\n stimulation_id text NOT NULL,\n attempt_number int NOT NULL,\n status text NOT NULL,\n started_at bigint NOT NULL,\n completed_at bigint,\n hop_count int NOT NULL,\n has_error boolean NOT NULL,\n replay_of text,\n entry jsonb NOT NULL,\n UNIQUE (stimulation_id, attempt_number)\n);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_idx ON ${p}stimulation_attempt (stimulation_id, attempt_number);\n\n-- NOTE: no FK on stimulation_attempt_id — the persistor streams settled tasks\n-- (appendTask) as hops complete, BEFORE the attempt marker is flushed (saveAttempt),\n-- so a hard FK would reject the insert. delete() removes tasks explicitly instead.\nCREATE TABLE IF NOT EXISTS ${p}task (\n stimulation_attempt_id text NOT NULL,\n \"index\" int NOT NULL,\n neuron_name text NOT NULL,\n dendrite_collateral_name text NOT NULL,\n input_index int NOT NULL,\n output jsonb,\n status text NOT NULL,\n error text,\n started_at bigint NOT NULL,\n duration int,\n PRIMARY KEY (stimulation_attempt_id, \"index\")\n);\n`;\n}\n\n/** Idempotently create all tables + indexes. Safe to run repeatedly. */\nexport async function ensureSchema(\n db: IPgQueryable,\n prefix: string = DEFAULT_PREFIX\n): Promise<void> {\n await db.query(persistPostgresSchemaSql(prefix));\n}\n"],"mappings":"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","names":["DEFAULT_PREFIX","assertSafePrefix","prefix","test","Error","JSON","stringify","persistPostgresSchemaSql","p","ensureSchema","db","query","j","v","JSON","stringify","num","Number","numOrNull","CNSPostgresStimulationRepository","opts","db","pool","ownsPool","p","autoMigrate","ready","connectionString","Pool","queryable","Error","assertSafePrefix","tablePrefix","DEFAULT_PREFIX","ensure","Promise","resolve","ensureSchema","tx","fn","client","connect","query","out","e","release","saveStimulation","s","stimulationId","entry","status","scopeName","i","progress","tasks","length","t","neuronName","dendriteCollateralName","input","value","Object","entries","context","saveAttempt","a","stimulationAttemptId","attemptNumber","startedAt","completedAt","hopCount","hasError","replayOf","appendTask","index","inputIndex","output","error","duration","getStimulation","stimRes","row","rows","undefined","frontier","all","buildProgress","scope_name","listStimulations","filter","scope","stimsRes","stims","ids","map","r","stimulation_id","frontierRes","contextRes","frontierBy","groupBy","contextBy","_a","get","getAttempts","res","rowToAttempt","getTasks","rowToTask","delete","close","end","sort","b","ord","f","neuron_name","dendrite_collateral_name","ctx","c","stimulation_attempt_id","attempt_number","started_at","completed_at","hop_count","Boolean","has_error","replay_of","input_index","key","m","Map","k","push","set"]}
|