@owlmeans/planning-postgres 0.1.18-rc.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.
Files changed (93) hide show
  1. package/README.md +88 -0
  2. package/agent-meta/manifest.json +16 -0
  3. package/agent-meta/skills/planning-postgres/SKILL.md +292 -0
  4. package/build/consts.d.ts +45 -0
  5. package/build/consts.d.ts.map +1 -0
  6. package/build/consts.js +45 -0
  7. package/build/consts.js.map +1 -0
  8. package/build/errors.d.ts +10 -0
  9. package/build/errors.d.ts.map +1 -0
  10. package/build/errors.js +13 -0
  11. package/build/errors.js.map +1 -0
  12. package/build/index.d.ts +9 -0
  13. package/build/index.d.ts.map +1 -0
  14. package/build/index.js +8 -0
  15. package/build/index.js.map +1 -0
  16. package/build/resource.d.ts +32 -0
  17. package/build/resource.d.ts.map +1 -0
  18. package/build/resource.js +94 -0
  19. package/build/resource.js.map +1 -0
  20. package/build/schemas.d.ts +20 -0
  21. package/build/schemas.d.ts.map +1 -0
  22. package/build/schemas.js +64 -0
  23. package/build/schemas.js.map +1 -0
  24. package/build/service.d.ts +16 -0
  25. package/build/service.d.ts.map +1 -0
  26. package/build/service.js +34 -0
  27. package/build/service.js.map +1 -0
  28. package/build/sql.d.ts +61 -0
  29. package/build/sql.d.ts.map +1 -0
  30. package/build/sql.js +90 -0
  31. package/build/sql.js.map +1 -0
  32. package/build/store/bus.d.ts +52 -0
  33. package/build/store/bus.d.ts.map +1 -0
  34. package/build/store/bus.js +167 -0
  35. package/build/store/bus.js.map +1 -0
  36. package/build/store/cards.d.ts +35 -0
  37. package/build/store/cards.d.ts.map +1 -0
  38. package/build/store/cards.js +112 -0
  39. package/build/store/cards.js.map +1 -0
  40. package/build/store/fold.d.ts +56 -0
  41. package/build/store/fold.d.ts.map +1 -0
  42. package/build/store/fold.js +453 -0
  43. package/build/store/fold.js.map +1 -0
  44. package/build/store/index.d.ts +22 -0
  45. package/build/store/index.d.ts.map +1 -0
  46. package/build/store/index.js +206 -0
  47. package/build/store/index.js.map +1 -0
  48. package/build/store/links.d.ts +18 -0
  49. package/build/store/links.d.ts.map +1 -0
  50. package/build/store/links.js +54 -0
  51. package/build/store/links.js.map +1 -0
  52. package/build/store/schemas.d.ts +32 -0
  53. package/build/store/schemas.d.ts.map +1 -0
  54. package/build/store/schemas.js +143 -0
  55. package/build/store/schemas.js.map +1 -0
  56. package/build/store/specs.d.ts +10 -0
  57. package/build/store/specs.d.ts.map +1 -0
  58. package/build/store/specs.js +47 -0
  59. package/build/store/specs.js.map +1 -0
  60. package/build/store/transitions.d.ts +44 -0
  61. package/build/store/transitions.d.ts.map +1 -0
  62. package/build/store/transitions.js +164 -0
  63. package/build/store/transitions.js.map +1 -0
  64. package/build/types.d.ts +72 -0
  65. package/build/types.d.ts.map +1 -0
  66. package/build/types.js +2 -0
  67. package/build/types.js.map +1 -0
  68. package/package.json +56 -0
  69. package/src/consts.ts +54 -0
  70. package/src/errors.ts +15 -0
  71. package/src/index.ts +9 -0
  72. package/src/resource.ts +133 -0
  73. package/src/schemas.ts +84 -0
  74. package/src/service.ts +45 -0
  75. package/src/sql.ts +135 -0
  76. package/src/store/bus.ts +217 -0
  77. package/src/store/cards.ts +153 -0
  78. package/src/store/fold.ts +560 -0
  79. package/src/store/index.ts +237 -0
  80. package/src/store/links.ts +77 -0
  81. package/src/store/schemas.ts +174 -0
  82. package/src/store/specs.ts +72 -0
  83. package/src/store/transitions.ts +221 -0
  84. package/src/types.ts +73 -0
  85. package/tests/bus.spec.ts +142 -0
  86. package/tests/conformance.spec.ts +37 -0
  87. package/tests/context.ts +143 -0
  88. package/tests/fold.spec.ts +213 -0
  89. package/tests/schema.spec.ts +89 -0
  90. package/tests/sql.spec.ts +50 -0
  91. package/tests/sync.spec.ts +61 -0
  92. package/tests/tsconfig.json +17 -0
  93. package/tsconfig.json +11 -0
@@ -0,0 +1,167 @@
1
+ import { advisoryKey, quoteIdent } from '@owlmeans/postgres-resource';
2
+ import { Client } from 'pg';
3
+ import { BUS_BACKOFF, NOTIFY_PAYLOAD_MAX } from '../consts.js';
4
+ /**
5
+ * The channel of one transition table: `planning_` and a short hash of its qualified name, so two
6
+ * schemas in one database never hear each other.
7
+ */
8
+ export const planningChannel = (qualified) => {
9
+ const [first, second] = advisoryKey(`planning-channel:${qualified}`);
10
+ return `planning_${(first >>> 0).toString(16).padStart(8, '0')}${(second >>> 0).toString(16).padStart(8, '0')}`;
11
+ };
12
+ const payloadOf = (frame) => {
13
+ const text = JSON.stringify(frame);
14
+ if (new TextEncoder().encode(text).length < NOTIFY_PAYLOAD_MAX) {
15
+ return text;
16
+ }
17
+ if (frame.t === 'c' && frame.e.error != null) {
18
+ // The reason is in the row; a listener reads it with the status.
19
+ const { error: _error, ...event } = frame.e;
20
+ return payloadOf({ ...frame, e: event });
21
+ }
22
+ return null;
23
+ };
24
+ /**
25
+ * The cross-process commit bus over LISTEN/NOTIFY.
26
+ *
27
+ * NOTIFY runs inside the transaction that settled the commit, so a frame is heard only if that
28
+ * commit landed. LISTEN holds ONE dedicated connection — a `pg.Client` built from the pool's own
29
+ * configuration, never a pooled one, so it takes no pool slot — opened on the first subscription
30
+ * or wait. A dropped connection reconnects with backoff (1 s doubling to 30 s); meanwhile the commit
31
+ * hub's poll ladder still answers every waiter. A process ignores its own frames (`p`): it delivered
32
+ * them itself, after its commit.
33
+ */
34
+ export const makePlanningBus = (opts) => {
35
+ const commitListeners = new Set();
36
+ const schemaListeners = new Set();
37
+ let client;
38
+ let connecting;
39
+ let retry;
40
+ let backoff = BUS_BACKOFF[0];
41
+ let closed = false;
42
+ let live = false;
43
+ let channel;
44
+ const channelOf = async () => channel ??= planningChannel(await opts.table());
45
+ const dispatch = (message) => {
46
+ if (message.channel !== channel || message.payload == null) {
47
+ return;
48
+ }
49
+ let frame;
50
+ try {
51
+ frame = JSON.parse(message.payload);
52
+ }
53
+ catch {
54
+ return;
55
+ }
56
+ if (frame.p === opts.processId) {
57
+ return;
58
+ }
59
+ if (frame.t === 'c') {
60
+ for (const listener of [...commitListeners]) {
61
+ void Promise.resolve().then(() => listener(frame.e))
62
+ .catch(error => console.error('planning-postgres: commit listener failed:', error));
63
+ }
64
+ return;
65
+ }
66
+ if (frame.t === 's') {
67
+ for (const listener of [...schemaListeners]) {
68
+ try {
69
+ listener(frame.e);
70
+ }
71
+ catch (error) {
72
+ console.error('planning-postgres: schema listener failed:', error);
73
+ }
74
+ }
75
+ }
76
+ };
77
+ const lost = (target, error) => {
78
+ if (client !== target) {
79
+ return;
80
+ }
81
+ client = undefined;
82
+ live = false;
83
+ void target.end().catch(() => undefined);
84
+ if (closed) {
85
+ return;
86
+ }
87
+ if (error != null) {
88
+ console.warn('planning-postgres: commit bus connection lost, reconnecting:', error instanceof Error ? error.message : error);
89
+ }
90
+ retry = setTimeout(() => { retry = undefined; bus.ensure(); }, backoff);
91
+ backoff = Math.min(backoff * 2, BUS_BACKOFF[1]);
92
+ };
93
+ const connect = async () => {
94
+ let pool;
95
+ let listen;
96
+ try {
97
+ pool = await opts.pool();
98
+ listen = await channelOf();
99
+ }
100
+ catch {
101
+ // The store cannot reach its tables yet (its calls say why); ask again later.
102
+ if (!closed) {
103
+ retry = setTimeout(() => { retry = undefined; bus.ensure(); }, backoff);
104
+ backoff = Math.min(backoff * 2, BUS_BACKOFF[1]);
105
+ }
106
+ return;
107
+ }
108
+ // The pool's own client class and configuration (the password stays a non-enumerable property
109
+ // of that object, so the object itself is handed over, never a copy).
110
+ const Factory = pool.Client ?? Client;
111
+ const target = new Factory(pool.options);
112
+ target.on('error', error => lost(target, error));
113
+ target.on('end', () => lost(target));
114
+ target.on('notification', dispatch);
115
+ client = target;
116
+ try {
117
+ await target.connect();
118
+ await target.query(`LISTEN ${quoteIdent(listen)}`);
119
+ live = true;
120
+ backoff = BUS_BACKOFF[0];
121
+ }
122
+ catch (error) {
123
+ lost(target, error);
124
+ }
125
+ };
126
+ const bus = {
127
+ enabled: opts.enabled,
128
+ notify: async (runner, frame) => {
129
+ if (!opts.enabled) {
130
+ return;
131
+ }
132
+ const payload = payloadOf({ ...frame, p: opts.processId });
133
+ if (payload != null) {
134
+ await runner.query('SELECT pg_notify($1, $2)', [await channelOf(), payload]);
135
+ }
136
+ },
137
+ onCommit: listener => {
138
+ commitListeners.add(listener);
139
+ return () => { commitListeners.delete(listener); };
140
+ },
141
+ onSchema: listener => {
142
+ schemaListeners.add(listener);
143
+ return () => { schemaListeners.delete(listener); };
144
+ },
145
+ ensure: () => {
146
+ if (!opts.enabled || closed || client != null || connecting != null || retry != null) {
147
+ return;
148
+ }
149
+ connecting = connect().finally(() => { connecting = undefined; });
150
+ },
151
+ connected: () => live,
152
+ close: async () => {
153
+ closed = true;
154
+ if (retry != null) {
155
+ clearTimeout(retry);
156
+ retry = undefined;
157
+ }
158
+ await connecting?.catch(() => undefined);
159
+ const target = client;
160
+ client = undefined;
161
+ live = false;
162
+ await target?.end().catch(() => undefined);
163
+ },
164
+ };
165
+ return bus;
166
+ };
167
+ //# sourceMappingURL=bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bus.js","sourceRoot":"","sources":["../../src/store/bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ProjectionStore, Workcard } from '@owlmeans/planning';
2
+ import type { Criteria } from '@owlmeans/resource';
3
+ import type { SqlContext } from '../sql.js';
4
+ import type { PlanningCardRecord, PlanningCardResource } from '../types.js';
5
+ /** Columns no caller ever sees. */
6
+ export declare const CARD_PRIVATE: readonly ['headAt'];
7
+ /** A card list names specifications only when it asks for them — the rule every store keeps. */
8
+ export declare const routedCriteria: (where?: Criteria<Workcard>) => Criteria<Workcard>;
9
+ /** A card row with its private bookkeeping (what the fold reads), or `null`. */
10
+ export declare const readCardRow: (sql: SqlContext, id: string, entityId?: string) => Promise<PlanningCardRecord | null>;
11
+ export declare const readCard: (sql: SqlContext, id: string, entityId: string) => Promise<Workcard | null>;
12
+ /**
13
+ * Write a folded card whole. `head` is never lowered — an allocation made while the fold ran stays
14
+ * — and `headAt` is never touched.
15
+ *
16
+ * A create (`seq` 1) is an upsert. Anything later is an UPDATE of a row that must still exist: a
17
+ * card purged with its project while one of its own transitions was folding is not resurrected.
18
+ *
19
+ * @throws {PlanningPostgresError} `fold:card-vanished:<id>`
20
+ */
21
+ export declare const writeCard: (sql: SqlContext, card: Workcard) => Promise<void>;
22
+ export declare const dropCard: (sql: SqlContext, id: string, entityId: string) => Promise<void>;
23
+ export interface CardPortDeps {
24
+ sql: () => Promise<SqlContext>;
25
+ resource: () => PlanningCardResource;
26
+ project: (card: string) => Promise<void>;
27
+ purge: (project: string, entityId: string) => Promise<number>;
28
+ }
29
+ /**
30
+ * The projection port. Point reads and writes are this package's own statements; lists, counts and
31
+ * summaries go through the resource, so a criteria means here what it means everywhere
32
+ * (`criteriaToSql`).
33
+ */
34
+ export declare const makeCardPort: (deps: CardPortDeps) => ProjectionStore;
35
+ //# sourceMappingURL=cards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../../src/store/cards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAe,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAIlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE3E,mCAAmC;AACnC,eAAO,MAAM,YAAY,YAAI,QAAQ,CAAU,CAAA;AAS/C,gGAAgG;AAChG,eAAO,MAAM,cAAc,WAAY,QAAQ,CAAC,QAAQ,CAAC,KAAG,QAAQ,CAAC,QAAQ,CACqB,CAAA;AAElG,gFAAgF;AAChF,eAAO,MAAM,WAAW,QAAe,UAAU,MAAM,MAAM,aAAa,MAAM,KAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAOnH,CAAA;AAED,eAAO,MAAM,QAAQ,QAAe,UAAU,MAAM,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAGrG,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,QAAe,UAAU,QAAQ,QAAQ,KAAG,OAAO,CAAC,IAAI,CAkC7E,CAAA;AAED,eAAO,MAAM,QAAQ,QAAe,UAAU,MAAM,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,IAAI,CAK1F,CAAA;AAMD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;IAC9B,QAAQ,EAAE,MAAM,oBAAoB,CAAA;IACpC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC9D;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,YAAY,KAAG,eA6ChD,CAAA"}
@@ -0,0 +1,112 @@
1
+ import { IntrinsicStatus, PlanningError, WorkcardKind } from '@owlmeans/planning';
2
+ import { quoteIdent } from '@owlmeans/postgres-resource';
3
+ import { wantsSpecifications } from '@owlmeans/server-planning/store';
4
+ import { PlanningPostgresError } from '../errors.js';
5
+ import { cleanRecord, col, driverValue, insertOf, recordOf } from '../sql.js';
6
+ /** Columns no caller ever sees. */
7
+ export const CARD_PRIVATE = ['headAt'];
8
+ const and = (where, ...extra) => {
9
+ const base = (where ?? {});
10
+ return { ...base, $and: [...(base.$and ?? []), ...extra] };
11
+ };
12
+ const NOT_SPECIFICATION = { kind: { $ne: WorkcardKind.Specification } };
13
+ /** A card list names specifications only when it asks for them — the rule every store keeps. */
14
+ export const routedCriteria = (where) => wantsSpecifications(where) ? (where ?? {}) : and(where, NOT_SPECIFICATION);
15
+ /** A card row with its private bookkeeping (what the fold reads), or `null`. */
16
+ export const readCardRow = async (sql, id, entityId) => {
17
+ const spec = sql.tables.card;
18
+ const rows = await sql.runner.query(`SELECT * FROM ${spec.qualified} WHERE ${col(spec, 'id')} = $1${entityId != null ? ` AND ${col(spec, 'entityId')} = $2` : ''}`, entityId != null ? [id, entityId] : [id]);
19
+ return rows[0] == null ? null : recordOf(rows[0], spec);
20
+ };
21
+ export const readCard = async (sql, id, entityId) => {
22
+ const row = await readCardRow(sql, id, entityId);
23
+ return row == null ? null : cleanRecord(row, CARD_PRIVATE);
24
+ };
25
+ /**
26
+ * Write a folded card whole. `head` is never lowered — an allocation made while the fold ran stays
27
+ * — and `headAt` is never touched.
28
+ *
29
+ * A create (`seq` 1) is an upsert. Anything later is an UPDATE of a row that must still exist: a
30
+ * card purged with its project while one of its own transitions was folding is not resurrected.
31
+ *
32
+ * @throws {PlanningPostgresError} `fold:card-vanished:<id>`
33
+ */
34
+ export const writeCard = async (sql, card) => {
35
+ const spec = sql.tables.card;
36
+ if (card.id == null) {
37
+ throw new PlanningPostgresError('malformed:put-without-id');
38
+ }
39
+ const record = { ...card, head: Math.max(card.head ?? card.seq, card.seq) };
40
+ const head = col(spec, 'head');
41
+ const seq = col(spec, 'seq');
42
+ const assigned = spec.columns
43
+ .filter(column => !['id', 'headAt', 'head'].includes(column.property))
44
+ .map(column => column.column);
45
+ if (card.seq <= 1) {
46
+ const insert = insertOf(spec, record, {
47
+ skip: CARD_PRIVATE,
48
+ as: 'existing',
49
+ tail: `ON CONFLICT (${col(spec, 'id')}) DO UPDATE SET `
50
+ + [...assigned.map(name => `${quoteIdent(name)} = EXCLUDED.${quoteIdent(name)}`),
51
+ `${head} = GREATEST(COALESCE(existing.${head}, existing.${seq}), EXCLUDED.${head})`].join(', '),
52
+ });
53
+ await sql.runner.query(insert.text, insert.params);
54
+ return;
55
+ }
56
+ const columns = spec.columns.filter(column => assigned.includes(column.column));
57
+ const params = [card.id, ...columns.map(column => driverValue(column, record[column.property])), record.head];
58
+ const updated = await sql.runner.query(`UPDATE ${spec.qualified} SET ${columns.map((column, index) => `${quoteIdent(column.column)} = $${index + 2}`).join(', ')},`
59
+ + ` ${head} = GREATEST(COALESCE(${head}, ${seq}), $${columns.length + 2}) WHERE ${col(spec, 'id')} = $1 RETURNING ${col(spec, 'id')}`, params);
60
+ if (updated.length === 0) {
61
+ throw new PlanningPostgresError(`fold:card-vanished:${card.id}`);
62
+ }
63
+ };
64
+ export const dropCard = async (sql, id, entityId) => {
65
+ const spec = sql.tables.card;
66
+ await sql.runner.query(`DELETE FROM ${spec.qualified} WHERE ${col(spec, 'id')} = $1 AND ${col(spec, 'entityId')} = $2`, [id, entityId]);
67
+ };
68
+ const emptyCounts = () => ({
69
+ total: 0, [IntrinsicStatus.Planned]: 0, [IntrinsicStatus.InProgress]: 0, [IntrinsicStatus.Closed]: 0,
70
+ });
71
+ /**
72
+ * The projection port. Point reads and writes are this package's own statements; lists, counts and
73
+ * summaries go through the resource, so a criteria means here what it means everywhere
74
+ * (`criteriaToSql`).
75
+ */
76
+ export const makeCardPort = (deps) => ({
77
+ get: async (id, entityId) => await readCard(await deps.sql(), id, entityId),
78
+ list: async (where, opts) => {
79
+ await deps.sql();
80
+ const listed = await deps.resource().list(routedCriteria(where), opts);
81
+ return { ...listed, items: listed.items.map(item => cleanRecord(item, CARD_PRIVATE)) };
82
+ },
83
+ count: async (where) => {
84
+ await deps.sql();
85
+ return await deps.resource().count(routedCriteria(where));
86
+ },
87
+ summary: async (parents, where) => {
88
+ if (parents.length === 0) {
89
+ return {};
90
+ }
91
+ await deps.sql();
92
+ const rows = await deps.resource().countBy(and(where, NOT_SPECIFICATION, { parent: parents }), ['parent', 'intrinsic']);
93
+ return rows.reduce((view, row) => {
94
+ const parent = row.parent;
95
+ const counts = view[parent] ?? emptyCounts();
96
+ counts.total += row.count;
97
+ counts[row.intrinsic] = (counts[row.intrinsic] ?? 0) + row.count;
98
+ view[parent] = counts;
99
+ return view;
100
+ }, {});
101
+ },
102
+ put: async (card) => { await writeCard(await deps.sql(), card); },
103
+ drop: async (id, entityId) => { await dropCard(await deps.sql(), id, entityId); },
104
+ project: async (card) => { await deps.project(card); },
105
+ purge: async (project, entityId) => {
106
+ if (project == null || project === '') {
107
+ throw new PlanningError('purge:empty');
108
+ }
109
+ return await deps.purge(project, entityId);
110
+ },
111
+ });
112
+ //# sourceMappingURL=cards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/store/cards.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { CommitEvent } from '@owlmeans/planning';
2
+ import type { CommitHub, CommitListener, FoldResult } from '@owlmeans/server-planning/store';
3
+ import type { Pool } from 'pg';
4
+ import type { PlanningTables, SqlContext } from '../sql.js';
5
+ import type { PlanningPostgresLimits } from '../types.js';
6
+ import type { PlanningBus } from './bus.js';
7
+ /** One open fold transaction: its runner, a savepoint helper, and what must happen after it commits. */
8
+ export interface FoldContext extends SqlContext {
9
+ /** Run as one unit: a failure rolls back to before it, and the transaction stays usable. */
10
+ savepoint: <R>(run: () => Promise<R>) => Promise<R>;
11
+ /** Settled events, delivered in order once the transaction commits. */
12
+ events: CommitEvent[];
13
+ /** Organizations whose schema revision moved in this transaction. */
14
+ schemas: Set<string>;
15
+ }
16
+ export interface FoldEngineDeps {
17
+ pool: () => Promise<Pool>;
18
+ tables: () => Promise<PlanningTables>;
19
+ limits: PlanningPostgresLimits;
20
+ ids: () => string;
21
+ now: () => string;
22
+ bus: PlanningBus;
23
+ hub: CommitHub;
24
+ committed: () => CommitListener | undefined;
25
+ /** Told of every organization whose schemas this process changed, after the commit. */
26
+ schemasTouched: (entityId: string) => void;
27
+ closed: () => boolean;
28
+ }
29
+ export interface FoldEngine {
30
+ /** Fold now, waiting for another folder's lock — what `project()` runs. */
31
+ fold: (cardId: string) => Promise<FoldResult>;
32
+ /** Fold in the background unless another folder holds the card — deduplicated per card. */
33
+ heal: (cardId: string) => void;
34
+ recover: (opts?: {
35
+ olderThanMs?: number;
36
+ limit?: number;
37
+ }) => Promise<number>;
38
+ /** Purge a project (outside a fold) in one transaction under the project's lock. */
39
+ purge: (project: string, entityId: string) => Promise<number>;
40
+ }
41
+ /**
42
+ * The inline fold of a Postgres planning store.
43
+ *
44
+ * Every fold of a card is ONE transaction under `pg_advisory_xact_lock(planning:<card table>:<card>)`,
45
+ * with `SET LOCAL lock_timeout`. Inside it: the prelude, then `foldPending` over a view of the ports
46
+ * bound to that transaction (each transition's writes in a savepoint), then a NOTIFY per settled
47
+ * event. Events are delivered to the commit hub and the `after` hooks only once it COMMITS — never
48
+ * inside the transaction or the lock, so a hook may write to the card it saw commit.
49
+ *
50
+ * A transaction a statement failed in is poisoned and never committed. The fold is retried once;
51
+ * failing again, the card's pending transitions are failed in a fresh transaction (`failPending`)
52
+ * so no waiter is left to its timeout. A lock that could not be taken means another process is
53
+ * folding the card: nothing is failed, and a heal follows.
54
+ */
55
+ export declare const makeFoldEngine: (deps: FoldEngineDeps) => FoldEngine;
56
+ //# sourceMappingURL=fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/store/fold.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,oBAAoB,CAAA;AAG1F,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAK9B,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAgB3C,wGAAwG;AACxG,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,4FAA4F;IAC5F,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACnD,uEAAuE;IACvE,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,qEAAqE;IACrE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,MAAM,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;IACrC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,GAAG,EAAE,WAAW,CAAA;IAChB,GAAG,EAAE,SAAS,CAAA;IACd,SAAS,EAAE,MAAM,cAAc,GAAG,SAAS,CAAA;IAC3C,uFAAuF;IACvF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,MAAM,EAAE,MAAM,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IAC7C,2FAA2F;IAC3F,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7E,oFAAoF;IACpF,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC9D;AAoBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,SAAU,cAAc,KAAG,UA+crD,CAAA"}