@hogsend/cli 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +221 -192
- package/dist/bin.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/connect-flow.test.ts +90 -23
- package/src/commands/connect.ts +31 -2
- package/src/lib/connect-flow.ts +89 -45
- package/src/lib/oauth.ts +11 -2
package/dist/bin.js
CHANGED
|
@@ -61,13 +61,13 @@ async function request(baseUrl, key, missingKeyMessage, method, path, opts) {
|
|
|
61
61
|
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
62
62
|
throw makeHttpError(`cannot reach ${baseUrl} (${msg})`, 0, void 0);
|
|
63
63
|
}
|
|
64
|
-
const
|
|
64
|
+
const text4 = await res.text();
|
|
65
65
|
let parsed;
|
|
66
|
-
if (
|
|
66
|
+
if (text4.length > 0) {
|
|
67
67
|
try {
|
|
68
|
-
parsed = JSON.parse(
|
|
68
|
+
parsed = JSON.parse(text4);
|
|
69
69
|
} catch {
|
|
70
|
-
parsed =
|
|
70
|
+
parsed = text4;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
if (!res.ok) {
|
|
@@ -149,7 +149,7 @@ function renderTable(rows, columns) {
|
|
|
149
149
|
const widths = cols.map(
|
|
150
150
|
(c) => Math.max(c.length, ...rows.map((r) => cell(r[c]).length))
|
|
151
151
|
);
|
|
152
|
-
const pad = (
|
|
152
|
+
const pad = (text4, width) => text4 + " ".repeat(width - text4.length);
|
|
153
153
|
const header = cols.map((c, i) => color.bold(pad(c, widths[i] ?? 0))).join(" ");
|
|
154
154
|
const sep4 = cols.map((_, i) => "-".repeat(widths[i] ?? 0)).join(" ");
|
|
155
155
|
const body = rows.map((r) => cols.map((c, i) => pad(cell(r[c]), widths[i] ?? 0)).join(" ")).join("\n");
|
|
@@ -472,7 +472,7 @@ var campaignsCommand = {
|
|
|
472
472
|
|
|
473
473
|
// src/commands/connect.ts
|
|
474
474
|
import { parseArgs as parseArgs2 } from "util";
|
|
475
|
-
import { confirm } from "@clack/prompts";
|
|
475
|
+
import { confirm, select, text } from "@clack/prompts";
|
|
476
476
|
|
|
477
477
|
// src/lib/browser.ts
|
|
478
478
|
import { spawn } from "child_process";
|
|
@@ -497,7 +497,7 @@ import { createServer } from "http";
|
|
|
497
497
|
// src/lib/oauth.ts
|
|
498
498
|
import { createHash, randomBytes } from "crypto";
|
|
499
499
|
var POSTHOG_CLIENT_ID = "https://hogsend.com/.well-known/hogsend-posthog-client.json";
|
|
500
|
-
var POSTHOG_SCOPES = "person:read person:write project:read hog_function:write";
|
|
500
|
+
var POSTHOG_SCOPES = "person:read person:write project:read organization:read hog_function:read hog_function:write feature_flag:read cohort:read cohort:write query:read insight:read event_definition:read property_definition:read";
|
|
501
501
|
var LOOPBACK_PORTS = [8423, 8424, 8425];
|
|
502
502
|
var CALLBACK_PATH = "/callback";
|
|
503
503
|
var CALLBACK_TIMEOUT_MS = 3e5;
|
|
@@ -584,10 +584,10 @@ async function exchangeCode(opts) {
|
|
|
584
584
|
})
|
|
585
585
|
});
|
|
586
586
|
if (!res.ok) {
|
|
587
|
-
const
|
|
588
|
-
let detail =
|
|
587
|
+
const text4 = await res.text().catch(() => "");
|
|
588
|
+
let detail = text4;
|
|
589
589
|
try {
|
|
590
|
-
const parsed = JSON.parse(
|
|
590
|
+
const parsed = JSON.parse(text4);
|
|
591
591
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.error === "string") {
|
|
592
592
|
detail = parsed.error;
|
|
593
593
|
}
|
|
@@ -776,7 +776,10 @@ var ConnectError = class extends Error {
|
|
|
776
776
|
this.hint = hint;
|
|
777
777
|
}
|
|
778
778
|
};
|
|
779
|
-
var HINT_NOT_CONFIGURED = "
|
|
779
|
+
var HINT_NOT_CONFIGURED = "Pass --posthog-host https://eu.posthog.com (or https://us.posthog.com, or your self-hosted app URL) to pick the region to authorize against. Alternatively set POSTHOG_HOST on the instance, redeploy, then re-run.";
|
|
780
|
+
var POSTHOG_EU_HOST = "https://eu.posthog.com";
|
|
781
|
+
var POSTHOG_US_HOST = "https://us.posthog.com";
|
|
782
|
+
var normalizeHost = (host) => host.replace(/\/+$/, "");
|
|
780
783
|
var hintOauthUnsupported = (privateHost) => `${privateHost} doesn't advertise an OAuth server (discovery returned 404).
|
|
781
784
|
Self-hosted PostHog builds may not ship OAuth. Use a personal API key instead:
|
|
782
785
|
|
|
@@ -804,12 +807,6 @@ skipped (a destination pointing at localhost would be unreachable).
|
|
|
804
807
|
Once deployed, wire the loop against the real instance:
|
|
805
808
|
|
|
806
809
|
hogsend connect posthog --provision-only --url https://your-instance`;
|
|
807
|
-
var WEBHOOK_SECRET_NOTE = `Credential stored \u2014 but the PostHog -> Hogsend event loop needs a shared
|
|
808
|
-
webhook secret, and this instance doesn't have one yet. Finish the loop:
|
|
809
|
-
|
|
810
|
-
1. Generate a secret: openssl rand -hex 32
|
|
811
|
-
2. Set it on the instance: POSTHOG_WEBHOOK_SECRET=<secret> (api AND worker)
|
|
812
|
-
3. Redeploy, then run: hogsend connect posthog --provision-only`;
|
|
813
810
|
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
814
811
|
var httpErrorBody = (err) => {
|
|
815
812
|
if (!isHttpError(err)) return void 0;
|
|
@@ -843,13 +840,6 @@ function fromLoopbackError(err) {
|
|
|
843
840
|
}
|
|
844
841
|
}
|
|
845
842
|
async function runProvisionOnly(deps, info, base) {
|
|
846
|
-
if (info.webhookSecretConfigured === false) {
|
|
847
|
-
deps.out.note(WEBHOOK_SECRET_NOTE, "Webhook secret missing");
|
|
848
|
-
throw new ConnectError(
|
|
849
|
-
"webhook_secret_missing",
|
|
850
|
-
"POSTHOG_WEBHOOK_SECRET is not set on the instance \u2014 nothing to provision against"
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
843
|
if (isLoopbackUrl(info.apiPublicUrl)) {
|
|
854
844
|
deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
|
|
855
845
|
throw new ConnectError(
|
|
@@ -902,24 +892,39 @@ function printProvisioned(out, result) {
|
|
|
902
892
|
].join("\n")
|
|
903
893
|
);
|
|
904
894
|
}
|
|
895
|
+
async function resolvePrivateHost(deps, info, opts) {
|
|
896
|
+
if (info.privateHost !== null) {
|
|
897
|
+
return info.privateHost;
|
|
898
|
+
}
|
|
899
|
+
if (opts.posthogHost) {
|
|
900
|
+
return normalizeHost(opts.posthogHost);
|
|
901
|
+
}
|
|
902
|
+
if (deps.interactive) {
|
|
903
|
+
if (deps.selectRegion) {
|
|
904
|
+
return normalizeHost(await deps.selectRegion());
|
|
905
|
+
}
|
|
906
|
+
const useUs = await deps.confirm(
|
|
907
|
+
`Use PostHog US Cloud (${POSTHOG_US_HOST})? (No selects PostHog EU Cloud, ${POSTHOG_EU_HOST})`
|
|
908
|
+
);
|
|
909
|
+
return useUs ? POSTHOG_US_HOST : POSTHOG_EU_HOST;
|
|
910
|
+
}
|
|
911
|
+
throw new ConnectError(
|
|
912
|
+
"not_configured",
|
|
913
|
+
"this instance has no PostHog configuration",
|
|
914
|
+
HINT_NOT_CONFIGURED
|
|
915
|
+
);
|
|
916
|
+
}
|
|
905
917
|
async function runConnectPosthog(deps, opts) {
|
|
906
918
|
const base = deps.http.cfg.baseUrl;
|
|
907
919
|
const info = await deps.out.step(
|
|
908
920
|
`GET ${base}/v1/admin/analytics/connect-info`,
|
|
909
921
|
() => deps.http.get("/v1/admin/analytics/connect-info")
|
|
910
922
|
);
|
|
911
|
-
const privateHost = info.privateHost;
|
|
912
|
-
if (privateHost === null) {
|
|
913
|
-
throw new ConnectError(
|
|
914
|
-
"not_configured",
|
|
915
|
-
"this instance has no PostHog configuration",
|
|
916
|
-
HINT_NOT_CONFIGURED
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
923
|
if (opts.provisionOnly) {
|
|
920
924
|
return runProvisionOnly(deps, info, base);
|
|
921
925
|
}
|
|
922
|
-
|
|
926
|
+
const privateHost = await resolvePrivateHost(deps, info, opts);
|
|
927
|
+
if (info.privateHost !== null && info.hostExplicit === false) {
|
|
923
928
|
if (deps.interactive) {
|
|
924
929
|
const proceed = await deps.confirm(
|
|
925
930
|
`No POSTHOG_HOST set on the instance \u2014 assume PostHog US Cloud (${privateHost})?`
|
|
@@ -1068,6 +1073,13 @@ async function runConnectPosthog(deps, opts) {
|
|
|
1068
1073
|
},
|
|
1069
1074
|
credential: { stored: true, expiresAt }
|
|
1070
1075
|
};
|
|
1076
|
+
const requestedScopes = POSTHOG_SCOPES.split(" ");
|
|
1077
|
+
const missingScopes = requestedScopes.filter((s) => !scopes.includes(s));
|
|
1078
|
+
if (missingScopes.length > 0) {
|
|
1079
|
+
deps.out.log(
|
|
1080
|
+
`note: PostHog granted ${scopes.length}/${requestedScopes.length} requested scope(s); missing: ${missingScopes.join(", ")}. Re-run \`hogsend connect posthog\` to grant the full set.`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1071
1083
|
if (opts.noProvision) {
|
|
1072
1084
|
return {
|
|
1073
1085
|
verdict: "connected_no_provision",
|
|
@@ -1075,14 +1087,6 @@ async function runConnectPosthog(deps, opts) {
|
|
|
1075
1087
|
provision: { attempted: false, skipped: "no_provision_flag" }
|
|
1076
1088
|
};
|
|
1077
1089
|
}
|
|
1078
|
-
if (info.webhookSecretConfigured === false) {
|
|
1079
|
-
deps.out.note(WEBHOOK_SECRET_NOTE, "Webhook secret missing");
|
|
1080
|
-
return {
|
|
1081
|
-
verdict: "connected_no_provision",
|
|
1082
|
-
...stored,
|
|
1083
|
-
provision: { attempted: false, skipped: "webhook_secret_missing" }
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
1090
|
if (isLoopbackUrl(info.apiPublicUrl)) {
|
|
1087
1091
|
deps.out.note(LOOPBACK_URL_NOTE, "Instance not publicly reachable");
|
|
1088
1092
|
return {
|
|
@@ -1135,7 +1139,7 @@ function bail(value) {
|
|
|
1135
1139
|
}
|
|
1136
1140
|
|
|
1137
1141
|
// src/commands/connect.ts
|
|
1138
|
-
var usage2 = `hogsend connect <provider> [--provision-only] [--no-provision] [--no-browser] [--json]
|
|
1142
|
+
var usage2 = `hogsend connect <provider> [--posthog-host <url>] [--provision-only] [--no-provision] [--no-browser] [--json]
|
|
1139
1143
|
|
|
1140
1144
|
Connect this Hogsend instance to an analytics provider via OAuth. Providers:
|
|
1141
1145
|
|
|
@@ -1149,6 +1153,10 @@ The browser consent must happen on THIS machine (the OAuth callback lands on
|
|
|
1149
1153
|
this command from your laptop, not from an SSH session on the server.
|
|
1150
1154
|
|
|
1151
1155
|
Options:
|
|
1156
|
+
--posthog-host PostHog app/private host to authorize against, e.g.
|
|
1157
|
+
https://eu.posthog.com or https://us.posthog.com (NOT the
|
|
1158
|
+
i. ingestion host). Required when the instance has no
|
|
1159
|
+
PostHog config and you're running non-interactively.
|
|
1152
1160
|
--provision-only Skip OAuth; (re-)provision the event loop using the
|
|
1153
1161
|
already-stored credential.
|
|
1154
1162
|
--no-provision Stop after storing the credential.
|
|
@@ -1163,6 +1171,7 @@ async function run2(ctx) {
|
|
|
1163
1171
|
allowPositionals: true,
|
|
1164
1172
|
strict: false,
|
|
1165
1173
|
options: {
|
|
1174
|
+
"posthog-host": { type: "string" },
|
|
1166
1175
|
"provision-only": { type: "boolean", default: false },
|
|
1167
1176
|
"no-provision": { type: "boolean", default: false },
|
|
1168
1177
|
"no-browser": { type: "boolean", default: false },
|
|
@@ -1204,13 +1213,33 @@ async function run2(ctx) {
|
|
|
1204
1213
|
exchangeCode,
|
|
1205
1214
|
openBrowser,
|
|
1206
1215
|
confirm: async (message) => bail(await confirm({ message })),
|
|
1216
|
+
selectRegion: async () => {
|
|
1217
|
+
const choice = bail(
|
|
1218
|
+
await select({
|
|
1219
|
+
message: "Which PostHog region should Hogsend authorize against?",
|
|
1220
|
+
options: [
|
|
1221
|
+
{ value: "https://eu.posthog.com", label: "PostHog EU Cloud" },
|
|
1222
|
+
{ value: "https://us.posthog.com", label: "PostHog US Cloud" },
|
|
1223
|
+
{ value: "custom", label: "Custom / self-hosted" }
|
|
1224
|
+
]
|
|
1225
|
+
})
|
|
1226
|
+
);
|
|
1227
|
+
if (choice !== "custom") return choice;
|
|
1228
|
+
return bail(
|
|
1229
|
+
await text({
|
|
1230
|
+
message: "PostHog app/private host URL (e.g. https://posthog.example.com)",
|
|
1231
|
+
placeholder: "https://posthog.example.com"
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
},
|
|
1207
1235
|
now: () => /* @__PURE__ */ new Date()
|
|
1208
1236
|
};
|
|
1209
1237
|
try {
|
|
1210
1238
|
const result = await runConnectPosthog(deps, {
|
|
1211
1239
|
provisionOnly: Boolean(values2["provision-only"]),
|
|
1212
1240
|
noProvision: Boolean(values2["no-provision"]),
|
|
1213
|
-
noBrowser: Boolean(values2["no-browser"])
|
|
1241
|
+
noBrowser: Boolean(values2["no-browser"]),
|
|
1242
|
+
posthogHost: typeof values2["posthog-host"] === "string" ? values2["posthog-host"] : void 0
|
|
1214
1243
|
});
|
|
1215
1244
|
if (ctx.json) {
|
|
1216
1245
|
ctx.out.json({ ok: true, ...result });
|
|
@@ -4928,7 +4957,7 @@ import { parseArgs as parseArgs18 } from "util";
|
|
|
4928
4957
|
|
|
4929
4958
|
// src/commands/studio-admin.ts
|
|
4930
4959
|
import { parseArgs as parseArgs17 } from "util";
|
|
4931
|
-
import { password as passwordPrompt, text as
|
|
4960
|
+
import { password as passwordPrompt, text as text3 } from "@clack/prompts";
|
|
4932
4961
|
|
|
4933
4962
|
// ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/index.js
|
|
4934
4963
|
import os from "os";
|
|
@@ -5234,7 +5263,7 @@ function values(first, rest, parameters, types2, options) {
|
|
|
5234
5263
|
const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first);
|
|
5235
5264
|
return valuesBuilder(multi ? first : [first], parameters, types2, columns, options);
|
|
5236
5265
|
}
|
|
5237
|
-
function
|
|
5266
|
+
function select2(first, rest, parameters, types2, options) {
|
|
5238
5267
|
typeof first === "string" && (first = [first].concat(rest));
|
|
5239
5268
|
if (Array.isArray(first))
|
|
5240
5269
|
return escapeIdentifiers(first, options);
|
|
@@ -5251,10 +5280,10 @@ var builders = Object.entries({
|
|
|
5251
5280
|
const x = values(...xs);
|
|
5252
5281
|
return x === "()" ? "(null)" : x;
|
|
5253
5282
|
},
|
|
5254
|
-
select,
|
|
5255
|
-
as:
|
|
5256
|
-
returning:
|
|
5257
|
-
"\\(":
|
|
5283
|
+
select: select2,
|
|
5284
|
+
as: select2,
|
|
5285
|
+
returning: select2,
|
|
5286
|
+
"\\(": select2,
|
|
5258
5287
|
update(first, rest, parameters, types2, options) {
|
|
5259
5288
|
return (rest.length ? rest.flat() : Object.keys(first)).map(
|
|
5260
5289
|
(x) => escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) + "=" + stringifyValue("values", first[x], parameters, types2, options)
|
|
@@ -9602,7 +9631,7 @@ var PgText = class extends PgColumn {
|
|
|
9602
9631
|
return "text";
|
|
9603
9632
|
}
|
|
9604
9633
|
};
|
|
9605
|
-
function
|
|
9634
|
+
function text2(a, b2 = {}) {
|
|
9606
9635
|
const { name, config } = getColumnNameAndConfig(a, b2);
|
|
9607
9636
|
return new PgTextBuilder(name, config);
|
|
9608
9637
|
}
|
|
@@ -9924,7 +9953,7 @@ function getPgColumnBuilders() {
|
|
|
9924
9953
|
serial,
|
|
9925
9954
|
smallint,
|
|
9926
9955
|
smallserial,
|
|
9927
|
-
text,
|
|
9956
|
+
text: text2,
|
|
9928
9957
|
time,
|
|
9929
9958
|
timestamp,
|
|
9930
9959
|
uuid,
|
|
@@ -10933,14 +10962,14 @@ var PgDialect = class {
|
|
|
10933
10962
|
const offsetSql = offset ? sql` offset ${offset}` : void 0;
|
|
10934
10963
|
return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`;
|
|
10935
10964
|
}
|
|
10936
|
-
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select:
|
|
10965
|
+
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select: select3, overridingSystemValue_ }) {
|
|
10937
10966
|
const valuesSqlList = [];
|
|
10938
10967
|
const columns = table[Table.Symbol.Columns];
|
|
10939
10968
|
const colEntries = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert());
|
|
10940
10969
|
const insertOrder = colEntries.map(
|
|
10941
10970
|
([, column]) => sql.identifier(this.casing.getColumnCasing(column))
|
|
10942
10971
|
);
|
|
10943
|
-
if (
|
|
10972
|
+
if (select3) {
|
|
10944
10973
|
const select22 = valuesOrSelect;
|
|
10945
10974
|
if (is(select22, SQL)) {
|
|
10946
10975
|
valuesSqlList.push(select22);
|
|
@@ -12513,10 +12542,10 @@ var PgSelectBase = class extends PgSelectQueryBuilderBase {
|
|
|
12513
12542
|
applyMixins(PgSelectBase, [QueryPromise]);
|
|
12514
12543
|
function createSetOperator(type, isAll) {
|
|
12515
12544
|
return (leftSelect, rightSelect, ...restSelects) => {
|
|
12516
|
-
const setOperators = [rightSelect, ...restSelects].map((
|
|
12545
|
+
const setOperators = [rightSelect, ...restSelects].map((select3) => ({
|
|
12517
12546
|
type,
|
|
12518
12547
|
isAll,
|
|
12519
|
-
rightSelect:
|
|
12548
|
+
rightSelect: select3
|
|
12520
12549
|
}));
|
|
12521
12550
|
for (const setOperator of setOperators) {
|
|
12522
12551
|
if (!haveSameKeys(leftSelect.getSelectedFields(), setOperator.rightSelect.getSelectedFields())) {
|
|
@@ -12572,7 +12601,7 @@ var QueryBuilder = class {
|
|
|
12572
12601
|
};
|
|
12573
12602
|
with(...queries) {
|
|
12574
12603
|
const self = this;
|
|
12575
|
-
function
|
|
12604
|
+
function select3(fields) {
|
|
12576
12605
|
return new PgSelectBuilder({
|
|
12577
12606
|
fields: fields ?? void 0,
|
|
12578
12607
|
session: void 0,
|
|
@@ -12596,7 +12625,7 @@ var QueryBuilder = class {
|
|
|
12596
12625
|
distinct: { on }
|
|
12597
12626
|
});
|
|
12598
12627
|
}
|
|
12599
|
-
return { select:
|
|
12628
|
+
return { select: select3, selectDistinct, selectDistinctOn };
|
|
12600
12629
|
}
|
|
12601
12630
|
select(fields) {
|
|
12602
12631
|
return new PgSelectBuilder({
|
|
@@ -12785,21 +12814,21 @@ var PgInsertBuilder = class {
|
|
|
12785
12814
|
).setToken(this.authToken);
|
|
12786
12815
|
}
|
|
12787
12816
|
select(selectQuery) {
|
|
12788
|
-
const
|
|
12789
|
-
if (!is(
|
|
12817
|
+
const select3 = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery;
|
|
12818
|
+
if (!is(select3, SQL) && !haveSameKeys(this.table[Columns], select3._.selectedFields)) {
|
|
12790
12819
|
throw new Error(
|
|
12791
12820
|
"Insert select error: selected fields are not the same or are in a different order compared to the table definition"
|
|
12792
12821
|
);
|
|
12793
12822
|
}
|
|
12794
|
-
return new PgInsertBase(this.table,
|
|
12823
|
+
return new PgInsertBase(this.table, select3, this.session, this.dialect, this.withList, true);
|
|
12795
12824
|
}
|
|
12796
12825
|
};
|
|
12797
12826
|
var PgInsertBase = class extends QueryPromise {
|
|
12798
|
-
constructor(table, values2, session2, dialect, withList,
|
|
12827
|
+
constructor(table, values2, session2, dialect, withList, select3, overridingSystemValue_) {
|
|
12799
12828
|
super();
|
|
12800
12829
|
this.session = session2;
|
|
12801
12830
|
this.dialect = dialect;
|
|
12802
|
-
this.config = { table, values: values2, withList, select:
|
|
12831
|
+
this.config = { table, values: values2, withList, select: select3, overridingSystemValue_ };
|
|
12803
12832
|
}
|
|
12804
12833
|
static [entityKind] = "PgInsert";
|
|
12805
12834
|
config;
|
|
@@ -13502,7 +13531,7 @@ var PgDatabase = class {
|
|
|
13502
13531
|
*/
|
|
13503
13532
|
with(...queries) {
|
|
13504
13533
|
const self = this;
|
|
13505
|
-
function
|
|
13534
|
+
function select3(fields) {
|
|
13506
13535
|
return new PgSelectBuilder({
|
|
13507
13536
|
fields: fields ?? void 0,
|
|
13508
13537
|
session: self.session,
|
|
@@ -13537,7 +13566,7 @@ var PgDatabase = class {
|
|
|
13537
13566
|
function delete_(table) {
|
|
13538
13567
|
return new PgDeleteBase(table, self.session, self.dialect, queries);
|
|
13539
13568
|
}
|
|
13540
|
-
return { select:
|
|
13569
|
+
return { select: select3, selectDistinct, selectDistinctOn, update, insert, delete: delete_ };
|
|
13541
13570
|
}
|
|
13542
13571
|
select(fields) {
|
|
13543
13572
|
return new PgSelectBuilder({
|
|
@@ -14201,7 +14230,7 @@ var alertRules = pgTable(
|
|
|
14201
14230
|
"alert_rules",
|
|
14202
14231
|
{
|
|
14203
14232
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14204
|
-
name:
|
|
14233
|
+
name: text2("name").notNull(),
|
|
14205
14234
|
type: alertRuleTypeEnum("type").notNull(),
|
|
14206
14235
|
threshold: jsonb("threshold").$type().notNull(),
|
|
14207
14236
|
channel: alertChannelEnum("channel").notNull(),
|
|
@@ -14226,8 +14255,8 @@ var alertHistory = pgTable(
|
|
|
14226
14255
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14227
14256
|
alertRuleId: uuid("alert_rule_id").notNull().references(() => alertRules.id),
|
|
14228
14257
|
payload: jsonb("payload").$type(),
|
|
14229
|
-
deliveryStatus:
|
|
14230
|
-
error:
|
|
14258
|
+
deliveryStatus: text2("delivery_status").notNull(),
|
|
14259
|
+
error: text2("error"),
|
|
14231
14260
|
...timestamps
|
|
14232
14261
|
},
|
|
14233
14262
|
(table) => [
|
|
@@ -14241,12 +14270,12 @@ var apiKeys = pgTable(
|
|
|
14241
14270
|
"api_keys",
|
|
14242
14271
|
{
|
|
14243
14272
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14244
|
-
organizationId:
|
|
14245
|
-
name:
|
|
14246
|
-
keyPrefix:
|
|
14247
|
-
keyHash:
|
|
14273
|
+
organizationId: text2("organization_id"),
|
|
14274
|
+
name: text2("name").notNull(),
|
|
14275
|
+
keyPrefix: text2("key_prefix").notNull(),
|
|
14276
|
+
keyHash: text2("key_hash").notNull().unique(),
|
|
14248
14277
|
scopes: jsonb("scopes").$type().notNull().default(["read"]),
|
|
14249
|
-
createdBy:
|
|
14278
|
+
createdBy: text2("created_by"),
|
|
14250
14279
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
14251
14280
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
14252
14281
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
@@ -14263,13 +14292,13 @@ var auditLogs = pgTable(
|
|
|
14263
14292
|
"audit_logs",
|
|
14264
14293
|
{
|
|
14265
14294
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14266
|
-
actor:
|
|
14295
|
+
actor: text2("actor").notNull(),
|
|
14267
14296
|
actorKeyId: uuid("actor_key_id"),
|
|
14268
|
-
action:
|
|
14269
|
-
resource:
|
|
14270
|
-
resourceId:
|
|
14297
|
+
action: text2("action").notNull(),
|
|
14298
|
+
resource: text2("resource").notNull(),
|
|
14299
|
+
resourceId: text2("resource_id"),
|
|
14271
14300
|
detail: jsonb("detail").$type(),
|
|
14272
|
-
ipAddress:
|
|
14301
|
+
ipAddress: text2("ip_address"),
|
|
14273
14302
|
...timestamps
|
|
14274
14303
|
},
|
|
14275
14304
|
(table) => [
|
|
@@ -14281,71 +14310,71 @@ var auditLogs = pgTable(
|
|
|
14281
14310
|
|
|
14282
14311
|
// ../db/src/schema/auth.ts
|
|
14283
14312
|
var user = pgTable("user", {
|
|
14284
|
-
id:
|
|
14285
|
-
name:
|
|
14286
|
-
email:
|
|
14313
|
+
id: text2("id").primaryKey(),
|
|
14314
|
+
name: text2("name").notNull(),
|
|
14315
|
+
email: text2("email").notNull().unique(),
|
|
14287
14316
|
emailVerified: boolean("email_verified").notNull().default(false),
|
|
14288
|
-
image:
|
|
14317
|
+
image: text2("image"),
|
|
14289
14318
|
...timestamps
|
|
14290
14319
|
});
|
|
14291
14320
|
var session = pgTable("session", {
|
|
14292
|
-
id:
|
|
14321
|
+
id: text2("id").primaryKey(),
|
|
14293
14322
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14294
|
-
token:
|
|
14295
|
-
ipAddress:
|
|
14296
|
-
userAgent:
|
|
14297
|
-
userId:
|
|
14298
|
-
activeOrganizationId:
|
|
14323
|
+
token: text2("token").notNull().unique(),
|
|
14324
|
+
ipAddress: text2("ip_address"),
|
|
14325
|
+
userAgent: text2("user_agent"),
|
|
14326
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14327
|
+
activeOrganizationId: text2("active_organization_id"),
|
|
14299
14328
|
...timestamps
|
|
14300
14329
|
});
|
|
14301
14330
|
var account = pgTable("account", {
|
|
14302
|
-
id:
|
|
14303
|
-
accountId:
|
|
14304
|
-
providerId:
|
|
14305
|
-
userId:
|
|
14306
|
-
accessToken:
|
|
14307
|
-
refreshToken:
|
|
14308
|
-
idToken:
|
|
14331
|
+
id: text2("id").primaryKey(),
|
|
14332
|
+
accountId: text2("account_id").notNull(),
|
|
14333
|
+
providerId: text2("provider_id").notNull(),
|
|
14334
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14335
|
+
accessToken: text2("access_token"),
|
|
14336
|
+
refreshToken: text2("refresh_token"),
|
|
14337
|
+
idToken: text2("id_token"),
|
|
14309
14338
|
accessTokenExpiresAt: timestamp("access_token_expires_at", {
|
|
14310
14339
|
withTimezone: true
|
|
14311
14340
|
}),
|
|
14312
14341
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
|
|
14313
14342
|
withTimezone: true
|
|
14314
14343
|
}),
|
|
14315
|
-
scope:
|
|
14316
|
-
password:
|
|
14344
|
+
scope: text2("scope"),
|
|
14345
|
+
password: text2("password"),
|
|
14317
14346
|
...timestamps
|
|
14318
14347
|
});
|
|
14319
14348
|
var verification = pgTable("verification", {
|
|
14320
|
-
id:
|
|
14321
|
-
identifier:
|
|
14322
|
-
value:
|
|
14349
|
+
id: text2("id").primaryKey(),
|
|
14350
|
+
identifier: text2("identifier").notNull(),
|
|
14351
|
+
value: text2("value").notNull(),
|
|
14323
14352
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14324
14353
|
...timestamps
|
|
14325
14354
|
});
|
|
14326
14355
|
var organization = pgTable("organization", {
|
|
14327
|
-
id:
|
|
14328
|
-
name:
|
|
14329
|
-
slug:
|
|
14330
|
-
logo:
|
|
14331
|
-
metadata:
|
|
14356
|
+
id: text2("id").primaryKey(),
|
|
14357
|
+
name: text2("name").notNull(),
|
|
14358
|
+
slug: text2("slug").unique(),
|
|
14359
|
+
logo: text2("logo"),
|
|
14360
|
+
metadata: text2("metadata"),
|
|
14332
14361
|
...timestamps
|
|
14333
14362
|
});
|
|
14334
14363
|
var member = pgTable("member", {
|
|
14335
|
-
id:
|
|
14336
|
-
organizationId:
|
|
14337
|
-
userId:
|
|
14338
|
-
role:
|
|
14364
|
+
id: text2("id").primaryKey(),
|
|
14365
|
+
organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
|
|
14366
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14367
|
+
role: text2("role").notNull().default("member"),
|
|
14339
14368
|
...timestamps
|
|
14340
14369
|
});
|
|
14341
14370
|
var invitation = pgTable("invitation", {
|
|
14342
|
-
id:
|
|
14343
|
-
organizationId:
|
|
14344
|
-
email:
|
|
14345
|
-
role:
|
|
14346
|
-
status:
|
|
14371
|
+
id: text2("id").primaryKey(),
|
|
14372
|
+
organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
|
|
14373
|
+
email: text2("email").notNull(),
|
|
14374
|
+
role: text2("role"),
|
|
14375
|
+
status: text2("status").notNull().default("pending"),
|
|
14347
14376
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14348
|
-
inviterId:
|
|
14377
|
+
inviterId: text2("inviter_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14349
14378
|
...timestamps
|
|
14350
14379
|
});
|
|
14351
14380
|
|
|
@@ -14354,12 +14383,12 @@ var bucketConfigs = pgTable(
|
|
|
14354
14383
|
"bucket_configs",
|
|
14355
14384
|
{
|
|
14356
14385
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14357
|
-
bucketId:
|
|
14386
|
+
bucketId: text2("bucket_id").notNull(),
|
|
14358
14387
|
enabled: boolean("enabled").notNull().default(true),
|
|
14359
14388
|
// Stable hash of the normalized ConditionEval, written at boot. Diffed on the
|
|
14360
14389
|
// next boot to detect a CRITERIA CHANGE and enqueue the re-evaluation job
|
|
14361
14390
|
// (Section 6.6 B). Nullable until the first registration.
|
|
14362
|
-
criteriaHash:
|
|
14391
|
+
criteriaHash: text2("criteria_hash"),
|
|
14363
14392
|
...timestamps
|
|
14364
14393
|
},
|
|
14365
14394
|
(table) => [uniqueIndex("bucket_configs_bucket_id_idx").on(table.bucketId)]
|
|
@@ -14371,13 +14400,13 @@ var bucketMemberships = pgTable(
|
|
|
14371
14400
|
{
|
|
14372
14401
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14373
14402
|
// multi-tenant insurance (nullable today, NOT in the unique key — see note)
|
|
14374
|
-
organizationId:
|
|
14403
|
+
organizationId: text2("organization_id"),
|
|
14375
14404
|
// logical join to contacts.externalId — NO FK (matches userEvents /
|
|
14376
14405
|
// journeyStates; membership rows can predate a contacts row).
|
|
14377
|
-
userId:
|
|
14378
|
-
userEmail:
|
|
14406
|
+
userId: text2("user_id").notNull(),
|
|
14407
|
+
userEmail: text2("user_email"),
|
|
14379
14408
|
// denormalized so emitted events carry it
|
|
14380
|
-
bucketId:
|
|
14409
|
+
bucketId: text2("bucket_id").notNull(),
|
|
14381
14410
|
status: bucketMembershipStatusEnum("status").notNull().default("active"),
|
|
14382
14411
|
enteredAt: timestamp("entered_at", { withTimezone: true }).defaultNow().notNull(),
|
|
14383
14412
|
leftAt: timestamp("left_at", { withTimezone: true }),
|
|
@@ -14390,7 +14419,7 @@ var bucketMemberships = pgTable(
|
|
|
14390
14419
|
maxDwellAt: timestamp("max_dwell_at", { withTimezone: true }),
|
|
14391
14420
|
lastEvaluatedAt: timestamp("last_evaluated_at", { withTimezone: true }),
|
|
14392
14421
|
entryCount: integer("entry_count").notNull().default(1),
|
|
14393
|
-
source:
|
|
14422
|
+
source: text2("source"),
|
|
14394
14423
|
// "event" | "reconcile" | "backfill" | "manual"
|
|
14395
14424
|
context: jsonb("context").$type().default({}),
|
|
14396
14425
|
// Per-membership dwell bookkeeping. JSON map keyed by dwellLabel → ISO of
|
|
@@ -14458,18 +14487,18 @@ var campaigns = pgTable(
|
|
|
14458
14487
|
"campaigns",
|
|
14459
14488
|
{
|
|
14460
14489
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14461
|
-
organizationId:
|
|
14462
|
-
name:
|
|
14490
|
+
organizationId: text2("organization_id"),
|
|
14491
|
+
name: text2("name").notNull(),
|
|
14463
14492
|
// queued | sending | sent | failed
|
|
14464
|
-
status:
|
|
14493
|
+
status: text2("status").notNull().default("queued"),
|
|
14465
14494
|
// "list" | "bucket"
|
|
14466
|
-
audienceKind:
|
|
14495
|
+
audienceKind: text2("audience_kind").notNull(),
|
|
14467
14496
|
// the list id (ListRegistry) or bucket id (BucketRegistry)
|
|
14468
|
-
audienceId:
|
|
14469
|
-
templateKey:
|
|
14497
|
+
audienceId: text2("audience_id").notNull(),
|
|
14498
|
+
templateKey: text2("template_key").notNull(),
|
|
14470
14499
|
props: jsonb("props").$type().default({}),
|
|
14471
|
-
fromEmail:
|
|
14472
|
-
subject:
|
|
14500
|
+
fromEmail: text2("from_email"),
|
|
14501
|
+
subject: text2("subject"),
|
|
14473
14502
|
/**
|
|
14474
14503
|
* Optional client-supplied idempotency key (POST /v1/campaigns
|
|
14475
14504
|
* `Idempotency-Key` header / body field). A retried create with the same key
|
|
@@ -14478,7 +14507,7 @@ var campaigns = pgTable(
|
|
|
14478
14507
|
* idempotency key, double-sending the blast). Uniqueness is enforced by the
|
|
14479
14508
|
* partial-unique index below (NULL keys are unconstrained).
|
|
14480
14509
|
*/
|
|
14481
|
-
idempotencyKey:
|
|
14510
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14482
14511
|
totalRecipients: integer("total_recipients").notNull().default(0),
|
|
14483
14512
|
sentCount: integer("sent_count").notNull().default(0),
|
|
14484
14513
|
skippedCount: integer("skipped_count").notNull().default(0),
|
|
@@ -14502,7 +14531,7 @@ var contacts = pgTable(
|
|
|
14502
14531
|
"contacts",
|
|
14503
14532
|
{
|
|
14504
14533
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14505
|
-
organizationId:
|
|
14534
|
+
organizationId: text2("organization_id"),
|
|
14506
14535
|
/**
|
|
14507
14536
|
* Stable external/distinct id (= the `user_id` text key joined by every
|
|
14508
14537
|
* contact-referencing table). NULLABLE since D1: contacts can be email-only
|
|
@@ -14511,21 +14540,21 @@ var contacts = pgTable(
|
|
|
14511
14540
|
* — a soft-deleted loser row must be able to keep its stale external_id
|
|
14512
14541
|
* until a merge re-points it.
|
|
14513
14542
|
*/
|
|
14514
|
-
externalId:
|
|
14515
|
-
email:
|
|
14543
|
+
externalId: text2("external_id"),
|
|
14544
|
+
email: text2("email"),
|
|
14516
14545
|
/**
|
|
14517
14546
|
* Stable anonymous/distinct id for the future anonymous→identified path.
|
|
14518
14547
|
* NULLABLE. Like external_id, uniqueness is enforced by a partial-unique
|
|
14519
14548
|
* index scoped to live, non-deleted rows.
|
|
14520
14549
|
*/
|
|
14521
|
-
anonymousId:
|
|
14550
|
+
anonymousId: text2("anonymous_id"),
|
|
14522
14551
|
/**
|
|
14523
14552
|
* Opportunistic IANA-timezone cache (e.g. "America/New_York"). Populated
|
|
14524
14553
|
* best-effort when a tz is resolved from PostHog person props. PostHog and
|
|
14525
14554
|
* `properties` jsonb remain authoritative sources — this column sits below
|
|
14526
14555
|
* them in the resolution precedence, so nothing is blocked on it.
|
|
14527
14556
|
*/
|
|
14528
|
-
timezone:
|
|
14557
|
+
timezone: text2("timezone"),
|
|
14529
14558
|
properties: jsonb("properties").$type().default({}),
|
|
14530
14559
|
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).defaultNow().notNull(),
|
|
14531
14560
|
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).defaultNow().notNull(),
|
|
@@ -14556,15 +14585,15 @@ var contactAliases = pgTable(
|
|
|
14556
14585
|
// The SURVIVOR a stale key resolves TO.
|
|
14557
14586
|
contactId: uuid("contact_id").notNull().references(() => contacts.id, { onDelete: "cascade" }),
|
|
14558
14587
|
// 'email' | 'external' | 'anonymous'
|
|
14559
|
-
aliasKind:
|
|
14588
|
+
aliasKind: text2("alias_kind").notNull(),
|
|
14560
14589
|
// The stale key value (the loser's old external_id / normalized email /
|
|
14561
14590
|
// anonymous_id).
|
|
14562
|
-
aliasValue:
|
|
14591
|
+
aliasValue: text2("alias_value").notNull(),
|
|
14563
14592
|
// Provenance: the loser contact id this alias came from (nullable — a
|
|
14564
14593
|
// 'promote' alias may have no distinct loser row).
|
|
14565
14594
|
fromContactId: uuid("from_contact_id"),
|
|
14566
14595
|
// 'merge' | 'promote'
|
|
14567
|
-
reason:
|
|
14596
|
+
reason: text2("reason").notNull(),
|
|
14568
14597
|
...timestamps
|
|
14569
14598
|
},
|
|
14570
14599
|
(table) => [
|
|
@@ -14582,10 +14611,10 @@ var deadLetterQueue = pgTable(
|
|
|
14582
14611
|
"dead_letter_queue",
|
|
14583
14612
|
{
|
|
14584
14613
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14585
|
-
source:
|
|
14586
|
-
sourceId:
|
|
14614
|
+
source: text2("source").notNull(),
|
|
14615
|
+
sourceId: text2("source_id"),
|
|
14587
14616
|
payload: jsonb("payload").$type().notNull(),
|
|
14588
|
-
error:
|
|
14617
|
+
error: text2("error").notNull(),
|
|
14589
14618
|
retryCount: integer("retry_count").notNull().default(0),
|
|
14590
14619
|
status: dlqStatusEnum("status").notNull().default("pending"),
|
|
14591
14620
|
retriedAt: timestamp("retried_at", { withTimezone: true }),
|
|
@@ -14603,8 +14632,8 @@ var emailPreferences = pgTable(
|
|
|
14603
14632
|
"email_preferences",
|
|
14604
14633
|
{
|
|
14605
14634
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14606
|
-
userId:
|
|
14607
|
-
email:
|
|
14635
|
+
userId: text2("user_id").notNull(),
|
|
14636
|
+
email: text2("email").notNull(),
|
|
14608
14637
|
unsubscribedAll: boolean("unsubscribed_all").notNull().default(false),
|
|
14609
14638
|
suppressed: boolean("suppressed").notNull().default(false),
|
|
14610
14639
|
bounceCount: integer("bounce_count").notNull().default(0),
|
|
@@ -14626,15 +14655,15 @@ var journeyStates = pgTable(
|
|
|
14626
14655
|
"journey_states",
|
|
14627
14656
|
{
|
|
14628
14657
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14629
|
-
organizationId:
|
|
14630
|
-
userId:
|
|
14631
|
-
userEmail:
|
|
14632
|
-
journeyId:
|
|
14633
|
-
currentNodeId:
|
|
14658
|
+
organizationId: text2("organization_id"),
|
|
14659
|
+
userId: text2("user_id").notNull(),
|
|
14660
|
+
userEmail: text2("user_email").notNull(),
|
|
14661
|
+
journeyId: text2("journey_id").notNull(),
|
|
14662
|
+
currentNodeId: text2("current_node_id").notNull(),
|
|
14634
14663
|
status: journeyStatusEnum("status").notNull().default("active"),
|
|
14635
|
-
hatchetRunId:
|
|
14664
|
+
hatchetRunId: text2("hatchet_run_id"),
|
|
14636
14665
|
context: jsonb("context").$type().default({}),
|
|
14637
|
-
errorMessage:
|
|
14666
|
+
errorMessage: text2("error_message"),
|
|
14638
14667
|
entryCount: integer("entry_count").notNull().default(1),
|
|
14639
14668
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
14640
14669
|
exitedAt: timestamp("exited_at", { withTimezone: true }),
|
|
@@ -14672,19 +14701,19 @@ var emailSends = pgTable(
|
|
|
14672
14701
|
"email_sends",
|
|
14673
14702
|
{
|
|
14674
14703
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14675
|
-
organizationId:
|
|
14704
|
+
organizationId: text2("organization_id"),
|
|
14676
14705
|
journeyStateId: uuid("journey_state_id").references(() => journeyStates.id),
|
|
14677
14706
|
// Denormalized recipient identity, set at send time. Lets reporting attribute
|
|
14678
14707
|
// a send to a contact without joining journey_states, and captures journeyless
|
|
14679
14708
|
// (raw/batch) sends that have no journey linkage. Both nullable.
|
|
14680
|
-
userId:
|
|
14681
|
-
userEmail:
|
|
14682
|
-
templateKey:
|
|
14683
|
-
messageId:
|
|
14684
|
-
fromEmail:
|
|
14685
|
-
toEmail:
|
|
14686
|
-
subject:
|
|
14687
|
-
category:
|
|
14709
|
+
userId: text2("user_id"),
|
|
14710
|
+
userEmail: text2("user_email"),
|
|
14711
|
+
templateKey: text2("template_key"),
|
|
14712
|
+
messageId: text2("message_id"),
|
|
14713
|
+
fromEmail: text2("from_email").notNull(),
|
|
14714
|
+
toEmail: text2("to_email").notNull(),
|
|
14715
|
+
subject: text2("subject").notNull(),
|
|
14716
|
+
category: text2("category"),
|
|
14688
14717
|
status: emailSendStatusEnum("status").notNull().default("queued"),
|
|
14689
14718
|
sentAt: timestamp("sent_at", { withTimezone: true }),
|
|
14690
14719
|
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
@@ -14693,13 +14722,13 @@ var emailSends = pgTable(
|
|
|
14693
14722
|
bouncedAt: timestamp("bounced_at", { withTimezone: true }),
|
|
14694
14723
|
complainedAt: timestamp("complained_at", { withTimezone: true }),
|
|
14695
14724
|
// Bounce classification from the Resend webhook (hard/soft/transient + reason).
|
|
14696
|
-
bounceType:
|
|
14697
|
-
bounceReason:
|
|
14725
|
+
bounceType: text2("bounce_type"),
|
|
14726
|
+
bounceReason: text2("bounce_reason"),
|
|
14698
14727
|
// Caller-supplied idempotency key (POST /v1/emails). A retry with the same
|
|
14699
14728
|
// key short-circuits to the prior send instead of dispatching a duplicate —
|
|
14700
14729
|
// mirrors the user_events idempotency pattern. Nullable: journey/system sends
|
|
14701
14730
|
// don't set it.
|
|
14702
|
-
idempotencyKey:
|
|
14731
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14703
14732
|
// Free-form per-send annotations. Set ONLY by test-mode redirected sends
|
|
14704
14733
|
// today — `{ testMode: true, originalTo: <real recipient> }` — so Studio can
|
|
14705
14734
|
// flag a TEST row and show who the mail was REALLY for. Nullable: normal
|
|
@@ -14735,8 +14764,8 @@ var importJobs = pgTable(
|
|
|
14735
14764
|
"import_jobs",
|
|
14736
14765
|
{
|
|
14737
14766
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14738
|
-
fileName:
|
|
14739
|
-
format:
|
|
14767
|
+
fileName: text2("file_name"),
|
|
14768
|
+
format: text2("format").notNull(),
|
|
14740
14769
|
status: importJobStatusEnum("status").notNull().default("pending"),
|
|
14741
14770
|
totalRows: integer("total_rows"),
|
|
14742
14771
|
processedRows: integer("processed_rows").notNull().default(0),
|
|
@@ -14752,7 +14781,7 @@ var journeyConfigs = pgTable(
|
|
|
14752
14781
|
"journey_configs",
|
|
14753
14782
|
{
|
|
14754
14783
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14755
|
-
journeyId:
|
|
14784
|
+
journeyId: text2("journey_id").notNull(),
|
|
14756
14785
|
enabled: boolean("enabled").notNull().default(true),
|
|
14757
14786
|
...timestamps
|
|
14758
14787
|
},
|
|
@@ -14767,9 +14796,9 @@ var journeyLogs = pgTable(
|
|
|
14767
14796
|
{
|
|
14768
14797
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14769
14798
|
journeyStateId: uuid("journey_state_id").notNull().references(() => journeyStates.id, { onDelete: "cascade" }),
|
|
14770
|
-
fromNodeId:
|
|
14771
|
-
toNodeId:
|
|
14772
|
-
action:
|
|
14799
|
+
fromNodeId: text2("from_node_id"),
|
|
14800
|
+
toNodeId: text2("to_node_id"),
|
|
14801
|
+
action: text2("action").notNull(),
|
|
14773
14802
|
detail: jsonb("detail").$type(),
|
|
14774
14803
|
...timestamps
|
|
14775
14804
|
},
|
|
@@ -14784,12 +14813,12 @@ var trackedLinks = pgTable(
|
|
|
14784
14813
|
{
|
|
14785
14814
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14786
14815
|
emailSendId: uuid("email_send_id").notNull().references(() => emailSends.id, { onDelete: "cascade" }),
|
|
14787
|
-
originalUrl:
|
|
14816
|
+
originalUrl: text2("original_url").notNull(),
|
|
14788
14817
|
clickCount: integer("click_count").notNull().default(0),
|
|
14789
14818
|
// Semantic link metadata, lifted from the template's data-hs-* attributes
|
|
14790
14819
|
// at send time. NULL for plain tracked links. `event` is the consumer event
|
|
14791
14820
|
// name emitted at click time; `eventProperties` its scalar payload.
|
|
14792
|
-
event:
|
|
14821
|
+
event: text2("event"),
|
|
14793
14822
|
eventProperties: jsonb("event_properties").$type(),
|
|
14794
14823
|
// Set exactly once by the click route when the semantic event is emitted —
|
|
14795
14824
|
// the per-link emit-once gate today, and the provisional-then-confirm
|
|
@@ -14808,8 +14837,8 @@ var linkClicks = pgTable(
|
|
|
14808
14837
|
{
|
|
14809
14838
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14810
14839
|
trackedLinkId: uuid("tracked_link_id").notNull().references(() => trackedLinks.id, { onDelete: "cascade" }),
|
|
14811
|
-
ipAddress:
|
|
14812
|
-
userAgent:
|
|
14840
|
+
ipAddress: text2("ip_address"),
|
|
14841
|
+
userAgent: text2("user_agent"),
|
|
14813
14842
|
clickedAt: timestamp("clicked_at", { withTimezone: true }).defaultNow().notNull()
|
|
14814
14843
|
},
|
|
14815
14844
|
(table) => [
|
|
@@ -14825,11 +14854,11 @@ var providerCredentials = pgTable(
|
|
|
14825
14854
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14826
14855
|
// e.g. "posthog" — matches an AnalyticsProvider meta.id by convention,
|
|
14827
14856
|
// but deliberately NOT foreign-keyed: providers are code-defined.
|
|
14828
|
-
providerId:
|
|
14857
|
+
providerId: text2("provider_id").notNull(),
|
|
14829
14858
|
// "oauth" today; "api_key" is the anticipated future kind.
|
|
14830
|
-
kind:
|
|
14859
|
+
kind: text2("kind").notNull().default("oauth"),
|
|
14831
14860
|
// Encrypted JSON: base64url(iv || ciphertext || gcmTag).
|
|
14832
|
-
payload:
|
|
14861
|
+
payload: text2("payload").notNull(),
|
|
14833
14862
|
...timestamps
|
|
14834
14863
|
},
|
|
14835
14864
|
(table) => [
|
|
@@ -14847,11 +14876,11 @@ var userEvents = pgTable(
|
|
|
14847
14876
|
"user_events",
|
|
14848
14877
|
{
|
|
14849
14878
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14850
|
-
organizationId:
|
|
14851
|
-
userId:
|
|
14852
|
-
event:
|
|
14879
|
+
organizationId: text2("organization_id"),
|
|
14880
|
+
userId: text2("user_id").notNull(),
|
|
14881
|
+
event: text2("event").notNull(),
|
|
14853
14882
|
properties: jsonb("properties").$type(),
|
|
14854
|
-
idempotencyKey:
|
|
14883
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14855
14884
|
occurredAt: timestamp("occurred_at", { withTimezone: true }).defaultNow().notNull()
|
|
14856
14885
|
},
|
|
14857
14886
|
(table) => [
|
|
@@ -14872,15 +14901,15 @@ var webhookEndpoints = pgTable(
|
|
|
14872
14901
|
"webhook_endpoints",
|
|
14873
14902
|
{
|
|
14874
14903
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14875
|
-
organizationId:
|
|
14876
|
-
url:
|
|
14877
|
-
description:
|
|
14904
|
+
organizationId: text2("organization_id"),
|
|
14905
|
+
url: text2("url").notNull(),
|
|
14906
|
+
description: text2("description"),
|
|
14878
14907
|
// The delivery adapter selector. "webhook" (the default) is the signed
|
|
14879
14908
|
// Standard-Webhooks POST that existing subscribers receive — byte-identical
|
|
14880
14909
|
// to before this column existed. Any other value (e.g. "posthog") selects a
|
|
14881
14910
|
// delivery-time TRANSFORM adapter that reuses the same durable delivery
|
|
14882
14911
|
// machinery but rewrites url/headers/body for a vendor destination.
|
|
14883
|
-
kind:
|
|
14912
|
+
kind: text2("kind").notNull().default("webhook"),
|
|
14884
14913
|
// Per-destination configuration for keyed adapters (e.g. PostHog's
|
|
14885
14914
|
// `{ apiKey, host }`). Null for `kind="webhook"` (it reads `secret` instead).
|
|
14886
14915
|
// Keyed destinations keep their credentials HERE, not in a fake `whsec_`.
|
|
@@ -14889,9 +14918,9 @@ var webhookEndpoints = pgTable(
|
|
|
14889
14918
|
// Nullable: only `kind="webhook"` carries a signing secret; keyed
|
|
14890
14919
|
// destinations authenticate via `config` and the webhook adapter is the only
|
|
14891
14920
|
// reader of this column.
|
|
14892
|
-
secret:
|
|
14921
|
+
secret: text2("secret"),
|
|
14893
14922
|
// e.g. "whsec_AbCd" — safe to show on list/get. Nullable alongside `secret`.
|
|
14894
|
-
secretPrefix:
|
|
14923
|
+
secretPrefix: text2("secret_prefix"),
|
|
14895
14924
|
eventTypes: jsonb("event_types").$type().notNull().default([]),
|
|
14896
14925
|
disabled: boolean("disabled").notNull().default(false),
|
|
14897
14926
|
// written by the delivery task on a successful (2xx) delivery.
|
|
@@ -14912,13 +14941,13 @@ var webhookDeliveries = pgTable(
|
|
|
14912
14941
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14913
14942
|
endpointId: uuid("endpoint_id").notNull().references(() => webhookEndpoints.id, { onDelete: "cascade" }),
|
|
14914
14943
|
// denormalized, nullable (MT deferred).
|
|
14915
|
-
organizationId:
|
|
14944
|
+
organizationId: text2("organization_id"),
|
|
14916
14945
|
// == Webhook-Id header; ONE per logical event, shared across endpoints +
|
|
14917
14946
|
// reused across retries.
|
|
14918
|
-
webhookId:
|
|
14919
|
-
eventType:
|
|
14947
|
+
webhookId: text2("webhook_id").notNull(),
|
|
14948
|
+
eventType: text2("event_type").notNull(),
|
|
14920
14949
|
// producer-side dedup (idempotencyKey/stateId/emailSendId/...).
|
|
14921
|
-
dedupeKey:
|
|
14950
|
+
dedupeKey: text2("dedupe_key"),
|
|
14922
14951
|
// the EXACT signed envelope { id, type, timestamp, data }.
|
|
14923
14952
|
payload: jsonb("payload").$type().notNull(),
|
|
14924
14953
|
status: webhookDeliveryStatusEnum("status").notNull().default("pending"),
|
|
@@ -14927,9 +14956,9 @@ var webhookDeliveries = pgTable(
|
|
|
14927
14956
|
lastAttemptAt: timestamp("last_attempt_at", { withTimezone: true }),
|
|
14928
14957
|
responseStatus: integer("response_status"),
|
|
14929
14958
|
// truncated to ≤1KB in app.
|
|
14930
|
-
responseBodySnippet:
|
|
14959
|
+
responseBodySnippet: text2("response_body_snippet"),
|
|
14931
14960
|
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
14932
|
-
lastError:
|
|
14961
|
+
lastError: text2("last_error"),
|
|
14933
14962
|
...timestamps
|
|
14934
14963
|
},
|
|
14935
14964
|
(table) => [
|
|
@@ -15386,7 +15415,7 @@ async function resolveEmail(ctx, flags, defaultEmail) {
|
|
|
15386
15415
|
ctx.out.fail("--email is required (no TTY to prompt).");
|
|
15387
15416
|
}
|
|
15388
15417
|
const value = bail(
|
|
15389
|
-
await
|
|
15418
|
+
await text3({
|
|
15390
15419
|
message: "Admin email",
|
|
15391
15420
|
placeholder: defaultEmail ?? "admin@example.com",
|
|
15392
15421
|
initialValue: defaultEmail,
|