@cnstra/persist-postgres 0.3.0 → 0.5.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 +60 -20
- package/dist/chunk-BPKIKGKB.mjs +310 -0
- package/dist/chunk-BPKIKGKB.mjs.map +1 -0
- package/dist/cli.js +277 -57
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +6 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +54 -51
- package/dist/index.d.ts +54 -51
- package/dist/index.js +656 -264
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +388 -213
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -3
- package/dist/chunk-5FLIKV6I.mjs +0 -94
- package/dist/chunk-5FLIKV6I.mjs.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,52 +1,266 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
4
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
6
|
|
|
5
7
|
// src/cli.ts
|
|
6
8
|
var import_pg = require("pg");
|
|
7
9
|
|
|
8
|
-
// src/
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// ../postgres-schema/src/CNSPostgresSchemaMigrator.ts
|
|
11
|
+
var _CNSPostgresSchemaMigrator = class _CNSPostgresSchemaMigrator {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
__publicField(this, "options");
|
|
14
|
+
__publicField(this, "steps");
|
|
15
|
+
var _a;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.steps = [
|
|
18
|
+
...options.steps
|
|
19
|
+
].sort((left, right) => left.step - right.step);
|
|
20
|
+
this.steps.forEach((step, index) => {
|
|
21
|
+
if (step.step !== index + 1) throw new Error(`[${options.name}] schema steps must be numbered 1, 2, 3\u2026; found ${step.step} at position ${index + 1}`);
|
|
22
|
+
});
|
|
23
|
+
if (((_a = this.steps[0]) == null ? void 0 : _a.kind) !== "expand") throw new Error(`[${options.name}] schema step 1 must be an expand step`);
|
|
13
24
|
}
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
migrationTable(tablePrefix) {
|
|
26
|
+
return `${tablePrefix}${this.options.name}_migration`;
|
|
27
|
+
}
|
|
28
|
+
async migrate(database, tablePrefix, options = {}) {
|
|
29
|
+
await database.transaction(async (connection) => {
|
|
30
|
+
await connection.query("SELECT pg_advisory_xact_lock($1, hashtext($2))", [
|
|
31
|
+
_CNSPostgresSchemaMigrator.LOCK_NAMESPACE,
|
|
32
|
+
tablePrefix + this.options.name
|
|
33
|
+
]);
|
|
34
|
+
await connection.query(`SET LOCAL lock_timeout = '${_CNSPostgresSchemaMigrator.LOCK_TIMEOUT}'`);
|
|
35
|
+
const applied = await this.readAppliedSteps(connection, tablePrefix);
|
|
36
|
+
if (applied === void 0) await this.assertNoUntrackedTables(connection, tablePrefix);
|
|
37
|
+
const pending = this.pendingSteps(applied != null ? applied : /* @__PURE__ */ new Map(), applied === void 0 || options.contract === true);
|
|
38
|
+
if (pending.length > 0) await connection.query(this.sqlFor(tablePrefix, pending, applied === void 0));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async verify(connection, tablePrefix) {
|
|
42
|
+
const applied = await this.readAppliedSteps(connection, tablePrefix);
|
|
43
|
+
if (applied === void 0) {
|
|
44
|
+
throw new Error(`[${this.options.name}] the schema is not installed under prefix "${tablePrefix}": run migrate()`);
|
|
45
|
+
}
|
|
46
|
+
const missing = this.steps.filter((step) => step.kind === "expand" && !applied.has(step.step)).map((step) => step.step);
|
|
47
|
+
if (missing.length > 0) {
|
|
48
|
+
throw new Error(`[${this.options.name}] schema steps ${missing.join(", ")} are not applied under prefix "${tablePrefix}": run migrate()`);
|
|
49
|
+
}
|
|
50
|
+
const unknown = [
|
|
51
|
+
...applied
|
|
52
|
+
].filter(([step, kind]) => kind === "contract" && step > this.steps.length).map(([step]) => step);
|
|
53
|
+
if (unknown.length > 0) {
|
|
54
|
+
throw new Error(`[${this.options.name}] the database has contract steps ${unknown.join(", ")} that this version does not know: upgrade the package`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
migrationSql(tablePrefix, appliedSteps = [], options = {}) {
|
|
58
|
+
const fresh = appliedSteps.length === 0;
|
|
59
|
+
const applied = new Map(appliedSteps.map((step) => [
|
|
60
|
+
step,
|
|
61
|
+
"expand"
|
|
62
|
+
]));
|
|
63
|
+
return this.sqlFor(tablePrefix, this.pendingSteps(applied, fresh || options.contract === true), fresh);
|
|
64
|
+
}
|
|
65
|
+
assertTablePrefix(tablePrefix, longestSuffix) {
|
|
66
|
+
const longest = Math.max(longestSuffix.length, `${this.options.name}_migration`.length);
|
|
67
|
+
const limit = _CNSPostgresSchemaMigrator.MAX_IDENTIFIER_LENGTH - longest;
|
|
68
|
+
if (!new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${limit - 1}}$`).test(tablePrefix)) {
|
|
69
|
+
throw new Error(`[${this.options.name}] unsafe table prefix ${JSON.stringify(tablePrefix)}: letters, digits and _ only, at most ${limit} characters`);
|
|
70
|
+
}
|
|
71
|
+
return tablePrefix;
|
|
72
|
+
}
|
|
73
|
+
pendingSteps(applied, withContract) {
|
|
74
|
+
return this.steps.filter((step) => !applied.has(step.step) && (step.kind === "expand" || withContract));
|
|
75
|
+
}
|
|
76
|
+
sqlFor(tablePrefix, steps, createMigrationTable) {
|
|
77
|
+
const table = this.migrationTable(tablePrefix);
|
|
78
|
+
const parts = createMigrationTable ? [
|
|
79
|
+
`CREATE TABLE ${table} (step integer PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('expand', 'contract')));`
|
|
80
|
+
] : [];
|
|
81
|
+
for (const step of steps) {
|
|
82
|
+
parts.push(step.sql(tablePrefix), `INSERT INTO ${table} (step, kind) VALUES (${step.step}, '${step.kind}');`);
|
|
83
|
+
}
|
|
84
|
+
return parts.join("\n");
|
|
85
|
+
}
|
|
86
|
+
async readAppliedSteps(connection, tablePrefix) {
|
|
87
|
+
var _a;
|
|
88
|
+
const table = this.migrationTable(tablePrefix);
|
|
89
|
+
const exists = await connection.query("SELECT to_regclass($1)::text AS found", [
|
|
90
|
+
table
|
|
91
|
+
]);
|
|
92
|
+
if (((_a = exists.rows[0]) == null ? void 0 : _a.found) == null) return void 0;
|
|
93
|
+
const rows = await connection.query(`SELECT step, kind FROM ${table}`);
|
|
94
|
+
return new Map(rows.rows.map((row) => [
|
|
95
|
+
Number(row.step),
|
|
96
|
+
row.kind
|
|
97
|
+
]));
|
|
98
|
+
}
|
|
99
|
+
async assertNoUntrackedTables(connection, tablePrefix) {
|
|
100
|
+
const tables = this.options.tables(tablePrefix);
|
|
101
|
+
const found = await connection.query("SELECT name FROM unnest($1::text[]) AS name WHERE to_regclass(name) IS NOT NULL", [
|
|
102
|
+
tables
|
|
103
|
+
]);
|
|
104
|
+
if (found.rows.length > 0) {
|
|
105
|
+
throw new Error(`[${this.options.name}] tables ${found.rows.map((row) => row.name).join(", ")} exist without ${this.migrationTable(tablePrefix)}: they were created by an earlier unversioned release; drop them or move the data before migrate()`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
__name(_CNSPostgresSchemaMigrator, "CNSPostgresSchemaMigrator");
|
|
110
|
+
__publicField(_CNSPostgresSchemaMigrator, "LOCK_NAMESPACE", 6);
|
|
111
|
+
__publicField(_CNSPostgresSchemaMigrator, "LOCK_TIMEOUT", "5s");
|
|
112
|
+
__publicField(_CNSPostgresSchemaMigrator, "MAX_IDENTIFIER_LENGTH", 63);
|
|
113
|
+
var CNSPostgresSchemaMigrator = _CNSPostgresSchemaMigrator;
|
|
114
|
+
|
|
115
|
+
// ../postgres-schema/src/CNSPostgresSchemaPgDatabase.ts
|
|
116
|
+
var _CNSPostgresSchemaPgDatabase = class _CNSPostgresSchemaPgDatabase {
|
|
117
|
+
constructor(open) {
|
|
118
|
+
__publicField(this, "open");
|
|
119
|
+
this.open = open;
|
|
120
|
+
}
|
|
121
|
+
static fromPool(pool) {
|
|
122
|
+
return new _CNSPostgresSchemaPgDatabase(async () => {
|
|
123
|
+
const client = await pool.connect();
|
|
124
|
+
return {
|
|
125
|
+
connection: client,
|
|
126
|
+
release: /* @__PURE__ */ __name((error) => client.release(error), "release")
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async transaction(work) {
|
|
131
|
+
const { connection, release } = await this.open();
|
|
132
|
+
try {
|
|
133
|
+
await connection.query("BEGIN");
|
|
134
|
+
const result = await work(connection);
|
|
135
|
+
await connection.query("COMMIT");
|
|
136
|
+
release();
|
|
137
|
+
return result;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const rollback = await connection.query("ROLLBACK").then(() => void 0, (rollbackError) => rollbackError);
|
|
140
|
+
release(rollback instanceof Error ? rollback : void 0);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
__name(_CNSPostgresSchemaPgDatabase, "CNSPostgresSchemaPgDatabase");
|
|
146
|
+
var CNSPostgresSchemaPgDatabase = _CNSPostgresSchemaPgDatabase;
|
|
147
|
+
|
|
148
|
+
// ../postgres-schema/src/CNSPostgresSchemaPartitionRotation.ts
|
|
149
|
+
var _CNSPostgresSchemaPartitionRotation = class _CNSPostgresSchemaPartitionRotation {
|
|
150
|
+
constructor(options) {
|
|
151
|
+
__publicField(this, "options");
|
|
152
|
+
__publicField(this, "rotatedFor", /* @__PURE__ */ new Map());
|
|
153
|
+
this.options = options;
|
|
154
|
+
if (!(options.premakeDays >= 1) || !Number.isInteger(options.premakeDays)) {
|
|
155
|
+
throw new Error(`[partition rotation] premakeDays must be a positive integer, got ${options.premakeDays}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async rotate(database, table, now, dropBefore, beforeDrop) {
|
|
159
|
+
const today = Math.floor(now / _CNSPostgresSchemaPartitionRotation.DAY_MS);
|
|
160
|
+
const days = `${today}:${Math.floor(dropBefore / _CNSPostgresSchemaPartitionRotation.DAY_MS)}`;
|
|
161
|
+
if (this.rotatedFor.get(table) === days) return [];
|
|
162
|
+
const dropped = await this.underLock(database, table, async (connection) => {
|
|
163
|
+
const expired = (await this.readRangePartitions(connection, table)).filter((partition) => partition.to <= dropBefore);
|
|
164
|
+
if (beforeDrop) for (const partition of expired) await beforeDrop(connection, partition.name);
|
|
165
|
+
for (const partition of expired) await connection.query(`DROP TABLE ${partition.name}`);
|
|
166
|
+
return expired.map((partition) => partition.name);
|
|
167
|
+
});
|
|
168
|
+
await this.underLock(database, table, async (connection) => {
|
|
169
|
+
const partitions = await this.readRangePartitions(connection, table);
|
|
170
|
+
for (let ahead = 1; ahead <= this.options.premakeDays; ahead++) {
|
|
171
|
+
const from = (today + ahead) * _CNSPostgresSchemaPartitionRotation.DAY_MS;
|
|
172
|
+
const to = from + _CNSPostgresSchemaPartitionRotation.DAY_MS;
|
|
173
|
+
if (partitions.some((partition) => partition.from < to && from < partition.to)) continue;
|
|
174
|
+
const name = `${table}_p${new Date(from).toISOString().slice(0, 10).replace(/-/g, "")}`;
|
|
175
|
+
await connection.query(`CREATE TABLE ${name} (LIKE ${table} INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`);
|
|
176
|
+
await connection.query(`ALTER TABLE ${table} ATTACH PARTITION ${name} FOR VALUES FROM (${from}) TO (${to})`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this.rotatedFor.set(table, days);
|
|
180
|
+
return dropped;
|
|
181
|
+
}
|
|
182
|
+
underLock(database, table, work) {
|
|
183
|
+
return database.transaction(async (connection) => {
|
|
184
|
+
await connection.query("SELECT pg_advisory_xact_lock($1, hashtext($2))", [
|
|
185
|
+
_CNSPostgresSchemaPartitionRotation.LOCK_NAMESPACE,
|
|
186
|
+
table
|
|
187
|
+
]);
|
|
188
|
+
await connection.query(`SET LOCAL lock_timeout = '${_CNSPostgresSchemaPartitionRotation.LOCK_TIMEOUT}'`);
|
|
189
|
+
return work(connection);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async readRangePartitions(connection, table) {
|
|
193
|
+
const rows = await connection.query(`SELECT child.oid::regclass::text AS name, pg_get_expr(child.relpartbound, child.oid) AS bound
|
|
194
|
+
FROM pg_inherits
|
|
195
|
+
JOIN pg_class child ON child.oid = pg_inherits.inhrelid
|
|
196
|
+
WHERE pg_inherits.inhparent = $1::regclass`, [
|
|
197
|
+
table
|
|
198
|
+
]);
|
|
199
|
+
const partitions = [];
|
|
200
|
+
for (const row of rows.rows) {
|
|
201
|
+
const range = /^FOR VALUES FROM \('?(-?[\d.e+]+)'?\) TO \('?(-?[\d.e+]+)'?\)$/.exec(row.bound);
|
|
202
|
+
if (range) partitions.push({
|
|
203
|
+
name: row.name,
|
|
204
|
+
from: Number(range[1]),
|
|
205
|
+
to: Number(range[2])
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return partitions;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
__name(_CNSPostgresSchemaPartitionRotation, "CNSPostgresSchemaPartitionRotation");
|
|
212
|
+
__publicField(_CNSPostgresSchemaPartitionRotation, "LOCK_NAMESPACE", 8);
|
|
213
|
+
__publicField(_CNSPostgresSchemaPartitionRotation, "LOCK_TIMEOUT", "5s");
|
|
214
|
+
__publicField(_CNSPostgresSchemaPartitionRotation, "DAY_MS", 24 * 60 * 60 * 1e3);
|
|
215
|
+
var CNSPostgresSchemaPartitionRotation = _CNSPostgresSchemaPartitionRotation;
|
|
216
|
+
|
|
217
|
+
// src/schema/CNSPostgresStimulationSchema.ts
|
|
218
|
+
var _CNSPostgresStimulationSchema = class _CNSPostgresStimulationSchema {
|
|
219
|
+
static assertTablePrefix(tablePrefix) {
|
|
220
|
+
return _CNSPostgresStimulationSchema.migrator.assertTablePrefix(tablePrefix, _CNSPostgresStimulationSchema.LONGEST_SUFFIX);
|
|
221
|
+
}
|
|
222
|
+
static tables(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
|
|
223
|
+
const p = _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix);
|
|
224
|
+
return [
|
|
225
|
+
`${p}stimulation`,
|
|
226
|
+
`${p}stimulation_attempt`,
|
|
227
|
+
`${p}stimulation_task`
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
static migrationTable(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
|
|
231
|
+
return _CNSPostgresStimulationSchema.migrator.migrationTable(_CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));
|
|
232
|
+
}
|
|
233
|
+
static migrationSql(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX, appliedSteps = [], options) {
|
|
234
|
+
return _CNSPostgresStimulationSchema.migrator.migrationSql(_CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), appliedSteps, options);
|
|
235
|
+
}
|
|
236
|
+
static async migrate(pool, tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX, options) {
|
|
237
|
+
await _CNSPostgresStimulationSchema.migrator.migrate(CNSPostgresSchemaPgDatabase.fromPool(pool), _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), options);
|
|
238
|
+
}
|
|
239
|
+
static async verify(database, tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
|
|
240
|
+
await _CNSPostgresStimulationSchema.migrator.verify(database, _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));
|
|
241
|
+
}
|
|
242
|
+
static initialSql(p) {
|
|
243
|
+
return `
|
|
244
|
+
CREATE TABLE ${p}stimulation (
|
|
21
245
|
stimulation_id text PRIMARY KEY,
|
|
22
246
|
entry jsonb NOT NULL,
|
|
23
|
-
status text NOT NULL,
|
|
247
|
+
status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),
|
|
24
248
|
scope_name text,
|
|
25
|
-
|
|
26
|
-
updated_at
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
249
|
+
progress jsonb NOT NULL,
|
|
250
|
+
updated_at bigint NOT NULL,
|
|
251
|
+
started_at bigint NOT NULL DEFAULT 0,
|
|
252
|
+
hop_count int NOT NULL DEFAULT 0,
|
|
253
|
+
last_completed_at bigint
|
|
254
|
+
) WITH (fillfactor = 70);
|
|
255
|
+
CREATE INDEX ${p}stimulation_started_idx ON ${p}stimulation (started_at DESC, stimulation_id COLLATE "C" DESC);
|
|
256
|
+
CREATE INDEX ${p}stimulation_scope_started_idx ON ${p}stimulation (scope_name, started_at DESC, stimulation_id COLLATE "C" DESC);
|
|
30
257
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ord int NOT NULL,
|
|
34
|
-
neuron_name text NOT NULL,
|
|
35
|
-
dendrite_collateral_name text NOT NULL,
|
|
36
|
-
input jsonb,
|
|
37
|
-
PRIMARY KEY (stimulation_id, ord)
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
-- NOTE: no FK on stimulation_id \u2014 over the devtools wire the persistor's
|
|
41
|
-
-- saveStimulation and saveAttempt ride SEPARATE async batches processed
|
|
42
|
-
-- concurrently by the server, so an attempt insert can reach Postgres before its
|
|
43
|
-
-- stimulation row's transaction commits; a hard FK would reject it (23503). Same
|
|
44
|
-
-- exposure as ${p}task; delete() removes attempts explicitly instead of cascading.
|
|
45
|
-
CREATE TABLE IF NOT EXISTS ${p}stimulation_attempt (
|
|
258
|
+
-- No foreign keys below: attempts and tasks arrive over separate writes, often before their parent row.
|
|
259
|
+
CREATE TABLE ${p}stimulation_attempt (
|
|
46
260
|
stimulation_attempt_id text PRIMARY KEY,
|
|
47
261
|
stimulation_id text NOT NULL,
|
|
48
262
|
attempt_number int NOT NULL,
|
|
49
|
-
status text NOT NULL,
|
|
263
|
+
status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),
|
|
50
264
|
started_at bigint NOT NULL,
|
|
51
265
|
completed_at bigint,
|
|
52
266
|
hop_count int NOT NULL,
|
|
@@ -55,35 +269,41 @@ CREATE TABLE IF NOT EXISTS ${p}stimulation_attempt (
|
|
|
55
269
|
entry jsonb NOT NULL,
|
|
56
270
|
UNIQUE (stimulation_id, attempt_number)
|
|
57
271
|
);
|
|
58
|
-
CREATE INDEX
|
|
59
|
-
-- roster query: MIN(started_at)/keyset ordering per run.
|
|
60
|
-
CREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (stimulation_id, started_at);
|
|
272
|
+
CREATE INDEX ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (started_at);
|
|
61
273
|
|
|
62
|
-
|
|
63
|
-
-- (appendTask) as hops complete, BEFORE the attempt marker is flushed (saveAttempt),
|
|
64
|
-
-- so a hard FK would reject the insert. delete() removes tasks explicitly instead.
|
|
65
|
-
CREATE TABLE IF NOT EXISTS ${p}task (
|
|
274
|
+
CREATE TABLE ${p}stimulation_task (
|
|
66
275
|
stimulation_attempt_id text NOT NULL,
|
|
67
|
-
|
|
276
|
+
task_index int NOT NULL,
|
|
68
277
|
neuron_name text NOT NULL,
|
|
69
278
|
dendrite_collateral_name text NOT NULL,
|
|
70
279
|
input_index int NOT NULL,
|
|
71
280
|
output jsonb,
|
|
72
|
-
status text NOT NULL,
|
|
281
|
+
status text NOT NULL CHECK (status IN ('done', 'failed')),
|
|
73
282
|
error text,
|
|
74
283
|
started_at bigint NOT NULL,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
CREATE
|
|
284
|
+
duration_ms bigint,
|
|
285
|
+
activation_context jsonb,
|
|
286
|
+
PRIMARY KEY (stimulation_attempt_id, task_index, started_at)
|
|
287
|
+
) PARTITION BY RANGE (started_at);
|
|
288
|
+
CREATE TABLE ${p}stimulation_task_default PARTITION OF ${p}stimulation_task DEFAULT;
|
|
80
289
|
`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
__name(_CNSPostgresStimulationSchema, "CNSPostgresStimulationSchema");
|
|
293
|
+
__publicField(_CNSPostgresStimulationSchema, "DEFAULT_TABLE_PREFIX", "cns_");
|
|
294
|
+
__publicField(_CNSPostgresStimulationSchema, "LONGEST_SUFFIX", "stimulation_attempt_started_idx");
|
|
295
|
+
__publicField(_CNSPostgresStimulationSchema, "migrator", new CNSPostgresSchemaMigrator({
|
|
296
|
+
name: "stimulation",
|
|
297
|
+
tables: /* @__PURE__ */ __name((tablePrefix) => _CNSPostgresStimulationSchema.tables(tablePrefix), "tables"),
|
|
298
|
+
steps: [
|
|
299
|
+
{
|
|
300
|
+
step: 1,
|
|
301
|
+
kind: "expand",
|
|
302
|
+
sql: /* @__PURE__ */ __name((tablePrefix) => _CNSPostgresStimulationSchema.initialSql(tablePrefix), "sql")
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}));
|
|
306
|
+
var CNSPostgresStimulationSchema = _CNSPostgresStimulationSchema;
|
|
87
307
|
|
|
88
308
|
// src/cli.ts
|
|
89
309
|
async function main() {
|
|
@@ -98,13 +318,13 @@ async function main() {
|
|
|
98
318
|
console.error("DATABASE_URL is required");
|
|
99
319
|
process.exit(1);
|
|
100
320
|
}
|
|
101
|
-
const prefix = (_a = process.env.CNS_TABLE_PREFIX) != null ? _a :
|
|
321
|
+
const prefix = (_a = process.env.CNS_TABLE_PREFIX) != null ? _a : CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX;
|
|
102
322
|
const pool = new import_pg.Pool({
|
|
103
323
|
connectionString: url
|
|
104
324
|
});
|
|
105
325
|
try {
|
|
106
|
-
await
|
|
107
|
-
console.log(`\u2713
|
|
326
|
+
await CNSPostgresStimulationSchema.migrate(pool, prefix);
|
|
327
|
+
console.log(`\u2713 stimulation/attempt/task tables are up to date (prefix "${prefix}")`);
|
|
108
328
|
} finally {
|
|
109
329
|
await pool.end();
|
|
110
330
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/migrate.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-persist-postgres migrate` — create the stimulation/attempt/task tables.\n *\n * DATABASE_URL=postgres://... cnstra-persist-postgres migrate\n * CNS_TABLE_PREFIX=my_ (optional; default cns_)\n */\nimport { Pool } from 'pg';\nimport { ensureSchema, DEFAULT_PREFIX } from './migrate';\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2];\n if (cmd !== 'migrate') {\n console.error('usage: cnstra-persist-postgres migrate'); // eslint-disable-line no-console\n process.exit(1);\n }\n const url = process.env.DATABASE_URL;\n if (!url) {\n console.error('DATABASE_URL is required'); // eslint-disable-line no-console\n process.exit(1);\n }\n const prefix = process.env.CNS_TABLE_PREFIX ?? DEFAULT_PREFIX;\n const pool = new Pool({ connectionString: url });\n try {\n await ensureSchema(pool, prefix);\n // eslint-disable-next-line no-console\n console.log(`✓ ensured stimulation/attempt/task tables (prefix \"${prefix}\")`);\n } finally {\n await pool.end();\n }\n}\n\nvoid main().catch(e => {\n console.error(e); // eslint-disable-line no-console\n process.exit(1);\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. Tables\n * under a configurable prefix (default `cns_`); the stimulation's `progress` is\n * NORMALISED into `stimulation_frontier` (one row per outstanding task).\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\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-- roster query: MIN(started_at)/keyset ordering per run.\nCREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (stimulation_id, started_at);\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-- roster neurons lens: DISTINCT neuron_name per attempt / neuronName filter.\nCREATE INDEX IF NOT EXISTS ${p}task_neuron_idx ON ${p}task (neuron_name);\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":";;;;;AAOA,gBAAqB;;;ACOd,IAAMA,iBAAiB;AAGvB,SAASC,iBAAiBC,QAAc;AAC3C,MAAI,CAAC,2BAA2BC,KAAKD,MAAAA,GAAS;AAC1C,UAAM,IAAIE,MACN,mDAAmDC,KAAKC,UAAUJ,MAAAA,CAAAA,EAAS;EAEnF;AACA,SAAOA;AACX;AAPgBD;AAUT,SAASM,yBAAyBL,SAAiBF,gBAAc;AACpE,QAAMQ,IAAIP,iBAAiBC,MAAAA;AAC3B,SAAO;6BACkBM,CAAAA;;;;;;;;6BAQAA,CAAAA,8BAA+BA,CAAAA;6BAC/BA,CAAAA,4BAA6BA,CAAAA;;6BAE7BA,CAAAA;8CACiBA,CAAAA;;;;;;;;;;;;iBAY7BA,CAAAA;6BACYA,CAAAA;;;;;;;;;;;;;6BAaAA,CAAAA,8BAA+BA,CAAAA;;6BAE/BA,CAAAA,sCAAuCA,CAAAA;;;;;6BAKvCA,CAAAA;;;;;;;;;;;;;;6BAcAA,CAAAA,sBAAuBA,CAAAA;;AAEpD;AAhEgBD;AAmEhB,eAAsBE,aAClBC,IACAR,SAAiBF,gBAAc;AAE/B,QAAMU,GAAGC,MAAMJ,yBAAyBL,MAAAA,CAAAA;AAC5C;AALsBO;;;ADpFtB,eAAeG,OAAAA;;AACX,QAAMC,MAAMC,QAAQC,KAAK,CAAA;AACzB,MAAIF,QAAQ,WAAW;AACnBG,YAAQC,MAAM,wCAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMC,MAAML,QAAQM,IAAIC;AACxB,MAAI,CAACF,KAAK;AACNH,YAAQC,MAAM,0BAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMI,UAASR,aAAQM,IAAIG,qBAAZT,YAAgCU;AAC/C,QAAMC,OAAO,IAAIC,eAAK;IAAEC,kBAAkBR;EAAI,CAAA;AAC9C,MAAI;AACA,UAAMS,aAAaH,MAAMH,MAAAA;AAEzBN,YAAQa,IAAI,2DAAsDP,MAAAA,IAAU;EAChF,UAAA;AACI,UAAMG,KAAKK,IAAG;EAClB;AACJ;AApBelB;AAsBf,KAAKA,KAAAA,EAAOmB,MAAMC,CAAAA,MAAAA;AACdhB,UAAQC,MAAMe,CAAAA;AACdlB,UAAQI,KAAK,CAAA;AACjB,CAAA;","names":["DEFAULT_PREFIX","assertSafePrefix","prefix","test","Error","JSON","stringify","persistPostgresSchemaSql","p","ensureSchema","db","query","main","cmd","process","argv","console","error","exit","url","env","DATABASE_URL","prefix","CNS_TABLE_PREFIX","DEFAULT_PREFIX","pool","Pool","connectionString","ensureSchema","log","end","catch","e"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../../postgres-schema/src/CNSPostgresSchemaMigrator.ts","../../postgres-schema/src/CNSPostgresSchemaPgDatabase.ts","../../postgres-schema/src/CNSPostgresSchemaPartitionRotation.ts","../src/schema/CNSPostgresStimulationSchema.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-persist-postgres migrate` — create the stimulation/attempt/task tables.\n *\n * DATABASE_URL=postgres://... cnstra-persist-postgres migrate\n * CNS_TABLE_PREFIX=my_ (optional; default cns_)\n */\nimport { Pool } from 'pg';\nimport { CNSPostgresStimulationSchema } from './schema/CNSPostgresStimulationSchema';\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2];\n if (cmd !== 'migrate') {\n console.error('usage: cnstra-persist-postgres migrate'); // eslint-disable-line no-console\n process.exit(1);\n }\n const url = process.env.DATABASE_URL;\n if (!url) {\n console.error('DATABASE_URL is required'); // eslint-disable-line no-console\n process.exit(1);\n }\n const prefix = process.env.CNS_TABLE_PREFIX ?? CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX;\n const pool = new Pool({ connectionString: url });\n try {\n await CNSPostgresStimulationSchema.migrate(pool, prefix);\n // eslint-disable-next-line no-console\n console.log(`✓ stimulation/attempt/task tables are up to date (prefix \"${prefix}\")`);\n } finally {\n await pool.end();\n }\n}\n\nvoid main().catch(e => {\n console.error(e); // eslint-disable-line no-console\n process.exit(1);\n});\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { ECNSPostgresSchemaStepKind } from './types/ECNSPostgresSchemaStepKind';\nimport type { TCNSPostgresSchemaMigrateOptions } from './types/TCNSPostgresSchemaMigrateOptions';\nimport type { TCNSPostgresSchemaMigratorOptions } from './types/TCNSPostgresSchemaMigratorOptions';\nimport type { TCNSPostgresSchemaStep } from './types/TCNSPostgresSchemaStep';\n\nexport class CNSPostgresSchemaMigrator {\n static readonly LOCK_NAMESPACE = 6;\n static readonly LOCK_TIMEOUT = '5s';\n static readonly MAX_IDENTIFIER_LENGTH = 63;\n\n private readonly steps: readonly TCNSPostgresSchemaStep[];\n\n constructor(private readonly options: TCNSPostgresSchemaMigratorOptions) {\n this.steps = [...options.steps].sort((left, right) => left.step - right.step);\n this.steps.forEach((step, index) => {\n if (step.step !== index + 1) throw new Error(`[${options.name}] schema steps must be numbered 1, 2, 3…; found ${step.step} at position ${index + 1}`);\n });\n if (this.steps[0]?.kind !== 'expand') throw new Error(`[${options.name}] schema step 1 must be an expand step`);\n }\n\n migrationTable(tablePrefix: string): string {\n return `${tablePrefix}${this.options.name}_migration`;\n }\n\n async migrate(database: ICNSPostgresSchemaDatabase, tablePrefix: string, options: TCNSPostgresSchemaMigrateOptions = {}): Promise<void> {\n await database.transaction(async connection => {\n await connection.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [CNSPostgresSchemaMigrator.LOCK_NAMESPACE, tablePrefix + this.options.name]);\n await connection.query(`SET LOCAL lock_timeout = '${CNSPostgresSchemaMigrator.LOCK_TIMEOUT}'`);\n const applied = await this.readAppliedSteps(connection, tablePrefix);\n if (applied === undefined) await this.assertNoUntrackedTables(connection, tablePrefix);\n const pending = this.pendingSteps(applied ?? new Map(), applied === undefined || options.contract === true);\n if (pending.length > 0) await connection.query(this.sqlFor(tablePrefix, pending, applied === undefined));\n });\n }\n\n async verify(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<void> {\n const applied = await this.readAppliedSteps(connection, tablePrefix);\n if (applied === undefined) {\n throw new Error(`[${this.options.name}] the schema is not installed under prefix \"${tablePrefix}\": run migrate()`);\n }\n const missing = this.steps.filter(step => step.kind === 'expand' && !applied.has(step.step)).map(step => step.step);\n if (missing.length > 0) {\n throw new Error(`[${this.options.name}] schema steps ${missing.join(', ')} are not applied under prefix \"${tablePrefix}\": run migrate()`);\n }\n const unknown = [...applied].filter(([step, kind]) => kind === 'contract' && step > this.steps.length).map(([step]) => step);\n if (unknown.length > 0) {\n throw new Error(`[${this.options.name}] the database has contract steps ${unknown.join(', ')} that this version does not know: upgrade the package`);\n }\n }\n\n migrationSql(tablePrefix: string, appliedSteps: readonly number[] = [], options: TCNSPostgresSchemaMigrateOptions = {}): string {\n const fresh = appliedSteps.length === 0;\n const applied = new Map<number, ECNSPostgresSchemaStepKind>(appliedSteps.map(step => [step, 'expand']));\n return this.sqlFor(tablePrefix, this.pendingSteps(applied, fresh || options.contract === true), fresh);\n }\n\n assertTablePrefix(tablePrefix: string, longestSuffix: string): string {\n const longest = Math.max(longestSuffix.length, `${this.options.name}_migration`.length);\n const limit = CNSPostgresSchemaMigrator.MAX_IDENTIFIER_LENGTH - longest;\n if (!new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${limit - 1}}$`).test(tablePrefix)) {\n throw new Error(`[${this.options.name}] unsafe table prefix ${JSON.stringify(tablePrefix)}: letters, digits and _ only, at most ${limit} characters`);\n }\n return tablePrefix;\n }\n\n private pendingSteps(applied: ReadonlyMap<number, ECNSPostgresSchemaStepKind>, withContract: boolean): TCNSPostgresSchemaStep[] {\n return this.steps.filter(step => !applied.has(step.step) && (step.kind === 'expand' || withContract));\n }\n\n private sqlFor(tablePrefix: string, steps: readonly TCNSPostgresSchemaStep[], createMigrationTable: boolean): string {\n const table = this.migrationTable(tablePrefix);\n const parts = createMigrationTable\n ? [`CREATE TABLE ${table} (step integer PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('expand', 'contract')));`]\n : [];\n for (const step of steps) {\n parts.push(step.sql(tablePrefix), `INSERT INTO ${table} (step, kind) VALUES (${step.step}, '${step.kind}');`);\n }\n return parts.join('\\n');\n }\n\n private async readAppliedSteps(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<Map<number, ECNSPostgresSchemaStepKind> | undefined> {\n const table = this.migrationTable(tablePrefix);\n const exists = await connection.query<{ found: string | null }>('SELECT to_regclass($1)::text AS found', [table]);\n if (exists.rows[0]?.found == null) return undefined;\n const rows = await connection.query<{ step: number; kind: ECNSPostgresSchemaStepKind }>(`SELECT step, kind FROM ${table}`);\n return new Map(rows.rows.map(row => [Number(row.step), row.kind]));\n }\n\n private async assertNoUntrackedTables(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<void> {\n const tables = this.options.tables(tablePrefix);\n const found = await connection.query<{ name: string }>(\n 'SELECT name FROM unnest($1::text[]) AS name WHERE to_regclass(name) IS NOT NULL',\n [tables]\n );\n if (found.rows.length > 0) {\n throw new Error(\n `[${this.options.name}] tables ${found.rows.map(row => row.name).join(', ')} exist without ${this.migrationTable(tablePrefix)}: they were created by an earlier unversioned release; drop them or move the data before migrate()`\n );\n }\n }\n}\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { ICNSPostgresSchemaPgPool } from './interfaces/ICNSPostgresSchemaPgPool';\n\nexport class CNSPostgresSchemaPgDatabase implements ICNSPostgresSchemaDatabase {\n private constructor(\n private readonly open: () => Promise<{ connection: ICNSPostgresSchemaConnection; release: (error?: Error) => void }>\n ) {}\n\n static fromPool(pool: ICNSPostgresSchemaPgPool): CNSPostgresSchemaPgDatabase {\n return new CNSPostgresSchemaPgDatabase(async () => {\n const client = await pool.connect();\n return { connection: client, release: error => client.release(error) };\n });\n }\n\n async transaction<TResult>(work: (connection: ICNSPostgresSchemaConnection) => Promise<TResult>): Promise<TResult> {\n const { connection, release } = await this.open();\n try {\n await connection.query('BEGIN');\n const result = await work(connection);\n await connection.query('COMMIT');\n release();\n return result;\n } catch (error) {\n const rollback = await connection.query('ROLLBACK').then(\n () => undefined,\n (rollbackError: unknown) => rollbackError\n );\n release(rollback instanceof Error ? rollback : undefined);\n throw error;\n }\n }\n}\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { TCNSPostgresSchemaPartitionRotationOptions } from './types/TCNSPostgresSchemaPartitionRotationOptions';\n\ntype TRangePartition = { readonly name: string; readonly from: number; readonly to: number };\n\nexport class CNSPostgresSchemaPartitionRotation {\n static readonly LOCK_NAMESPACE = 8;\n static readonly LOCK_TIMEOUT = '5s';\n static readonly DAY_MS = 24 * 60 * 60 * 1000;\n\n private readonly rotatedFor = new Map<string, string>();\n\n constructor(private readonly options: TCNSPostgresSchemaPartitionRotationOptions) {\n if (!(options.premakeDays >= 1) || !Number.isInteger(options.premakeDays)) {\n throw new Error(`[partition rotation] premakeDays must be a positive integer, got ${options.premakeDays}`);\n }\n }\n\n async rotate(\n database: ICNSPostgresSchemaDatabase,\n table: string,\n now: number,\n dropBefore: number,\n beforeDrop?: (connection: ICNSPostgresSchemaConnection, partition: string) => Promise<void>\n ): Promise<string[]> {\n const today = Math.floor(now / CNSPostgresSchemaPartitionRotation.DAY_MS);\n const days = `${today}:${Math.floor(dropBefore / CNSPostgresSchemaPartitionRotation.DAY_MS)}`;\n if (this.rotatedFor.get(table) === days) return [];\n const dropped = await this.underLock(database, table, async connection => {\n const expired = (await this.readRangePartitions(connection, table)).filter(partition => partition.to <= dropBefore);\n if (beforeDrop) for (const partition of expired) await beforeDrop(connection, partition.name);\n for (const partition of expired) await connection.query(`DROP TABLE ${partition.name}`);\n return expired.map(partition => partition.name);\n });\n await this.underLock(database, table, async connection => {\n const partitions = await this.readRangePartitions(connection, table);\n for (let ahead = 1; ahead <= this.options.premakeDays; ahead++) {\n const from = (today + ahead) * CNSPostgresSchemaPartitionRotation.DAY_MS;\n const to = from + CNSPostgresSchemaPartitionRotation.DAY_MS;\n if (partitions.some(partition => partition.from < to && from < partition.to)) continue;\n const name = `${table}_p${new Date(from).toISOString().slice(0, 10).replace(/-/g, '')}`;\n await connection.query(`CREATE TABLE ${name} (LIKE ${table} INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`);\n await connection.query(`ALTER TABLE ${table} ATTACH PARTITION ${name} FOR VALUES FROM (${from}) TO (${to})`);\n }\n });\n this.rotatedFor.set(table, days);\n return dropped;\n }\n\n private underLock<TResult>(database: ICNSPostgresSchemaDatabase, table: string, work: (connection: ICNSPostgresSchemaConnection) => Promise<TResult>): Promise<TResult> {\n return database.transaction(async connection => {\n await connection.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [CNSPostgresSchemaPartitionRotation.LOCK_NAMESPACE, table]);\n await connection.query(`SET LOCAL lock_timeout = '${CNSPostgresSchemaPartitionRotation.LOCK_TIMEOUT}'`);\n return work(connection);\n });\n }\n\n private async readRangePartitions(connection: ICNSPostgresSchemaConnection, table: string): Promise<TRangePartition[]> {\n const rows = await connection.query<{ name: string; bound: string }>(\n `SELECT child.oid::regclass::text AS name, pg_get_expr(child.relpartbound, child.oid) AS bound\n FROM pg_inherits\n JOIN pg_class child ON child.oid = pg_inherits.inhrelid\n WHERE pg_inherits.inhparent = $1::regclass`,\n [table]\n );\n const partitions: TRangePartition[] = [];\n for (const row of rows.rows) {\n const range = /^FOR VALUES FROM \\('?(-?[\\d.e+]+)'?\\) TO \\('?(-?[\\d.e+]+)'?\\)$/.exec(row.bound);\n if (range) partitions.push({ name: row.name, from: Number(range[1]), to: Number(range[2]) });\n }\n return partitions;\n }\n}\n","import { CNSPostgresSchemaMigrator, CNSPostgresSchemaPgDatabase } from '@cnstra/postgres-schema';\nimport type { ICNSPostgresSchemaConnection, ICNSPostgresSchemaPgPool } from '@cnstra/postgres-schema';\nimport type { Pool } from 'pg';\nimport type { ICNSPgQueryable } from '../interfaces/ICNSPgQueryable';\nimport type { TCNSPostgresStimulationMigrateOptions } from '../types/TCNSPostgresStimulationMigrateOptions';\n\nexport class CNSPostgresStimulationSchema {\n static readonly DEFAULT_TABLE_PREFIX = 'cns_';\n private static readonly LONGEST_SUFFIX = 'stimulation_attempt_started_idx';\n\n private static readonly migrator = new CNSPostgresSchemaMigrator({\n name: 'stimulation',\n tables: tablePrefix => CNSPostgresStimulationSchema.tables(tablePrefix),\n steps: [{ step: 1, kind: 'expand', sql: tablePrefix => CNSPostgresStimulationSchema.initialSql(tablePrefix) }],\n });\n\n static assertTablePrefix(tablePrefix: string): string {\n return CNSPostgresStimulationSchema.migrator.assertTablePrefix(tablePrefix, CNSPostgresStimulationSchema.LONGEST_SUFFIX);\n }\n\n static tables(tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): string[] {\n const p = CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix);\n return [`${p}stimulation`, `${p}stimulation_attempt`, `${p}stimulation_task`];\n }\n\n static migrationTable(tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): string {\n return CNSPostgresStimulationSchema.migrator.migrationTable(CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));\n }\n\n static migrationSql(\n tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX,\n appliedSteps: readonly number[] = [],\n options?: TCNSPostgresStimulationMigrateOptions\n ): string {\n return CNSPostgresStimulationSchema.migrator.migrationSql(CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), appliedSteps, options);\n }\n\n static async migrate(\n pool: Pool,\n tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX,\n options?: TCNSPostgresStimulationMigrateOptions\n ): Promise<void> {\n await CNSPostgresStimulationSchema.migrator.migrate(\n CNSPostgresSchemaPgDatabase.fromPool(pool as unknown as ICNSPostgresSchemaPgPool),\n CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix),\n options\n );\n }\n\n static async verify(database: ICNSPgQueryable, tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): Promise<void> {\n await CNSPostgresStimulationSchema.migrator.verify(\n database as unknown as ICNSPostgresSchemaConnection,\n CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix)\n );\n }\n\n private static initialSql(p: string): string {\n return `\nCREATE TABLE ${p}stimulation (\n stimulation_id text PRIMARY KEY,\n entry jsonb NOT NULL,\n status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),\n scope_name text,\n progress jsonb NOT NULL,\n updated_at bigint NOT NULL,\n started_at bigint NOT NULL DEFAULT 0,\n hop_count int NOT NULL DEFAULT 0,\n last_completed_at bigint\n) WITH (fillfactor = 70);\nCREATE INDEX ${p}stimulation_started_idx ON ${p}stimulation (started_at DESC, stimulation_id COLLATE \"C\" DESC);\nCREATE INDEX ${p}stimulation_scope_started_idx ON ${p}stimulation (scope_name, started_at DESC, stimulation_id COLLATE \"C\" DESC);\n\n-- No foreign keys below: attempts and tasks arrive over separate writes, often before their parent row.\nCREATE TABLE ${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 CHECK (status IN ('running', 'completed', 'failed', 'aborted')),\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 ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (started_at);\n\nCREATE TABLE ${p}stimulation_task (\n stimulation_attempt_id text NOT NULL,\n task_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 CHECK (status IN ('done', 'failed')),\n error text,\n started_at bigint NOT NULL,\n duration_ms bigint,\n activation_context jsonb,\n PRIMARY KEY (stimulation_attempt_id, task_index, started_at)\n) PARTITION BY RANGE (started_at);\nCREATE TABLE ${p}stimulation_task_default PARTITION OF ${p}stimulation_task DEFAULT;\n`;\n }\n}\n"],"mappings":"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","names":["CNSPostgresSchemaMigrator","options","steps","sort","left","right","step","forEach","index","Error","name","kind","migrationTable","tablePrefix","migrate","database","transaction","connection","query","LOCK_NAMESPACE","LOCK_TIMEOUT","applied","readAppliedSteps","undefined","assertNoUntrackedTables","pending","pendingSteps","Map","contract","length","sqlFor","verify","missing","filter","has","map","join","unknown","migrationSql","appliedSteps","fresh","assertTablePrefix","longestSuffix","longest","Math","max","limit","MAX_IDENTIFIER_LENGTH","RegExp","test","JSON","stringify","withContract","createMigrationTable","table","parts","push","sql","exists","rows","found","row","Number","tables","CNSPostgresSchemaPgDatabase","open","fromPool","pool","client","connect","connection","release","error","transaction","work","query","result","rollback","then","undefined","rollbackError","Error","CNSPostgresSchemaPartitionRotation","options","rotatedFor","Map","premakeDays","Number","isInteger","Error","rotate","database","table","now","dropBefore","beforeDrop","today","Math","floor","DAY_MS","days","get","dropped","underLock","connection","expired","readRangePartitions","filter","partition","to","name","query","map","partitions","ahead","from","some","Date","toISOString","slice","replace","set","work","transaction","LOCK_NAMESPACE","LOCK_TIMEOUT","rows","row","range","exec","bound","push","CNSPostgresStimulationSchema","assertTablePrefix","tablePrefix","migrator","LONGEST_SUFFIX","tables","DEFAULT_TABLE_PREFIX","p","migrationTable","migrationSql","appliedSteps","options","migrate","pool","CNSPostgresSchemaPgDatabase","fromPool","verify","database","initialSql","CNSPostgresSchemaMigrator","name","steps","step","kind","sql","main","cmd","process","argv","console","error","exit","url","env","DATABASE_URL","prefix","CNS_TABLE_PREFIX","CNSPostgresStimulationSchema","DEFAULT_TABLE_PREFIX","pool","Pool","connectionString","migrate","log","end","catch","e"]}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
__name
|
|
5
|
-
|
|
6
|
-
} from "./chunk-5FLIKV6I.mjs";
|
|
3
|
+
CNSPostgresStimulationSchema,
|
|
4
|
+
__name
|
|
5
|
+
} from "./chunk-BPKIKGKB.mjs";
|
|
7
6
|
|
|
8
7
|
// src/cli.ts
|
|
9
8
|
import { Pool } from "pg";
|
|
@@ -19,13 +18,13 @@ async function main() {
|
|
|
19
18
|
console.error("DATABASE_URL is required");
|
|
20
19
|
process.exit(1);
|
|
21
20
|
}
|
|
22
|
-
const prefix = (_a = process.env.CNS_TABLE_PREFIX) != null ? _a :
|
|
21
|
+
const prefix = (_a = process.env.CNS_TABLE_PREFIX) != null ? _a : CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX;
|
|
23
22
|
const pool = new Pool({
|
|
24
23
|
connectionString: url
|
|
25
24
|
});
|
|
26
25
|
try {
|
|
27
|
-
await
|
|
28
|
-
console.log(`\u2713
|
|
26
|
+
await CNSPostgresStimulationSchema.migrate(pool, prefix);
|
|
27
|
+
console.log(`\u2713 stimulation/attempt/task tables are up to date (prefix "${prefix}")`);
|
|
29
28
|
} finally {
|
|
30
29
|
await pool.end();
|
|
31
30
|
}
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-persist-postgres migrate` — create the stimulation/attempt/task tables.\n *\n * DATABASE_URL=postgres://... cnstra-persist-postgres migrate\n * CNS_TABLE_PREFIX=my_ (optional; default cns_)\n */\nimport { Pool } from 'pg';\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-persist-postgres migrate` — create the stimulation/attempt/task tables.\n *\n * DATABASE_URL=postgres://... cnstra-persist-postgres migrate\n * CNS_TABLE_PREFIX=my_ (optional; default cns_)\n */\nimport { Pool } from 'pg';\nimport { CNSPostgresStimulationSchema } from './schema/CNSPostgresStimulationSchema';\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2];\n if (cmd !== 'migrate') {\n console.error('usage: cnstra-persist-postgres migrate'); // eslint-disable-line no-console\n process.exit(1);\n }\n const url = process.env.DATABASE_URL;\n if (!url) {\n console.error('DATABASE_URL is required'); // eslint-disable-line no-console\n process.exit(1);\n }\n const prefix = process.env.CNS_TABLE_PREFIX ?? CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX;\n const pool = new Pool({ connectionString: url });\n try {\n await CNSPostgresStimulationSchema.migrate(pool, prefix);\n // eslint-disable-next-line no-console\n console.log(`✓ stimulation/attempt/task tables are up to date (prefix \"${prefix}\")`);\n } finally {\n await pool.end();\n }\n}\n\nvoid main().catch(e => {\n console.error(e); // eslint-disable-line no-console\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAOA,SAASA,YAAY;AAGrB,eAAeC,OAAAA;;AACX,QAAMC,MAAMC,QAAQC,KAAK,CAAA;AACzB,MAAIF,QAAQ,WAAW;AACnBG,YAAQC,MAAM,wCAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMC,MAAML,QAAQM,IAAIC;AACxB,MAAI,CAACF,KAAK;AACNH,YAAQC,MAAM,0BAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMI,UAASR,aAAQM,IAAIG,qBAAZT,YAAgCU,6BAA6BC;AAC5E,QAAMC,OAAO,IAAIC,KAAK;IAAEC,kBAAkBT;EAAI,CAAA;AAC9C,MAAI;AACA,UAAMK,6BAA6BK,QAAQH,MAAMJ,MAAAA;AAEjDN,YAAQc,IAAI,kEAA6DR,MAAAA,IAAU;EACvF,UAAA;AACI,UAAMI,KAAKK,IAAG;EAClB;AACJ;AApBenB;AAsBf,KAAKA,KAAAA,EAAOoB,MAAMC,CAAAA,MAAAA;AACdjB,UAAQC,MAAMgB,CAAAA;AACdnB,UAAQI,KAAK,CAAA;AACjB,CAAA;","names":["Pool","main","cmd","process","argv","console","error","exit","url","env","DATABASE_URL","prefix","CNS_TABLE_PREFIX","CNSPostgresStimulationSchema","DEFAULT_TABLE_PREFIX","pool","Pool","connectionString","migrate","log","end","catch","e"]}
|