@shware/workflow 5.0.1 → 6.0.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 +165 -42
- package/dist/aws/index.cjs +1 -1
- package/dist/aws/index.mjs +1 -1
- package/dist/client.cjs +45 -4
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +25 -3
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +25 -3
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +45 -4
- package/dist/client.mjs.map +1 -1
- package/dist/cloudflare/index.cjs +2 -2
- package/dist/cloudflare/index.mjs +2 -2
- package/dist/cloudflare/runner.cjs +1 -1
- package/dist/cloudflare/runner.d.cts +1 -1
- package/dist/cloudflare/runner.d.mts +1 -1
- package/dist/cloudflare/runner.mjs +1 -1
- package/dist/config.cjs +24 -3
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +15 -9
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +15 -9
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +25 -4
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +11 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +28 -7
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +28 -7
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +11 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/dsl/bundle.cjs +4 -3
- package/dist/dsl/bundle.cjs.map +1 -1
- package/dist/dsl/bundle.d.cts +5 -1
- package/dist/dsl/bundle.d.cts.map +1 -1
- package/dist/dsl/bundle.d.mts +5 -1
- package/dist/dsl/bundle.d.mts.map +1 -1
- package/dist/dsl/bundle.mjs +4 -3
- package/dist/dsl/bundle.mjs.map +1 -1
- package/dist/dsl/flow.cjs +2 -1
- package/dist/dsl/flow.cjs.map +1 -1
- package/dist/dsl/flow.d.cts +1 -1
- package/dist/dsl/flow.d.cts.map +1 -1
- package/dist/dsl/flow.d.mts +1 -1
- package/dist/dsl/flow.d.mts.map +1 -1
- package/dist/dsl/flow.mjs +2 -1
- package/dist/dsl/flow.mjs.map +1 -1
- package/dist/dsl/workflow.cjs +2 -2
- package/dist/dsl/workflow.mjs +2 -2
- package/dist/email-sender.cjs +1 -1
- package/dist/email-sender.cjs.map +1 -1
- package/dist/email-sender.d.cts +1 -1
- package/dist/email-sender.d.mts +1 -1
- package/dist/email-sender.mjs +1 -1
- package/dist/email-sender.mjs.map +1 -1
- package/dist/email.cjs +32 -5
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +38 -9
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +38 -9
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +32 -6
- package/dist/email.mjs.map +1 -1
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts.map +1 -1
- package/dist/engine/condition.d.mts.map +1 -1
- package/dist/engine/condition.mjs.map +1 -1
- package/dist/engine/index.cjs +1 -1
- package/dist/engine/index.mjs +1 -1
- package/dist/engine/interpreter.cjs +21 -4
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.mjs +21 -4
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +8 -2
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +8 -2
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/host/channels.cjs +1 -1
- package/dist/host/channels.mjs +1 -1
- package/dist/host/execute.cjs +2 -2
- package/dist/host/execute.d.cts +1 -1
- package/dist/host/execute.d.mts +1 -1
- package/dist/host/execute.mjs +2 -2
- package/dist/host/http.cjs +38 -3
- package/dist/host/http.cjs.map +1 -1
- package/dist/host/http.d.cts +2 -2
- package/dist/host/http.d.cts.map +1 -1
- package/dist/host/http.d.mts +2 -2
- package/dist/host/http.d.mts.map +1 -1
- package/dist/host/http.mjs +39 -4
- package/dist/host/http.mjs.map +1 -1
- package/dist/host/ingest.cjs +128 -4
- package/dist/host/ingest.cjs.map +1 -1
- package/dist/host/ingest.d.cts +71 -0
- package/dist/host/ingest.d.cts.map +1 -1
- package/dist/host/ingest.d.mts +71 -0
- package/dist/host/ingest.d.mts.map +1 -1
- package/dist/host/ingest.mjs +125 -5
- package/dist/host/ingest.mjs.map +1 -1
- package/dist/host/journey-host.cjs +10 -8
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.mjs +10 -8
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/index.cjs +5 -2
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/ir.cjs +11 -2
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +17 -0
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +17 -0
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +11 -3
- package/dist/ir.mjs.map +1 -1
- package/dist/reporting/observer.cjs +1 -0
- package/dist/reporting/observer.cjs.map +1 -1
- package/dist/reporting/observer.mjs +1 -0
- package/dist/reporting/observer.mjs.map +1 -1
- package/dist/reporting/tracking.cjs +28 -10
- package/dist/reporting/tracking.cjs.map +1 -1
- package/dist/reporting/tracking.d.cts +13 -6
- package/dist/reporting/tracking.d.cts.map +1 -1
- package/dist/reporting/tracking.d.mts +13 -6
- package/dist/reporting/tracking.d.mts.map +1 -1
- package/dist/reporting/tracking.mjs +28 -10
- package/dist/reporting/tracking.mjs.map +1 -1
- package/dist/store/facts.cjs +3 -3
- package/dist/store/facts.cjs.map +1 -1
- package/dist/store/facts.d.cts +2 -2
- package/dist/store/facts.d.cts.map +1 -1
- package/dist/store/facts.d.mts +2 -2
- package/dist/store/facts.d.mts.map +1 -1
- package/dist/store/facts.mjs +3 -3
- package/dist/store/facts.mjs.map +1 -1
- package/dist/store/index.cjs +1 -1
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +44 -13
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +44 -13
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +1 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres-reporting.cjs +30 -10
- package/dist/store/postgres-reporting.cjs.map +1 -1
- package/dist/store/postgres-reporting.d.cts +4 -2
- package/dist/store/postgres-reporting.d.cts.map +1 -1
- package/dist/store/postgres-reporting.d.mts +4 -2
- package/dist/store/postgres-reporting.d.mts.map +1 -1
- package/dist/store/postgres-reporting.mjs +30 -10
- package/dist/store/postgres-reporting.mjs.map +1 -1
- package/dist/store/postgres.cjs +39 -3
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +4 -2
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +4 -2
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +40 -4
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +75 -11
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +17 -1
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +17 -1
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +74 -12
- package/dist/store/tables.mjs.map +1 -1
- package/dist/templates.cjs +60 -7
- package/dist/templates.cjs.map +1 -1
- package/dist/templates.d.cts +101 -13
- package/dist/templates.d.cts.map +1 -1
- package/dist/templates.d.mts +101 -13
- package/dist/templates.d.mts.map +1 -1
- package/dist/templates.mjs +61 -9
- package/dist/templates.mjs.map +1 -1
- package/package.json +1 -1
package/dist/store/tables.d.cts
CHANGED
|
@@ -18,16 +18,28 @@ export interface ColumnSpec {
|
|
|
18
18
|
/** One line for the generated file. */
|
|
19
19
|
doc?: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A unique constraint whose key may hold nulls, with `NULLS NOT DISTINCT` (Postgres 15+) so two
|
|
23
|
+
* rows null in the same column still collide — the identity of a table whose key has an
|
|
24
|
+
* optional part, where a primary key (NOT NULL throughout) cannot be.
|
|
25
|
+
*/
|
|
26
|
+
export interface UniqueConstraintSpec<K extends string = string> {
|
|
27
|
+
columns: readonly K[];
|
|
28
|
+
nullsNotDistinct: true;
|
|
29
|
+
}
|
|
21
30
|
export interface TableSpec<K extends string = string> {
|
|
22
31
|
/** Physical table name inside the schema. */
|
|
23
32
|
name: string;
|
|
24
33
|
doc: string;
|
|
25
34
|
/** Keyed by the drizzle property name; the column name is its snake_case. */
|
|
26
35
|
columns: Record<K, ColumnSpec>;
|
|
27
|
-
|
|
36
|
+
/** Absent for a table whose identity is a `uniqueConstraints` entry (a nullable key part). */
|
|
37
|
+
primaryKey?: readonly K[];
|
|
28
38
|
/** A uuid column whose values must be version 7. */
|
|
29
39
|
uuidV7?: K;
|
|
30
40
|
uniqueIndexes?: readonly (readonly K[])[];
|
|
41
|
+
/** Named `<table>_<columns>_unique`, as drizzle names an unnamed `unique()`. */
|
|
42
|
+
uniqueConstraints?: readonly UniqueConstraintSpec<K>[];
|
|
31
43
|
indexes?: readonly (readonly K[])[];
|
|
32
44
|
/** Foreign keys, to another table's logical key. */
|
|
33
45
|
references?: readonly {
|
|
@@ -64,6 +76,10 @@ export type TableKey = keyof typeof TABLES;
|
|
|
64
76
|
export declare const DEFAULT_SCHEMA = "workflow";
|
|
65
77
|
/** `eventId` → `event_id`. */
|
|
66
78
|
export declare function columnName(key: string): string;
|
|
79
|
+
/** `<table>_<columns>_unique`: drizzle's name for an unnamed `unique()`, and the index behind it. */
|
|
80
|
+
export declare function uniqueConstraintName(spec: TableSpec, unique: UniqueConstraintSpec): string;
|
|
81
|
+
/** `<table>_<columns>_index`: the name drizzle-kit and `renderPostgresDdl` both give an unnamed index. */
|
|
82
|
+
export declare function indexName(spec: TableSpec, keys: readonly string[]): string;
|
|
67
83
|
/**
|
|
68
84
|
* Plain Postgres DDL for every table — for a host without a migration tool, and the tests. A
|
|
69
85
|
* host that migrates with drizzle generates `renderDrizzleSchema` instead and lets drizzle-kit
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.cts","names":[],"sources":["../../src/store/tables.ts"],"mappings":";;;;;;;;;;;;YAaY;iBAEK;EACf,MAAM;;EAEN;;EAEA;;iBAGe,UAAU;;EAEzB;EACA;;EAEA,SAAS,OAAO,GAAG
|
|
1
|
+
{"version":3,"file":"tables.d.cts","names":[],"sources":["../../src/store/tables.ts"],"mappings":";;;;;;;;;;;;YAaY;iBAEK;EACf,MAAM;;EAEN;;EAEA;;;;;;;iBAQe,qBAAqB;EACpC,kBAAkB;EAClB;;iBAGe,UAAU;;EAEzB;EACA;;EAEA,SAAS,OAAO,GAAG;;EAEnB,sBAAsB;;EAEtB,SAAS;EACT,mCAAmC;;EAEnC,6BAA6B,qBAAqB;EAClD,6BAA6B;;EAE7B;IACE,kBAAkB;IAClB;IACA;;;qBAoBS;;;;;;;;;;;;;;;;;;;;;;;YA4RD,wBAAwB;;qBAGvB;;wBAGG,WAAW;;wBAOX,qBAAqB,MAAM,WAAW,QAAQ;;wBAK9C,UAAU,MAAM,WAAW;;;;;;wBAkB3B,kBAAkB;;;;;;;;wBAqHlB,oBAAoB"}
|
package/dist/store/tables.d.mts
CHANGED
|
@@ -18,16 +18,28 @@ export interface ColumnSpec {
|
|
|
18
18
|
/** One line for the generated file. */
|
|
19
19
|
doc?: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A unique constraint whose key may hold nulls, with `NULLS NOT DISTINCT` (Postgres 15+) so two
|
|
23
|
+
* rows null in the same column still collide — the identity of a table whose key has an
|
|
24
|
+
* optional part, where a primary key (NOT NULL throughout) cannot be.
|
|
25
|
+
*/
|
|
26
|
+
export interface UniqueConstraintSpec<K extends string = string> {
|
|
27
|
+
columns: readonly K[];
|
|
28
|
+
nullsNotDistinct: true;
|
|
29
|
+
}
|
|
21
30
|
export interface TableSpec<K extends string = string> {
|
|
22
31
|
/** Physical table name inside the schema. */
|
|
23
32
|
name: string;
|
|
24
33
|
doc: string;
|
|
25
34
|
/** Keyed by the drizzle property name; the column name is its snake_case. */
|
|
26
35
|
columns: Record<K, ColumnSpec>;
|
|
27
|
-
|
|
36
|
+
/** Absent for a table whose identity is a `uniqueConstraints` entry (a nullable key part). */
|
|
37
|
+
primaryKey?: readonly K[];
|
|
28
38
|
/** A uuid column whose values must be version 7. */
|
|
29
39
|
uuidV7?: K;
|
|
30
40
|
uniqueIndexes?: readonly (readonly K[])[];
|
|
41
|
+
/** Named `<table>_<columns>_unique`, as drizzle names an unnamed `unique()`. */
|
|
42
|
+
uniqueConstraints?: readonly UniqueConstraintSpec<K>[];
|
|
31
43
|
indexes?: readonly (readonly K[])[];
|
|
32
44
|
/** Foreign keys, to another table's logical key. */
|
|
33
45
|
references?: readonly {
|
|
@@ -64,6 +76,10 @@ export type TableKey = keyof typeof TABLES;
|
|
|
64
76
|
export declare const DEFAULT_SCHEMA = "workflow";
|
|
65
77
|
/** `eventId` → `event_id`. */
|
|
66
78
|
export declare function columnName(key: string): string;
|
|
79
|
+
/** `<table>_<columns>_unique`: drizzle's name for an unnamed `unique()`, and the index behind it. */
|
|
80
|
+
export declare function uniqueConstraintName(spec: TableSpec, unique: UniqueConstraintSpec): string;
|
|
81
|
+
/** `<table>_<columns>_index`: the name drizzle-kit and `renderPostgresDdl` both give an unnamed index. */
|
|
82
|
+
export declare function indexName(spec: TableSpec, keys: readonly string[]): string;
|
|
67
83
|
/**
|
|
68
84
|
* Plain Postgres DDL for every table — for a host without a migration tool, and the tests. A
|
|
69
85
|
* host that migrates with drizzle generates `renderDrizzleSchema` instead and lets drizzle-kit
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.mts","names":[],"sources":["../../src/store/tables.ts"],"mappings":";;;;;;;;;;;;YAaY;iBAEK;EACf,MAAM;;EAEN;;EAEA;;iBAGe,UAAU;;EAEzB;EACA;;EAEA,SAAS,OAAO,GAAG
|
|
1
|
+
{"version":3,"file":"tables.d.mts","names":[],"sources":["../../src/store/tables.ts"],"mappings":";;;;;;;;;;;;YAaY;iBAEK;EACf,MAAM;;EAEN;;EAEA;;;;;;;iBAQe,qBAAqB;EACpC,kBAAkB;EAClB;;iBAGe,UAAU;;EAEzB;EACA;;EAEA,SAAS,OAAO,GAAG;;EAEnB,sBAAsB;;EAEtB,SAAS;EACT,mCAAmC;;EAEnC,6BAA6B,qBAAqB;EAClD,6BAA6B;;EAE7B;IACE,kBAAkB;IAClB;IACA;;;qBAoBS;;;;;;;;;;;;;;;;;;;;;;;YA4RD,wBAAwB;;qBAGvB;;wBAGG,WAAW;;wBAOX,qBAAqB,MAAM,WAAW,QAAQ;;wBAK9C,UAAU,MAAM,WAAW;;;;;;wBAkB3B,kBAAkB;;;;;;;;wBAqHlB,oBAAoB"}
|
package/dist/store/tables.mjs
CHANGED
|
@@ -67,13 +67,21 @@ const TABLES = {
|
|
|
67
67
|
}),
|
|
68
68
|
suppression: table({
|
|
69
69
|
name: "suppression",
|
|
70
|
-
doc: "
|
|
70
|
+
doc: "What a user opted out of: a topic on a channel (an email unsubscribe from one topic, a push topic switched off in the app), or the whole channel when `topic` is null — the engine sends nothing there, and `reachable(channel)` is false. One row per (user, channel, topic), nulls included.",
|
|
71
71
|
columns: {
|
|
72
72
|
userId: text,
|
|
73
73
|
channel: text,
|
|
74
|
+
topic: nullable(text, "The subscription topic opted out of; null for the whole channel (every row from before topics existed)."),
|
|
74
75
|
ts
|
|
75
76
|
},
|
|
76
|
-
|
|
77
|
+
uniqueConstraints: [{
|
|
78
|
+
columns: [
|
|
79
|
+
"userId",
|
|
80
|
+
"channel",
|
|
81
|
+
"topic"
|
|
82
|
+
],
|
|
83
|
+
nullsNotDistinct: true
|
|
84
|
+
}]
|
|
77
85
|
}),
|
|
78
86
|
segment: table({
|
|
79
87
|
name: "segment",
|
|
@@ -249,6 +257,7 @@ const TABLES = {
|
|
|
249
257
|
template: text,
|
|
250
258
|
channel: text,
|
|
251
259
|
userId: text,
|
|
260
|
+
topic: nullable(text, "An email or push copy: its template's subscription topic (what an email's unsubscribe link opts out of); null for a transactional message, and for every other channel."),
|
|
252
261
|
sentAt: ts,
|
|
253
262
|
deliveredAt: nullable(ts, "The transport accepted it."),
|
|
254
263
|
deviceDeliveredAt: nullable(ts, "A push: the device reported it received (iOS's notification service extension, a browser's service worker)."),
|
|
@@ -335,6 +344,14 @@ function columnName(key) {
|
|
|
335
344
|
return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
336
345
|
}
|
|
337
346
|
const quote = (name) => `"${name.replace(/"/g, "\"\"")}"`;
|
|
347
|
+
/** `<table>_<columns>_unique`: drizzle's name for an unnamed `unique()`, and the index behind it. */
|
|
348
|
+
function uniqueConstraintName(spec, unique) {
|
|
349
|
+
return `${spec.name}_${unique.columns.map(columnName).join("_")}_unique`;
|
|
350
|
+
}
|
|
351
|
+
/** `<table>_<columns>_index`: the name drizzle-kit and `renderPostgresDdl` both give an unnamed index. */
|
|
352
|
+
function indexName(spec, keys) {
|
|
353
|
+
return `${spec.name}_${keys.map(columnName).join("_")}_index`;
|
|
354
|
+
}
|
|
338
355
|
const SQL_TYPES = {
|
|
339
356
|
uuid: "uuid",
|
|
340
357
|
text: "text",
|
|
@@ -353,7 +370,11 @@ function renderPostgresDdl(schema = DEFAULT_SCHEMA) {
|
|
|
353
370
|
const out = [`CREATE SCHEMA IF NOT EXISTS ${s};`];
|
|
354
371
|
for (const spec of Object.values(TABLES)) {
|
|
355
372
|
const lines = Object.entries(spec.columns).map(([key, column]) => ` ${quote(columnName(key))} ${SQL_TYPES[column.type]}${column.nullable ? "" : " NOT NULL"}`);
|
|
356
|
-
lines.push(` PRIMARY KEY (${spec.primaryKey.map((k) => quote(columnName(k))).join(", ")})`);
|
|
373
|
+
if (spec.primaryKey !== void 0) lines.push(` PRIMARY KEY (${spec.primaryKey.map((k) => quote(columnName(k))).join(", ")})`);
|
|
374
|
+
for (const unique of spec.uniqueConstraints ?? []) {
|
|
375
|
+
const cols = unique.columns.map(columnName);
|
|
376
|
+
lines.push(` CONSTRAINT ${quote(uniqueConstraintName(spec, unique))} UNIQUE NULLS NOT DISTINCT (${cols.map(quote).join(", ")})`);
|
|
377
|
+
}
|
|
357
378
|
if (spec.uuidV7 !== void 0) lines.push(` CONSTRAINT ${quote(`${spec.name}_uuid_v7`)} CHECK (substring(${quote(columnName(spec.uuidV7))}::text, 15, 1) = '7')`);
|
|
358
379
|
for (const ref of spec.references ?? []) {
|
|
359
380
|
const target = TABLES[ref.table];
|
|
@@ -362,21 +383,57 @@ function renderPostgresDdl(schema = DEFAULT_SCHEMA) {
|
|
|
362
383
|
out.push(`CREATE TABLE IF NOT EXISTS ${s}.${quote(spec.name)} (\n${lines.join(",\n")}\n);`);
|
|
363
384
|
const index = (unique) => (keys) => {
|
|
364
385
|
const cols = keys.map(columnName);
|
|
365
|
-
out.push(`CREATE ${unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${quote(
|
|
386
|
+
out.push(`CREATE ${unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${quote(indexName(spec, keys))} ON ${s}.${quote(spec.name)} (${cols.map(quote).join(", ")});`);
|
|
366
387
|
};
|
|
367
388
|
(spec.uniqueIndexes ?? []).forEach(index(true));
|
|
368
389
|
(spec.indexes ?? []).forEach(index(false));
|
|
369
390
|
}
|
|
370
391
|
return `${out.join("\n")}\n`;
|
|
371
392
|
}
|
|
393
|
+
const TIMESTAMP_OPTIONS = "{ mode: 'string', withTimezone: true }";
|
|
372
394
|
const DRIZZLE_TYPES = {
|
|
373
395
|
uuid: (c) => `uuid('${c}')`,
|
|
374
396
|
text: (c) => `text('${c}')`,
|
|
375
397
|
jsonb: (c) => `jsonb('${c}')`,
|
|
376
|
-
timestamptz: (c) => `timestamp('${c}', {
|
|
398
|
+
timestamptz: (c) => `timestamp('${c}', ${TIMESTAMP_OPTIONS})`,
|
|
377
399
|
integer: (c) => `integer('${c}')`,
|
|
378
400
|
boolean: (c) => `boolean('${c}')`
|
|
379
401
|
};
|
|
402
|
+
/** The line width the generated file is laid out for: the formatters' common default. */
|
|
403
|
+
const PRINT_WIDTH = 100;
|
|
404
|
+
/**
|
|
405
|
+
* One `workflowSchema.table(...)` statement, laid out the way a prettier-compatible formatter
|
|
406
|
+
* (prettier, oxfmt, biome) at `PRINT_WIDTH` lays it out, so that formatting the generated file
|
|
407
|
+
* changes nothing and a regeneration shows only real changes: on one line when it fits; else
|
|
408
|
+
* with the last argument "hugged" (the columns object, or the constraints arrow when the columns
|
|
409
|
+
* fit on the first line) and its items one per line; else every argument on its own line, the
|
|
410
|
+
* constraints array still collapsed when it fits. A column with a doc comment keeps the columns
|
|
411
|
+
* object expanded, as a comment does for a formatter.
|
|
412
|
+
*/
|
|
413
|
+
/** A column line past the width: a formatter breaks the timestamp options object, so this does too. */
|
|
414
|
+
function wrapColumn(text, indent) {
|
|
415
|
+
if (`${indent}${text},`.length <= PRINT_WIDTH || !text.includes(TIMESTAMP_OPTIONS)) return text;
|
|
416
|
+
return text.replace(TIMESTAMP_OPTIONS, `{\n${indent} mode: 'string',\n${indent} withTimezone: true,\n${indent}}`);
|
|
417
|
+
}
|
|
418
|
+
function renderTable(name, table, columns, extras) {
|
|
419
|
+
const head = `export const ${name} = workflowSchema.table(`;
|
|
420
|
+
const flatColumns = columns.some((c) => c.doc !== void 0) ? void 0 : `{ ${columns.map((c) => c.text).join(", ")} }`;
|
|
421
|
+
const expandedColumns = (indent) => `{\n${columns.map((c) => `${c.doc === void 0 ? "" : `${indent} /** ${c.doc} */\n`}${indent} ${wrapColumn(c.text, `${indent} `)},`).join("\n")}\n${indent}}`;
|
|
422
|
+
const flatExtras = `(table) => [${extras.join(", ")}]`;
|
|
423
|
+
const expandedExtras = (indent) => `(table) => [\n${extras.map((e) => `${indent} ${e},`).join("\n")}\n${indent}]`;
|
|
424
|
+
const fits = (line) => line.length <= PRINT_WIDTH;
|
|
425
|
+
if (flatColumns !== void 0) {
|
|
426
|
+
const oneLine = `${head}'${table}', ${flatColumns}${extras.length === 0 ? "" : `, ${flatExtras}`});`;
|
|
427
|
+
if (fits(oneLine)) return oneLine;
|
|
428
|
+
}
|
|
429
|
+
if (extras.length === 0) return `${head}'${table}', ${expandedColumns("")});`;
|
|
430
|
+
if (flatColumns !== void 0) {
|
|
431
|
+
const first = `${head}'${table}', ${flatColumns}, (table) => [`;
|
|
432
|
+
if (fits(first)) return `${first}\n${extras.map((e) => ` ${e},`).join("\n")}\n]);`;
|
|
433
|
+
}
|
|
434
|
+
const third = fits(` ${flatExtras}`) ? flatExtras : expandedExtras(" ");
|
|
435
|
+
return `${head}\n '${table}',\n ${expandedColumns(" ")},\n ${third}\n);`;
|
|
436
|
+
}
|
|
380
437
|
const exportName = (key) => `workflow${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
381
438
|
/**
|
|
382
439
|
* The tables as one drizzle file: `export const workflowSchema = pgSchema(<schema>)` and every
|
|
@@ -393,12 +450,14 @@ function renderDrizzleSchema(schema = DEFAULT_SCHEMA) {
|
|
|
393
450
|
for (const [key, spec] of specs) {
|
|
394
451
|
const columns = Object.entries(spec.columns).map(([prop, column]) => {
|
|
395
452
|
used.add(column.type === "timestamptz" ? "timestamp" : column.type);
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
453
|
+
const key = spec.primaryKey?.length === 1 && spec.primaryKey[0] === prop ? ".primaryKey()" : "";
|
|
454
|
+
return {
|
|
455
|
+
doc: column.doc,
|
|
456
|
+
text: `${prop}: ${DRIZZLE_TYPES[column.type](columnName(prop))}${key}${column.nullable ? "" : ".notNull()"}`
|
|
457
|
+
};
|
|
399
458
|
});
|
|
400
459
|
const extras = [];
|
|
401
|
-
if (spec.primaryKey.length > 1) {
|
|
460
|
+
if (spec.primaryKey !== void 0 && spec.primaryKey.length > 1) {
|
|
402
461
|
used.add("primaryKey");
|
|
403
462
|
extras.push(`primaryKey({ columns: [${spec.primaryKey.map((k) => `table.${k}`).join(", ")}] })`);
|
|
404
463
|
}
|
|
@@ -406,6 +465,10 @@ function renderDrizzleSchema(schema = DEFAULT_SCHEMA) {
|
|
|
406
465
|
used.add("uniqueIndex");
|
|
407
466
|
extras.push(`uniqueIndex().on(${keys.map((k) => `table.${k}`).join(", ")})`);
|
|
408
467
|
}
|
|
468
|
+
for (const unique of spec.uniqueConstraints ?? []) {
|
|
469
|
+
used.add("unique");
|
|
470
|
+
extras.push(`unique().on(${unique.columns.map((k) => `table.${k}`).join(", ")}).nullsNotDistinct()`);
|
|
471
|
+
}
|
|
409
472
|
for (const keys of spec.indexes ?? []) {
|
|
410
473
|
used.add("index");
|
|
411
474
|
extras.push(`index().on(${keys.map((k) => `table.${k}`).join(", ")})`);
|
|
@@ -419,8 +482,7 @@ function renderDrizzleSchema(schema = DEFAULT_SCHEMA) {
|
|
|
419
482
|
needsSql = true;
|
|
420
483
|
extras.push(`check('${spec.name}_uuid_v7', sql\`substring(\${table.${spec.uuidV7}}::text, 15, 1) = '7'\`)`);
|
|
421
484
|
}
|
|
422
|
-
|
|
423
|
-
body.push(`/** ${spec.doc} */\nexport const ${exportName(key)} = workflowSchema.table(\n '${spec.name}',\n {\n${columns.join("\n")}\n }${third}\n);`);
|
|
485
|
+
body.push(`/** ${spec.doc} */\n${renderTable(exportName(key), spec.name, columns, extras)}`);
|
|
424
486
|
}
|
|
425
487
|
const imports = [...used].sort();
|
|
426
488
|
return [
|
|
@@ -435,6 +497,6 @@ function renderDrizzleSchema(schema = DEFAULT_SCHEMA) {
|
|
|
435
497
|
""
|
|
436
498
|
].join("\n");
|
|
437
499
|
}
|
|
438
|
-
export { DEFAULT_SCHEMA, TABLES, columnName, renderDrizzleSchema, renderPostgresDdl };
|
|
500
|
+
export { DEFAULT_SCHEMA, TABLES, columnName, indexName, renderDrizzleSchema, renderPostgresDdl, uniqueConstraintName };
|
|
439
501
|
|
|
440
502
|
//# sourceMappingURL=tables.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.mjs","names":[],"sources":["../../src/store/tables.ts"],"sourcesContent":["/**\n * The engine's Postgres tables, declared once. Everything that needs their shape derives it from\n * here: the drizzle schema file a host generates and migrates with its own tooling\n * (`renderDrizzleSchema`), the plain DDL (`renderPostgresDdl`), the store's table names, and the\n * check a deploy runs against the live database (`checkPostgresSchema`).\n *\n * The house rules hold for every table: no database defaults (the store writes every value,\n * timestamps included), timestamptz for time, and a UUID v7 row id — generated by the store and\n * guarded by a CHECK — wherever a table has a surrogate key. All tables live in one Postgres\n * schema of their own (`workflow` unless the config names another), so their short names never\n * meet the app's.\n */\n\nexport type ColumnType = 'uuid' | 'text' | 'jsonb' | 'timestamptz' | 'integer' | 'boolean';\n\nexport interface ColumnSpec {\n type: ColumnType;\n /** Columns are NOT NULL unless marked. */\n nullable?: boolean;\n /** One line for the generated file. */\n doc?: string;\n}\n\nexport interface TableSpec<K extends string = string> {\n /** Physical table name inside the schema. */\n name: string;\n doc: string;\n /** Keyed by the drizzle property name; the column name is its snake_case. */\n columns: Record<K, ColumnSpec>;\n primaryKey: readonly K[];\n /** A uuid column whose values must be version 7. */\n uuidV7?: K;\n uniqueIndexes?: readonly (readonly K[])[];\n indexes?: readonly (readonly K[])[];\n /** Foreign keys, to another table's logical key. */\n references?: readonly {\n columns: readonly K[];\n table: string;\n foreignColumns: readonly string[];\n }[];\n}\n\nfunction table<K extends string>(spec: TableSpec<K>): TableSpec<K> {\n return spec;\n}\n\nconst uuid = { type: 'uuid' } as const;\nconst text = { type: 'text' } as const;\nconst jsonb = { type: 'jsonb' } as const;\nconst ts = { type: 'timestamptz' } as const;\nconst integer = { type: 'integer' } as const;\nconst boolean = { type: 'boolean' } as const;\nconst nullable = <C extends ColumnSpec>(column: C, doc?: string): ColumnSpec => ({\n ...column,\n nullable: true,\n ...(doc === undefined ? {} : { doc }),\n});\n\nexport const TABLES = {\n event: table({\n name: 'event',\n doc: \"Every ingested event, the fact base for `performed(...)` conditions. `event_id` is the occurrence's identity as the sender chose it (a retrying producer sends the same one again), scoped to the user: the (user_id, event_id) unique index makes a second delivery a no-op, and one sender's id can never block another user's event.\",\n columns: { id: uuid, eventId: text, userId: text, name: text, ts, payload: jsonb },\n primaryKey: ['id'],\n uuidV7: 'id',\n uniqueIndexes: [['userId', 'eventId']],\n indexes: [['userId', 'name', 'ts']],\n }),\n profile: table({\n name: 'profile',\n doc: 'Identified properties per user (`/identify` merges into `props`; a null value means unset).',\n columns: { userId: text, props: jsonb },\n primaryKey: ['userId'],\n }),\n device: table({\n name: 'device',\n doc: 'Push devices, registered through the Worker (`JourneyClient.registerDevice`). Signing out unbinds a device (`user_id` null) rather than removing it; a token APNs / FCM reports as gone is removed.',\n columns: { token: text, userId: nullable(text), platform: text, ts },\n primaryKey: ['token'],\n indexes: [['userId']],\n }),\n erasure: table({\n name: 'erasure',\n doc: \"Erasure tombstones (`forgetUser`): every write about a user is conditional on no row here. While `erased_at` is null the user's data stays until every running instance is stopped, and the scheduled sweep retries what is left; once set, the data is gone and the row stays so the user id is never written again.\",\n columns: {\n userId: text,\n requestedAt: ts,\n erasedAt: nullable(ts, 'When the data was erased; null while the erasure is under way.'),\n },\n primaryKey: ['userId'],\n }),\n suppression: table({\n name: 'suppression',\n doc: 'Channels a user opted out of (an email unsubscribe): the engine sends nothing there, and `reachable(channel)` is false.',\n columns: { userId: text, channel: text, ts },\n primaryKey: ['userId', 'channel'],\n }),\n segment: table({\n name: 'segment',\n doc: 'Deployed segment definitions, resolved by name at evaluation time.',\n columns: { id: text, condition: jsonb, hash: text },\n primaryKey: ['id'],\n }),\n trigger: table({\n name: 'trigger',\n doc: 'Event-trigger routes: `where` gates on the event payload, `filter` on the profile.',\n columns: {\n workflow: text,\n hash: text,\n event: text,\n where: nullable(jsonb),\n filter: nullable(jsonb),\n version: nullable(\n text,\n 'The code version whose compiled bundle put this route here (null after a forced deploy): an instance it starts runs on that version, so its IR and its code always match.'\n ),\n },\n primaryKey: ['workflow'],\n indexes: [['event']],\n }),\n segmentTrigger: table({\n name: 'segment_trigger',\n doc: 'Segment-entry trigger routes.',\n columns: {\n workflow: text,\n hash: text,\n segment: text,\n version: nullable(\n text,\n 'The code version whose compiled bundle put this route here (null after a forced deploy): an instance it starts runs on that version, so its IR and its code always match.'\n ),\n },\n primaryKey: ['workflow'],\n indexes: [['segment']],\n }),\n segmentMember: table({\n name: 'segment_member',\n doc: 'Who is in each deployed segment and since when: kept by the scheduled sweep, and for trigger-routed segments on every event too.',\n columns: { segment: text, userId: text, ts },\n primaryKey: ['segment', 'userId'],\n }),\n segmentStart: table({\n name: 'segment_start',\n doc: 'Workflows a segment entry still has to start for a member: written with the membership, removed as each one starts, so a failed start is retried by the next evaluation instead of being hidden behind the membership.',\n columns: { segment: text, workflow: text, userId: text, ts },\n primaryKey: ['segment', 'workflow', 'userId'],\n }),\n segmentSnapshot: table({\n name: 'segment_snapshot',\n doc: \"Each segment's size at every sweep, the Studio's sparklines.\",\n columns: { segment: text, takenAt: ts, size: integer },\n primaryKey: ['segment', 'takenAt'],\n }),\n entry: table({\n name: 'entry',\n doc: 'Entry ledger: the (workflow, user) primary key is the once policy.',\n columns: {\n workflow: text,\n userId: text,\n instanceId: text,\n hash: text,\n status: text,\n ts,\n admittedAt: nullable(\n ts,\n 'When the runner admitted itself (`admitInstance`), before its first observable effect; null until then, so an erasure knows nothing of the instance has run yet.'\n ),\n },\n primaryKey: ['workflow', 'userId'],\n indexes: [['instanceId']],\n }),\n subscription: table({\n name: 'subscription',\n doc: 'wait_until wake routing; the primary key makes repeated subscribes idempotent.',\n columns: { userId: text, event: text, wakeHandle: text, ts },\n primaryKey: ['wakeHandle', 'event'],\n indexes: [['userId', 'event']],\n }),\n execution: table({\n name: 'execution',\n doc: 'Durable executions whose start is an asynchronous submit (AWS Lambda durable functions): written with the entry, so a lost submit response is retried with the same name, target and payload instead of starting a second execution; and the wake callback the instance is parked on now. Unused on Cloudflare.',\n columns: {\n instanceId: text,\n workflow: text,\n userId: text,\n name: text,\n target: text,\n input: text,\n createdAt: ts,\n submittedAt: nullable(\n ts,\n 'The runtime accepted the start; null while it may still have to be retried.'\n ),\n arn: nullable(text),\n unresolvedAt: nullable(\n ts,\n 'Retries ran out before the runtime confirmed the start: whether it runs is unknown, and nothing retries it.'\n ),\n callbackId: nullable(text, 'The callback the current wait parks on; a wake completes it.'),\n generation: integer,\n deadlineAt: nullable(ts),\n checkedAt: nullable(\n ts,\n \"When maintenance last compared the runtime's status with the ledger.\"\n ),\n stoppedAt: nullable(\n ts,\n 'When it was stopped (an erasure): an invocation already running goes on to its next checkpoint, so the data waits until it has drained.'\n ),\n },\n primaryKey: ['instanceId'],\n indexes: [['workflow', 'userId'], ['submittedAt'], ['userId']],\n }),\n deployment: table({\n name: 'deployment',\n doc: \"The Worker version whose compiled bundle the routes came from, one row (`scope` = 'routes'): journeyWorker applies its own bundle only over an older version, so one still serving during a rollout cannot put its routes back. A forced `POST /bundle/deploy` leaves it as it is.\",\n columns: { scope: text, versionId: text, versionCreatedAt: ts, appliedAt: ts },\n primaryKey: ['scope'],\n }),\n ir: table({\n name: 'ir',\n doc: 'Workflow bodies (WorkflowIR), content-addressed.',\n columns: { contentHash: text, ir: jsonb },\n primaryKey: ['contentHash'],\n }),\n instance: table({\n name: 'instance',\n doc: 'One row per journey instance, written when it enters: the node it sits on or last passed (none when a guard ended it before the first), and how it ended (`running` until then; `goal` is a conversion, at `finished_at`).',\n columns: {\n instanceId: text,\n workflow: text,\n userId: text,\n enteredAt: ts,\n nodeId: nullable(text),\n nodeAt: nullable(ts),\n status: text,\n finishedAt: nullable(ts),\n convertedAt: nullable(\n ts,\n 'When the goal was met, whatever the status: with `exitOnMatch: false` a converted instance ends `completed`.'\n ),\n },\n primaryKey: ['instanceId'],\n indexes: [\n ['workflow', 'status'],\n ['workflow', 'finishedAt'],\n ],\n }),\n message: table({\n name: 'message',\n doc: \"One row per message copy (a push fans out per device). The idempotency key is the engine's copy identity, so a retried step finds the row it wrote; `channel` is where the copy landed (`email`, `push_ios`, `push_android`).\",\n columns: {\n id: uuid,\n idempotencyKey: text,\n workflow: text,\n instanceId: text,\n nodeId: text,\n template: text,\n channel: text,\n userId: text,\n sentAt: ts,\n deliveredAt: nullable(ts, 'The transport accepted it.'),\n deviceDeliveredAt: nullable(\n ts,\n \"A push: the device reported it received (iOS's notification service extension, a browser's service worker).\"\n ),\n undeliveredReason: nullable(\n text,\n 'The transport settled it without delivering (a dead push token).'\n ),\n openedAt: nullable(ts, 'First open of any kind, by a person or a machine.'),\n humanOpenedAt: nullable(ts, 'First open a person made.'),\n clickedAt: nullable(ts),\n humanClickedAt: nullable(ts),\n unsubscribedAt: nullable(ts),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n uniqueIndexes: [['idempotencyKey']],\n indexes: [['workflow', 'sentAt'], ['instanceId']],\n }),\n messageLink: table({\n name: 'message_link',\n doc: \"A sent email's links in document order: the tracked URL names a message and a position, and the redirect goes only to the URL recorded here.\",\n columns: { messageId: uuid, position: integer, url: text },\n primaryKey: ['messageId', 'position'],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n messageOpen: table({\n name: 'message_open',\n doc: \"Every open-pixel load or push tap, classified when recorded: `machine` when no person caused it, with the deciding rule in `reason`; the request's network (never its address) kept for revisiting the rules.\",\n columns: {\n id: uuid,\n messageId: uuid,\n openedAt: ts,\n machine: boolean,\n reason: nullable(text),\n userAgent: nullable(text),\n asn: nullable(integer),\n asOrganization: nullable(text),\n country: nullable(text),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n indexes: [['messageId']],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n messageClick: table({\n name: 'message_click',\n doc: 'Every click on a tracked link, classified like opens.',\n columns: {\n id: uuid,\n messageId: uuid,\n position: integer,\n url: text,\n clickedAt: ts,\n machine: boolean,\n reason: nullable(text),\n userAgent: nullable(text),\n asn: nullable(integer),\n asOrganization: nullable(text),\n country: nullable(text),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n indexes: [['messageId']],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n} satisfies Record<string, TableSpec>;\n\nexport type TableKey = keyof typeof TABLES;\n\n/** The engine's schema when the config names none. */\nexport const DEFAULT_SCHEMA = 'workflow';\n\n/** `eventId` → `event_id`. */\nexport function columnName(key: string): string {\n return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n}\n\nconst quote = (name: string) => `\"${name.replace(/\"/g, '\"\"')}\"`;\n\nconst SQL_TYPES: Record<ColumnType, string> = {\n uuid: 'uuid',\n text: 'text',\n jsonb: 'jsonb',\n timestamptz: 'timestamptz',\n integer: 'integer',\n boolean: 'boolean',\n};\n\n/**\n * Plain Postgres DDL for every table — for a host without a migration tool, and the tests. A\n * host that migrates with drizzle generates `renderDrizzleSchema` instead and lets drizzle-kit\n * write the migration.\n */\nexport function renderPostgresDdl(schema = DEFAULT_SCHEMA): string {\n const s = quote(schema);\n const out = [`CREATE SCHEMA IF NOT EXISTS ${s};`];\n for (const spec of Object.values(TABLES)) {\n const lines = Object.entries(spec.columns).map(\n ([key, column]) =>\n ` ${quote(columnName(key))} ${SQL_TYPES[column.type]}${column.nullable ? '' : ' NOT NULL'}`\n );\n lines.push(` PRIMARY KEY (${spec.primaryKey.map((k) => quote(columnName(k))).join(', ')})`);\n if (spec.uuidV7 !== undefined) {\n lines.push(\n ` CONSTRAINT ${quote(`${spec.name}_uuid_v7`)} CHECK (substring(${quote(columnName(spec.uuidV7))}::text, 15, 1) = '7')`\n );\n }\n for (const ref of spec.references ?? []) {\n const target = TABLES[ref.table as TableKey];\n lines.push(\n ` FOREIGN KEY (${ref.columns.map((k) => quote(columnName(k))).join(', ')}) REFERENCES ${s}.${quote(target.name)} (${ref.foreignColumns.map((k) => quote(columnName(k))).join(', ')})`\n );\n }\n out.push(`CREATE TABLE IF NOT EXISTS ${s}.${quote(spec.name)} (\\n${lines.join(',\\n')}\\n);`);\n const index = (unique: boolean) => (keys: readonly string[]) => {\n const cols = keys.map(columnName);\n out.push(\n `CREATE ${unique ? 'UNIQUE ' : ''}INDEX IF NOT EXISTS ${quote(`${spec.name}_${cols.join('_')}_index`)} ON ${s}.${quote(spec.name)} (${cols.map(quote).join(', ')});`\n );\n };\n (spec.uniqueIndexes ?? []).forEach(index(true));\n (spec.indexes ?? []).forEach(index(false));\n }\n return `${out.join('\\n')}\\n`;\n}\n\nconst DRIZZLE_TYPES: Record<ColumnType, (column: string) => string> = {\n uuid: (c) => `uuid('${c}')`,\n text: (c) => `text('${c}')`,\n jsonb: (c) => `jsonb('${c}')`,\n timestamptz: (c) => `timestamp('${c}', { mode: 'string', withTimezone: true })`,\n integer: (c) => `integer('${c}')`,\n boolean: (c) => `boolean('${c}')`,\n};\n\nconst exportName = (key: string) => `workflow${key.charAt(0).toUpperCase()}${key.slice(1)}`;\n\n/**\n * The tables as one drizzle file: `export const workflowSchema = pgSchema(<schema>)` and every\n * table as `workflowSchema.table(...)`. The host re-exports it from its drizzle schema and\n * generates the migration with drizzle-kit, so the migration history stays the host's. The\n * output is deterministic — regenerate and diff it in CI to catch an SDK upgrade that changed the\n * tables.\n */\nexport function renderDrizzleSchema(schema = DEFAULT_SCHEMA): string {\n const specs = Object.entries(TABLES) as [TableKey, TableSpec][];\n const used = new Set<string>(['pgSchema']);\n const body: string[] = [];\n let needsSql = false;\n for (const [key, spec] of specs) {\n const columns = Object.entries(spec.columns).map(([prop, column]) => {\n used.add(column.type === 'timestamptz' ? 'timestamp' : column.type);\n const doc = column.doc === undefined ? '' : ` /** ${column.doc} */\\n`;\n const key =\n spec.primaryKey.length === 1 && spec.primaryKey[0] === prop ? '.primaryKey()' : '';\n return `${doc} ${prop}: ${DRIZZLE_TYPES[column.type](columnName(prop))}${key}${column.nullable ? '' : '.notNull()'},`;\n });\n const extras: string[] = [];\n if (spec.primaryKey.length > 1) {\n used.add('primaryKey');\n extras.push(\n `primaryKey({ columns: [${spec.primaryKey.map((k) => `table.${k}`).join(', ')}] })`\n );\n }\n for (const keys of spec.uniqueIndexes ?? []) {\n used.add('uniqueIndex');\n extras.push(`uniqueIndex().on(${keys.map((k) => `table.${k}`).join(', ')})`);\n }\n for (const keys of spec.indexes ?? []) {\n used.add('index');\n extras.push(`index().on(${keys.map((k) => `table.${k}`).join(', ')})`);\n }\n for (const ref of spec.references ?? []) {\n used.add('foreignKey');\n extras.push(\n `foreignKey({ columns: [${ref.columns.map((k) => `table.${k}`).join(', ')}], foreignColumns: [${ref.foreignColumns.map((k) => `${exportName(ref.table)}.${k}`).join(', ')}] })`\n );\n }\n if (spec.uuidV7 !== undefined) {\n used.add('check');\n needsSql = true;\n extras.push(\n `check('${spec.name}_uuid_v7', sql\\`substring(\\${table.${spec.uuidV7}}::text, 15, 1) = '7'\\`)`\n );\n }\n const third =\n extras.length === 0\n ? ''\n : `,\\n (table) => [\\n${extras.map((e) => ` ${e},`).join('\\n')}\\n ]`;\n body.push(\n `/** ${spec.doc} */\\nexport const ${exportName(key)} = workflowSchema.table(\\n '${spec.name}',\\n {\\n${columns.join('\\n')}\\n }${third}\\n);`\n );\n }\n const imports = [...used].sort();\n return [\n '// Generated by `workflow-ui generate` from @shware/workflow — do not edit; regenerate after',\n '// upgrading the SDK and let drizzle-kit write the migration.',\n ...(needsSql ? [\"import { sql } from 'drizzle-orm';\"] : []),\n `import {\\n${imports.map((name) => ` ${name},`).join('\\n')}\\n} from 'drizzle-orm/pg-core';`,\n '',\n `export const workflowSchema = pgSchema('${schema}');`,\n '',\n body.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tables.mjs","names":[],"sources":["../../src/store/tables.ts"],"sourcesContent":["/**\n * The engine's Postgres tables, declared once. Everything that needs their shape derives it from\n * here: the drizzle schema file a host generates and migrates with its own tooling\n * (`renderDrizzleSchema`), the plain DDL (`renderPostgresDdl`), the store's table names, and the\n * check a deploy runs against the live database (`checkPostgresSchema`).\n *\n * The house rules hold for every table: no database defaults (the store writes every value,\n * timestamps included), timestamptz for time, and a UUID v7 row id — generated by the store and\n * guarded by a CHECK — wherever a table has a surrogate key. All tables live in one Postgres\n * schema of their own (`workflow` unless the config names another), so their short names never\n * meet the app's.\n */\n\nexport type ColumnType = 'uuid' | 'text' | 'jsonb' | 'timestamptz' | 'integer' | 'boolean';\n\nexport interface ColumnSpec {\n type: ColumnType;\n /** Columns are NOT NULL unless marked. */\n nullable?: boolean;\n /** One line for the generated file. */\n doc?: string;\n}\n\n/**\n * A unique constraint whose key may hold nulls, with `NULLS NOT DISTINCT` (Postgres 15+) so two\n * rows null in the same column still collide — the identity of a table whose key has an\n * optional part, where a primary key (NOT NULL throughout) cannot be.\n */\nexport interface UniqueConstraintSpec<K extends string = string> {\n columns: readonly K[];\n nullsNotDistinct: true;\n}\n\nexport interface TableSpec<K extends string = string> {\n /** Physical table name inside the schema. */\n name: string;\n doc: string;\n /** Keyed by the drizzle property name; the column name is its snake_case. */\n columns: Record<K, ColumnSpec>;\n /** Absent for a table whose identity is a `uniqueConstraints` entry (a nullable key part). */\n primaryKey?: readonly K[];\n /** A uuid column whose values must be version 7. */\n uuidV7?: K;\n uniqueIndexes?: readonly (readonly K[])[];\n /** Named `<table>_<columns>_unique`, as drizzle names an unnamed `unique()`. */\n uniqueConstraints?: readonly UniqueConstraintSpec<K>[];\n indexes?: readonly (readonly K[])[];\n /** Foreign keys, to another table's logical key. */\n references?: readonly {\n columns: readonly K[];\n table: string;\n foreignColumns: readonly string[];\n }[];\n}\n\nfunction table<K extends string>(spec: TableSpec<K>): TableSpec<K> {\n return spec;\n}\n\nconst uuid = { type: 'uuid' } as const;\nconst text = { type: 'text' } as const;\nconst jsonb = { type: 'jsonb' } as const;\nconst ts = { type: 'timestamptz' } as const;\nconst integer = { type: 'integer' } as const;\nconst boolean = { type: 'boolean' } as const;\nconst nullable = <C extends ColumnSpec>(column: C, doc?: string): ColumnSpec => ({\n ...column,\n nullable: true,\n ...(doc === undefined ? {} : { doc }),\n});\n\nexport const TABLES = {\n event: table({\n name: 'event',\n doc: \"Every ingested event, the fact base for `performed(...)` conditions. `event_id` is the occurrence's identity as the sender chose it (a retrying producer sends the same one again), scoped to the user: the (user_id, event_id) unique index makes a second delivery a no-op, and one sender's id can never block another user's event.\",\n columns: { id: uuid, eventId: text, userId: text, name: text, ts, payload: jsonb },\n primaryKey: ['id'],\n uuidV7: 'id',\n uniqueIndexes: [['userId', 'eventId']],\n indexes: [['userId', 'name', 'ts']],\n }),\n profile: table({\n name: 'profile',\n doc: 'Identified properties per user (`/identify` merges into `props`; a null value means unset).',\n columns: { userId: text, props: jsonb },\n primaryKey: ['userId'],\n }),\n device: table({\n name: 'device',\n doc: 'Push devices, registered through the Worker (`JourneyClient.registerDevice`). Signing out unbinds a device (`user_id` null) rather than removing it; a token APNs / FCM reports as gone is removed.',\n columns: { token: text, userId: nullable(text), platform: text, ts },\n primaryKey: ['token'],\n indexes: [['userId']],\n }),\n erasure: table({\n name: 'erasure',\n doc: \"Erasure tombstones (`forgetUser`): every write about a user is conditional on no row here. While `erased_at` is null the user's data stays until every running instance is stopped, and the scheduled sweep retries what is left; once set, the data is gone and the row stays so the user id is never written again.\",\n columns: {\n userId: text,\n requestedAt: ts,\n erasedAt: nullable(ts, 'When the data was erased; null while the erasure is under way.'),\n },\n primaryKey: ['userId'],\n }),\n suppression: table({\n name: 'suppression',\n doc: 'What a user opted out of: a topic on a channel (an email unsubscribe from one topic, a push topic switched off in the app), or the whole channel when `topic` is null — the engine sends nothing there, and `reachable(channel)` is false. One row per (user, channel, topic), nulls included.',\n columns: {\n userId: text,\n channel: text,\n topic: nullable(\n text,\n 'The subscription topic opted out of; null for the whole channel (every row from before topics existed).'\n ),\n ts,\n },\n uniqueConstraints: [{ columns: ['userId', 'channel', 'topic'], nullsNotDistinct: true }],\n }),\n segment: table({\n name: 'segment',\n doc: 'Deployed segment definitions, resolved by name at evaluation time.',\n columns: { id: text, condition: jsonb, hash: text },\n primaryKey: ['id'],\n }),\n trigger: table({\n name: 'trigger',\n doc: 'Event-trigger routes: `where` gates on the event payload, `filter` on the profile.',\n columns: {\n workflow: text,\n hash: text,\n event: text,\n where: nullable(jsonb),\n filter: nullable(jsonb),\n version: nullable(\n text,\n 'The code version whose compiled bundle put this route here (null after a forced deploy): an instance it starts runs on that version, so its IR and its code always match.'\n ),\n },\n primaryKey: ['workflow'],\n indexes: [['event']],\n }),\n segmentTrigger: table({\n name: 'segment_trigger',\n doc: 'Segment-entry trigger routes.',\n columns: {\n workflow: text,\n hash: text,\n segment: text,\n version: nullable(\n text,\n 'The code version whose compiled bundle put this route here (null after a forced deploy): an instance it starts runs on that version, so its IR and its code always match.'\n ),\n },\n primaryKey: ['workflow'],\n indexes: [['segment']],\n }),\n segmentMember: table({\n name: 'segment_member',\n doc: 'Who is in each deployed segment and since when: kept by the scheduled sweep, and for trigger-routed segments on every event too.',\n columns: { segment: text, userId: text, ts },\n primaryKey: ['segment', 'userId'],\n }),\n segmentStart: table({\n name: 'segment_start',\n doc: 'Workflows a segment entry still has to start for a member: written with the membership, removed as each one starts, so a failed start is retried by the next evaluation instead of being hidden behind the membership.',\n columns: { segment: text, workflow: text, userId: text, ts },\n primaryKey: ['segment', 'workflow', 'userId'],\n }),\n segmentSnapshot: table({\n name: 'segment_snapshot',\n doc: \"Each segment's size at every sweep, the Studio's sparklines.\",\n columns: { segment: text, takenAt: ts, size: integer },\n primaryKey: ['segment', 'takenAt'],\n }),\n entry: table({\n name: 'entry',\n doc: 'Entry ledger: the (workflow, user) primary key is the once policy.',\n columns: {\n workflow: text,\n userId: text,\n instanceId: text,\n hash: text,\n status: text,\n ts,\n admittedAt: nullable(\n ts,\n 'When the runner admitted itself (`admitInstance`), before its first observable effect; null until then, so an erasure knows nothing of the instance has run yet.'\n ),\n },\n primaryKey: ['workflow', 'userId'],\n indexes: [['instanceId']],\n }),\n subscription: table({\n name: 'subscription',\n doc: 'wait_until wake routing; the primary key makes repeated subscribes idempotent.',\n columns: { userId: text, event: text, wakeHandle: text, ts },\n primaryKey: ['wakeHandle', 'event'],\n indexes: [['userId', 'event']],\n }),\n execution: table({\n name: 'execution',\n doc: 'Durable executions whose start is an asynchronous submit (AWS Lambda durable functions): written with the entry, so a lost submit response is retried with the same name, target and payload instead of starting a second execution; and the wake callback the instance is parked on now. Unused on Cloudflare.',\n columns: {\n instanceId: text,\n workflow: text,\n userId: text,\n name: text,\n target: text,\n input: text,\n createdAt: ts,\n submittedAt: nullable(\n ts,\n 'The runtime accepted the start; null while it may still have to be retried.'\n ),\n arn: nullable(text),\n unresolvedAt: nullable(\n ts,\n 'Retries ran out before the runtime confirmed the start: whether it runs is unknown, and nothing retries it.'\n ),\n callbackId: nullable(text, 'The callback the current wait parks on; a wake completes it.'),\n generation: integer,\n deadlineAt: nullable(ts),\n checkedAt: nullable(\n ts,\n \"When maintenance last compared the runtime's status with the ledger.\"\n ),\n stoppedAt: nullable(\n ts,\n 'When it was stopped (an erasure): an invocation already running goes on to its next checkpoint, so the data waits until it has drained.'\n ),\n },\n primaryKey: ['instanceId'],\n indexes: [['workflow', 'userId'], ['submittedAt'], ['userId']],\n }),\n deployment: table({\n name: 'deployment',\n doc: \"The Worker version whose compiled bundle the routes came from, one row (`scope` = 'routes'): journeyWorker applies its own bundle only over an older version, so one still serving during a rollout cannot put its routes back. A forced `POST /bundle/deploy` leaves it as it is.\",\n columns: { scope: text, versionId: text, versionCreatedAt: ts, appliedAt: ts },\n primaryKey: ['scope'],\n }),\n ir: table({\n name: 'ir',\n doc: 'Workflow bodies (WorkflowIR), content-addressed.',\n columns: { contentHash: text, ir: jsonb },\n primaryKey: ['contentHash'],\n }),\n instance: table({\n name: 'instance',\n doc: 'One row per journey instance, written when it enters: the node it sits on or last passed (none when a guard ended it before the first), and how it ended (`running` until then; `goal` is a conversion, at `finished_at`).',\n columns: {\n instanceId: text,\n workflow: text,\n userId: text,\n enteredAt: ts,\n nodeId: nullable(text),\n nodeAt: nullable(ts),\n status: text,\n finishedAt: nullable(ts),\n convertedAt: nullable(\n ts,\n 'When the goal was met, whatever the status: with `exitOnMatch: false` a converted instance ends `completed`.'\n ),\n },\n primaryKey: ['instanceId'],\n indexes: [\n ['workflow', 'status'],\n ['workflow', 'finishedAt'],\n ],\n }),\n message: table({\n name: 'message',\n doc: \"One row per message copy (a push fans out per device). The idempotency key is the engine's copy identity, so a retried step finds the row it wrote; `channel` is where the copy landed (`email`, `push_ios`, `push_android`).\",\n columns: {\n id: uuid,\n idempotencyKey: text,\n workflow: text,\n instanceId: text,\n nodeId: text,\n template: text,\n channel: text,\n userId: text,\n topic: nullable(\n text,\n \"An email or push copy: its template's subscription topic (what an email's unsubscribe link opts out of); null for a transactional message, and for every other channel.\"\n ),\n sentAt: ts,\n deliveredAt: nullable(ts, 'The transport accepted it.'),\n deviceDeliveredAt: nullable(\n ts,\n \"A push: the device reported it received (iOS's notification service extension, a browser's service worker).\"\n ),\n undeliveredReason: nullable(\n text,\n 'The transport settled it without delivering (a dead push token).'\n ),\n openedAt: nullable(ts, 'First open of any kind, by a person or a machine.'),\n humanOpenedAt: nullable(ts, 'First open a person made.'),\n clickedAt: nullable(ts),\n humanClickedAt: nullable(ts),\n unsubscribedAt: nullable(ts),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n uniqueIndexes: [['idempotencyKey']],\n indexes: [['workflow', 'sentAt'], ['instanceId']],\n }),\n messageLink: table({\n name: 'message_link',\n doc: \"A sent email's links in document order: the tracked URL names a message and a position, and the redirect goes only to the URL recorded here.\",\n columns: { messageId: uuid, position: integer, url: text },\n primaryKey: ['messageId', 'position'],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n messageOpen: table({\n name: 'message_open',\n doc: \"Every open-pixel load or push tap, classified when recorded: `machine` when no person caused it, with the deciding rule in `reason`; the request's network (never its address) kept for revisiting the rules.\",\n columns: {\n id: uuid,\n messageId: uuid,\n openedAt: ts,\n machine: boolean,\n reason: nullable(text),\n userAgent: nullable(text),\n asn: nullable(integer),\n asOrganization: nullable(text),\n country: nullable(text),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n indexes: [['messageId']],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n messageClick: table({\n name: 'message_click',\n doc: 'Every click on a tracked link, classified like opens.',\n columns: {\n id: uuid,\n messageId: uuid,\n position: integer,\n url: text,\n clickedAt: ts,\n machine: boolean,\n reason: nullable(text),\n userAgent: nullable(text),\n asn: nullable(integer),\n asOrganization: nullable(text),\n country: nullable(text),\n },\n primaryKey: ['id'],\n uuidV7: 'id',\n indexes: [['messageId']],\n references: [{ columns: ['messageId'], table: 'message', foreignColumns: ['id'] }],\n }),\n} satisfies Record<string, TableSpec>;\n\nexport type TableKey = keyof typeof TABLES;\n\n/** The engine's schema when the config names none. */\nexport const DEFAULT_SCHEMA = 'workflow';\n\n/** `eventId` → `event_id`. */\nexport function columnName(key: string): string {\n return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n}\n\nconst quote = (name: string) => `\"${name.replace(/\"/g, '\"\"')}\"`;\n\n/** `<table>_<columns>_unique`: drizzle's name for an unnamed `unique()`, and the index behind it. */\nexport function uniqueConstraintName(spec: TableSpec, unique: UniqueConstraintSpec): string {\n return `${spec.name}_${unique.columns.map(columnName).join('_')}_unique`;\n}\n\n/** `<table>_<columns>_index`: the name drizzle-kit and `renderPostgresDdl` both give an unnamed index. */\nexport function indexName(spec: TableSpec, keys: readonly string[]): string {\n return `${spec.name}_${keys.map(columnName).join('_')}_index`;\n}\n\nconst SQL_TYPES: Record<ColumnType, string> = {\n uuid: 'uuid',\n text: 'text',\n jsonb: 'jsonb',\n timestamptz: 'timestamptz',\n integer: 'integer',\n boolean: 'boolean',\n};\n\n/**\n * Plain Postgres DDL for every table — for a host without a migration tool, and the tests. A\n * host that migrates with drizzle generates `renderDrizzleSchema` instead and lets drizzle-kit\n * write the migration.\n */\nexport function renderPostgresDdl(schema = DEFAULT_SCHEMA): string {\n const s = quote(schema);\n const out = [`CREATE SCHEMA IF NOT EXISTS ${s};`];\n for (const spec of Object.values(TABLES)) {\n const lines = Object.entries(spec.columns).map(\n ([key, column]) =>\n ` ${quote(columnName(key))} ${SQL_TYPES[column.type]}${column.nullable ? '' : ' NOT NULL'}`\n );\n if (spec.primaryKey !== undefined) {\n lines.push(` PRIMARY KEY (${spec.primaryKey.map((k) => quote(columnName(k))).join(', ')})`);\n }\n for (const unique of spec.uniqueConstraints ?? []) {\n const cols = unique.columns.map(columnName);\n lines.push(\n ` CONSTRAINT ${quote(uniqueConstraintName(spec, unique))} UNIQUE NULLS NOT DISTINCT (${cols.map(quote).join(', ')})`\n );\n }\n if (spec.uuidV7 !== undefined) {\n lines.push(\n ` CONSTRAINT ${quote(`${spec.name}_uuid_v7`)} CHECK (substring(${quote(columnName(spec.uuidV7))}::text, 15, 1) = '7')`\n );\n }\n for (const ref of spec.references ?? []) {\n const target = TABLES[ref.table as TableKey];\n lines.push(\n ` FOREIGN KEY (${ref.columns.map((k) => quote(columnName(k))).join(', ')}) REFERENCES ${s}.${quote(target.name)} (${ref.foreignColumns.map((k) => quote(columnName(k))).join(', ')})`\n );\n }\n out.push(`CREATE TABLE IF NOT EXISTS ${s}.${quote(spec.name)} (\\n${lines.join(',\\n')}\\n);`);\n const index = (unique: boolean) => (keys: readonly string[]) => {\n const cols = keys.map(columnName);\n out.push(\n `CREATE ${unique ? 'UNIQUE ' : ''}INDEX IF NOT EXISTS ${quote(indexName(spec, keys))} ON ${s}.${quote(spec.name)} (${cols.map(quote).join(', ')});`\n );\n };\n (spec.uniqueIndexes ?? []).forEach(index(true));\n (spec.indexes ?? []).forEach(index(false));\n }\n return `${out.join('\\n')}\\n`;\n}\n\nconst TIMESTAMP_OPTIONS = \"{ mode: 'string', withTimezone: true }\";\n\nconst DRIZZLE_TYPES: Record<ColumnType, (column: string) => string> = {\n uuid: (c) => `uuid('${c}')`,\n text: (c) => `text('${c}')`,\n jsonb: (c) => `jsonb('${c}')`,\n timestamptz: (c) => `timestamp('${c}', ${TIMESTAMP_OPTIONS})`,\n integer: (c) => `integer('${c}')`,\n boolean: (c) => `boolean('${c}')`,\n};\n\n/** The line width the generated file is laid out for: the formatters' common default. */\nconst PRINT_WIDTH = 100;\n\n/**\n * One `workflowSchema.table(...)` statement, laid out the way a prettier-compatible formatter\n * (prettier, oxfmt, biome) at `PRINT_WIDTH` lays it out, so that formatting the generated file\n * changes nothing and a regeneration shows only real changes: on one line when it fits; else\n * with the last argument \"hugged\" (the columns object, or the constraints arrow when the columns\n * fit on the first line) and its items one per line; else every argument on its own line, the\n * constraints array still collapsed when it fits. A column with a doc comment keeps the columns\n * object expanded, as a comment does for a formatter.\n */\n/** A column line past the width: a formatter breaks the timestamp options object, so this does too. */\nfunction wrapColumn(text: string, indent: string): string {\n if (`${indent}${text},`.length <= PRINT_WIDTH || !text.includes(TIMESTAMP_OPTIONS)) return text;\n return text.replace(\n TIMESTAMP_OPTIONS,\n `{\\n${indent} mode: 'string',\\n${indent} withTimezone: true,\\n${indent}}`\n );\n}\n\nfunction renderTable(\n name: string,\n table: string,\n columns: { doc: string | undefined; text: string }[],\n extras: string[]\n): string {\n const head = `export const ${name} = workflowSchema.table(`;\n const flatColumns = columns.some((c) => c.doc !== undefined)\n ? undefined\n : `{ ${columns.map((c) => c.text).join(', ')} }`;\n const expandedColumns = (indent: string) =>\n `{\\n${columns\n .map(\n (c) =>\n `${c.doc === undefined ? '' : `${indent} /** ${c.doc} */\\n`}${indent} ${wrapColumn(c.text, `${indent} `)},`\n )\n .join('\\n')}\\n${indent}}`;\n const flatExtras = `(table) => [${extras.join(', ')}]`;\n const expandedExtras = (indent: string) =>\n `(table) => [\\n${extras.map((e) => `${indent} ${e},`).join('\\n')}\\n${indent}]`;\n const fits = (line: string) => line.length <= PRINT_WIDTH;\n\n if (flatColumns !== undefined) {\n const oneLine = `${head}'${table}', ${flatColumns}${extras.length === 0 ? '' : `, ${flatExtras}`});`;\n if (fits(oneLine)) return oneLine;\n }\n if (extras.length === 0) return `${head}'${table}', ${expandedColumns('')});`;\n if (flatColumns !== undefined) {\n const first = `${head}'${table}', ${flatColumns}, (table) => [`;\n if (fits(first)) return `${first}\\n${extras.map((e) => ` ${e},`).join('\\n')}\\n]);`;\n }\n const third = fits(` ${flatExtras}`) ? flatExtras : expandedExtras(' ');\n return `${head}\\n '${table}',\\n ${expandedColumns(' ')},\\n ${third}\\n);`;\n}\n\nconst exportName = (key: string) => `workflow${key.charAt(0).toUpperCase()}${key.slice(1)}`;\n\n/**\n * The tables as one drizzle file: `export const workflowSchema = pgSchema(<schema>)` and every\n * table as `workflowSchema.table(...)`. The host re-exports it from its drizzle schema and\n * generates the migration with drizzle-kit, so the migration history stays the host's. The\n * output is deterministic — regenerate and diff it in CI to catch an SDK upgrade that changed the\n * tables.\n */\nexport function renderDrizzleSchema(schema = DEFAULT_SCHEMA): string {\n const specs = Object.entries(TABLES) as [TableKey, TableSpec][];\n const used = new Set<string>(['pgSchema']);\n const body: string[] = [];\n let needsSql = false;\n for (const [key, spec] of specs) {\n const columns = Object.entries(spec.columns).map(([prop, column]) => {\n used.add(column.type === 'timestamptz' ? 'timestamp' : column.type);\n const key =\n spec.primaryKey?.length === 1 && spec.primaryKey[0] === prop ? '.primaryKey()' : '';\n return {\n doc: column.doc,\n text: `${prop}: ${DRIZZLE_TYPES[column.type](columnName(prop))}${key}${column.nullable ? '' : '.notNull()'}`,\n };\n });\n const extras: string[] = [];\n if (spec.primaryKey !== undefined && spec.primaryKey.length > 1) {\n used.add('primaryKey');\n extras.push(\n `primaryKey({ columns: [${spec.primaryKey.map((k) => `table.${k}`).join(', ')}] })`\n );\n }\n for (const keys of spec.uniqueIndexes ?? []) {\n used.add('uniqueIndex');\n extras.push(`uniqueIndex().on(${keys.map((k) => `table.${k}`).join(', ')})`);\n }\n for (const unique of spec.uniqueConstraints ?? []) {\n used.add('unique');\n extras.push(\n `unique().on(${unique.columns.map((k) => `table.${k}`).join(', ')}).nullsNotDistinct()`\n );\n }\n for (const keys of spec.indexes ?? []) {\n used.add('index');\n extras.push(`index().on(${keys.map((k) => `table.${k}`).join(', ')})`);\n }\n for (const ref of spec.references ?? []) {\n used.add('foreignKey');\n extras.push(\n `foreignKey({ columns: [${ref.columns.map((k) => `table.${k}`).join(', ')}], foreignColumns: [${ref.foreignColumns.map((k) => `${exportName(ref.table)}.${k}`).join(', ')}] })`\n );\n }\n if (spec.uuidV7 !== undefined) {\n used.add('check');\n needsSql = true;\n extras.push(\n `check('${spec.name}_uuid_v7', sql\\`substring(\\${table.${spec.uuidV7}}::text, 15, 1) = '7'\\`)`\n );\n }\n body.push(`/** ${spec.doc} */\\n${renderTable(exportName(key), spec.name, columns, extras)}`);\n }\n const imports = [...used].sort();\n return [\n '// Generated by `workflow-ui generate` from @shware/workflow — do not edit; regenerate after',\n '// upgrading the SDK and let drizzle-kit write the migration.',\n ...(needsSql ? [\"import { sql } from 'drizzle-orm';\"] : []),\n `import {\\n${imports.map((name) => ` ${name},`).join('\\n')}\\n} from 'drizzle-orm/pg-core';`,\n '',\n `export const workflowSchema = pgSchema('${schema}');`,\n '',\n body.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
package/dist/templates.cjs
CHANGED
|
@@ -27,31 +27,83 @@ function slackTemplate(definition) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Declare an email template
|
|
31
|
-
*
|
|
30
|
+
* Declare an email template: with a `topic` the contract declares, or `transactional: true`
|
|
31
|
+
* (then `T` is `never`, and the template's `topic` is null). Throws when neither (or both) is
|
|
32
|
+
* given, a transactional template carries an `unsubscribeUrl`, `from` is missing or invalid,
|
|
33
|
+
* `replyTo` or `unsubscribeUrl` is invalid, `props` uses an unsupported schema or `preview` does
|
|
34
|
+
* not parse. Whether the topic is one the contract declares is checked by `compileConfig`,
|
|
35
|
+
* where the contract is at hand — or at the template itself by `templatesFor(contract)`'s
|
|
36
|
+
* `emailTemplate`, the form to prefer in a project with a contract.
|
|
32
37
|
*/
|
|
33
38
|
function emailTemplate(definition) {
|
|
39
|
+
return declareEmail(definition, void 0);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Declare a push template: with a `topic` the contract declares (the user can opt out of it in
|
|
43
|
+
* the app — App Store Review Guideline 4.5.4 requires that for marketing pushes), or
|
|
44
|
+
* `transactional: true` (then `T` is `never`, and the template's `topic` is null). Throws when
|
|
45
|
+
* neither (or both) is given, `props` uses an unsupported schema or `preview` does not parse.
|
|
46
|
+
* Whether the topic is one the contract declares is checked by `compileConfig`, where the
|
|
47
|
+
* contract is at hand — or at the template itself by `templatesFor(contract)`'s `pushTemplate`.
|
|
48
|
+
*/
|
|
49
|
+
function pushTemplate(definition) {
|
|
50
|
+
return declarePush(definition, void 0);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The template factories bound to a contract: the same `emailTemplate` and `pushTemplate`, with
|
|
54
|
+
* the topic typed by the contract's `subscriptions.topics` — so a topic the contract does not
|
|
55
|
+
* declare is an error at the template's own `topic` line (in the editor, and when the module
|
|
56
|
+
* loads), not a type chain at the config's `templates` where the registry meets the contract.
|
|
57
|
+
* The form to use wherever a contract exists:
|
|
58
|
+
*
|
|
59
|
+
* const { emailTemplate, pushTemplate } = templatesFor(contract);
|
|
60
|
+
*
|
|
61
|
+
* The bare `emailTemplate` / `pushTemplate` stay for a project without a contract (transactional
|
|
62
|
+
* templates only) and for the compile, which checks every template again.
|
|
63
|
+
*/
|
|
64
|
+
function templatesFor(contract) {
|
|
65
|
+
return {
|
|
66
|
+
emailTemplate: (definition) => declareEmail(definition, contract),
|
|
67
|
+
pushTemplate: (definition) => declarePush(definition, contract)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function declareEmail(definition, contract) {
|
|
34
71
|
assertTemplateId(definition.id, "emailTemplate");
|
|
35
|
-
|
|
72
|
+
const where = `emailTemplate('${definition.id}')`;
|
|
73
|
+
require_email.assertEmailMetadata(definition, where);
|
|
74
|
+
assertDeclaredTopic(definition, where, contract);
|
|
36
75
|
assertSupportedSchema(definition.props, definition.title);
|
|
37
76
|
parsePreview(definition.props, definition.preview, "email", definition.title);
|
|
77
|
+
const { topic, transactional, ...rest } = definition;
|
|
38
78
|
return {
|
|
39
79
|
kind: "email-template",
|
|
40
80
|
version: 1,
|
|
41
|
-
...
|
|
81
|
+
...rest,
|
|
82
|
+
topic: topic ?? null
|
|
42
83
|
};
|
|
43
84
|
}
|
|
44
|
-
|
|
45
|
-
function pushTemplate(definition) {
|
|
85
|
+
function declarePush(definition, contract) {
|
|
46
86
|
assertTemplateId(definition.id, "pushTemplate");
|
|
87
|
+
const where = `pushTemplate('${definition.id}')`;
|
|
88
|
+
require_email.assertTemplateTopic(definition, where, "push");
|
|
89
|
+
assertDeclaredTopic(definition, where, contract);
|
|
47
90
|
assertSupportedSchema(definition.props, definition.title);
|
|
48
91
|
parsePreview(definition.props, definition.preview, "push", definition.title);
|
|
92
|
+
const { topic, transactional, ...rest } = definition;
|
|
49
93
|
return {
|
|
50
94
|
kind: "push-template",
|
|
51
95
|
version: 1,
|
|
52
|
-
...
|
|
96
|
+
...rest,
|
|
97
|
+
topic: topic ?? null
|
|
53
98
|
};
|
|
54
99
|
}
|
|
100
|
+
/** With a contract at hand (`templatesFor`), a topic it does not declare fails here, naming the list. */
|
|
101
|
+
function assertDeclaredTopic(definition, where, contract) {
|
|
102
|
+
const { topic } = definition;
|
|
103
|
+
if (contract === void 0 || typeof topic !== "string" || contract.topics.includes(topic)) return;
|
|
104
|
+
const list = contract.topics.length === 0 ? "the contract declares no topics (defineContract({ subscriptions: { topics: [...] } }))" : `the contract declares ${contract.topics.map((t) => `'${t}'`).join(", ")}`;
|
|
105
|
+
throw new TypeError(`${where}: topic '${topic}' is not declared — ${list}`);
|
|
106
|
+
}
|
|
55
107
|
function assertTemplateId(id, factory) {
|
|
56
108
|
if (!isTemplateId(id)) throw new TypeError(`${factory}(): 'id' must be a non-empty string`);
|
|
57
109
|
}
|
|
@@ -302,5 +354,6 @@ exports.renderDiscordContent = renderDiscordContent;
|
|
|
302
354
|
exports.renderPushContent = renderPushContent;
|
|
303
355
|
exports.renderSlackContent = renderSlackContent;
|
|
304
356
|
exports.slackTemplate = slackTemplate;
|
|
357
|
+
exports.templatesFor = templatesFor;
|
|
305
358
|
|
|
306
359
|
//# sourceMappingURL=templates.cjs.map
|