@hogsend/cli 0.20.0 → 0.21.1
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 +231 -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 +41 -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,43 @@ 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
|
+
validate: (value) => {
|
|
1233
|
+
try {
|
|
1234
|
+
const url = new URL(value ?? "");
|
|
1235
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1236
|
+
return "Enter a full URL, e.g. https://posthog.example.com";
|
|
1237
|
+
}
|
|
1238
|
+
} catch {
|
|
1239
|
+
return "Enter a full URL, e.g. https://posthog.example.com";
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
},
|
|
1207
1245
|
now: () => /* @__PURE__ */ new Date()
|
|
1208
1246
|
};
|
|
1209
1247
|
try {
|
|
1210
1248
|
const result = await runConnectPosthog(deps, {
|
|
1211
1249
|
provisionOnly: Boolean(values2["provision-only"]),
|
|
1212
1250
|
noProvision: Boolean(values2["no-provision"]),
|
|
1213
|
-
noBrowser: Boolean(values2["no-browser"])
|
|
1251
|
+
noBrowser: Boolean(values2["no-browser"]),
|
|
1252
|
+
posthogHost: typeof values2["posthog-host"] === "string" ? values2["posthog-host"] : void 0
|
|
1214
1253
|
});
|
|
1215
1254
|
if (ctx.json) {
|
|
1216
1255
|
ctx.out.json({ ok: true, ...result });
|
|
@@ -4928,7 +4967,7 @@ import { parseArgs as parseArgs18 } from "util";
|
|
|
4928
4967
|
|
|
4929
4968
|
// src/commands/studio-admin.ts
|
|
4930
4969
|
import { parseArgs as parseArgs17 } from "util";
|
|
4931
|
-
import { password as passwordPrompt, text as
|
|
4970
|
+
import { password as passwordPrompt, text as text3 } from "@clack/prompts";
|
|
4932
4971
|
|
|
4933
4972
|
// ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/index.js
|
|
4934
4973
|
import os from "os";
|
|
@@ -5234,7 +5273,7 @@ function values(first, rest, parameters, types2, options) {
|
|
|
5234
5273
|
const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first);
|
|
5235
5274
|
return valuesBuilder(multi ? first : [first], parameters, types2, columns, options);
|
|
5236
5275
|
}
|
|
5237
|
-
function
|
|
5276
|
+
function select2(first, rest, parameters, types2, options) {
|
|
5238
5277
|
typeof first === "string" && (first = [first].concat(rest));
|
|
5239
5278
|
if (Array.isArray(first))
|
|
5240
5279
|
return escapeIdentifiers(first, options);
|
|
@@ -5251,10 +5290,10 @@ var builders = Object.entries({
|
|
|
5251
5290
|
const x = values(...xs);
|
|
5252
5291
|
return x === "()" ? "(null)" : x;
|
|
5253
5292
|
},
|
|
5254
|
-
select,
|
|
5255
|
-
as:
|
|
5256
|
-
returning:
|
|
5257
|
-
"\\(":
|
|
5293
|
+
select: select2,
|
|
5294
|
+
as: select2,
|
|
5295
|
+
returning: select2,
|
|
5296
|
+
"\\(": select2,
|
|
5258
5297
|
update(first, rest, parameters, types2, options) {
|
|
5259
5298
|
return (rest.length ? rest.flat() : Object.keys(first)).map(
|
|
5260
5299
|
(x) => escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) + "=" + stringifyValue("values", first[x], parameters, types2, options)
|
|
@@ -9602,7 +9641,7 @@ var PgText = class extends PgColumn {
|
|
|
9602
9641
|
return "text";
|
|
9603
9642
|
}
|
|
9604
9643
|
};
|
|
9605
|
-
function
|
|
9644
|
+
function text2(a, b2 = {}) {
|
|
9606
9645
|
const { name, config } = getColumnNameAndConfig(a, b2);
|
|
9607
9646
|
return new PgTextBuilder(name, config);
|
|
9608
9647
|
}
|
|
@@ -9924,7 +9963,7 @@ function getPgColumnBuilders() {
|
|
|
9924
9963
|
serial,
|
|
9925
9964
|
smallint,
|
|
9926
9965
|
smallserial,
|
|
9927
|
-
text,
|
|
9966
|
+
text: text2,
|
|
9928
9967
|
time,
|
|
9929
9968
|
timestamp,
|
|
9930
9969
|
uuid,
|
|
@@ -10933,14 +10972,14 @@ var PgDialect = class {
|
|
|
10933
10972
|
const offsetSql = offset ? sql` offset ${offset}` : void 0;
|
|
10934
10973
|
return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`;
|
|
10935
10974
|
}
|
|
10936
|
-
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select:
|
|
10975
|
+
buildInsertQuery({ table, values: valuesOrSelect, onConflict, returning, withList, select: select3, overridingSystemValue_ }) {
|
|
10937
10976
|
const valuesSqlList = [];
|
|
10938
10977
|
const columns = table[Table.Symbol.Columns];
|
|
10939
10978
|
const colEntries = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert());
|
|
10940
10979
|
const insertOrder = colEntries.map(
|
|
10941
10980
|
([, column]) => sql.identifier(this.casing.getColumnCasing(column))
|
|
10942
10981
|
);
|
|
10943
|
-
if (
|
|
10982
|
+
if (select3) {
|
|
10944
10983
|
const select22 = valuesOrSelect;
|
|
10945
10984
|
if (is(select22, SQL)) {
|
|
10946
10985
|
valuesSqlList.push(select22);
|
|
@@ -12513,10 +12552,10 @@ var PgSelectBase = class extends PgSelectQueryBuilderBase {
|
|
|
12513
12552
|
applyMixins(PgSelectBase, [QueryPromise]);
|
|
12514
12553
|
function createSetOperator(type, isAll) {
|
|
12515
12554
|
return (leftSelect, rightSelect, ...restSelects) => {
|
|
12516
|
-
const setOperators = [rightSelect, ...restSelects].map((
|
|
12555
|
+
const setOperators = [rightSelect, ...restSelects].map((select3) => ({
|
|
12517
12556
|
type,
|
|
12518
12557
|
isAll,
|
|
12519
|
-
rightSelect:
|
|
12558
|
+
rightSelect: select3
|
|
12520
12559
|
}));
|
|
12521
12560
|
for (const setOperator of setOperators) {
|
|
12522
12561
|
if (!haveSameKeys(leftSelect.getSelectedFields(), setOperator.rightSelect.getSelectedFields())) {
|
|
@@ -12572,7 +12611,7 @@ var QueryBuilder = class {
|
|
|
12572
12611
|
};
|
|
12573
12612
|
with(...queries) {
|
|
12574
12613
|
const self = this;
|
|
12575
|
-
function
|
|
12614
|
+
function select3(fields) {
|
|
12576
12615
|
return new PgSelectBuilder({
|
|
12577
12616
|
fields: fields ?? void 0,
|
|
12578
12617
|
session: void 0,
|
|
@@ -12596,7 +12635,7 @@ var QueryBuilder = class {
|
|
|
12596
12635
|
distinct: { on }
|
|
12597
12636
|
});
|
|
12598
12637
|
}
|
|
12599
|
-
return { select:
|
|
12638
|
+
return { select: select3, selectDistinct, selectDistinctOn };
|
|
12600
12639
|
}
|
|
12601
12640
|
select(fields) {
|
|
12602
12641
|
return new PgSelectBuilder({
|
|
@@ -12785,21 +12824,21 @@ var PgInsertBuilder = class {
|
|
|
12785
12824
|
).setToken(this.authToken);
|
|
12786
12825
|
}
|
|
12787
12826
|
select(selectQuery) {
|
|
12788
|
-
const
|
|
12789
|
-
if (!is(
|
|
12827
|
+
const select3 = typeof selectQuery === "function" ? selectQuery(new QueryBuilder()) : selectQuery;
|
|
12828
|
+
if (!is(select3, SQL) && !haveSameKeys(this.table[Columns], select3._.selectedFields)) {
|
|
12790
12829
|
throw new Error(
|
|
12791
12830
|
"Insert select error: selected fields are not the same or are in a different order compared to the table definition"
|
|
12792
12831
|
);
|
|
12793
12832
|
}
|
|
12794
|
-
return new PgInsertBase(this.table,
|
|
12833
|
+
return new PgInsertBase(this.table, select3, this.session, this.dialect, this.withList, true);
|
|
12795
12834
|
}
|
|
12796
12835
|
};
|
|
12797
12836
|
var PgInsertBase = class extends QueryPromise {
|
|
12798
|
-
constructor(table, values2, session2, dialect, withList,
|
|
12837
|
+
constructor(table, values2, session2, dialect, withList, select3, overridingSystemValue_) {
|
|
12799
12838
|
super();
|
|
12800
12839
|
this.session = session2;
|
|
12801
12840
|
this.dialect = dialect;
|
|
12802
|
-
this.config = { table, values: values2, withList, select:
|
|
12841
|
+
this.config = { table, values: values2, withList, select: select3, overridingSystemValue_ };
|
|
12803
12842
|
}
|
|
12804
12843
|
static [entityKind] = "PgInsert";
|
|
12805
12844
|
config;
|
|
@@ -13502,7 +13541,7 @@ var PgDatabase = class {
|
|
|
13502
13541
|
*/
|
|
13503
13542
|
with(...queries) {
|
|
13504
13543
|
const self = this;
|
|
13505
|
-
function
|
|
13544
|
+
function select3(fields) {
|
|
13506
13545
|
return new PgSelectBuilder({
|
|
13507
13546
|
fields: fields ?? void 0,
|
|
13508
13547
|
session: self.session,
|
|
@@ -13537,7 +13576,7 @@ var PgDatabase = class {
|
|
|
13537
13576
|
function delete_(table) {
|
|
13538
13577
|
return new PgDeleteBase(table, self.session, self.dialect, queries);
|
|
13539
13578
|
}
|
|
13540
|
-
return { select:
|
|
13579
|
+
return { select: select3, selectDistinct, selectDistinctOn, update, insert, delete: delete_ };
|
|
13541
13580
|
}
|
|
13542
13581
|
select(fields) {
|
|
13543
13582
|
return new PgSelectBuilder({
|
|
@@ -14201,7 +14240,7 @@ var alertRules = pgTable(
|
|
|
14201
14240
|
"alert_rules",
|
|
14202
14241
|
{
|
|
14203
14242
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14204
|
-
name:
|
|
14243
|
+
name: text2("name").notNull(),
|
|
14205
14244
|
type: alertRuleTypeEnum("type").notNull(),
|
|
14206
14245
|
threshold: jsonb("threshold").$type().notNull(),
|
|
14207
14246
|
channel: alertChannelEnum("channel").notNull(),
|
|
@@ -14226,8 +14265,8 @@ var alertHistory = pgTable(
|
|
|
14226
14265
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14227
14266
|
alertRuleId: uuid("alert_rule_id").notNull().references(() => alertRules.id),
|
|
14228
14267
|
payload: jsonb("payload").$type(),
|
|
14229
|
-
deliveryStatus:
|
|
14230
|
-
error:
|
|
14268
|
+
deliveryStatus: text2("delivery_status").notNull(),
|
|
14269
|
+
error: text2("error"),
|
|
14231
14270
|
...timestamps
|
|
14232
14271
|
},
|
|
14233
14272
|
(table) => [
|
|
@@ -14241,12 +14280,12 @@ var apiKeys = pgTable(
|
|
|
14241
14280
|
"api_keys",
|
|
14242
14281
|
{
|
|
14243
14282
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14244
|
-
organizationId:
|
|
14245
|
-
name:
|
|
14246
|
-
keyPrefix:
|
|
14247
|
-
keyHash:
|
|
14283
|
+
organizationId: text2("organization_id"),
|
|
14284
|
+
name: text2("name").notNull(),
|
|
14285
|
+
keyPrefix: text2("key_prefix").notNull(),
|
|
14286
|
+
keyHash: text2("key_hash").notNull().unique(),
|
|
14248
14287
|
scopes: jsonb("scopes").$type().notNull().default(["read"]),
|
|
14249
|
-
createdBy:
|
|
14288
|
+
createdBy: text2("created_by"),
|
|
14250
14289
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
14251
14290
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
14252
14291
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
@@ -14263,13 +14302,13 @@ var auditLogs = pgTable(
|
|
|
14263
14302
|
"audit_logs",
|
|
14264
14303
|
{
|
|
14265
14304
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14266
|
-
actor:
|
|
14305
|
+
actor: text2("actor").notNull(),
|
|
14267
14306
|
actorKeyId: uuid("actor_key_id"),
|
|
14268
|
-
action:
|
|
14269
|
-
resource:
|
|
14270
|
-
resourceId:
|
|
14307
|
+
action: text2("action").notNull(),
|
|
14308
|
+
resource: text2("resource").notNull(),
|
|
14309
|
+
resourceId: text2("resource_id"),
|
|
14271
14310
|
detail: jsonb("detail").$type(),
|
|
14272
|
-
ipAddress:
|
|
14311
|
+
ipAddress: text2("ip_address"),
|
|
14273
14312
|
...timestamps
|
|
14274
14313
|
},
|
|
14275
14314
|
(table) => [
|
|
@@ -14281,71 +14320,71 @@ var auditLogs = pgTable(
|
|
|
14281
14320
|
|
|
14282
14321
|
// ../db/src/schema/auth.ts
|
|
14283
14322
|
var user = pgTable("user", {
|
|
14284
|
-
id:
|
|
14285
|
-
name:
|
|
14286
|
-
email:
|
|
14323
|
+
id: text2("id").primaryKey(),
|
|
14324
|
+
name: text2("name").notNull(),
|
|
14325
|
+
email: text2("email").notNull().unique(),
|
|
14287
14326
|
emailVerified: boolean("email_verified").notNull().default(false),
|
|
14288
|
-
image:
|
|
14327
|
+
image: text2("image"),
|
|
14289
14328
|
...timestamps
|
|
14290
14329
|
});
|
|
14291
14330
|
var session = pgTable("session", {
|
|
14292
|
-
id:
|
|
14331
|
+
id: text2("id").primaryKey(),
|
|
14293
14332
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14294
|
-
token:
|
|
14295
|
-
ipAddress:
|
|
14296
|
-
userAgent:
|
|
14297
|
-
userId:
|
|
14298
|
-
activeOrganizationId:
|
|
14333
|
+
token: text2("token").notNull().unique(),
|
|
14334
|
+
ipAddress: text2("ip_address"),
|
|
14335
|
+
userAgent: text2("user_agent"),
|
|
14336
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14337
|
+
activeOrganizationId: text2("active_organization_id"),
|
|
14299
14338
|
...timestamps
|
|
14300
14339
|
});
|
|
14301
14340
|
var account = pgTable("account", {
|
|
14302
|
-
id:
|
|
14303
|
-
accountId:
|
|
14304
|
-
providerId:
|
|
14305
|
-
userId:
|
|
14306
|
-
accessToken:
|
|
14307
|
-
refreshToken:
|
|
14308
|
-
idToken:
|
|
14341
|
+
id: text2("id").primaryKey(),
|
|
14342
|
+
accountId: text2("account_id").notNull(),
|
|
14343
|
+
providerId: text2("provider_id").notNull(),
|
|
14344
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14345
|
+
accessToken: text2("access_token"),
|
|
14346
|
+
refreshToken: text2("refresh_token"),
|
|
14347
|
+
idToken: text2("id_token"),
|
|
14309
14348
|
accessTokenExpiresAt: timestamp("access_token_expires_at", {
|
|
14310
14349
|
withTimezone: true
|
|
14311
14350
|
}),
|
|
14312
14351
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
|
|
14313
14352
|
withTimezone: true
|
|
14314
14353
|
}),
|
|
14315
|
-
scope:
|
|
14316
|
-
password:
|
|
14354
|
+
scope: text2("scope"),
|
|
14355
|
+
password: text2("password"),
|
|
14317
14356
|
...timestamps
|
|
14318
14357
|
});
|
|
14319
14358
|
var verification = pgTable("verification", {
|
|
14320
|
-
id:
|
|
14321
|
-
identifier:
|
|
14322
|
-
value:
|
|
14359
|
+
id: text2("id").primaryKey(),
|
|
14360
|
+
identifier: text2("identifier").notNull(),
|
|
14361
|
+
value: text2("value").notNull(),
|
|
14323
14362
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14324
14363
|
...timestamps
|
|
14325
14364
|
});
|
|
14326
14365
|
var organization = pgTable("organization", {
|
|
14327
|
-
id:
|
|
14328
|
-
name:
|
|
14329
|
-
slug:
|
|
14330
|
-
logo:
|
|
14331
|
-
metadata:
|
|
14366
|
+
id: text2("id").primaryKey(),
|
|
14367
|
+
name: text2("name").notNull(),
|
|
14368
|
+
slug: text2("slug").unique(),
|
|
14369
|
+
logo: text2("logo"),
|
|
14370
|
+
metadata: text2("metadata"),
|
|
14332
14371
|
...timestamps
|
|
14333
14372
|
});
|
|
14334
14373
|
var member = pgTable("member", {
|
|
14335
|
-
id:
|
|
14336
|
-
organizationId:
|
|
14337
|
-
userId:
|
|
14338
|
-
role:
|
|
14374
|
+
id: text2("id").primaryKey(),
|
|
14375
|
+
organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
|
|
14376
|
+
userId: text2("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14377
|
+
role: text2("role").notNull().default("member"),
|
|
14339
14378
|
...timestamps
|
|
14340
14379
|
});
|
|
14341
14380
|
var invitation = pgTable("invitation", {
|
|
14342
|
-
id:
|
|
14343
|
-
organizationId:
|
|
14344
|
-
email:
|
|
14345
|
-
role:
|
|
14346
|
-
status:
|
|
14381
|
+
id: text2("id").primaryKey(),
|
|
14382
|
+
organizationId: text2("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
|
|
14383
|
+
email: text2("email").notNull(),
|
|
14384
|
+
role: text2("role"),
|
|
14385
|
+
status: text2("status").notNull().default("pending"),
|
|
14347
14386
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
14348
|
-
inviterId:
|
|
14387
|
+
inviterId: text2("inviter_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
14349
14388
|
...timestamps
|
|
14350
14389
|
});
|
|
14351
14390
|
|
|
@@ -14354,12 +14393,12 @@ var bucketConfigs = pgTable(
|
|
|
14354
14393
|
"bucket_configs",
|
|
14355
14394
|
{
|
|
14356
14395
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14357
|
-
bucketId:
|
|
14396
|
+
bucketId: text2("bucket_id").notNull(),
|
|
14358
14397
|
enabled: boolean("enabled").notNull().default(true),
|
|
14359
14398
|
// Stable hash of the normalized ConditionEval, written at boot. Diffed on the
|
|
14360
14399
|
// next boot to detect a CRITERIA CHANGE and enqueue the re-evaluation job
|
|
14361
14400
|
// (Section 6.6 B). Nullable until the first registration.
|
|
14362
|
-
criteriaHash:
|
|
14401
|
+
criteriaHash: text2("criteria_hash"),
|
|
14363
14402
|
...timestamps
|
|
14364
14403
|
},
|
|
14365
14404
|
(table) => [uniqueIndex("bucket_configs_bucket_id_idx").on(table.bucketId)]
|
|
@@ -14371,13 +14410,13 @@ var bucketMemberships = pgTable(
|
|
|
14371
14410
|
{
|
|
14372
14411
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14373
14412
|
// multi-tenant insurance (nullable today, NOT in the unique key — see note)
|
|
14374
|
-
organizationId:
|
|
14413
|
+
organizationId: text2("organization_id"),
|
|
14375
14414
|
// logical join to contacts.externalId — NO FK (matches userEvents /
|
|
14376
14415
|
// journeyStates; membership rows can predate a contacts row).
|
|
14377
|
-
userId:
|
|
14378
|
-
userEmail:
|
|
14416
|
+
userId: text2("user_id").notNull(),
|
|
14417
|
+
userEmail: text2("user_email"),
|
|
14379
14418
|
// denormalized so emitted events carry it
|
|
14380
|
-
bucketId:
|
|
14419
|
+
bucketId: text2("bucket_id").notNull(),
|
|
14381
14420
|
status: bucketMembershipStatusEnum("status").notNull().default("active"),
|
|
14382
14421
|
enteredAt: timestamp("entered_at", { withTimezone: true }).defaultNow().notNull(),
|
|
14383
14422
|
leftAt: timestamp("left_at", { withTimezone: true }),
|
|
@@ -14390,7 +14429,7 @@ var bucketMemberships = pgTable(
|
|
|
14390
14429
|
maxDwellAt: timestamp("max_dwell_at", { withTimezone: true }),
|
|
14391
14430
|
lastEvaluatedAt: timestamp("last_evaluated_at", { withTimezone: true }),
|
|
14392
14431
|
entryCount: integer("entry_count").notNull().default(1),
|
|
14393
|
-
source:
|
|
14432
|
+
source: text2("source"),
|
|
14394
14433
|
// "event" | "reconcile" | "backfill" | "manual"
|
|
14395
14434
|
context: jsonb("context").$type().default({}),
|
|
14396
14435
|
// Per-membership dwell bookkeeping. JSON map keyed by dwellLabel → ISO of
|
|
@@ -14458,18 +14497,18 @@ var campaigns = pgTable(
|
|
|
14458
14497
|
"campaigns",
|
|
14459
14498
|
{
|
|
14460
14499
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14461
|
-
organizationId:
|
|
14462
|
-
name:
|
|
14500
|
+
organizationId: text2("organization_id"),
|
|
14501
|
+
name: text2("name").notNull(),
|
|
14463
14502
|
// queued | sending | sent | failed
|
|
14464
|
-
status:
|
|
14503
|
+
status: text2("status").notNull().default("queued"),
|
|
14465
14504
|
// "list" | "bucket"
|
|
14466
|
-
audienceKind:
|
|
14505
|
+
audienceKind: text2("audience_kind").notNull(),
|
|
14467
14506
|
// the list id (ListRegistry) or bucket id (BucketRegistry)
|
|
14468
|
-
audienceId:
|
|
14469
|
-
templateKey:
|
|
14507
|
+
audienceId: text2("audience_id").notNull(),
|
|
14508
|
+
templateKey: text2("template_key").notNull(),
|
|
14470
14509
|
props: jsonb("props").$type().default({}),
|
|
14471
|
-
fromEmail:
|
|
14472
|
-
subject:
|
|
14510
|
+
fromEmail: text2("from_email"),
|
|
14511
|
+
subject: text2("subject"),
|
|
14473
14512
|
/**
|
|
14474
14513
|
* Optional client-supplied idempotency key (POST /v1/campaigns
|
|
14475
14514
|
* `Idempotency-Key` header / body field). A retried create with the same key
|
|
@@ -14478,7 +14517,7 @@ var campaigns = pgTable(
|
|
|
14478
14517
|
* idempotency key, double-sending the blast). Uniqueness is enforced by the
|
|
14479
14518
|
* partial-unique index below (NULL keys are unconstrained).
|
|
14480
14519
|
*/
|
|
14481
|
-
idempotencyKey:
|
|
14520
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14482
14521
|
totalRecipients: integer("total_recipients").notNull().default(0),
|
|
14483
14522
|
sentCount: integer("sent_count").notNull().default(0),
|
|
14484
14523
|
skippedCount: integer("skipped_count").notNull().default(0),
|
|
@@ -14502,7 +14541,7 @@ var contacts = pgTable(
|
|
|
14502
14541
|
"contacts",
|
|
14503
14542
|
{
|
|
14504
14543
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14505
|
-
organizationId:
|
|
14544
|
+
organizationId: text2("organization_id"),
|
|
14506
14545
|
/**
|
|
14507
14546
|
* Stable external/distinct id (= the `user_id` text key joined by every
|
|
14508
14547
|
* contact-referencing table). NULLABLE since D1: contacts can be email-only
|
|
@@ -14511,21 +14550,21 @@ var contacts = pgTable(
|
|
|
14511
14550
|
* — a soft-deleted loser row must be able to keep its stale external_id
|
|
14512
14551
|
* until a merge re-points it.
|
|
14513
14552
|
*/
|
|
14514
|
-
externalId:
|
|
14515
|
-
email:
|
|
14553
|
+
externalId: text2("external_id"),
|
|
14554
|
+
email: text2("email"),
|
|
14516
14555
|
/**
|
|
14517
14556
|
* Stable anonymous/distinct id for the future anonymous→identified path.
|
|
14518
14557
|
* NULLABLE. Like external_id, uniqueness is enforced by a partial-unique
|
|
14519
14558
|
* index scoped to live, non-deleted rows.
|
|
14520
14559
|
*/
|
|
14521
|
-
anonymousId:
|
|
14560
|
+
anonymousId: text2("anonymous_id"),
|
|
14522
14561
|
/**
|
|
14523
14562
|
* Opportunistic IANA-timezone cache (e.g. "America/New_York"). Populated
|
|
14524
14563
|
* best-effort when a tz is resolved from PostHog person props. PostHog and
|
|
14525
14564
|
* `properties` jsonb remain authoritative sources — this column sits below
|
|
14526
14565
|
* them in the resolution precedence, so nothing is blocked on it.
|
|
14527
14566
|
*/
|
|
14528
|
-
timezone:
|
|
14567
|
+
timezone: text2("timezone"),
|
|
14529
14568
|
properties: jsonb("properties").$type().default({}),
|
|
14530
14569
|
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).defaultNow().notNull(),
|
|
14531
14570
|
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).defaultNow().notNull(),
|
|
@@ -14556,15 +14595,15 @@ var contactAliases = pgTable(
|
|
|
14556
14595
|
// The SURVIVOR a stale key resolves TO.
|
|
14557
14596
|
contactId: uuid("contact_id").notNull().references(() => contacts.id, { onDelete: "cascade" }),
|
|
14558
14597
|
// 'email' | 'external' | 'anonymous'
|
|
14559
|
-
aliasKind:
|
|
14598
|
+
aliasKind: text2("alias_kind").notNull(),
|
|
14560
14599
|
// The stale key value (the loser's old external_id / normalized email /
|
|
14561
14600
|
// anonymous_id).
|
|
14562
|
-
aliasValue:
|
|
14601
|
+
aliasValue: text2("alias_value").notNull(),
|
|
14563
14602
|
// Provenance: the loser contact id this alias came from (nullable — a
|
|
14564
14603
|
// 'promote' alias may have no distinct loser row).
|
|
14565
14604
|
fromContactId: uuid("from_contact_id"),
|
|
14566
14605
|
// 'merge' | 'promote'
|
|
14567
|
-
reason:
|
|
14606
|
+
reason: text2("reason").notNull(),
|
|
14568
14607
|
...timestamps
|
|
14569
14608
|
},
|
|
14570
14609
|
(table) => [
|
|
@@ -14582,10 +14621,10 @@ var deadLetterQueue = pgTable(
|
|
|
14582
14621
|
"dead_letter_queue",
|
|
14583
14622
|
{
|
|
14584
14623
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14585
|
-
source:
|
|
14586
|
-
sourceId:
|
|
14624
|
+
source: text2("source").notNull(),
|
|
14625
|
+
sourceId: text2("source_id"),
|
|
14587
14626
|
payload: jsonb("payload").$type().notNull(),
|
|
14588
|
-
error:
|
|
14627
|
+
error: text2("error").notNull(),
|
|
14589
14628
|
retryCount: integer("retry_count").notNull().default(0),
|
|
14590
14629
|
status: dlqStatusEnum("status").notNull().default("pending"),
|
|
14591
14630
|
retriedAt: timestamp("retried_at", { withTimezone: true }),
|
|
@@ -14603,8 +14642,8 @@ var emailPreferences = pgTable(
|
|
|
14603
14642
|
"email_preferences",
|
|
14604
14643
|
{
|
|
14605
14644
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14606
|
-
userId:
|
|
14607
|
-
email:
|
|
14645
|
+
userId: text2("user_id").notNull(),
|
|
14646
|
+
email: text2("email").notNull(),
|
|
14608
14647
|
unsubscribedAll: boolean("unsubscribed_all").notNull().default(false),
|
|
14609
14648
|
suppressed: boolean("suppressed").notNull().default(false),
|
|
14610
14649
|
bounceCount: integer("bounce_count").notNull().default(0),
|
|
@@ -14626,15 +14665,15 @@ var journeyStates = pgTable(
|
|
|
14626
14665
|
"journey_states",
|
|
14627
14666
|
{
|
|
14628
14667
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14629
|
-
organizationId:
|
|
14630
|
-
userId:
|
|
14631
|
-
userEmail:
|
|
14632
|
-
journeyId:
|
|
14633
|
-
currentNodeId:
|
|
14668
|
+
organizationId: text2("organization_id"),
|
|
14669
|
+
userId: text2("user_id").notNull(),
|
|
14670
|
+
userEmail: text2("user_email").notNull(),
|
|
14671
|
+
journeyId: text2("journey_id").notNull(),
|
|
14672
|
+
currentNodeId: text2("current_node_id").notNull(),
|
|
14634
14673
|
status: journeyStatusEnum("status").notNull().default("active"),
|
|
14635
|
-
hatchetRunId:
|
|
14674
|
+
hatchetRunId: text2("hatchet_run_id"),
|
|
14636
14675
|
context: jsonb("context").$type().default({}),
|
|
14637
|
-
errorMessage:
|
|
14676
|
+
errorMessage: text2("error_message"),
|
|
14638
14677
|
entryCount: integer("entry_count").notNull().default(1),
|
|
14639
14678
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
14640
14679
|
exitedAt: timestamp("exited_at", { withTimezone: true }),
|
|
@@ -14672,19 +14711,19 @@ var emailSends = pgTable(
|
|
|
14672
14711
|
"email_sends",
|
|
14673
14712
|
{
|
|
14674
14713
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14675
|
-
organizationId:
|
|
14714
|
+
organizationId: text2("organization_id"),
|
|
14676
14715
|
journeyStateId: uuid("journey_state_id").references(() => journeyStates.id),
|
|
14677
14716
|
// Denormalized recipient identity, set at send time. Lets reporting attribute
|
|
14678
14717
|
// a send to a contact without joining journey_states, and captures journeyless
|
|
14679
14718
|
// (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:
|
|
14719
|
+
userId: text2("user_id"),
|
|
14720
|
+
userEmail: text2("user_email"),
|
|
14721
|
+
templateKey: text2("template_key"),
|
|
14722
|
+
messageId: text2("message_id"),
|
|
14723
|
+
fromEmail: text2("from_email").notNull(),
|
|
14724
|
+
toEmail: text2("to_email").notNull(),
|
|
14725
|
+
subject: text2("subject").notNull(),
|
|
14726
|
+
category: text2("category"),
|
|
14688
14727
|
status: emailSendStatusEnum("status").notNull().default("queued"),
|
|
14689
14728
|
sentAt: timestamp("sent_at", { withTimezone: true }),
|
|
14690
14729
|
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
@@ -14693,13 +14732,13 @@ var emailSends = pgTable(
|
|
|
14693
14732
|
bouncedAt: timestamp("bounced_at", { withTimezone: true }),
|
|
14694
14733
|
complainedAt: timestamp("complained_at", { withTimezone: true }),
|
|
14695
14734
|
// Bounce classification from the Resend webhook (hard/soft/transient + reason).
|
|
14696
|
-
bounceType:
|
|
14697
|
-
bounceReason:
|
|
14735
|
+
bounceType: text2("bounce_type"),
|
|
14736
|
+
bounceReason: text2("bounce_reason"),
|
|
14698
14737
|
// Caller-supplied idempotency key (POST /v1/emails). A retry with the same
|
|
14699
14738
|
// key short-circuits to the prior send instead of dispatching a duplicate —
|
|
14700
14739
|
// mirrors the user_events idempotency pattern. Nullable: journey/system sends
|
|
14701
14740
|
// don't set it.
|
|
14702
|
-
idempotencyKey:
|
|
14741
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14703
14742
|
// Free-form per-send annotations. Set ONLY by test-mode redirected sends
|
|
14704
14743
|
// today — `{ testMode: true, originalTo: <real recipient> }` — so Studio can
|
|
14705
14744
|
// flag a TEST row and show who the mail was REALLY for. Nullable: normal
|
|
@@ -14735,8 +14774,8 @@ var importJobs = pgTable(
|
|
|
14735
14774
|
"import_jobs",
|
|
14736
14775
|
{
|
|
14737
14776
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14738
|
-
fileName:
|
|
14739
|
-
format:
|
|
14777
|
+
fileName: text2("file_name"),
|
|
14778
|
+
format: text2("format").notNull(),
|
|
14740
14779
|
status: importJobStatusEnum("status").notNull().default("pending"),
|
|
14741
14780
|
totalRows: integer("total_rows"),
|
|
14742
14781
|
processedRows: integer("processed_rows").notNull().default(0),
|
|
@@ -14752,7 +14791,7 @@ var journeyConfigs = pgTable(
|
|
|
14752
14791
|
"journey_configs",
|
|
14753
14792
|
{
|
|
14754
14793
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14755
|
-
journeyId:
|
|
14794
|
+
journeyId: text2("journey_id").notNull(),
|
|
14756
14795
|
enabled: boolean("enabled").notNull().default(true),
|
|
14757
14796
|
...timestamps
|
|
14758
14797
|
},
|
|
@@ -14767,9 +14806,9 @@ var journeyLogs = pgTable(
|
|
|
14767
14806
|
{
|
|
14768
14807
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14769
14808
|
journeyStateId: uuid("journey_state_id").notNull().references(() => journeyStates.id, { onDelete: "cascade" }),
|
|
14770
|
-
fromNodeId:
|
|
14771
|
-
toNodeId:
|
|
14772
|
-
action:
|
|
14809
|
+
fromNodeId: text2("from_node_id"),
|
|
14810
|
+
toNodeId: text2("to_node_id"),
|
|
14811
|
+
action: text2("action").notNull(),
|
|
14773
14812
|
detail: jsonb("detail").$type(),
|
|
14774
14813
|
...timestamps
|
|
14775
14814
|
},
|
|
@@ -14784,12 +14823,12 @@ var trackedLinks = pgTable(
|
|
|
14784
14823
|
{
|
|
14785
14824
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14786
14825
|
emailSendId: uuid("email_send_id").notNull().references(() => emailSends.id, { onDelete: "cascade" }),
|
|
14787
|
-
originalUrl:
|
|
14826
|
+
originalUrl: text2("original_url").notNull(),
|
|
14788
14827
|
clickCount: integer("click_count").notNull().default(0),
|
|
14789
14828
|
// Semantic link metadata, lifted from the template's data-hs-* attributes
|
|
14790
14829
|
// at send time. NULL for plain tracked links. `event` is the consumer event
|
|
14791
14830
|
// name emitted at click time; `eventProperties` its scalar payload.
|
|
14792
|
-
event:
|
|
14831
|
+
event: text2("event"),
|
|
14793
14832
|
eventProperties: jsonb("event_properties").$type(),
|
|
14794
14833
|
// Set exactly once by the click route when the semantic event is emitted —
|
|
14795
14834
|
// the per-link emit-once gate today, and the provisional-then-confirm
|
|
@@ -14808,8 +14847,8 @@ var linkClicks = pgTable(
|
|
|
14808
14847
|
{
|
|
14809
14848
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14810
14849
|
trackedLinkId: uuid("tracked_link_id").notNull().references(() => trackedLinks.id, { onDelete: "cascade" }),
|
|
14811
|
-
ipAddress:
|
|
14812
|
-
userAgent:
|
|
14850
|
+
ipAddress: text2("ip_address"),
|
|
14851
|
+
userAgent: text2("user_agent"),
|
|
14813
14852
|
clickedAt: timestamp("clicked_at", { withTimezone: true }).defaultNow().notNull()
|
|
14814
14853
|
},
|
|
14815
14854
|
(table) => [
|
|
@@ -14825,11 +14864,11 @@ var providerCredentials = pgTable(
|
|
|
14825
14864
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14826
14865
|
// e.g. "posthog" — matches an AnalyticsProvider meta.id by convention,
|
|
14827
14866
|
// but deliberately NOT foreign-keyed: providers are code-defined.
|
|
14828
|
-
providerId:
|
|
14867
|
+
providerId: text2("provider_id").notNull(),
|
|
14829
14868
|
// "oauth" today; "api_key" is the anticipated future kind.
|
|
14830
|
-
kind:
|
|
14869
|
+
kind: text2("kind").notNull().default("oauth"),
|
|
14831
14870
|
// Encrypted JSON: base64url(iv || ciphertext || gcmTag).
|
|
14832
|
-
payload:
|
|
14871
|
+
payload: text2("payload").notNull(),
|
|
14833
14872
|
...timestamps
|
|
14834
14873
|
},
|
|
14835
14874
|
(table) => [
|
|
@@ -14847,11 +14886,11 @@ var userEvents = pgTable(
|
|
|
14847
14886
|
"user_events",
|
|
14848
14887
|
{
|
|
14849
14888
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14850
|
-
organizationId:
|
|
14851
|
-
userId:
|
|
14852
|
-
event:
|
|
14889
|
+
organizationId: text2("organization_id"),
|
|
14890
|
+
userId: text2("user_id").notNull(),
|
|
14891
|
+
event: text2("event").notNull(),
|
|
14853
14892
|
properties: jsonb("properties").$type(),
|
|
14854
|
-
idempotencyKey:
|
|
14893
|
+
idempotencyKey: text2("idempotency_key"),
|
|
14855
14894
|
occurredAt: timestamp("occurred_at", { withTimezone: true }).defaultNow().notNull()
|
|
14856
14895
|
},
|
|
14857
14896
|
(table) => [
|
|
@@ -14872,15 +14911,15 @@ var webhookEndpoints = pgTable(
|
|
|
14872
14911
|
"webhook_endpoints",
|
|
14873
14912
|
{
|
|
14874
14913
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14875
|
-
organizationId:
|
|
14876
|
-
url:
|
|
14877
|
-
description:
|
|
14914
|
+
organizationId: text2("organization_id"),
|
|
14915
|
+
url: text2("url").notNull(),
|
|
14916
|
+
description: text2("description"),
|
|
14878
14917
|
// The delivery adapter selector. "webhook" (the default) is the signed
|
|
14879
14918
|
// Standard-Webhooks POST that existing subscribers receive — byte-identical
|
|
14880
14919
|
// to before this column existed. Any other value (e.g. "posthog") selects a
|
|
14881
14920
|
// delivery-time TRANSFORM adapter that reuses the same durable delivery
|
|
14882
14921
|
// machinery but rewrites url/headers/body for a vendor destination.
|
|
14883
|
-
kind:
|
|
14922
|
+
kind: text2("kind").notNull().default("webhook"),
|
|
14884
14923
|
// Per-destination configuration for keyed adapters (e.g. PostHog's
|
|
14885
14924
|
// `{ apiKey, host }`). Null for `kind="webhook"` (it reads `secret` instead).
|
|
14886
14925
|
// Keyed destinations keep their credentials HERE, not in a fake `whsec_`.
|
|
@@ -14889,9 +14928,9 @@ var webhookEndpoints = pgTable(
|
|
|
14889
14928
|
// Nullable: only `kind="webhook"` carries a signing secret; keyed
|
|
14890
14929
|
// destinations authenticate via `config` and the webhook adapter is the only
|
|
14891
14930
|
// reader of this column.
|
|
14892
|
-
secret:
|
|
14931
|
+
secret: text2("secret"),
|
|
14893
14932
|
// e.g. "whsec_AbCd" — safe to show on list/get. Nullable alongside `secret`.
|
|
14894
|
-
secretPrefix:
|
|
14933
|
+
secretPrefix: text2("secret_prefix"),
|
|
14895
14934
|
eventTypes: jsonb("event_types").$type().notNull().default([]),
|
|
14896
14935
|
disabled: boolean("disabled").notNull().default(false),
|
|
14897
14936
|
// written by the delivery task on a successful (2xx) delivery.
|
|
@@ -14912,13 +14951,13 @@ var webhookDeliveries = pgTable(
|
|
|
14912
14951
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
14913
14952
|
endpointId: uuid("endpoint_id").notNull().references(() => webhookEndpoints.id, { onDelete: "cascade" }),
|
|
14914
14953
|
// denormalized, nullable (MT deferred).
|
|
14915
|
-
organizationId:
|
|
14954
|
+
organizationId: text2("organization_id"),
|
|
14916
14955
|
// == Webhook-Id header; ONE per logical event, shared across endpoints +
|
|
14917
14956
|
// reused across retries.
|
|
14918
|
-
webhookId:
|
|
14919
|
-
eventType:
|
|
14957
|
+
webhookId: text2("webhook_id").notNull(),
|
|
14958
|
+
eventType: text2("event_type").notNull(),
|
|
14920
14959
|
// producer-side dedup (idempotencyKey/stateId/emailSendId/...).
|
|
14921
|
-
dedupeKey:
|
|
14960
|
+
dedupeKey: text2("dedupe_key"),
|
|
14922
14961
|
// the EXACT signed envelope { id, type, timestamp, data }.
|
|
14923
14962
|
payload: jsonb("payload").$type().notNull(),
|
|
14924
14963
|
status: webhookDeliveryStatusEnum("status").notNull().default("pending"),
|
|
@@ -14927,9 +14966,9 @@ var webhookDeliveries = pgTable(
|
|
|
14927
14966
|
lastAttemptAt: timestamp("last_attempt_at", { withTimezone: true }),
|
|
14928
14967
|
responseStatus: integer("response_status"),
|
|
14929
14968
|
// truncated to ≤1KB in app.
|
|
14930
|
-
responseBodySnippet:
|
|
14969
|
+
responseBodySnippet: text2("response_body_snippet"),
|
|
14931
14970
|
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
14932
|
-
lastError:
|
|
14971
|
+
lastError: text2("last_error"),
|
|
14933
14972
|
...timestamps
|
|
14934
14973
|
},
|
|
14935
14974
|
(table) => [
|
|
@@ -15386,7 +15425,7 @@ async function resolveEmail(ctx, flags, defaultEmail) {
|
|
|
15386
15425
|
ctx.out.fail("--email is required (no TTY to prompt).");
|
|
15387
15426
|
}
|
|
15388
15427
|
const value = bail(
|
|
15389
|
-
await
|
|
15428
|
+
await text3({
|
|
15390
15429
|
message: "Admin email",
|
|
15391
15430
|
placeholder: defaultEmail ?? "admin@example.com",
|
|
15392
15431
|
initialValue: defaultEmail,
|