@uptimizr/db-postgres 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,4 +23,7 @@ export { upsertSceneProxy, getSceneRepresentation, listSceneRepresentations, } f
23
23
  export type { SceneRepresentation, SceneRepresentationKind, SceneRepresentationSummary, } from "./sceneRegistry.js";
24
24
  export { putSceneRegions, getSceneRegions, listSceneRegions } from "./sceneRegions.js";
25
25
  export type { SceneRegionRecord, SceneRegionSummary } from "./sceneRegions.js";
26
+ export { createAnnotation, listAnnotations, deleteAnnotation, putGlossaryEntry, listGlossary, deleteGlossaryEntry, createSavedAnalysis, listSavedAnalyses, deleteSavedAnalysis, } from "./projectMetadata.js";
27
+ export { createPanelSpec, listPanelSpecs, updatePanelSpec, deletePanelSpec } from "./panelSpecs.js";
28
+ export { listSubscriptions, listEnabledSubscriptions, getSubscription, createSubscription, setSubscriptionEnabled, deleteSubscription, recordSubscriptionOutcome, getWebhookSecret, recordSubscriptionEvent, listSubscriptionEvents, } from "./subscriptions.js";
26
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClG,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvF,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClG,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvF,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEpG,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -18,4 +18,10 @@ export { createProject, getProject, createApiKey, resolveApiKey, hashApiKey, api
18
18
  export { recordAudit, listAudit, pruneAudit } from "./audit.js";
19
19
  export { upsertSceneProxy, getSceneRepresentation, listSceneRepresentations, } from "./sceneRegistry.js";
20
20
  export { putSceneRegions, getSceneRegions, listSceneRegions } from "./sceneRegions.js";
21
+ // Project metadata: annotations, glossary, saved analyses (#310, ADR 0051 §5).
22
+ export { createAnnotation, listAnnotations, deleteAnnotation, putGlossaryEntry, listGlossary, deleteGlossaryEntry, createSavedAnalysis, listSavedAnalyses, deleteSavedAnalysis, } from "./projectMetadata.js";
23
+ // Declarative panel specs (#315, ADR 0051 §7).
24
+ export { createPanelSpec, listPanelSpecs, updatePanelSpec, deletePanelSpec } from "./panelSpecs.js";
25
+ // Conditional subscriptions (#311, ADR 0051 §6).
26
+ export { listSubscriptions, listEnabledSubscriptions, getSubscription, createSubscription, setSubscriptionEnabled, deleteSubscription, recordSubscriptionOutcome, getWebhookSecret, recordSubscriptionEvent, listSubscriptionEvents, } from "./subscriptions.js";
21
27
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGzE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlG,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGzE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlG,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGvF,+EAA+E;AAC/E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,+CAA+C;AAC/C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACpG,iDAAiD;AACjD,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA0P1E,CAAC;AASF;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAoX1E,CAAC;AASF;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -280,6 +280,128 @@ export const POSTGRES_MIGRATIONS = [
280
280
  );
281
281
  `,
282
282
  },
283
+ // Project metadata (ADR 0051 §5 / sketch §E.2): annotations, glossary and
284
+ // saved analyses — the three tables a person or an agent can write to, gated
285
+ // by the `annotate` capability and audited on every write. Mirrors the DuckDB
286
+ // tables column-for-column; the events tables are untouched.
287
+ {
288
+ id: "0015_annotations",
289
+ sql: /* sql */ `
290
+ CREATE TABLE IF NOT EXISTS annotations (
291
+ id text PRIMARY KEY,
292
+ project_id text NOT NULL,
293
+ target_kind text NOT NULL,
294
+ target_id text,
295
+ since timestamp,
296
+ until timestamp,
297
+ text text NOT NULL,
298
+ author_kind text NOT NULL DEFAULT 'user',
299
+ author_key_id text,
300
+ created_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
301
+ updated_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc')
302
+ );
303
+ CREATE INDEX IF NOT EXISTS annotations_project_created_idx
304
+ ON annotations (project_id, created_at DESC);
305
+ `,
306
+ },
307
+ {
308
+ id: "0016_glossary",
309
+ sql: /* sql */ `
310
+ CREATE TABLE IF NOT EXISTS glossary (
311
+ project_id text NOT NULL,
312
+ term text NOT NULL,
313
+ meaning text NOT NULL,
314
+ updated_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
315
+ PRIMARY KEY (project_id, term)
316
+ );
317
+ `,
318
+ },
319
+ {
320
+ id: "0017_saved_analyses",
321
+ sql: /* sql */ `
322
+ CREATE TABLE IF NOT EXISTS saved_analyses (
323
+ id text PRIMARY KEY,
324
+ project_id text NOT NULL,
325
+ title text NOT NULL,
326
+ query text NOT NULL DEFAULT '{}',
327
+ conclusion text,
328
+ author_kind text NOT NULL DEFAULT 'user',
329
+ author_key_id text,
330
+ created_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc')
331
+ );
332
+ CREATE INDEX IF NOT EXISTS saved_analyses_project_created_idx
333
+ ON saved_analyses (project_id, created_at DESC);
334
+ `,
335
+ },
336
+ // Conditional subscriptions (#311, ADR 0051 §6 / sketch §F.1–F.2). One row per
337
+ // standing question; the declaration lives in a JSON `config` column and the
338
+ // scalars beside it are exactly what the store filters (`project_id`,
339
+ // `enabled`), orders (`created_at`) or updates (`last_fired_at`, `failures`).
340
+ //
341
+ // `webhook_secret` is the shared HMAC key: deliberately NOT hashed — a one-way
342
+ // digest cannot sign an outbound body — and never selected by any read path
343
+ // other than `getWebhookSecret`.
344
+ {
345
+ id: "0018_subscriptions",
346
+ sql: /* sql */ `
347
+ CREATE TABLE IF NOT EXISTS subscriptions (
348
+ id text PRIMARY KEY,
349
+ project_id text NOT NULL,
350
+ name text NOT NULL,
351
+ metric text NOT NULL,
352
+ config text NOT NULL,
353
+ webhook_secret text,
354
+ enabled boolean NOT NULL DEFAULT true,
355
+ created_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
356
+ updated_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
357
+ last_fired_at timestamp,
358
+ last_error text,
359
+ failures bigint NOT NULL DEFAULT 0
360
+ );
361
+ CREATE INDEX IF NOT EXISTS subscriptions_project_idx
362
+ ON subscriptions (project_id, created_at);
363
+ `,
364
+ },
365
+ // The bounded firing log: `{ subscriptionId, at, payload }`, last 100 per
366
+ // subscription, trimmed in the same transaction as the insert.
367
+ {
368
+ id: "0019_subscription_events",
369
+ sql: /* sql */ `
370
+ CREATE TABLE IF NOT EXISTS subscription_events (
371
+ id text PRIMARY KEY,
372
+ subscription_id text NOT NULL,
373
+ project_id text NOT NULL,
374
+ at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
375
+ payload text NOT NULL DEFAULT '{}'
376
+ );
377
+ CREATE INDEX IF NOT EXISTS subscription_events_sub_at_idx
378
+ ON subscription_events (subscription_id, at DESC);
379
+ `,
380
+ },
381
+ // Declarative panel specs (#315, ADR 0051 §7 / sketch §G.3). A fourth
382
+ // metadata table: what an agent left behind when an answer was worth keeping.
383
+ // The spec is a closed document validated at the request boundary and never
384
+ // queried *into*, so it lives in one JSON column like a subscription's config.
385
+ //
386
+ // The index ascends, unlike the other metadata tables': these rows are grid
387
+ // positions rather than a feed, and are listed oldest-first so pinning a new
388
+ // panel does not shuffle the ones already there.
389
+ {
390
+ id: "0020_panel_specs",
391
+ sql: /* sql */ `
392
+ CREATE TABLE IF NOT EXISTS panel_specs (
393
+ id text PRIMARY KEY,
394
+ project_id text NOT NULL,
395
+ spec text NOT NULL,
396
+ author_kind text NOT NULL DEFAULT 'user',
397
+ author_key_id text,
398
+ created_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'),
399
+ updated_at timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc')
400
+ );
401
+ CREATE INDEX IF NOT EXISTS panel_specs_project_created_idx
402
+ ON panel_specs (project_id, created_at);
403
+ `,
404
+ },
283
405
  ];
284
406
  /**
285
407
  * Stable advisory-lock key that serializes concurrent boots of several
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAuB,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAA+C;IAC7E,6EAA6E;IAC7E;QACE,EAAE,EAAE,aAAa;QACjB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2Cd;KACF;IACD;QACE,EAAE,EAAE,yBAAyB;QAC7B,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,yEAAyE;IACzE,wEAAwE;IACxE,mDAAmD;IACnD;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,uEAAuE;IACvE,gEAAgE;IAChE;QACE,EAAE,EAAE,mBAAmB;QACvB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;KAed;KACF;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,8EAA8E;IAC9E;QACE,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,SAAS,CAAC;;;;;;KAMd;KACF;IACD,wEAAwE;IACxE,wEAAwE;IACxE;QACE,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;KAUd;KACF;IACD,8EAA8E;IAC9E,4EAA4E;IAC5E,iEAAiE;IACjE;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;KAiBd;KACF;IACD,6EAA6E;IAC7E,+DAA+D;IAC/D,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE;QACE,EAAE,EAAE,sBAAsB;QAC1B,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;KAYd;KACF;IACD;QACE,EAAE,EAAE,wBAAwB;QAC5B,GAAG,EAAE,SAAS,CAAC;;;;;;;;;KASd;KACF;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,0DAA0D;IAC1D;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;;;KAKd;KACF;IACD,8EAA8E;IAC9E,+EAA+E;IAC/E,gEAAgE;IAChE;QACE,EAAE,EAAE,qCAAqC;QACzC,GAAG,EAAE,SAAS,CAAC;;;;KAId;KACF;IACD,+EAA+E;IAC/E,8EAA8E;IAC9E,gEAAgE;IAChE;QACE,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;KAcd;KACF;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,8EAA8E;IAC9E,gDAAgD;IAChD;QACE,EAAE,EAAE,oBAAoB;QACxB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;KAWd;KACF;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,SAAS;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAsB,EACtB,QAA0B;IAE1B,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrD,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACpC,MAAM,EAAE,CAAC,OAAO,CAAC,gCAAgC,kBAAkB,GAAG,CAAC,CAAC;QACxE,MAAM,EAAE,CAAC,OAAO,CAAC,+BAA+B,MAAM,EAAE,CAAC,CAAC;QAC1D,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE,CAAC;YAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAuB,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAA+C;IAC7E,6EAA6E;IAC7E;QACE,EAAE,EAAE,aAAa;QACjB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2Cd;KACF;IACD;QACE,EAAE,EAAE,yBAAyB;QAC7B,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,yEAAyE;IACzE,wEAAwE;IACxE,mDAAmD;IACnD;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,uEAAuE;IACvE,gEAAgE;IAChE;QACE,EAAE,EAAE,mBAAmB;QACvB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;KAed;KACF;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,GAAG,EAAE,SAAS,CAAC;;;KAGd;KACF;IACD,8EAA8E;IAC9E;QACE,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,SAAS,CAAC;;;;;;KAMd;KACF;IACD,wEAAwE;IACxE,wEAAwE;IACxE;QACE,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;KAUd;KACF;IACD,8EAA8E;IAC9E,4EAA4E;IAC5E,iEAAiE;IACjE;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;KAiBd;KACF;IACD,6EAA6E;IAC7E,+DAA+D;IAC/D,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE;QACE,EAAE,EAAE,sBAAsB;QAC1B,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;KAYd;KACF;IACD;QACE,EAAE,EAAE,wBAAwB;QAC5B,GAAG,EAAE,SAAS,CAAC;;;;;;;;;KASd;KACF;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,0DAA0D;IAC1D;QACE,EAAE,EAAE,4BAA4B;QAChC,GAAG,EAAE,SAAS,CAAC;;;;;KAKd;KACF;IACD,8EAA8E;IAC9E,+EAA+E;IAC/E,gEAAgE;IAChE;QACE,EAAE,EAAE,qCAAqC;QACzC,GAAG,EAAE,SAAS,CAAC;;;;KAId;KACF;IACD,+EAA+E;IAC/E,8EAA8E;IAC9E,gEAAgE;IAChE;QACE,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;KAcd;KACF;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,8EAA8E;IAC9E,gDAAgD;IAChD;QACE,EAAE,EAAE,oBAAoB;QACxB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;KAWd;KACF;IACD,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,6DAA6D;IAC7D;QACE,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;KAgBd;KACF;IACD;QACE,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,SAAS,CAAC;;;;;;;;KAQd;KACF;IACD;QACE,EAAE,EAAE,qBAAqB;QACzB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;KAad;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,sEAAsE;IACtE,8EAA8E;IAC9E,EAAE;IACF,+EAA+E;IAC/E,4EAA4E;IAC5E,iCAAiC;IACjC;QACE,EAAE,EAAE,oBAAoB;QACxB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;;;;;;KAiBd;KACF;IACD,0EAA0E;IAC1E,+DAA+D;IAC/D;QACE,EAAE,EAAE,0BAA0B;QAC9B,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;KAUd;KACF;IACD,sEAAsE;IACtE,8EAA8E;IAC9E,4EAA4E;IAC5E,+EAA+E;IAC/E,EAAE;IACF,4EAA4E;IAC5E,6EAA6E;IAC7E,iDAAiD;IACjD;QACE,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,SAAS,CAAC;;;;;;;;;;;;KAYd;KACF;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,SAAS;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAsB,EACtB,QAA0B;IAE1B,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrD,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACpC,MAAM,EAAE,CAAC,OAAO,CAAC,gCAAgC,kBAAkB,GAAG,CAAC,CAAC;QACxE,MAAM,EAAE,CAAC,OAAO,CAAC,+BAA+B,MAAM,EAAE,CAAC,CAAC;QAC1D,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE,CAAC;YAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type CreatePanelSpecInput, type MetadataListOptions, type PanelSpecRecord, type UpdatePanelSpecInput } from "@uptimizr/db";
2
+ import type { PostgresClient } from "./client.js";
3
+ /** Pin one panel and return the stored row. */
4
+ export declare function createPanelSpec(client: PostgresClient, projectId: string, input: CreatePanelSpecInput): Promise<PanelSpecRecord>;
5
+ /** A project's pinned panels, oldest first — a pinned panel keeps its place. */
6
+ export declare function listPanelSpecs(client: PostgresClient, projectId: string, opts?: MetadataListOptions): Promise<PanelSpecRecord[]>;
7
+ /**
8
+ * Replace one panel's spec, keeping its id, its place and its original author.
9
+ * `null` when the id is unknown or belongs to another project.
10
+ */
11
+ export declare function updatePanelSpec(client: PostgresClient, projectId: string, id: string, input: UpdatePanelSpecInput): Promise<PanelSpecRecord | null>;
12
+ /** Unpin one panel. Returns whether a row was removed. */
13
+ export declare function deletePanelSpec(client: PostgresClient, projectId: string, id: string): Promise<boolean>;
14
+ //# sourceMappingURL=panelSpecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelSpecs.d.ts","sourceRoot":"","sources":["../src/panelSpecs.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,oBAAoB,EAEzB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqDlD,+CAA+C;AAC/C,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED,gFAAgF;AAChF,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,eAAe,EAAE,CAAC,CAa5B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CASjC;AAED,0DAA0D;AAC1D,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,73 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { METADATA_LIMITS, MetadataLimitError, clampMetadataLimit, parsePanelSpec, } from "@uptimizr/db";
3
+ /**
4
+ * Declarative panel specs for the single-tenant Postgres store (#315,
5
+ * ADR 0051 §7 / sketch §G.3).
6
+ *
7
+ * Mirrors the DuckDB accessors row-for-row and semantics-for-semantics: the
8
+ * same per-project cap, the same oldest-first ordering (these are grid
9
+ * positions, not a feed), the same "an unparseable row is skipped rather than
10
+ * fatal", and the same `MetadataLimitError` when a project is full — so the
11
+ * collector behaves identically whichever engine is configured.
12
+ */
13
+ /** `timestamp` → epoch milliseconds, the form the row mapper expects. */
14
+ const ms = (column) => `(EXTRACT(EPOCH FROM ${column}) * 1000)::bigint`;
15
+ const PANEL_SPEC_COLS = `id, project_id, spec, author_kind, author_key_id,
16
+ ${ms("created_at")} AS created_at_ms, ${ms("updated_at")} AS updated_at_ms`;
17
+ /** Map one row, or `null` when its `spec` column is not a JSON object. */
18
+ function rowToPanelSpec(row) {
19
+ const spec = parsePanelSpec(row.spec);
20
+ if (spec == null)
21
+ return null;
22
+ return {
23
+ id: row.id,
24
+ projectId: row.project_id,
25
+ spec,
26
+ authorKind: row.author_kind,
27
+ authorKeyId: row.author_key_id,
28
+ createdAt: new Date(Number(row.created_at_ms)),
29
+ updatedAt: new Date(Number(row.updated_at_ms)),
30
+ };
31
+ }
32
+ async function countRows(client, sql, params) {
33
+ const rows = await client.query(sql, params);
34
+ return Number(rows[0]?.n ?? 0);
35
+ }
36
+ /** Pin one panel and return the stored row. */
37
+ export async function createPanelSpec(client, projectId, input) {
38
+ const count = await countRows(client, `SELECT count(*) AS n FROM panel_specs WHERE project_id = $1`, [projectId]);
39
+ if (count >= METADATA_LIMITS.panelSpecs) {
40
+ throw new MetadataLimitError("panelSpecs", METADATA_LIMITS.panelSpecs);
41
+ }
42
+ const rows = await client.query(`INSERT INTO panel_specs
43
+ (id, project_id, spec, author_kind, author_key_id, created_at, updated_at)
44
+ VALUES ($1, $2, $3, $4, $5, (now() AT TIME ZONE 'utc'), (now() AT TIME ZONE 'utc'))
45
+ RETURNING ${PANEL_SPEC_COLS}`, [randomUUID(), projectId, JSON.stringify(input.spec), input.authorKind, input.authorKeyId]);
46
+ // Written from a document that serialized a line above, so the parse holds.
47
+ return rowToPanelSpec(rows[0]);
48
+ }
49
+ /** A project's pinned panels, oldest first — a pinned panel keeps its place. */
50
+ export async function listPanelSpecs(client, projectId, opts = {}) {
51
+ const limit = clampMetadataLimit(opts.limit, METADATA_LIMITS.panelSpecs, METADATA_LIMITS.panelSpecs);
52
+ const rows = await client.query(`SELECT ${PANEL_SPEC_COLS} FROM panel_specs WHERE project_id = $1
53
+ ORDER BY created_at ASC, id ASC
54
+ LIMIT ${limit}`, [projectId]);
55
+ return rows.map(rowToPanelSpec).filter((record) => record !== null);
56
+ }
57
+ /**
58
+ * Replace one panel's spec, keeping its id, its place and its original author.
59
+ * `null` when the id is unknown or belongs to another project.
60
+ */
61
+ export async function updatePanelSpec(client, projectId, id, input) {
62
+ const rows = await client.query(`UPDATE panel_specs SET spec = $3, updated_at = (now() AT TIME ZONE 'utc')
63
+ WHERE project_id = $1 AND id = $2
64
+ RETURNING ${PANEL_SPEC_COLS}`, [projectId, id, JSON.stringify(input.spec)]);
65
+ const row = rows[0];
66
+ return row == null ? null : rowToPanelSpec(row);
67
+ }
68
+ /** Unpin one panel. Returns whether a row was removed. */
69
+ export async function deletePanelSpec(client, projectId, id) {
70
+ const rows = await client.query(`DELETE FROM panel_specs WHERE project_id = $1 AND id = $2 RETURNING id`, [projectId, id]);
71
+ return rows.length > 0;
72
+ }
73
+ //# sourceMappingURL=panelSpecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelSpecs.js","sourceRoot":"","sources":["../src/panelSpecs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type AnnotationRecord, type CreateAnnotationInput, type CreateSavedAnalysisInput, type GlossaryEntryRecord, type ListAnnotationsOptions, type MetadataListOptions, type PutGlossaryEntryInput, type SavedAnalysisRecord } from "@uptimizr/db";
2
+ import type { PostgresClient } from "./client.js";
3
+ /** Create one annotation and return the stored row. */
4
+ export declare function createAnnotation(client: PostgresClient, projectId: string, input: CreateAnnotationInput): Promise<AnnotationRecord>;
5
+ /** A project's annotations, newest first, optionally filtered by target/overlap. */
6
+ export declare function listAnnotations(client: PostgresClient, projectId: string, opts?: ListAnnotationsOptions): Promise<AnnotationRecord[]>;
7
+ /** Delete one annotation of this project. Returns whether a row was removed. */
8
+ export declare function deleteAnnotation(client: PostgresClient, projectId: string, id: string): Promise<boolean>;
9
+ /** Upsert one glossary entry (the term is the identity) and return the row. */
10
+ export declare function putGlossaryEntry(client: PostgresClient, projectId: string, input: PutGlossaryEntryInput): Promise<GlossaryEntryRecord>;
11
+ /** A project's whole glossary, ordered by term. */
12
+ export declare function listGlossary(client: PostgresClient, projectId: string, opts?: MetadataListOptions): Promise<GlossaryEntryRecord[]>;
13
+ /** Delete one term. Returns whether it existed. */
14
+ export declare function deleteGlossaryEntry(client: PostgresClient, projectId: string, term: string): Promise<boolean>;
15
+ /** Create one saved analysis and return the stored row. */
16
+ export declare function createSavedAnalysis(client: PostgresClient, projectId: string, input: CreateSavedAnalysisInput): Promise<SavedAnalysisRecord>;
17
+ /** A project's saved analyses, newest first. */
18
+ export declare function listSavedAnalyses(client: PostgresClient, projectId: string, opts?: MetadataListOptions): Promise<SavedAnalysisRecord[]>;
19
+ /** Delete one saved analysis. Returns whether it existed. */
20
+ export declare function deleteSavedAnalysis(client: PostgresClient, projectId: string, id: string): Promise<boolean>;
21
+ //# sourceMappingURL=projectMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectMetadata.d.ts","sourceRoot":"","sources":["../src/projectMetadata.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,gBAAgB,EAErB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAE3B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmElD,uDAAuD;AACvD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAgC3B;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA4B7B;AAED,gFAAgF;AAChF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CAMlB;AAoBD,+EAA+E;AAC/E,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAED,mDAAmD;AACnD,wBAAsB,YAAY,CAChC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAOhC;AAED,mDAAmD;AACnD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAMlB;AA6BD,2DAA2D;AAC3D,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAED,gDAAgD;AAChD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAShC;AAED,6DAA6D;AAC7D,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,186 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { METADATA_LIMITS, MetadataLimitError, clampMetadataLimit, parseSavedAnalysisQuery, } from "@uptimizr/db";
3
+ /**
4
+ * Project metadata — annotations, glossary, saved analyses — for the
5
+ * single-tenant Postgres store (ADR 0051 §5 / sketch §E.2).
6
+ *
7
+ * Mirrors the DuckDB accessors row-for-row and semantics-for-semantics: the
8
+ * same per-project caps, the same "newest first" ordering, the same overlap
9
+ * filter, and the same `MetadataLimitError` when a project is full, so the
10
+ * collector behaves identically whichever engine is configured.
11
+ */
12
+ /** `timestamp` → epoch milliseconds, the form the row mappers expect. */
13
+ const ms = (column) => `(EXTRACT(EPOCH FROM ${column}) * 1000)::bigint`;
14
+ /** Epoch milliseconds → a naive-UTC `timestamp`, matching every other write. */
15
+ const toTimestamp = (placeholder) => `to_timestamp(${placeholder}::double precision / 1000) AT TIME ZONE 'utc'`;
16
+ const ANNOTATION_COLS = `id, project_id, target_kind, target_id,
17
+ ${ms("since")} AS since_ms, ${ms("until")} AS until_ms, text,
18
+ author_kind, author_key_id,
19
+ ${ms("created_at")} AS created_at_ms, ${ms("updated_at")} AS updated_at_ms`;
20
+ function toDate(value) {
21
+ return value == null ? null : new Date(Number(value));
22
+ }
23
+ function rowToAnnotation(row) {
24
+ return {
25
+ id: row.id,
26
+ projectId: row.project_id,
27
+ targetKind: row.target_kind,
28
+ targetId: row.target_id,
29
+ since: toDate(row.since_ms),
30
+ until: toDate(row.until_ms),
31
+ text: row.text,
32
+ authorKind: row.author_kind,
33
+ authorKeyId: row.author_key_id,
34
+ createdAt: new Date(Number(row.created_at_ms)),
35
+ updatedAt: new Date(Number(row.updated_at_ms)),
36
+ };
37
+ }
38
+ async function countRows(client, sql, params) {
39
+ const rows = await client.query(sql, params);
40
+ return Number(rows[0]?.n ?? 0);
41
+ }
42
+ /** Create one annotation and return the stored row. */
43
+ export async function createAnnotation(client, projectId, input) {
44
+ const count = await countRows(client, `SELECT count(*) AS n FROM annotations WHERE project_id = $1`, [projectId]);
45
+ if (count >= METADATA_LIMITS.annotations) {
46
+ throw new MetadataLimitError("annotations", METADATA_LIMITS.annotations);
47
+ }
48
+ const { annotation } = input;
49
+ const rows = await client.query(`INSERT INTO annotations
50
+ (id, project_id, target_kind, target_id, since, until, text,
51
+ author_kind, author_key_id, created_at, updated_at)
52
+ VALUES ($1, $2, $3, $4,
53
+ -- to_timestamp(NULL) is NULL, so a standing note needs no branch.
54
+ ${toTimestamp("$5")}, ${toTimestamp("$6")},
55
+ $7, $8, $9, (now() AT TIME ZONE 'utc'), (now() AT TIME ZONE 'utc'))
56
+ RETURNING ${ANNOTATION_COLS}`, [
57
+ randomUUID(),
58
+ projectId,
59
+ annotation.targetKind,
60
+ annotation.targetId ?? null,
61
+ annotation.since ?? null,
62
+ annotation.until ?? null,
63
+ annotation.text,
64
+ input.authorKind,
65
+ input.authorKeyId,
66
+ ]);
67
+ return rowToAnnotation(rows[0]);
68
+ }
69
+ /** A project's annotations, newest first, optionally filtered by target/overlap. */
70
+ export async function listAnnotations(client, projectId, opts = {}) {
71
+ const limit = clampMetadataLimit(opts.limit);
72
+ const params = [projectId];
73
+ const where = ["project_id = $1"];
74
+ if (opts.targetKind != null) {
75
+ params.push(opts.targetKind);
76
+ where.push(`target_kind = $${params.length}`);
77
+ }
78
+ if (opts.targetId != null) {
79
+ params.push(opts.targetId);
80
+ where.push(`target_id = $${params.length}`);
81
+ }
82
+ if (opts.since != null) {
83
+ params.push(opts.since);
84
+ where.push(`(until IS NULL OR until >= ${toTimestamp(`$${params.length}`)})`);
85
+ }
86
+ if (opts.until != null) {
87
+ params.push(opts.until);
88
+ where.push(`(since IS NULL OR since < ${toTimestamp(`$${params.length}`)})`);
89
+ }
90
+ const rows = await client.query(`SELECT ${ANNOTATION_COLS} FROM annotations
91
+ WHERE ${where.join(" AND ")}
92
+ ORDER BY created_at DESC, id DESC
93
+ LIMIT ${limit}`, params);
94
+ return rows.map(rowToAnnotation);
95
+ }
96
+ /** Delete one annotation of this project. Returns whether a row was removed. */
97
+ export async function deleteAnnotation(client, projectId, id) {
98
+ const rows = await client.query(`DELETE FROM annotations WHERE project_id = $1 AND id = $2 RETURNING id`, [projectId, id]);
99
+ return rows.length > 0;
100
+ }
101
+ const GLOSSARY_COLS = `project_id, term, meaning, ${ms("updated_at")} AS updated_at_ms`;
102
+ function rowToGlossaryEntry(row) {
103
+ return {
104
+ projectId: row.project_id,
105
+ term: row.term,
106
+ meaning: row.meaning,
107
+ updatedAt: new Date(Number(row.updated_at_ms)),
108
+ };
109
+ }
110
+ /** Upsert one glossary entry (the term is the identity) and return the row. */
111
+ export async function putGlossaryEntry(client, projectId, input) {
112
+ const { term, meaning } = input.entry;
113
+ const existing = await countRows(client, `SELECT count(*) AS n FROM glossary WHERE project_id = $1 AND term = $2`, [projectId, term]);
114
+ if (existing === 0) {
115
+ const count = await countRows(client, `SELECT count(*) AS n FROM glossary WHERE project_id = $1`, [projectId]);
116
+ if (count >= METADATA_LIMITS.glossary) {
117
+ throw new MetadataLimitError("glossary", METADATA_LIMITS.glossary);
118
+ }
119
+ }
120
+ const rows = await client.query(`INSERT INTO glossary (project_id, term, meaning, updated_at)
121
+ VALUES ($1, $2, $3, (now() AT TIME ZONE 'utc'))
122
+ ON CONFLICT (project_id, term)
123
+ DO UPDATE SET meaning = EXCLUDED.meaning, updated_at = (now() AT TIME ZONE 'utc')
124
+ RETURNING ${GLOSSARY_COLS}`, [projectId, term, meaning]);
125
+ return rowToGlossaryEntry(rows[0]);
126
+ }
127
+ /** A project's whole glossary, ordered by term. */
128
+ export async function listGlossary(client, projectId, opts = {}) {
129
+ const limit = clampMetadataLimit(opts.limit, METADATA_LIMITS.glossary, METADATA_LIMITS.glossary);
130
+ const rows = await client.query(`SELECT ${GLOSSARY_COLS} FROM glossary WHERE project_id = $1 ORDER BY term LIMIT ${limit}`, [projectId]);
131
+ return rows.map(rowToGlossaryEntry);
132
+ }
133
+ /** Delete one term. Returns whether it existed. */
134
+ export async function deleteGlossaryEntry(client, projectId, term) {
135
+ const rows = await client.query(`DELETE FROM glossary WHERE project_id = $1 AND term = $2 RETURNING term`, [projectId, term]);
136
+ return rows.length > 0;
137
+ }
138
+ const ANALYSIS_COLS = `id, project_id, title, query, conclusion, author_kind, author_key_id,
139
+ ${ms("created_at")} AS created_at_ms`;
140
+ function rowToSavedAnalysis(row) {
141
+ return {
142
+ id: row.id,
143
+ projectId: row.project_id,
144
+ title: row.title,
145
+ query: parseSavedAnalysisQuery(row.query),
146
+ conclusion: row.conclusion,
147
+ authorKind: row.author_kind,
148
+ authorKeyId: row.author_key_id,
149
+ createdAt: new Date(Number(row.created_at_ms)),
150
+ };
151
+ }
152
+ /** Create one saved analysis and return the stored row. */
153
+ export async function createSavedAnalysis(client, projectId, input) {
154
+ const count = await countRows(client, `SELECT count(*) AS n FROM saved_analyses WHERE project_id = $1`, [projectId]);
155
+ if (count >= METADATA_LIMITS.savedAnalyses) {
156
+ throw new MetadataLimitError("savedAnalyses", METADATA_LIMITS.savedAnalyses);
157
+ }
158
+ const { analysis } = input;
159
+ const rows = await client.query(`INSERT INTO saved_analyses
160
+ (id, project_id, title, query, conclusion, author_kind, author_key_id, created_at)
161
+ VALUES ($1, $2, $3, $4, $5, $6, $7, (now() AT TIME ZONE 'utc'))
162
+ RETURNING ${ANALYSIS_COLS}`, [
163
+ randomUUID(),
164
+ projectId,
165
+ analysis.title,
166
+ JSON.stringify(analysis.query),
167
+ analysis.conclusion ?? null,
168
+ input.authorKind,
169
+ input.authorKeyId,
170
+ ]);
171
+ return rowToSavedAnalysis(rows[0]);
172
+ }
173
+ /** A project's saved analyses, newest first. */
174
+ export async function listSavedAnalyses(client, projectId, opts = {}) {
175
+ const limit = clampMetadataLimit(opts.limit);
176
+ const rows = await client.query(`SELECT ${ANALYSIS_COLS} FROM saved_analyses WHERE project_id = $1
177
+ ORDER BY created_at DESC, id DESC
178
+ LIMIT ${limit}`, [projectId]);
179
+ return rows.map(rowToSavedAnalysis);
180
+ }
181
+ /** Delete one saved analysis. Returns whether it existed. */
182
+ export async function deleteSavedAnalysis(client, projectId, id) {
183
+ const rows = await client.query(`DELETE FROM saved_analyses WHERE project_id = $1 AND id = $2 RETURNING id`, [projectId, id]);
184
+ return rows.length > 0;
185
+ }
186
+ //# sourceMappingURL=projectMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectMetadata.js","sourceRoot":"","sources":["../src/projectMetadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Subscription } from "@uptimizr/schema";
2
+ import { type SubscriptionDeliveryOutcome, type SubscriptionEventInput, type SubscriptionEventQueryOptions, type SubscriptionEventRecord, type SubscriptionRecord } from "@uptimizr/db";
3
+ import type { PostgresClient, PostgresExecutor } from "./client.js";
4
+ /** A project's subscriptions, oldest first. */
5
+ export declare function listSubscriptions(client: PostgresClient, projectId: string): Promise<SubscriptionRecord[]>;
6
+ /** Every enabled subscription across every project — the scheduler's resume read. */
7
+ export declare function listEnabledSubscriptions(client: PostgresClient, limit?: number): Promise<SubscriptionRecord[]>;
8
+ /** One subscription, or `null` when it does not exist in this project. */
9
+ export declare function getSubscription(client: PostgresClient | PostgresExecutor, projectId: string, id: string): Promise<SubscriptionRecord | null>;
10
+ /** Store a new subscription, enforcing the per-project cap under a row lock. */
11
+ export declare function createSubscription(client: PostgresClient, projectId: string, sub: Subscription): Promise<SubscriptionRecord>;
12
+ /** Enable or disable one subscription. `null` when it does not exist. */
13
+ export declare function setSubscriptionEnabled(client: PostgresClient, projectId: string, id: string, enabled: boolean): Promise<SubscriptionRecord | null>;
14
+ /** Delete a subscription and its firings. `false` when it did not exist. */
15
+ export declare function deleteSubscription(client: PostgresClient, projectId: string, id: string): Promise<boolean>;
16
+ /** Write back what a delivery attempt learned. */
17
+ export declare function recordSubscriptionOutcome(client: PostgresClient, projectId: string, id: string, outcome: SubscriptionDeliveryOutcome): Promise<void>;
18
+ /** The webhook signing secret, or `null`. The only path that reads the column. */
19
+ export declare function getWebhookSecret(client: PostgresClient, projectId: string, id: string): Promise<string | null>;
20
+ /** Append one firing and drop everything past the retained window. */
21
+ export declare function recordSubscriptionEvent(client: PostgresClient, entry: SubscriptionEventInput): Promise<void>;
22
+ /** A subscription's recent firings, newest first. */
23
+ export declare function listSubscriptionEvents(client: PostgresClient, projectId: string, id: string, opts?: SubscriptionEventQueryOptions): Promise<SubscriptionEventRecord[]>;
24
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAUL,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAE5B,KAAK,kBAAkB,EAExB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAuDpE,+CAA+C;AAC/C,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQ/B;AAED,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,MAAkC,GACxC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAU/B;AAED,0EAA0E;AAC1E,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,GAAG,gBAAgB,EACzC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAOpC;AAED,gFAAgF;AAChF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,kBAAkB,CAAC,CAuB7B;AAED,yEAAyE;AACzE,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAOpC;AAED,4EAA4E;AAC5E,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,kDAAkD;AAClD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,kFAAkF;AAClF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB;AAED,sEAAsE;AACtE,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,qDAAqD;AACrD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,6BAAkC,GACvC,OAAO,CAAC,uBAAuB,EAAE,CAAC,CASpC"}
@@ -0,0 +1,151 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { MAX_ENABLED_SUBSCRIPTIONS, MAX_SUBSCRIPTIONS_PER_PROJECT, MAX_SUBSCRIPTION_EVENTS, SubscriptionLimitError, clampEventLimit, clampSubscriptionError, rowToSubscription, rowToSubscriptionEvent, toSubscriptionColumns, } from "@uptimizr/db";
3
+ /**
4
+ * **Conditional subscriptions** for the single-tenant Postgres store (#311,
5
+ * ADR 0051 §6 / sketch §F.1–F.2).
6
+ *
7
+ * Mirrors the DuckDB accessors column-for-column: one `subscriptions` row per
8
+ * standing question with the declaration in a JSON `config` column, and a
9
+ * bounded `subscription_events` firing log (last
10
+ * {@link MAX_SUBSCRIPTION_EVENTS} per subscription).
11
+ *
12
+ * Two places where Postgres does it better than DuckDB can, and the difference
13
+ * is deliberate rather than accidental:
14
+ *
15
+ * - the create's cap check takes `FOR UPDATE` on the project's existing rows, so
16
+ * two collector instances sharing one database cannot both slip past the cap;
17
+ * - the firing-log trim is a single `DELETE … USING` against a row-numbered
18
+ * subquery rather than DuckDB's two-step `NOT IN`.
19
+ *
20
+ * `webhook_secret` is never selected by {@link SELECT_COLS} — see the module
21
+ * note in `@uptimizr/db`'s `subscriptions.ts`.
22
+ */
23
+ /** `timestamp` → epoch milliseconds, the unit every record mapper takes. */
24
+ const MS = (col) => `(EXTRACT(EPOCH FROM ${col}) * 1000)::bigint`;
25
+ const SELECT_COLS = `id, project_id, name, metric, config, enabled,
26
+ ${MS("created_at")} AS created_at_ms,
27
+ ${MS("updated_at")} AS updated_at_ms,
28
+ CASE WHEN last_fired_at IS NULL THEN NULL ELSE ${MS("last_fired_at")} END AS last_fired_at_ms,
29
+ last_error, failures`;
30
+ const EVENT_COLS = `id, subscription_id, project_id, ${MS("at")} AS at_ms, payload`;
31
+ function toRecord(row) {
32
+ return rowToSubscription({
33
+ ...row,
34
+ created_at_ms: Number(row.created_at_ms),
35
+ updated_at_ms: Number(row.updated_at_ms),
36
+ last_fired_at_ms: row.last_fired_at_ms == null ? null : Number(row.last_fired_at_ms),
37
+ failures: Number(row.failures),
38
+ });
39
+ }
40
+ /** A project's subscriptions, oldest first. */
41
+ export async function listSubscriptions(client, projectId) {
42
+ const rows = await client.query(`SELECT ${SELECT_COLS} FROM subscriptions
43
+ WHERE project_id = $1
44
+ ORDER BY created_at, id`, [projectId]);
45
+ return rows.map(toRecord);
46
+ }
47
+ /** Every enabled subscription across every project — the scheduler's resume read. */
48
+ export async function listEnabledSubscriptions(client, limit = MAX_ENABLED_SUBSCRIPTIONS) {
49
+ const capped = Math.min(Math.max(Math.trunc(limit), 1), MAX_ENABLED_SUBSCRIPTIONS);
50
+ const rows = await client.query(`SELECT ${SELECT_COLS} FROM subscriptions
51
+ WHERE enabled
52
+ ORDER BY created_at, id
53
+ LIMIT $1`, [capped]);
54
+ return rows.map(toRecord);
55
+ }
56
+ /** One subscription, or `null` when it does not exist in this project. */
57
+ export async function getSubscription(client, projectId, id) {
58
+ const rows = await client.query(`SELECT ${SELECT_COLS} FROM subscriptions WHERE project_id = $1 AND id = $2`, [projectId, id]);
59
+ const row = rows[0];
60
+ return row == null ? null : toRecord(row);
61
+ }
62
+ /** Store a new subscription, enforcing the per-project cap under a row lock. */
63
+ export async function createSubscription(client, projectId, sub) {
64
+ const id = `sub_${randomUUID().replace(/-/g, "").slice(0, 24)}`;
65
+ const cols = toSubscriptionColumns(sub);
66
+ return client.transaction(async (tx) => {
67
+ // `FOR UPDATE` on the project's existing rows serialises concurrent creates:
68
+ // the second transaction blocks until the first commits, then counts it.
69
+ const existing = await tx.query(`SELECT id FROM subscriptions WHERE project_id = $1 FOR UPDATE`, [projectId]);
70
+ if (existing.length >= MAX_SUBSCRIPTIONS_PER_PROJECT)
71
+ throw new SubscriptionLimitError();
72
+ await tx.query(`INSERT INTO subscriptions
73
+ (id, project_id, name, metric, config, webhook_secret, enabled,
74
+ created_at, updated_at, failures)
75
+ VALUES ($1, $2, $3, $4, $5, $6, $7,
76
+ (now() AT TIME ZONE 'utc'), (now() AT TIME ZONE 'utc'), 0)`, [id, projectId, cols.name, cols.metric, cols.config, cols.webhookSecret, cols.enabled]);
77
+ const stored = await getSubscription(tx, projectId, id);
78
+ if (stored == null)
79
+ throw new Error(`subscription ${id} vanished after insert`);
80
+ return stored;
81
+ });
82
+ }
83
+ /** Enable or disable one subscription. `null` when it does not exist. */
84
+ export async function setSubscriptionEnabled(client, projectId, id, enabled) {
85
+ await client.query(`UPDATE subscriptions SET enabled = $3, updated_at = (now() AT TIME ZONE 'utc')
86
+ WHERE project_id = $1 AND id = $2`, [projectId, id, enabled]);
87
+ return getSubscription(client, projectId, id);
88
+ }
89
+ /** Delete a subscription and its firings. `false` when it did not exist. */
90
+ export async function deleteSubscription(client, projectId, id) {
91
+ return client.transaction(async (tx) => {
92
+ await tx.query(`DELETE FROM subscription_events WHERE project_id = $1 AND subscription_id = $2`, [projectId, id]);
93
+ const deleted = await tx.query(`DELETE FROM subscriptions WHERE project_id = $1 AND id = $2 RETURNING id`, [projectId, id]);
94
+ return deleted.length > 0;
95
+ });
96
+ }
97
+ /** Write back what a delivery attempt learned. */
98
+ export async function recordSubscriptionOutcome(client, projectId, id, outcome) {
99
+ const sets = [`updated_at = (now() AT TIME ZONE 'utc')`];
100
+ const params = [projectId, id];
101
+ if (outcome.firedAt != null) {
102
+ params.push(outcome.firedAt.getTime());
103
+ sets.push(`last_fired_at = to_timestamp($${params.length}::double precision / 1000)
104
+ AT TIME ZONE 'utc'`);
105
+ }
106
+ if (outcome.lastError !== undefined) {
107
+ params.push(outcome.lastError == null ? null : clampSubscriptionError(outcome.lastError));
108
+ sets.push(`last_error = $${params.length}`);
109
+ }
110
+ if (outcome.failures !== undefined) {
111
+ params.push(Math.max(0, Math.trunc(outcome.failures)));
112
+ sets.push(`failures = $${params.length}`);
113
+ }
114
+ await client.query(`UPDATE subscriptions SET ${sets.join(", ")} WHERE project_id = $1 AND id = $2`, params);
115
+ }
116
+ /** The webhook signing secret, or `null`. The only path that reads the column. */
117
+ export async function getWebhookSecret(client, projectId, id) {
118
+ const rows = await client.query(`SELECT webhook_secret FROM subscriptions WHERE project_id = $1 AND id = $2`, [projectId, id]);
119
+ const secret = rows[0]?.webhook_secret;
120
+ return secret == null || secret === "" ? null : secret;
121
+ }
122
+ /** Append one firing and drop everything past the retained window. */
123
+ export async function recordSubscriptionEvent(client, entry) {
124
+ const at = entry.at ?? new Date();
125
+ await client.transaction(async (tx) => {
126
+ await tx.query(`INSERT INTO subscription_events (id, subscription_id, project_id, at, payload)
127
+ VALUES ($1, $2, $3, to_timestamp($4::double precision / 1000) AT TIME ZONE 'utc', $5)`, [
128
+ randomUUID(),
129
+ entry.subscriptionId,
130
+ entry.projectId,
131
+ at.getTime(),
132
+ JSON.stringify(entry.payload),
133
+ ]);
134
+ await tx.query(`DELETE FROM subscription_events e
135
+ USING (
136
+ SELECT id, row_number() OVER (ORDER BY at DESC, id DESC) AS rn
137
+ FROM subscription_events
138
+ WHERE subscription_id = $1
139
+ ) ranked
140
+ WHERE e.id = ranked.id AND ranked.rn > $2`, [entry.subscriptionId, MAX_SUBSCRIPTION_EVENTS]);
141
+ });
142
+ }
143
+ /** A subscription's recent firings, newest first. */
144
+ export async function listSubscriptionEvents(client, projectId, id, opts = {}) {
145
+ const rows = await client.query(`SELECT ${EVENT_COLS} FROM subscription_events
146
+ WHERE project_id = $1 AND subscription_id = $2
147
+ ORDER BY at DESC, id DESC
148
+ LIMIT $3`, [projectId, id, clampEventLimit(opts.limit)]);
149
+ return rows.map((row) => rowToSubscriptionEvent({ ...row, at_ms: Number(row.at_ms) }));
150
+ }
151
+ //# sourceMappingURL=subscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uptimizr/db-postgres",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Optional single-tenant PostgreSQL store for Uptimizr — composes the @uptimizr/db dialect-agnostic query layer and CollectorStore contract for self-hosters who already run Postgres and want a multi-writer relational backend.",
5
5
  "keywords": [
6
6
  "uptimizr",
@@ -42,14 +42,14 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "pg": "^8.23.0",
45
- "@uptimizr/db": "2.0.0",
46
- "@uptimizr/schema": "1.1.0"
45
+ "@uptimizr/schema": "1.2.0",
46
+ "@uptimizr/db": "2.1.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@types/node": "^26.4.1",
49
+ "@types/node": "^26.6.2",
50
50
  "@types/pg": "^8.23.1",
51
- "tsx": "^4.23.13",
52
- "vitest": "^4.1.11"
51
+ "tsx": "^4.23.15",
52
+ "vitest": "^5.0.1"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc -p tsconfig.json",