@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 CHANGED
@@ -22,45 +22,85 @@ npm install @cnstra/persist-postgres pg
22
22
  import { CNSPostgresStimulationRepository } from '@cnstra/persist-postgres';
23
23
 
24
24
  const repo = new CNSPostgresStimulationRepository({
25
- connectionString: process.env.DATABASE_URL, // or { pool } / { queryable }
25
+ connectionString: process.env.DATABASE_URL, // or { pool }; { queryable } needs autoMigrate: false
26
26
  tablePrefix: 'cns_', // default
27
- autoMigrate: true, // lazily create tables on first use
27
+ autoMigrate: true, // create or upgrade the tables on first use
28
28
  });
29
-
30
- // Drives the persistor (write) and the retry UI (read):
31
- // saveRun / saveStimulation / appendTask ; getRun / listRuns / getStimulations / getTasks / delete
32
29
  ```
33
30
 
34
- Reuses the `IPgQueryable` seam, so `pg` is an optional peer — inject any pg client/pool,
35
- or pass a `connectionString` for the repo to own a Pool (`close()` ends it).
31
+ It implements `ICNSStimulationRepository` and `ICNSStimulationRosterQuery`: the persistor writes
32
+ through `saveStimulation` / `saveAttempt` / `appendTask`, the retry UI reads through
33
+ `getStimulation` / `listStimulations` / `getAttempts` / `getTasks` / `queryRoster` / `queryFacets`,
34
+ and retention runs through `deleteStale(olderThanMs)`.
36
35
 
37
36
  ## Schema
38
37
 
39
- Five tables under the prefix (default `cns_`). The run's `progress` is **normalised**: the
40
- outstanding frontier into `cns_run_frontier` (one row per task) and per-neuron context into
41
- `cns_run_context`, so you can query the frontier — rebuilt into `TCNSProgress` on read.
38
+ Three tables under the prefix (default `cns_`, at most 32 characters):
42
39
 
43
40
  ```
44
- cns_run(stimulation_run_id PK, entry jsonb, status, created_at, updated_at)
45
- cns_run_frontier(run_id → cns_run, ord, neuron_name, dendrite_collateral_name, input jsonb)
46
- cns_run_context(run_id → cns_run, neuron_name, value jsonb)
47
- cns_stimulation(stimulation_id PK, run_id → cns_run, attempt_number, status, started_at,
48
- completed_at, hop_count, has_error, replay_of, entry jsonb,
49
- UNIQUE(run_id, attempt_number))
50
- cns_task(stimulation_id, index, neuron_name, dendrite_collateral_name, input_index,
51
- output jsonb, status, error, started_at, duration, PK(stimulation_id, index))
41
+ cns_stimulation(stimulation_id PK, entry jsonb, status, scope_name, progress jsonb, updated_at,
42
+ started_at, hop_count, last_completed_at)
43
+ cns_stimulation_attempt(stimulation_attempt_id PK, stimulation_id, attempt_number, status,
44
+ started_at, completed_at, hop_count, has_error, replay_of, entry jsonb,
45
+ UNIQUE(stimulation_id, attempt_number))
46
+ cns_stimulation_task(stimulation_attempt_id, task_index, neuron_name, dendrite_collateral_name,
47
+ input_index, output jsonb, status, error, started_at, duration_ms,
48
+ activation_context jsonb, PK(stimulation_attempt_id, task_index, started_at))
49
+ PARTITION BY RANGE (started_at), with cns_stimulation_task_default
52
50
  ```
53
51
 
52
+ Statuses are checked against the DTO enums. The roster is served by `stimulation(started_at DESC, stimulation_id)`
53
+ and, with a scope filter, `stimulation(scope_name, started_at DESC, stimulation_id)`. `updated_at` is not indexed
54
+ and the table keeps 30% of each page free, so the update every checkpoint makes can stay a HOT update;
55
+ `listStimulations` and `deleteStale` scan for it.
56
+
57
+ `cns_stimulation_task` holds a row per hop and is partitioned by day of `started_at`, so retention drops whole
58
+ partitions instead of deleting rows and leaves nothing for vacuum. With a `pool`, every `deleteStale(olderThanMs)`
59
+ deletes the stale runs row by row and then drops the days that ended before the window and attaches the next seven
60
+ days (named after the table and the UTC day, as cns_stimulation_task_p20261104), under an advisory lock so replicas take turns. Call it at least weekly:
61
+ tasks of days that have no partition yet go to `cns_stimulation_task_default`, which is cleaned only by row deletes.
62
+ Through a `queryable` no partitions are made and every task stays in the default partition.
63
+
64
+ A partition is attached only for days after today, because Postgres refuses a range the default partition already
65
+ has rows in. Attaching scans the default partition while holding it exclusively: reads of the whole task table and
66
+ tasks written into a day without a partition wait for that scan, once per attached day, so it costs as much as the
67
+ default partition is large (seven scans on the first call after install, one a day after that); the task table itself is locked only against other schema changes. Dropping runs in a transaction of its own
68
+ before attaching, so it locks the task table exclusively only for the drop itself. Either step gives up after 5 s of waiting for its lock: `deleteStale` then throws
69
+ after the row deletes are done, and the next call retries. Tasks are dropped by their own age, so a run still listed
70
+ whose tasks started before the window shows no tasks for those days (a retried old run loses its first attempt's
71
+ tasks; its resume does not need them, the frontier is in `progress`). Keep `olderThanMs` above the longest run.
72
+ A task redelivered as the same record is written once; an attempt id that runs a second time (a crashed worker's job
73
+ picked up again under the same attempt number) keeps the tasks of both runs.
74
+
75
+ Requires PostgreSQL 12 or later. Writes through a `queryable` run without a transaction, so the roster fields can
76
+ lag behind concurrent writes until the run's next checkpoint; pass a `pool` for exact ones. Tasks and attempts can be written before their parent row; `deleteStale(olderThanMs)`
77
+ also removes those older than the window whose parent never arrived, so keep `olderThanMs` well above the
78
+ persistor's flush interval.
79
+
80
+ `started_at`, `hop_count` and `last_completed_at` are the roster fields, recomputed from the run's attempts by
81
+ every `saveAttempt` and `saveStimulation` under a per-run advisory lock, so the roster reads one indexed table.
82
+
83
+ `progress` is the whole `TCNSProgress` a resume needs: the frontier, the run context, the
84
+ modality and every task's activation context. Times are milliseconds from the repository clock
85
+ (`now`, default `Date.now`).
86
+
54
87
  ## Migrations
55
88
 
56
- The package owns its schema. Auto-created on first use, or manage it yourself:
89
+ The package owns its schema and records every applied step in `cns_stimulation_migration`.
90
+ Auto-applied on first use, or run it yourself:
57
91
 
58
92
  ```bash
59
93
  DATABASE_URL=postgres://... npx cnstra-persist-postgres migrate
60
94
  # CNS_TABLE_PREFIX=my_ to override the prefix
61
95
  ```
62
96
 
63
- or call `ensureSchema(pool, prefix)` from `@cnstra/persist-postgres`.
97
+ or call `CNSPostgresStimulationSchema.migrate(pool, prefix)`. It runs in one transaction under an
98
+ advisory lock, so replicas starting together wait for each other. A step that removes something
99
+ (`contract`) is applied only with `{ contract: true }`, after no process of the previous release
100
+ is left. In a running service call `CNSPostgresStimulationSchema.verify(pool, prefix)` instead: it
101
+ writes nothing and fails at startup when a step this release needs is missing.
102
+ `CNSPostgresStimulationSchema.migrationSql(prefix, appliedSteps)` hands the same SQL to your own
103
+ migration tool.
64
104
 
65
105
  ## License
66
106
 
@@ -0,0 +1,310 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // ../postgres-schema/src/CNSPostgresSchemaMigrator.ts
7
+ var _CNSPostgresSchemaMigrator = class _CNSPostgresSchemaMigrator {
8
+ constructor(options) {
9
+ __publicField(this, "options");
10
+ __publicField(this, "steps");
11
+ var _a;
12
+ this.options = options;
13
+ this.steps = [
14
+ ...options.steps
15
+ ].sort((left, right) => left.step - right.step);
16
+ this.steps.forEach((step, index) => {
17
+ 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}`);
18
+ });
19
+ if (((_a = this.steps[0]) == null ? void 0 : _a.kind) !== "expand") throw new Error(`[${options.name}] schema step 1 must be an expand step`);
20
+ }
21
+ migrationTable(tablePrefix) {
22
+ return `${tablePrefix}${this.options.name}_migration`;
23
+ }
24
+ async migrate(database, tablePrefix, options = {}) {
25
+ await database.transaction(async (connection) => {
26
+ await connection.query("SELECT pg_advisory_xact_lock($1, hashtext($2))", [
27
+ _CNSPostgresSchemaMigrator.LOCK_NAMESPACE,
28
+ tablePrefix + this.options.name
29
+ ]);
30
+ await connection.query(`SET LOCAL lock_timeout = '${_CNSPostgresSchemaMigrator.LOCK_TIMEOUT}'`);
31
+ const applied = await this.readAppliedSteps(connection, tablePrefix);
32
+ if (applied === void 0) await this.assertNoUntrackedTables(connection, tablePrefix);
33
+ const pending = this.pendingSteps(applied != null ? applied : /* @__PURE__ */ new Map(), applied === void 0 || options.contract === true);
34
+ if (pending.length > 0) await connection.query(this.sqlFor(tablePrefix, pending, applied === void 0));
35
+ });
36
+ }
37
+ async verify(connection, tablePrefix) {
38
+ const applied = await this.readAppliedSteps(connection, tablePrefix);
39
+ if (applied === void 0) {
40
+ throw new Error(`[${this.options.name}] the schema is not installed under prefix "${tablePrefix}": run migrate()`);
41
+ }
42
+ const missing = this.steps.filter((step) => step.kind === "expand" && !applied.has(step.step)).map((step) => step.step);
43
+ if (missing.length > 0) {
44
+ throw new Error(`[${this.options.name}] schema steps ${missing.join(", ")} are not applied under prefix "${tablePrefix}": run migrate()`);
45
+ }
46
+ const unknown = [
47
+ ...applied
48
+ ].filter(([step, kind]) => kind === "contract" && step > this.steps.length).map(([step]) => step);
49
+ if (unknown.length > 0) {
50
+ throw new Error(`[${this.options.name}] the database has contract steps ${unknown.join(", ")} that this version does not know: upgrade the package`);
51
+ }
52
+ }
53
+ migrationSql(tablePrefix, appliedSteps = [], options = {}) {
54
+ const fresh = appliedSteps.length === 0;
55
+ const applied = new Map(appliedSteps.map((step) => [
56
+ step,
57
+ "expand"
58
+ ]));
59
+ return this.sqlFor(tablePrefix, this.pendingSteps(applied, fresh || options.contract === true), fresh);
60
+ }
61
+ assertTablePrefix(tablePrefix, longestSuffix) {
62
+ const longest = Math.max(longestSuffix.length, `${this.options.name}_migration`.length);
63
+ const limit = _CNSPostgresSchemaMigrator.MAX_IDENTIFIER_LENGTH - longest;
64
+ if (!new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${limit - 1}}$`).test(tablePrefix)) {
65
+ throw new Error(`[${this.options.name}] unsafe table prefix ${JSON.stringify(tablePrefix)}: letters, digits and _ only, at most ${limit} characters`);
66
+ }
67
+ return tablePrefix;
68
+ }
69
+ pendingSteps(applied, withContract) {
70
+ return this.steps.filter((step) => !applied.has(step.step) && (step.kind === "expand" || withContract));
71
+ }
72
+ sqlFor(tablePrefix, steps, createMigrationTable) {
73
+ const table = this.migrationTable(tablePrefix);
74
+ const parts = createMigrationTable ? [
75
+ `CREATE TABLE ${table} (step integer PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('expand', 'contract')));`
76
+ ] : [];
77
+ for (const step of steps) {
78
+ parts.push(step.sql(tablePrefix), `INSERT INTO ${table} (step, kind) VALUES (${step.step}, '${step.kind}');`);
79
+ }
80
+ return parts.join("\n");
81
+ }
82
+ async readAppliedSteps(connection, tablePrefix) {
83
+ var _a;
84
+ const table = this.migrationTable(tablePrefix);
85
+ const exists = await connection.query("SELECT to_regclass($1)::text AS found", [
86
+ table
87
+ ]);
88
+ if (((_a = exists.rows[0]) == null ? void 0 : _a.found) == null) return void 0;
89
+ const rows = await connection.query(`SELECT step, kind FROM ${table}`);
90
+ return new Map(rows.rows.map((row) => [
91
+ Number(row.step),
92
+ row.kind
93
+ ]));
94
+ }
95
+ async assertNoUntrackedTables(connection, tablePrefix) {
96
+ const tables = this.options.tables(tablePrefix);
97
+ const found = await connection.query("SELECT name FROM unnest($1::text[]) AS name WHERE to_regclass(name) IS NOT NULL", [
98
+ tables
99
+ ]);
100
+ if (found.rows.length > 0) {
101
+ 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()`);
102
+ }
103
+ }
104
+ };
105
+ __name(_CNSPostgresSchemaMigrator, "CNSPostgresSchemaMigrator");
106
+ __publicField(_CNSPostgresSchemaMigrator, "LOCK_NAMESPACE", 6);
107
+ __publicField(_CNSPostgresSchemaMigrator, "LOCK_TIMEOUT", "5s");
108
+ __publicField(_CNSPostgresSchemaMigrator, "MAX_IDENTIFIER_LENGTH", 63);
109
+ var CNSPostgresSchemaMigrator = _CNSPostgresSchemaMigrator;
110
+
111
+ // ../postgres-schema/src/CNSPostgresSchemaPgDatabase.ts
112
+ var _CNSPostgresSchemaPgDatabase = class _CNSPostgresSchemaPgDatabase {
113
+ constructor(open) {
114
+ __publicField(this, "open");
115
+ this.open = open;
116
+ }
117
+ static fromPool(pool) {
118
+ return new _CNSPostgresSchemaPgDatabase(async () => {
119
+ const client = await pool.connect();
120
+ return {
121
+ connection: client,
122
+ release: /* @__PURE__ */ __name((error) => client.release(error), "release")
123
+ };
124
+ });
125
+ }
126
+ async transaction(work) {
127
+ const { connection, release } = await this.open();
128
+ try {
129
+ await connection.query("BEGIN");
130
+ const result = await work(connection);
131
+ await connection.query("COMMIT");
132
+ release();
133
+ return result;
134
+ } catch (error) {
135
+ const rollback = await connection.query("ROLLBACK").then(() => void 0, (rollbackError) => rollbackError);
136
+ release(rollback instanceof Error ? rollback : void 0);
137
+ throw error;
138
+ }
139
+ }
140
+ };
141
+ __name(_CNSPostgresSchemaPgDatabase, "CNSPostgresSchemaPgDatabase");
142
+ var CNSPostgresSchemaPgDatabase = _CNSPostgresSchemaPgDatabase;
143
+
144
+ // ../postgres-schema/src/CNSPostgresSchemaPartitionRotation.ts
145
+ var _CNSPostgresSchemaPartitionRotation = class _CNSPostgresSchemaPartitionRotation {
146
+ constructor(options) {
147
+ __publicField(this, "options");
148
+ __publicField(this, "rotatedFor", /* @__PURE__ */ new Map());
149
+ this.options = options;
150
+ if (!(options.premakeDays >= 1) || !Number.isInteger(options.premakeDays)) {
151
+ throw new Error(`[partition rotation] premakeDays must be a positive integer, got ${options.premakeDays}`);
152
+ }
153
+ }
154
+ async rotate(database, table, now, dropBefore, beforeDrop) {
155
+ const today = Math.floor(now / _CNSPostgresSchemaPartitionRotation.DAY_MS);
156
+ const days = `${today}:${Math.floor(dropBefore / _CNSPostgresSchemaPartitionRotation.DAY_MS)}`;
157
+ if (this.rotatedFor.get(table) === days) return [];
158
+ const dropped = await this.underLock(database, table, async (connection) => {
159
+ const expired = (await this.readRangePartitions(connection, table)).filter((partition) => partition.to <= dropBefore);
160
+ if (beforeDrop) for (const partition of expired) await beforeDrop(connection, partition.name);
161
+ for (const partition of expired) await connection.query(`DROP TABLE ${partition.name}`);
162
+ return expired.map((partition) => partition.name);
163
+ });
164
+ await this.underLock(database, table, async (connection) => {
165
+ const partitions = await this.readRangePartitions(connection, table);
166
+ for (let ahead = 1; ahead <= this.options.premakeDays; ahead++) {
167
+ const from = (today + ahead) * _CNSPostgresSchemaPartitionRotation.DAY_MS;
168
+ const to = from + _CNSPostgresSchemaPartitionRotation.DAY_MS;
169
+ if (partitions.some((partition) => partition.from < to && from < partition.to)) continue;
170
+ const name = `${table}_p${new Date(from).toISOString().slice(0, 10).replace(/-/g, "")}`;
171
+ await connection.query(`CREATE TABLE ${name} (LIKE ${table} INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`);
172
+ await connection.query(`ALTER TABLE ${table} ATTACH PARTITION ${name} FOR VALUES FROM (${from}) TO (${to})`);
173
+ }
174
+ });
175
+ this.rotatedFor.set(table, days);
176
+ return dropped;
177
+ }
178
+ underLock(database, table, work) {
179
+ return database.transaction(async (connection) => {
180
+ await connection.query("SELECT pg_advisory_xact_lock($1, hashtext($2))", [
181
+ _CNSPostgresSchemaPartitionRotation.LOCK_NAMESPACE,
182
+ table
183
+ ]);
184
+ await connection.query(`SET LOCAL lock_timeout = '${_CNSPostgresSchemaPartitionRotation.LOCK_TIMEOUT}'`);
185
+ return work(connection);
186
+ });
187
+ }
188
+ async readRangePartitions(connection, table) {
189
+ const rows = await connection.query(`SELECT child.oid::regclass::text AS name, pg_get_expr(child.relpartbound, child.oid) AS bound
190
+ FROM pg_inherits
191
+ JOIN pg_class child ON child.oid = pg_inherits.inhrelid
192
+ WHERE pg_inherits.inhparent = $1::regclass`, [
193
+ table
194
+ ]);
195
+ const partitions = [];
196
+ for (const row of rows.rows) {
197
+ const range = /^FOR VALUES FROM \('?(-?[\d.e+]+)'?\) TO \('?(-?[\d.e+]+)'?\)$/.exec(row.bound);
198
+ if (range) partitions.push({
199
+ name: row.name,
200
+ from: Number(range[1]),
201
+ to: Number(range[2])
202
+ });
203
+ }
204
+ return partitions;
205
+ }
206
+ };
207
+ __name(_CNSPostgresSchemaPartitionRotation, "CNSPostgresSchemaPartitionRotation");
208
+ __publicField(_CNSPostgresSchemaPartitionRotation, "LOCK_NAMESPACE", 8);
209
+ __publicField(_CNSPostgresSchemaPartitionRotation, "LOCK_TIMEOUT", "5s");
210
+ __publicField(_CNSPostgresSchemaPartitionRotation, "DAY_MS", 24 * 60 * 60 * 1e3);
211
+ var CNSPostgresSchemaPartitionRotation = _CNSPostgresSchemaPartitionRotation;
212
+
213
+ // src/schema/CNSPostgresStimulationSchema.ts
214
+ var _CNSPostgresStimulationSchema = class _CNSPostgresStimulationSchema {
215
+ static assertTablePrefix(tablePrefix) {
216
+ return _CNSPostgresStimulationSchema.migrator.assertTablePrefix(tablePrefix, _CNSPostgresStimulationSchema.LONGEST_SUFFIX);
217
+ }
218
+ static tables(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
219
+ const p = _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix);
220
+ return [
221
+ `${p}stimulation`,
222
+ `${p}stimulation_attempt`,
223
+ `${p}stimulation_task`
224
+ ];
225
+ }
226
+ static migrationTable(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
227
+ return _CNSPostgresStimulationSchema.migrator.migrationTable(_CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));
228
+ }
229
+ static migrationSql(tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX, appliedSteps = [], options) {
230
+ return _CNSPostgresStimulationSchema.migrator.migrationSql(_CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), appliedSteps, options);
231
+ }
232
+ static async migrate(pool, tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX, options) {
233
+ await _CNSPostgresStimulationSchema.migrator.migrate(CNSPostgresSchemaPgDatabase.fromPool(pool), _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), options);
234
+ }
235
+ static async verify(database, tablePrefix = _CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX) {
236
+ await _CNSPostgresStimulationSchema.migrator.verify(database, _CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));
237
+ }
238
+ static initialSql(p) {
239
+ return `
240
+ CREATE TABLE ${p}stimulation (
241
+ stimulation_id text PRIMARY KEY,
242
+ entry jsonb NOT NULL,
243
+ status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),
244
+ scope_name text,
245
+ progress jsonb NOT NULL,
246
+ updated_at bigint NOT NULL,
247
+ started_at bigint NOT NULL DEFAULT 0,
248
+ hop_count int NOT NULL DEFAULT 0,
249
+ last_completed_at bigint
250
+ ) WITH (fillfactor = 70);
251
+ CREATE INDEX ${p}stimulation_started_idx ON ${p}stimulation (started_at DESC, stimulation_id COLLATE "C" DESC);
252
+ CREATE INDEX ${p}stimulation_scope_started_idx ON ${p}stimulation (scope_name, started_at DESC, stimulation_id COLLATE "C" DESC);
253
+
254
+ -- No foreign keys below: attempts and tasks arrive over separate writes, often before their parent row.
255
+ CREATE TABLE ${p}stimulation_attempt (
256
+ stimulation_attempt_id text PRIMARY KEY,
257
+ stimulation_id text NOT NULL,
258
+ attempt_number int NOT NULL,
259
+ status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),
260
+ started_at bigint NOT NULL,
261
+ completed_at bigint,
262
+ hop_count int NOT NULL,
263
+ has_error boolean NOT NULL,
264
+ replay_of text,
265
+ entry jsonb NOT NULL,
266
+ UNIQUE (stimulation_id, attempt_number)
267
+ );
268
+ CREATE INDEX ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (started_at);
269
+
270
+ CREATE TABLE ${p}stimulation_task (
271
+ stimulation_attempt_id text NOT NULL,
272
+ task_index int NOT NULL,
273
+ neuron_name text NOT NULL,
274
+ dendrite_collateral_name text NOT NULL,
275
+ input_index int NOT NULL,
276
+ output jsonb,
277
+ status text NOT NULL CHECK (status IN ('done', 'failed')),
278
+ error text,
279
+ started_at bigint NOT NULL,
280
+ duration_ms bigint,
281
+ activation_context jsonb,
282
+ PRIMARY KEY (stimulation_attempt_id, task_index, started_at)
283
+ ) PARTITION BY RANGE (started_at);
284
+ CREATE TABLE ${p}stimulation_task_default PARTITION OF ${p}stimulation_task DEFAULT;
285
+ `;
286
+ }
287
+ };
288
+ __name(_CNSPostgresStimulationSchema, "CNSPostgresStimulationSchema");
289
+ __publicField(_CNSPostgresStimulationSchema, "DEFAULT_TABLE_PREFIX", "cns_");
290
+ __publicField(_CNSPostgresStimulationSchema, "LONGEST_SUFFIX", "stimulation_attempt_started_idx");
291
+ __publicField(_CNSPostgresStimulationSchema, "migrator", new CNSPostgresSchemaMigrator({
292
+ name: "stimulation",
293
+ tables: /* @__PURE__ */ __name((tablePrefix) => _CNSPostgresStimulationSchema.tables(tablePrefix), "tables"),
294
+ steps: [
295
+ {
296
+ step: 1,
297
+ kind: "expand",
298
+ sql: /* @__PURE__ */ __name((tablePrefix) => _CNSPostgresStimulationSchema.initialSql(tablePrefix), "sql")
299
+ }
300
+ ]
301
+ }));
302
+ var CNSPostgresStimulationSchema = _CNSPostgresStimulationSchema;
303
+
304
+ export {
305
+ __name,
306
+ __publicField,
307
+ CNSPostgresSchemaPartitionRotation,
308
+ CNSPostgresStimulationSchema
309
+ };
310
+ //# sourceMappingURL=chunk-BPKIKGKB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../postgres-schema/src/CNSPostgresSchemaMigrator.ts","../../postgres-schema/src/CNSPostgresSchemaPgDatabase.ts","../../postgres-schema/src/CNSPostgresSchemaPartitionRotation.ts","../src/schema/CNSPostgresStimulationSchema.ts"],"sourcesContent":["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":";;;;;;AAOO,IAAMA,6BAAN,MAAMA,2BAAAA;EAOT,YAA6BC,SAA4C;;AAFxDC;AALrB;SAOiCD,UAAAA;AACzB,SAAKC,QAAQ;SAAID,QAAQC;MAAOC,KAAK,CAACC,MAAMC,UAAUD,KAAKE,OAAOD,MAAMC,IAAI;AAC5E,SAAKJ,MAAMK,QAAQ,CAACD,MAAME,UAAAA;AACtB,UAAIF,KAAKA,SAASE,QAAQ,EAAG,OAAM,IAAIC,MAAM,IAAIR,QAAQS,IAAI,wDAAmDJ,KAAKA,IAAI,gBAAgBE,QAAQ,CAAA,EAAG;IACxJ,CAAA;AACA,UAAI,UAAKN,MAAM,CAAA,MAAX,mBAAeS,UAAS,SAAU,OAAM,IAAIF,MAAM,IAAIR,QAAQS,IAAI,wCAAwC;EAClH;EAEAE,eAAeC,aAA6B;AACxC,WAAO,GAAGA,WAAAA,GAAc,KAAKZ,QAAQS,IAAI;EAC7C;EAEA,MAAMI,QAAQC,UAAsCF,aAAqBZ,UAA4C,CAAC,GAAkB;AACpI,UAAMc,SAASC,YAAY,OAAMC,eAAAA;AAC7B,YAAMA,WAAWC,MAAM,kDAAkD;QAAClB,2BAA0BmB;QAAgBN,cAAc,KAAKZ,QAAQS;OAAK;AACpJ,YAAMO,WAAWC,MAAM,6BAA6BlB,2BAA0BoB,YAAY,GAAG;AAC7F,YAAMC,UAAU,MAAM,KAAKC,iBAAiBL,YAAYJ,WAAAA;AACxD,UAAIQ,YAAYE,OAAW,OAAM,KAAKC,wBAAwBP,YAAYJ,WAAAA;AAC1E,YAAMY,UAAU,KAAKC,aAAaL,4BAAW,oBAAIM,IAAAA,GAAON,YAAYE,UAAatB,QAAQ2B,aAAa,IAAA;AACtG,UAAIH,QAAQI,SAAS,EAAG,OAAMZ,WAAWC,MAAM,KAAKY,OAAOjB,aAAaY,SAASJ,YAAYE,MAAAA,CAAAA;IACjG,CAAA;EACJ;EAEA,MAAMQ,OAAOd,YAA0CJ,aAAoC;AACvF,UAAMQ,UAAU,MAAM,KAAKC,iBAAiBL,YAAYJ,WAAAA;AACxD,QAAIQ,YAAYE,QAAW;AACvB,YAAM,IAAId,MAAM,IAAI,KAAKR,QAAQS,IAAI,+CAA+CG,WAAAA,kBAA6B;IACrH;AACA,UAAMmB,UAAU,KAAK9B,MAAM+B,OAAO3B,CAAAA,SAAQA,KAAKK,SAAS,YAAY,CAACU,QAAQa,IAAI5B,KAAKA,IAAI,CAAA,EAAG6B,IAAI7B,CAAAA,SAAQA,KAAKA,IAAI;AAClH,QAAI0B,QAAQH,SAAS,GAAG;AACpB,YAAM,IAAIpB,MAAM,IAAI,KAAKR,QAAQS,IAAI,kBAAkBsB,QAAQI,KAAK,IAAA,CAAA,kCAAuCvB,WAAAA,kBAA6B;IAC5I;AACA,UAAMwB,UAAU;SAAIhB;MAASY,OAAO,CAAC,CAAC3B,MAAMK,IAAAA,MAAUA,SAAS,cAAcL,OAAO,KAAKJ,MAAM2B,MAAM,EAAEM,IAAI,CAAC,CAAC7B,IAAAA,MAAUA,IAAAA;AACvH,QAAI+B,QAAQR,SAAS,GAAG;AACpB,YAAM,IAAIpB,MAAM,IAAI,KAAKR,QAAQS,IAAI,qCAAqC2B,QAAQD,KAAK,IAAA,CAAA,uDAA4D;IACvJ;EACJ;EAEAE,aAAazB,aAAqB0B,eAAkC,CAAA,GAAItC,UAA4C,CAAC,GAAW;AAC5H,UAAMuC,QAAQD,aAAaV,WAAW;AACtC,UAAMR,UAAU,IAAIM,IAAwCY,aAAaJ,IAAI7B,CAAAA,SAAQ;MAACA;MAAM;KAAS,CAAA;AACrG,WAAO,KAAKwB,OAAOjB,aAAa,KAAKa,aAAaL,SAASmB,SAASvC,QAAQ2B,aAAa,IAAA,GAAOY,KAAAA;EACpG;EAEAC,kBAAkB5B,aAAqB6B,eAA+B;AAClE,UAAMC,UAAUC,KAAKC,IAAIH,cAAcb,QAAQ,GAAG,KAAK5B,QAAQS,IAAI,aAAamB,MAAM;AACtF,UAAMiB,QAAQ9C,2BAA0B+C,wBAAwBJ;AAChE,QAAI,CAAC,IAAIK,OAAO,4BAA4BF,QAAQ,CAAA,IAAK,EAAEG,KAAKpC,WAAAA,GAAc;AAC1E,YAAM,IAAIJ,MAAM,IAAI,KAAKR,QAAQS,IAAI,yBAAyBwC,KAAKC,UAAUtC,WAAAA,CAAAA,yCAAqDiC,KAAAA,aAAkB;IACxJ;AACA,WAAOjC;EACX;EAEQa,aAAaL,SAA0D+B,cAAiD;AAC5H,WAAO,KAAKlD,MAAM+B,OAAO3B,CAAAA,SAAQ,CAACe,QAAQa,IAAI5B,KAAKA,IAAI,MAAMA,KAAKK,SAAS,YAAYyC,aAAW;EACtG;EAEQtB,OAAOjB,aAAqBX,OAA0CmD,sBAAuC;AACjH,UAAMC,QAAQ,KAAK1C,eAAeC,WAAAA;AAClC,UAAM0C,QAAQF,uBACR;MAAC,gBAAgBC,KAAAA;QACjB,CAAA;AACN,eAAWhD,QAAQJ,OAAO;AACtBqD,YAAMC,KAAKlD,KAAKmD,IAAI5C,WAAAA,GAAc,eAAeyC,KAAAA,yBAA8BhD,KAAKA,IAAI,MAAMA,KAAKK,IAAI,KAAK;IAChH;AACA,WAAO4C,MAAMnB,KAAK,IAAA;EACtB;EAEA,MAAcd,iBAAiBL,YAA0CJ,aAAmF;AA3EhK;AA4EQ,UAAMyC,QAAQ,KAAK1C,eAAeC,WAAAA;AAClC,UAAM6C,SAAS,MAAMzC,WAAWC,MAAgC,yCAAyC;MAACoC;KAAM;AAChH,UAAII,YAAOC,KAAK,CAAA,MAAZD,mBAAgBE,UAAS,KAAM,QAAOrC;AAC1C,UAAMoC,OAAO,MAAM1C,WAAWC,MAA0D,0BAA0BoC,KAAAA,EAAO;AACzH,WAAO,IAAI3B,IAAIgC,KAAKA,KAAKxB,IAAI0B,CAAAA,QAAO;MAACC,OAAOD,IAAIvD,IAAI;MAAGuD,IAAIlD;KAAK,CAAA;EACpE;EAEA,MAAca,wBAAwBP,YAA0CJ,aAAoC;AAChH,UAAMkD,SAAS,KAAK9D,QAAQ8D,OAAOlD,WAAAA;AACnC,UAAM+C,QAAQ,MAAM3C,WAAWC,MAC3B,mFACA;MAAC6C;KAAO;AAEZ,QAAIH,MAAMD,KAAK9B,SAAS,GAAG;AACvB,YAAM,IAAIpB,MACN,IAAI,KAAKR,QAAQS,IAAI,YAAYkD,MAAMD,KAAKxB,IAAI0B,CAAAA,QAAOA,IAAInD,IAAI,EAAE0B,KAAK,IAAA,CAAA,kBAAuB,KAAKxB,eAAeC,WAAAA,CAAAA,oGAAgH;IAEzO;EACJ;AACJ;AA/Fab;AACT,cADSA,4BACOmB,kBAAiB;AACjC,cAFSnB,4BAEOoB,gBAAe;AAC/B,cAHSpB,4BAGO+C,yBAAwB;AAHrC,IAAM/C,4BAAN;;;ACHA,IAAMgE,+BAAN,MAAMA,6BAAAA;EACT,YACqBC,MACnB;;SADmBA,OAAAA;EAClB;EAEH,OAAOC,SAASC,MAA6D;AACzE,WAAO,IAAIH,6BAA4B,YAAA;AACnC,YAAMI,SAAS,MAAMD,KAAKE,QAAO;AACjC,aAAO;QAAEC,YAAYF;QAAQG,SAASC,wBAAAA,UAASJ,OAAOG,QAAQC,KAAAA,GAAxBA;MAA+B;IACzE,CAAA;EACJ;EAEA,MAAMC,YAAqBC,MAAwF;AAC/G,UAAM,EAAEJ,YAAYC,QAAO,IAAK,MAAM,KAAKN,KAAI;AAC/C,QAAI;AACA,YAAMK,WAAWK,MAAM,OAAA;AACvB,YAAMC,SAAS,MAAMF,KAAKJ,UAAAA;AAC1B,YAAMA,WAAWK,MAAM,QAAA;AACvBJ,cAAAA;AACA,aAAOK;IACX,SAASJ,OAAO;AACZ,YAAMK,WAAW,MAAMP,WAAWK,MAAM,UAAA,EAAYG,KAChD,MAAMC,QACN,CAACC,kBAA2BA,aAAAA;AAEhCT,cAAQM,oBAAoBI,QAAQJ,WAAWE,MAAAA;AAC/C,YAAMP;IACV;EACJ;AACJ;AA7BaR;AAAN,IAAMA,8BAAN;;;ACEA,IAAMkB,sCAAN,MAAMA,oCAAAA;EAOT,YAA6BC,SAAqD;;AAFjEC,sCAAa,oBAAIC,IAAAA;SAELF,UAAAA;AACzB,QAAI,EAAEA,QAAQG,eAAe,MAAM,CAACC,OAAOC,UAAUL,QAAQG,WAAW,GAAG;AACvE,YAAM,IAAIG,MAAM,oEAAoEN,QAAQG,WAAW,EAAE;IAC7G;EACJ;EAEA,MAAMI,OACFC,UACAC,OACAC,KACAC,YACAC,YACiB;AACjB,UAAMC,QAAQC,KAAKC,MAAML,MAAMX,oCAAmCiB,MAAM;AACxE,UAAMC,OAAO,GAAGJ,KAAAA,IAASC,KAAKC,MAAMJ,aAAaZ,oCAAmCiB,MAAM,CAAA;AAC1F,QAAI,KAAKf,WAAWiB,IAAIT,KAAAA,MAAWQ,KAAM,QAAO,CAAA;AAChD,UAAME,UAAU,MAAM,KAAKC,UAAUZ,UAAUC,OAAO,OAAMY,eAAAA;AACxD,YAAMC,WAAW,MAAM,KAAKC,oBAAoBF,YAAYZ,KAAAA,GAAQe,OAAOC,CAAAA,cAAaA,UAAUC,MAAMf,UAAAA;AACxG,UAAIC,WAAY,YAAWa,aAAaH,QAAS,OAAMV,WAAWS,YAAYI,UAAUE,IAAI;AAC5F,iBAAWF,aAAaH,QAAS,OAAMD,WAAWO,MAAM,cAAcH,UAAUE,IAAI,EAAE;AACtF,aAAOL,QAAQO,IAAIJ,CAAAA,cAAaA,UAAUE,IAAI;IAClD,CAAA;AACA,UAAM,KAAKP,UAAUZ,UAAUC,OAAO,OAAMY,eAAAA;AACxC,YAAMS,aAAa,MAAM,KAAKP,oBAAoBF,YAAYZ,KAAAA;AAC9D,eAASsB,QAAQ,GAAGA,SAAS,KAAK/B,QAAQG,aAAa4B,SAAS;AAC5D,cAAMC,QAAQnB,QAAQkB,SAAShC,oCAAmCiB;AAClE,cAAMU,KAAKM,OAAOjC,oCAAmCiB;AACrD,YAAIc,WAAWG,KAAKR,CAAAA,cAAaA,UAAUO,OAAON,MAAMM,OAAOP,UAAUC,EAAE,EAAG;AAC9E,cAAMC,OAAO,GAAGlB,KAAAA,KAAU,IAAIyB,KAAKF,IAAAA,EAAMG,YAAW,EAAGC,MAAM,GAAG,EAAA,EAAIC,QAAQ,MAAM,EAAA,CAAA;AAClF,cAAMhB,WAAWO,MAAM,gBAAgBD,IAAAA,UAAclB,KAAAA,4CAAiD;AACtG,cAAMY,WAAWO,MAAM,eAAenB,KAAAA,qBAA0BkB,IAAAA,qBAAyBK,IAAAA,SAAaN,EAAAA,GAAK;MAC/G;IACJ,CAAA;AACA,SAAKzB,WAAWqC,IAAI7B,OAAOQ,IAAAA;AAC3B,WAAOE;EACX;EAEQC,UAAmBZ,UAAsCC,OAAe8B,MAAwF;AACpK,WAAO/B,SAASgC,YAAY,OAAMnB,eAAAA;AAC9B,YAAMA,WAAWO,MAAM,kDAAkD;QAAC7B,oCAAmC0C;QAAgBhC;OAAM;AACnI,YAAMY,WAAWO,MAAM,6BAA6B7B,oCAAmC2C,YAAY,GAAG;AACtG,aAAOH,KAAKlB,UAAAA;IAChB,CAAA;EACJ;EAEA,MAAcE,oBAAoBF,YAA0CZ,OAA2C;AACnH,UAAMkC,OAAO,MAAMtB,WAAWO,MAC1B;;;0DAIA;MAACnB;KAAM;AAEX,UAAMqB,aAAgC,CAAA;AACtC,eAAWc,OAAOD,KAAKA,MAAM;AACzB,YAAME,QAAQ,iEAAiEC,KAAKF,IAAIG,KAAK;AAC7F,UAAIF,MAAOf,YAAWkB,KAAK;QAAErB,MAAMiB,IAAIjB;QAAMK,MAAM5B,OAAOyC,MAAM,CAAA,CAAE;QAAGnB,IAAItB,OAAOyC,MAAM,CAAA,CAAE;MAAE,CAAA;IAC9F;AACA,WAAOf;EACX;AACJ;AAnEa/B;AACT,cADSA,qCACO0C,kBAAiB;AACjC,cAFS1C,qCAEO2C,gBAAe;AAC/B,cAHS3C,qCAGOiB,UAAS,KAAK,KAAK,KAAK;AAHrC,IAAMjB,qCAAN;;;ACAA,IAAMkD,gCAAN,MAAMA,8BAAAA;EAUT,OAAOC,kBAAkBC,aAA6B;AAClD,WAAOF,8BAA6BG,SAASF,kBAAkBC,aAAaF,8BAA6BI,cAAc;EAC3H;EAEA,OAAOC,OAAOH,cAAsBF,8BAA6BM,sBAAgC;AAC7F,UAAMC,IAAIP,8BAA6BC,kBAAkBC,WAAAA;AACzD,WAAO;MAAC,GAAGK,CAAAA;MAAgB,GAAGA,CAAAA;MAAwB,GAAGA,CAAAA;;EAC7D;EAEA,OAAOC,eAAeN,cAAsBF,8BAA6BM,sBAA8B;AACnG,WAAON,8BAA6BG,SAASK,eAAeR,8BAA6BC,kBAAkBC,WAAAA,CAAAA;EAC/G;EAEA,OAAOO,aACHP,cAAsBF,8BAA6BM,sBACnDI,eAAkC,CAAA,GAClCC,SACM;AACN,WAAOX,8BAA6BG,SAASM,aAAaT,8BAA6BC,kBAAkBC,WAAAA,GAAcQ,cAAcC,OAAAA;EACzI;EAEA,aAAaC,QACTC,MACAX,cAAsBF,8BAA6BM,sBACnDK,SACa;AACb,UAAMX,8BAA6BG,SAASS,QACxCE,4BAA4BC,SAASF,IAAAA,GACrCb,8BAA6BC,kBAAkBC,WAAAA,GAC/CS,OAAAA;EAER;EAEA,aAAaK,OAAOC,UAA2Bf,cAAsBF,8BAA6BM,sBAAqC;AACnI,UAAMN,8BAA6BG,SAASa,OACxCC,UACAjB,8BAA6BC,kBAAkBC,WAAAA,CAAAA;EAEvD;EAEA,OAAegB,WAAWX,GAAmB;AACzC,WAAO;eACAA,CAAAA;;;;;;;;;;;eAWAA,CAAAA,8BAA+BA,CAAAA;eAC/BA,CAAAA,oCAAqCA,CAAAA;;;eAGrCA,CAAAA;;;;;;;;;;;;;eAaAA,CAAAA,sCAAuCA,CAAAA;;eAEvCA,CAAAA;;;;;;;;;;;;;;eAcAA,CAAAA,yCAA0CA,CAAAA;;EAErD;AACJ;AAnGaP;AACT,cADSA,+BACOM,wBAAuB;AACvC,cAFSN,+BAEeI,kBAAiB;AAEzC,cAJSJ,+BAIeG,YAAW,IAAIgB,0BAA0B;EAC7DC,MAAM;EACNf,QAAQH,wBAAAA,gBAAeF,8BAA6BK,OAAOH,WAAAA,GAAnDA;EACRmB,OAAO;IAAC;MAAEC,MAAM;MAAGC,MAAM;MAAUC,KAAKtB,wBAAAA,gBAAeF,8BAA6BkB,WAAWhB,WAAAA,GAAvDA;IAAoE;;AAChH,CAAA;AARG,IAAMF,+BAAN;","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"]}