@pipeline-builder/pipeline-data 3.4.114 → 3.4.116
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/lib/api/crud-service.d.ts +39 -12
- package/lib/api/crud-service.js +63 -37
- package/lib/api/query-builders.js +41 -3
- package/lib/api/soft-delete-sweep.js +13 -3
- package/lib/core/query-filters.d.ts +25 -0
- package/lib/core/query-filters.js +1 -1
- package/lib/database/drizzle-schema.d.ts +205 -0
- package/lib/database/drizzle-schema.js +4 -1
- package/lib/database/schema/dashboard.js +5 -2
- package/lib/database/schema/message-attachment.d.ts +183 -0
- package/lib/database/schema/message-attachment.js +40 -0
- package/lib/database/schema/message.d.ts +36 -0
- package/lib/database/schema/message.js +14 -1
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SQL } from 'drizzle-orm';
|
|
2
2
|
import type { AnyColumn } from 'drizzle-orm/column';
|
|
3
3
|
import type { PgTable } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { withTenantTx } from '../database/tenancy.js';
|
|
4
5
|
/**
|
|
5
6
|
* Cast Drizzle query results to a typed array.
|
|
6
7
|
* Drizzle's generic return type (`PgSelectBase<...>`) doesn't narrow to our
|
|
@@ -12,6 +13,10 @@ export declare function drizzleRows<T>(rows: unknown): T[];
|
|
|
12
13
|
export declare function drizzleCount(rows: unknown): [{
|
|
13
14
|
count: number;
|
|
14
15
|
}];
|
|
16
|
+
/** The Drizzle transaction object handed to `withTenantTx` callbacks. Passed to
|
|
17
|
+
* `onBeforePurge` so a subclass's dependent teardown runs in the SAME purge
|
|
18
|
+
* transaction (atomic with the parent DELETE), not a fresh connection. */
|
|
19
|
+
export type CrudTx = Parameters<Parameters<typeof withTenantTx>[0]>[0];
|
|
15
20
|
/**
|
|
16
21
|
* Base interface for entities with common fields
|
|
17
22
|
*/
|
|
@@ -53,6 +58,11 @@ export interface PaginatedResult<T> {
|
|
|
53
58
|
/** Cursor pointing to the last item, for cursor-based pagination. */
|
|
54
59
|
nextCursor?: string;
|
|
55
60
|
}
|
|
61
|
+
/** The shared soft-delete retention window in ms (`SOFT_DELETE_RETENTION_DAYS`,
|
|
62
|
+
* default 30d; 0 disables purge-deadline stamping). Exposed so non-CrudService
|
|
63
|
+
* soft-delete paths (e.g. platform's hand-rolled dashboard/alert services)
|
|
64
|
+
* stamp `purge_after` with the SAME window the CrudService entities use. */
|
|
65
|
+
export declare function softDeleteRetentionMs(): number;
|
|
56
66
|
export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, TInsert, TUpdate> {
|
|
57
67
|
/** Drizzle schema table for this entity */
|
|
58
68
|
protected abstract get schema(): PgTable;
|
|
@@ -113,10 +123,12 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
113
123
|
/** Called after a soft-deleted entity is restored (undo the delete). Override
|
|
114
124
|
* for entities whose restore has side-effects (e.g. re-notify). Best-effort. */
|
|
115
125
|
protected onAfterRestore(_id: string, _entity: TEntity, _userId: string): Promise<void>;
|
|
116
|
-
/** Called
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
|
|
126
|
+
/** Called with the ids about to be hard-purged, BEFORE the delete, inside the
|
|
127
|
+
* purge transaction (`tx`) — a subclass tearing down dependents that lack ON
|
|
128
|
+
* DELETE CASCADE MUST use `tx` so the teardown is atomic with the parent
|
|
129
|
+
* DELETE (and can't self-deadlock against its row locks). Throwing aborts that
|
|
130
|
+
* batch's purge (rows stay tombstoned, retried next tick). */
|
|
131
|
+
protected onBeforePurge(_ids: string[], _tx: CrudTx): Promise<void>;
|
|
120
132
|
/** Called after rows are hard-purged, for external side-effects (e.g. plugin
|
|
121
133
|
* image GC). Best-effort: errors are logged, never block the sweep. */
|
|
122
134
|
protected onAfterPurge(_ids: string[]): Promise<void>;
|
|
@@ -124,8 +136,11 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
124
136
|
* shared `SOFT_DELETE_RETENTION_DAYS`; override per entity for a bespoke window. */
|
|
125
137
|
protected get softDeleteRetentionMs(): number;
|
|
126
138
|
/** The `purge_after` value to stamp on a soft-delete: `now + retention`, or the
|
|
127
|
-
* spread-friendly empty object when the entity has no `purge_after` column.
|
|
128
|
-
|
|
139
|
+
* spread-friendly empty object when the entity has no `purge_after` column.
|
|
140
|
+
* `protected` so subclasses with hand-rolled soft-delete paths (e.g.
|
|
141
|
+
* message-service's thread cascade / sysadmin moderation) stamp it too — an
|
|
142
|
+
* un-stamped tombstone has NULL `purge_after` and is never hard-purged. */
|
|
143
|
+
protected purgeAfterStamp(now: Date): Record<string, unknown>;
|
|
129
144
|
/**
|
|
130
145
|
* Org → team hierarchy: when a read is widened to a parent org (`parentOrgId`
|
|
131
146
|
* set), the parent's rows are outside the caller's RLS scope, so the read must
|
|
@@ -153,6 +168,12 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
153
168
|
* @param orgId - User's organization ID (optional — omit for anonymous/system-public-only access)
|
|
154
169
|
*/
|
|
155
170
|
find(filter: Partial<TFilter>, orgId?: string, parentOrgId?: string): Promise<TEntity[]>;
|
|
171
|
+
/**
|
|
172
|
+
* Find the FIRST entity matching `filter` — a bounded (`LIMIT 1`) variant of
|
|
173
|
+
* `find` for the "match by filter, take one" read paths (e.g. `GET /find`), so
|
|
174
|
+
* they don't `SELECT` every matching org row just to return `[0]`.
|
|
175
|
+
*/
|
|
176
|
+
findFirst(filter: Partial<TFilter>, orgId?: string, parentOrgId?: string): Promise<TEntity | null>;
|
|
156
177
|
/**
|
|
157
178
|
* Find entities with pagination and sorting
|
|
158
179
|
*
|
|
@@ -210,7 +231,7 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
210
231
|
* is undefined) keep the supplied org, matching the RLS policy
|
|
211
232
|
* `current_is_sysadmin() OR org_id = current_org_id()`.
|
|
212
233
|
*/
|
|
213
|
-
protected enforceOrgId(data:
|
|
234
|
+
protected enforceOrgId<T>(data: T): T;
|
|
214
235
|
/**
|
|
215
236
|
* Create a new entity
|
|
216
237
|
*/
|
|
@@ -270,6 +291,11 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
270
291
|
* driver's unique-violation for the route to map to 409 — in practice the
|
|
271
292
|
* tombstone IS the key holder, so this is defensive.
|
|
272
293
|
*/
|
|
294
|
+
/** Conditions matching a GENUINE tombstone (`isActive=false` AND `deletedAt IS
|
|
295
|
+
* NOT NULL`), optionally pinned to `id` and/or `orgId`. Single source of truth
|
|
296
|
+
* for restore/findDeletedById/findDeleted so the "what is a tombstone" rule
|
|
297
|
+
* lives in one place. */
|
|
298
|
+
private tombstoneConditions;
|
|
273
299
|
restore(id: string, orgId: string, userId: string): Promise<TEntity | null>;
|
|
274
300
|
/**
|
|
275
301
|
* Read a soft-deleted entity by id (the inverse of the default `isActive=true`
|
|
@@ -277,7 +303,7 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
277
303
|
* gating before restoring. Returns null unless the row exists AND is a genuine
|
|
278
304
|
* tombstone (`isActive=false` + `deletedAt IS NOT NULL`).
|
|
279
305
|
*/
|
|
280
|
-
findDeletedById(id: string, orgId?: string
|
|
306
|
+
findDeletedById(id: string, orgId?: string): Promise<TEntity | null>;
|
|
281
307
|
/**
|
|
282
308
|
* List an org's soft-deleted tombstones (`isActive=false` + `deletedAt IS NOT
|
|
283
309
|
* NULL`), most-recently-deleted first — powers the "recently deleted" restore
|
|
@@ -288,14 +314,15 @@ export declare abstract class CrudService<TEntity extends BaseEntity, TFilter, T
|
|
|
288
314
|
findDeleted(orgId: string, opts?: {
|
|
289
315
|
limit?: number;
|
|
290
316
|
offset?: number;
|
|
291
|
-
}
|
|
317
|
+
}): Promise<TEntity[]>;
|
|
292
318
|
/**
|
|
293
319
|
* Hard-delete a batch of tombstones whose purge deadline has passed
|
|
294
320
|
* (`deletedAt IS NOT NULL AND purge_after < now`). Batched (`limit`) so a
|
|
295
321
|
* sweep tick stays bounded; the per-service sweep loops until a tick returns
|
|
296
|
-
* `< limit`. Runs across ALL orgs, so callers MUST invoke it from a
|
|
297
|
-
* (
|
|
298
|
-
* CASCADE
|
|
322
|
+
* `< limit`. Runs across ALL orgs, so callers MUST invoke it from a sysadmin
|
|
323
|
+
* scope (`runSoftDeletePurge` establishes one). `onBeforePurge(ids, tx)` tears
|
|
324
|
+
* down dependents lacking ON DELETE CASCADE inside the same tx; `onAfterPurge`
|
|
325
|
+
* handles external side-effects.
|
|
299
326
|
* Returns the number of rows purged. No-op (0) for entities without the columns.
|
|
300
327
|
*/
|
|
301
328
|
purgeExpired(now: Date, limit?: number): Promise<number>;
|