@columnus/shared 0.1.54 → 0.1.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/apiProfile.d.ts +39 -0
  2. package/dist/apiProfile.js +19 -2
  3. package/dist/apiProfile.js.map +1 -1
  4. package/dist/assetFamily.d.ts +36 -0
  5. package/dist/assetFamily.js +73 -0
  6. package/dist/assetFamily.js.map +1 -0
  7. package/dist/assetFieldValues.d.ts +21 -0
  8. package/dist/{storageFieldValues.js → assetFieldValues.js} +159 -60
  9. package/dist/assetFieldValues.js.map +1 -0
  10. package/dist/assetGraph.d.ts +199 -0
  11. package/dist/assetGraph.js +214 -0
  12. package/dist/assetGraph.js.map +1 -0
  13. package/dist/assetJobs.d.ts +95 -0
  14. package/dist/assetJobs.js +41 -0
  15. package/dist/assetJobs.js.map +1 -0
  16. package/dist/assetSettings.d.ts +129 -0
  17. package/dist/assetSettings.js +87 -0
  18. package/dist/assetSettings.js.map +1 -0
  19. package/dist/assetValidators.d.ts +475 -0
  20. package/dist/assetValidators.js +83 -0
  21. package/dist/assetValidators.js.map +1 -0
  22. package/dist/bffProtocol.d.ts +29 -24
  23. package/dist/bffProtocol.js +5 -1
  24. package/dist/bffProtocol.js.map +1 -1
  25. package/dist/ciPreviewEnv.js +20 -3
  26. package/dist/ciPreviewEnv.js.map +1 -1
  27. package/dist/clipboardTransfer.js +112 -12
  28. package/dist/clipboardTransfer.js.map +1 -1
  29. package/dist/csv.js +118 -18
  30. package/dist/csv.js.map +1 -1
  31. package/dist/displayLabel.js +1 -1
  32. package/dist/displayLabel.js.map +1 -1
  33. package/dist/dotenv.js +5 -1
  34. package/dist/dotenv.js.map +1 -1
  35. package/dist/importMatch.js +118 -18
  36. package/dist/importMatch.js.map +1 -1
  37. package/dist/index.d.ts +7 -1
  38. package/dist/index.js +741 -82
  39. package/dist/index.js.map +1 -1
  40. package/dist/postLoginPath.d.ts +21 -0
  41. package/dist/postLoginPath.js +24 -0
  42. package/dist/postLoginPath.js.map +1 -0
  43. package/dist/previewDeployName.d.ts +0 -7
  44. package/dist/previewDeployName.js +21 -4
  45. package/dist/previewDeployName.js.map +1 -1
  46. package/dist/recordTableColumns.js +112 -12
  47. package/dist/recordTableColumns.js.map +1 -1
  48. package/dist/recordTablePreload.js.map +1 -1
  49. package/dist/refDisplayRequests.js +112 -12
  50. package/dist/refDisplayRequests.js.map +1 -1
  51. package/dist/richDoc.d.ts +2 -2
  52. package/dist/richDoc.js +5 -5
  53. package/dist/richDoc.js.map +1 -1
  54. package/dist/schemaGenerator.d.ts +25 -1
  55. package/dist/schemaGenerator.js +4569 -18
  56. package/dist/schemaGenerator.js.map +1 -1
  57. package/dist/schemaShrink.d.ts +3 -1
  58. package/dist/schemaShrink.js +4451 -1
  59. package/dist/schemaShrink.js.map +1 -1
  60. package/dist/schemaShrinkValidators.d.ts +2 -2
  61. package/dist/schemaTypes.d.ts +123 -0
  62. package/dist/schemaTypes.js +107 -13
  63. package/dist/schemaTypes.js.map +1 -1
  64. package/dist/share.js +6 -1
  65. package/dist/share.js.map +1 -1
  66. package/dist/shareApiBridge.js +6 -1
  67. package/dist/shareApiBridge.js.map +1 -1
  68. package/dist/shareProposal.js +112 -12
  69. package/dist/shareProposal.js.map +1 -1
  70. package/dist/spaceBundle.js +112 -12
  71. package/dist/spaceBundle.js.map +1 -1
  72. package/dist/syncChannel.d.ts +15 -0
  73. package/dist/syncChannel.js +4642 -0
  74. package/dist/syncChannel.js.map +1 -0
  75. package/package.json +1 -1
  76. package/dist/storageFieldValues.d.ts +0 -10
  77. package/dist/storageFieldValues.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Where to send a session that has just signed in.
3
+ *
4
+ * Both front ends bounce an unauthenticated visitor to `/login?next=<where
5
+ * they were>` and follow `next` once the code checks out. That parameter
6
+ * arrives from the URL, so it is attacker-controllable through a crafted link
7
+ * — and it is obeyed at the exact moment the session becomes worth stealing.
8
+ * Anything that is not a path on this origin is refused.
9
+ *
10
+ * Shared rather than per-app on purpose: this existed in the control plane
11
+ * only, and Studio spent that time following `next` unchecked.
12
+ */
13
+ /** Allow only same-app relative paths after login. */
14
+ export declare function safePostLoginPath(raw: string | null | undefined, fallback?: string): string;
15
+ /**
16
+ * The whole post-login decision, from the login page's own query string.
17
+ *
18
+ * Takes `window.location.search` rather than a pre-extracted `next` so that no
19
+ * call site is left holding a raw parameter it could forget to check.
20
+ */
21
+ export declare function postLoginRedirectPath(search: string, fallback?: string): string;
@@ -0,0 +1,24 @@
1
+ // packages/@columnus/shared/src/postLoginPath.ts
2
+ var RESOLVE_BASE = "https://post-login.invalid";
3
+ function safePostLoginPath(raw, fallback = "/") {
4
+ if (raw == null) return fallback;
5
+ const trimmed = raw.trim();
6
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.includes("\\")) {
7
+ return fallback;
8
+ }
9
+ try {
10
+ const url = new URL(trimmed, RESOLVE_BASE);
11
+ if (url.origin !== RESOLVE_BASE) return fallback;
12
+ const path = `${url.pathname}${url.search}${url.hash}`;
13
+ return path.startsWith("/") ? path : fallback;
14
+ } catch {
15
+ return fallback;
16
+ }
17
+ }
18
+ function postLoginRedirectPath(search, fallback = "/") {
19
+ return safePostLoginPath(new URLSearchParams(search).get("next"), fallback);
20
+ }
21
+
22
+ export { postLoginRedirectPath, safePostLoginPath };
23
+ //# sourceMappingURL=postLoginPath.js.map
24
+ //# sourceMappingURL=postLoginPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/postLoginPath.ts"],"names":[],"mappings":";AAcA,IAAM,YAAA,GAAe,4BAAA;AAGd,SAAS,iBAAA,CAAkB,GAAA,EAAgC,QAAA,GAAW,GAAA,EAAa;AACxF,EAAA,IAAI,GAAA,IAAO,MAAM,OAAO,QAAA;AACxB,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,EAAK;AAGzB,EAAA,IAAI,CAAC,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,IAAK,OAAA,CAAQ,UAAA,CAAW,IAAI,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,EAAG;AAClF,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,OAAA,EAAS,YAAY,CAAA;AACzC,IAAA,IAAI,GAAA,CAAI,MAAA,KAAW,YAAA,EAAc,OAAO,QAAA;AACxC,IAAA,MAAM,IAAA,GAAO,GAAG,GAAA,CAAI,QAAQ,GAAG,GAAA,CAAI,MAAM,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,CAAA;AACpD,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,QAAA;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,QAAA;AAAA,EACT;AACF;AAQO,SAAS,qBAAA,CAAsB,MAAA,EAAgB,QAAA,GAAW,GAAA,EAAa;AAC5E,EAAA,OAAO,iBAAA,CAAkB,IAAI,eAAA,CAAgB,MAAM,EAAE,GAAA,CAAI,MAAM,GAAG,QAAQ,CAAA;AAC5E","file":"postLoginPath.js","sourcesContent":["/**\n * Where to send a session that has just signed in.\n *\n * Both front ends bounce an unauthenticated visitor to `/login?next=<where\n * they were>` and follow `next` once the code checks out. That parameter\n * arrives from the URL, so it is attacker-controllable through a crafted link\n * — and it is obeyed at the exact moment the session becomes worth stealing.\n * Anything that is not a path on this origin is refused.\n *\n * Shared rather than per-app on purpose: this existed in the control plane\n * only, and Studio spent that time following `next` unchecked.\n */\n\n/** A sentinel origin — only used to detect a path that escapes it. */\nconst RESOLVE_BASE = \"https://post-login.invalid\";\n\n/** Allow only same-app relative paths after login. */\nexport function safePostLoginPath(raw: string | null | undefined, fallback = \"/\"): string {\n if (raw == null) return fallback;\n const trimmed = raw.trim();\n // `//evil` is protocol-relative and `\\` is normalised to `/` by some parsers;\n // both reach another origin without ever looking absolute.\n if (!trimmed.startsWith(\"/\") || trimmed.startsWith(\"//\") || trimmed.includes(\"\\\\\")) {\n return fallback;\n }\n\n try {\n const url = new URL(trimmed, RESOLVE_BASE);\n if (url.origin !== RESOLVE_BASE) return fallback;\n const path = `${url.pathname}${url.search}${url.hash}`;\n return path.startsWith(\"/\") ? path : fallback;\n } catch {\n return fallback;\n }\n}\n\n/**\n * The whole post-login decision, from the login page's own query string.\n *\n * Takes `window.location.search` rather than a pre-extracted `next` so that no\n * call site is left holding a raw parameter it could forget to check.\n */\nexport function postLoginRedirectPath(search: string, fallback = \"/\"): string {\n return safePostLoginPath(new URLSearchParams(search).get(\"next\"), fallback);\n}\n"]}
@@ -1,10 +1,3 @@
1
- /**
2
- * Sanitized Convex preview deployment name for CI / Coolify preview builds.
3
- * "/" breaks preview/<name> lookups in Convex deployment references.
4
- *
5
- * Coolify preview env vars: COOLIFY_BRANCH, COOLIFY_IS_PREVIEW, COOLIFY_FQDN,
6
- * COOLIFY_URL (BuildKit build secrets).
7
- */
8
1
  export declare function coolifyHostnameFromEnv(env?: NodeJS.ProcessEnv): string | null;
9
2
  /**
10
3
  * Browser origin Better Auth expects on server-side OTP probes.
@@ -1,9 +1,26 @@
1
1
  // packages/@columnus/shared/src/previewDeployName.ts
2
+ function hostnameFromCoolifyToken(raw) {
3
+ const token = raw.trim();
4
+ if (!token || /^https?:?$/i.test(token)) return null;
5
+ try {
6
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) {
7
+ return new URL(token).hostname || null;
8
+ }
9
+ } catch {
10
+ return null;
11
+ }
12
+ const host = token.split("/")[0]?.split(":")[0] ?? "";
13
+ if (!host || !host.includes(".") && host !== "localhost") return null;
14
+ return host;
15
+ }
2
16
  function coolifyHostnameFromEnv(env = process.env) {
3
17
  const raw = env.COOLIFY_FQDN ?? env.COOLIFY_URL;
4
18
  if (!raw) return null;
5
- const host = raw.replace(/^https?:\/\//, "").split("/")[0] ?? "";
6
- return host || null;
19
+ for (const part of raw.split(",")) {
20
+ const host = hostnameFromCoolifyToken(part);
21
+ if (host) return host;
22
+ }
23
+ return null;
7
24
  }
8
25
  function resolveTrustedAuthOrigin(env = process.env) {
9
26
  const explicit = env.COLUMNUS_AUTH_ORIGIN?.trim();
@@ -21,10 +38,10 @@ function isCiPreviewBuild(env = process.env) {
21
38
  return true;
22
39
  }
23
40
  const host = coolifyHostnameFromEnv(env);
24
- return Boolean(host && /^(?:manage-|deploy-)?preview-\d+\./.test(host));
41
+ return Boolean(host && /^(?:manage-|deploy-|renderer-)?preview-\d+\./.test(host));
25
42
  }
26
43
  var PULL_HEAD_BRANCH_RE = /^pull\/(\d+)\/head$/i;
27
- var PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-)?preview-(\d+)\./;
44
+ var PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-|renderer-)?preview-(\d+)\./;
28
45
  function resolvePreviewPrId(env = process.env) {
29
46
  const host = coolifyHostnameFromEnv(env);
30
47
  if (host) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/previewDeployName.ts"],"names":[],"mappings":"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","file":"previewDeployName.js","sourcesContent":["/**\n * Sanitized Convex preview deployment name for CI / Coolify preview builds.\n * \"/\" breaks preview/<name> lookups in Convex deployment references.\n *\n * Coolify preview env vars: COOLIFY_BRANCH, COOLIFY_IS_PREVIEW, COOLIFY_FQDN,\n * COOLIFY_URL (BuildKit build secrets).\n */\nexport function coolifyHostnameFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {\n const raw = env.COOLIFY_FQDN ?? env.COOLIFY_URL;\n if (!raw) return null;\n const host = raw.replace(/^https?:\\/\\//, \"\").split(\"/\")[0] ?? \"\";\n return host || null;\n}\n\n/**\n * Browser origin Better Auth expects on server-side OTP probes.\n * Preview configure sets SITE_URL to `https://<coolify-host>`; Origin must match.\n * Coolify often injects `COOLIFY_URL=http://…` — do not use that scheme.\n */\nexport function resolveTrustedAuthOrigin(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicit = env.COLUMNUS_AUTH_ORIGIN?.trim();\n if (explicit) return explicit.replace(/\\/$/, \"\");\n\n const host = coolifyHostnameFromEnv(env);\n if (host) return `https://${host}`;\n\n const siteUrl = env.SITE_URL?.trim();\n if (siteUrl && !/\\.convex\\.site(?:\\/|$)/i.test(siteUrl)) {\n return siteUrl.replace(/\\/$/, \"\");\n }\n return undefined;\n}\n\n/** True when Coolify (or CI_PREVIEW) indicates a PR preview Docker build. */\nexport function isCiPreviewBuild(env: NodeJS.ProcessEnv = process.env): boolean {\n if (env.CI_PREVIEW === \"true\" || env.COOLIFY_IS_PREVIEW === \"true\") {\n return true;\n }\n const host = coolifyHostnameFromEnv(env);\n // preview-<pr>, manage-preview-<pr>, deploy-preview-<pr>\n return Boolean(host && /^(?:manage-|deploy-)?preview-\\d+\\./.test(host));\n}\n\nconst PULL_HEAD_BRANCH_RE = /^pull\\/(\\d+)\\/head$/i;\nconst PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-)?preview-(\\d+)\\./;\n\n/** PR id from `preview-<n>.<domain>` / `manage-preview-<n>` hostname or `pull/<n>/head` branch. */\nexport function resolvePreviewPrId(env: NodeJS.ProcessEnv = process.env): string | null {\n const host = coolifyHostnameFromEnv(env);\n if (host) {\n const fromHost = PREVIEW_HOST_PR_RE.exec(host)?.[1];\n if (fromHost) return fromHost;\n }\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n if (branch) {\n const fromBranch = PULL_HEAD_BRANCH_RE.exec(stripQualifiedBranch(branch))?.[1];\n if (fromBranch) return fromBranch;\n }\n\n return null;\n}\n\n/**\n * Resolve the git branch used for Convex `--preview-name`.\n * Coolify often sets COOLIFY_BRANCH to `pull/<n>/head` at runtime (see coollabsio/coolify#9572).\n */\nexport function resolvePreviewBranch(env: NodeJS.ProcessEnv = process.env): string | null {\n if (env.GITHUB_HEAD_REF) return stripQualifiedBranch(env.GITHUB_HEAD_REF);\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n const prId = resolvePreviewPrId(env);\n if (!branch) {\n return prId ? `preview-${prId}` : null;\n }\n\n const stripped = stripQualifiedBranch(branch);\n if (PULL_HEAD_BRANCH_RE.test(stripped)) {\n if (prId) return `preview-${prId}`;\n }\n // Coolify PR apps often leave COOLIFY_BRANCH=main while FQDN is *-preview-<n>.\n if ((stripped === \"main\" || stripped === \"master\") && prId) {\n return `preview-${prId}`;\n }\n return stripped;\n}\n\n/** Strip Coolify/Convex qualified suffixes (e.g. `pull-48-head:pr-48-coolify`). */\nfunction stripQualifiedBranch(branch: string): string {\n const colon = branch.indexOf(\":\");\n return colon > 0 ? branch.slice(0, colon) : branch;\n}\n\nexport function sanitizePreviewDeployName(branch: string): string {\n return stripQualifiedBranch(branch).replace(/\\//g, \"-\");\n}\n\n/** Normalize a preview name from Convex env before `convex deploy --preview-name`. */\nexport function normalizePreviewDeployName(name: string): string {\n return sanitizePreviewDeployName(name.trim());\n}\n\nexport function ciPreviewDeployName(env: NodeJS.ProcessEnv = process.env): string | null {\n if (!isCiPreviewBuild(env)) return null;\n\n const branch = resolvePreviewBranch(env);\n if (!branch) return null;\n return sanitizePreviewDeployName(branch);\n}\n\n/** Stale failures from broken preview deploy before the bridge was live. */\nexport const STALE_ENDPOINT_DEPLOY_ERROR =\n /^deploy endpoint returned (?:200(?: OK)?|500(?: Internal Server Error)?)$/;\n\nexport function isStaleEndpointDeployError(error: string | undefined): boolean {\n return error != null && STALE_ENDPOINT_DEPLOY_ERROR.test(error);\n}\n\n/** Infra failures from a prior broken deploy that a fresh save can retry. */\nexport function isRecoverableDeployError(error: string | undefined): boolean {\n if (error == null) return false;\n if (isStaleEndpointDeployError(error)) return true;\n if (error.includes(\"502 Bad Gateway\")) return true;\n if (error.includes(\"deploy endpoint unreachable\")) return true;\n if (error.includes(\"deploy endpoint timed out\")) return true;\n return (\n error.includes(\"convex/bin/main.js\") ||\n error.includes(\"convex CLI (bin/main.js\") ||\n error.includes(\"convex CLI not available\")\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/previewDeployName.ts"],"names":[],"mappings":"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","file":"previewDeployName.js","sourcesContent":["/**\n * Sanitized Convex preview deployment name for CI / Coolify preview builds.\n * \"/\" breaks preview/<name> lookups in Convex deployment references.\n *\n * Coolify preview env vars: COOLIFY_BRANCH, COOLIFY_IS_PREVIEW, COOLIFY_FQDN,\n * COOLIFY_URL (BuildKit build secrets).\n *\n * Production Coolify often injects a comma-separated FQDN/URL list\n * (`app.columnus.app,bro.columnus.app,…`). Take the first valid hostname —\n * Better Auth Origin must be a single origin, not the whole list.\n */\nfunction hostnameFromCoolifyToken(raw: string): string | null {\n const token = raw.trim();\n if (!token || /^https?:?$/i.test(token)) return null;\n\n try {\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(token)) {\n return new URL(token).hostname || null;\n }\n } catch {\n return null;\n }\n\n const host = token.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n if (!host || (!host.includes(\".\") && host !== \"localhost\")) return null;\n return host;\n}\n\nexport function coolifyHostnameFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {\n const raw = env.COOLIFY_FQDN ?? env.COOLIFY_URL;\n if (!raw) return null;\n for (const part of raw.split(\",\")) {\n const host = hostnameFromCoolifyToken(part);\n if (host) return host;\n }\n return null;\n}\n\n/**\n * Browser origin Better Auth expects on server-side OTP probes.\n * Preview configure sets SITE_URL to `https://<coolify-host>`; Origin must match.\n * Coolify often injects `COOLIFY_URL=http://…` — do not use that scheme.\n */\nexport function resolveTrustedAuthOrigin(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicit = env.COLUMNUS_AUTH_ORIGIN?.trim();\n if (explicit) return explicit.replace(/\\/$/, \"\");\n\n const host = coolifyHostnameFromEnv(env);\n if (host) return `https://${host}`;\n\n const siteUrl = env.SITE_URL?.trim();\n if (siteUrl && !/\\.convex\\.site(?:\\/|$)/i.test(siteUrl)) {\n return siteUrl.replace(/\\/$/, \"\");\n }\n return undefined;\n}\n\n/** True when Coolify (or CI_PREVIEW) indicates a PR preview Docker build. */\nexport function isCiPreviewBuild(env: NodeJS.ProcessEnv = process.env): boolean {\n if (env.CI_PREVIEW === \"true\" || env.COOLIFY_IS_PREVIEW === \"true\") {\n return true;\n }\n const host = coolifyHostnameFromEnv(env);\n // preview-<pr>, manage-preview-<pr>, deploy-preview-<pr>, renderer-preview-<pr>\n return Boolean(host && /^(?:manage-|deploy-|renderer-)?preview-\\d+\\./.test(host));\n}\n\nconst PULL_HEAD_BRANCH_RE = /^pull\\/(\\d+)\\/head$/i;\nconst PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-|renderer-)?preview-(\\d+)\\./;\n\n/** PR id from `preview-<n>.<domain>` / `manage-preview-<n>` hostname or `pull/<n>/head` branch. */\nexport function resolvePreviewPrId(env: NodeJS.ProcessEnv = process.env): string | null {\n const host = coolifyHostnameFromEnv(env);\n if (host) {\n const fromHost = PREVIEW_HOST_PR_RE.exec(host)?.[1];\n if (fromHost) return fromHost;\n }\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n if (branch) {\n const fromBranch = PULL_HEAD_BRANCH_RE.exec(stripQualifiedBranch(branch))?.[1];\n if (fromBranch) return fromBranch;\n }\n\n return null;\n}\n\n/**\n * Resolve the git branch used for Convex `--preview-name`.\n * Coolify often sets COOLIFY_BRANCH to `pull/<n>/head` at runtime (see coollabsio/coolify#9572).\n */\nexport function resolvePreviewBranch(env: NodeJS.ProcessEnv = process.env): string | null {\n if (env.GITHUB_HEAD_REF) return stripQualifiedBranch(env.GITHUB_HEAD_REF);\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n const prId = resolvePreviewPrId(env);\n if (!branch) {\n return prId ? `preview-${prId}` : null;\n }\n\n const stripped = stripQualifiedBranch(branch);\n if (PULL_HEAD_BRANCH_RE.test(stripped)) {\n if (prId) return `preview-${prId}`;\n }\n // Coolify PR apps often leave COOLIFY_BRANCH=main while FQDN is *-preview-<n>.\n if ((stripped === \"main\" || stripped === \"master\") && prId) {\n return `preview-${prId}`;\n }\n return stripped;\n}\n\n/** Strip Coolify/Convex qualified suffixes (e.g. `pull-48-head:pr-48-coolify`). */\nfunction stripQualifiedBranch(branch: string): string {\n const colon = branch.indexOf(\":\");\n return colon > 0 ? branch.slice(0, colon) : branch;\n}\n\nexport function sanitizePreviewDeployName(branch: string): string {\n return stripQualifiedBranch(branch).replace(/\\//g, \"-\");\n}\n\n/** Normalize a preview name from Convex env before `convex deploy --preview-name`. */\nexport function normalizePreviewDeployName(name: string): string {\n return sanitizePreviewDeployName(name.trim());\n}\n\nexport function ciPreviewDeployName(env: NodeJS.ProcessEnv = process.env): string | null {\n if (!isCiPreviewBuild(env)) return null;\n\n const branch = resolvePreviewBranch(env);\n if (!branch) return null;\n return sanitizePreviewDeployName(branch);\n}\n\n/** Stale failures from broken preview deploy before the bridge was live. */\nexport const STALE_ENDPOINT_DEPLOY_ERROR =\n /^deploy endpoint returned (?:200(?: OK)?|500(?: Internal Server Error)?)$/;\n\nexport function isStaleEndpointDeployError(error: string | undefined): boolean {\n return error != null && STALE_ENDPOINT_DEPLOY_ERROR.test(error);\n}\n\n/** Infra failures from a prior broken deploy that a fresh save can retry. */\nexport function isRecoverableDeployError(error: string | undefined): boolean {\n if (error == null) return false;\n if (isStaleEndpointDeployError(error)) return true;\n if (error.includes(\"502 Bad Gateway\")) return true;\n if (error.includes(\"deploy endpoint unreachable\")) return true;\n if (error.includes(\"deploy endpoint timed out\")) return true;\n return (\n error.includes(\"convex/bin/main.js\") ||\n error.includes(\"convex CLI (bin/main.js\") ||\n error.includes(\"convex CLI not available\")\n );\n}\n"]}
@@ -4338,7 +4338,13 @@ var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
4338
4338
  "columnus_api_output_secrets",
4339
4339
  "columnus_api_sync_routes",
4340
4340
  "columnus_api_outbox",
4341
- "columnus_api_delivery"
4341
+ "columnus_api_delivery",
4342
+ "columnus_assets",
4343
+ "columnus_asset_versions",
4344
+ "columnus_asset_refs",
4345
+ "columnus_asset_profiles",
4346
+ "columnus_asset_settings",
4347
+ "columnus_asset_jobs"
4342
4348
  ]);
4343
4349
  var displayTemplatesSchema = external_exports.object({
4344
4350
  id: external_exports.string().optional(),
@@ -4371,6 +4377,12 @@ var TABLE_COLORS = [
4371
4377
  "lavender"
4372
4378
  ];
4373
4379
  var tableColorSchema = external_exports.enum(TABLE_COLORS);
4380
+ var TABLE_KINDS = ["record", "asset"];
4381
+ var tableKindSchema = external_exports.enum(TABLE_KINDS);
4382
+ var assetBindingSchema = external_exports.object({
4383
+ profile: external_exports.string().min(1),
4384
+ enforced: external_exports.boolean().optional()
4385
+ });
4374
4386
  var fieldTypeSchema = external_exports.lazy(
4375
4387
  () => external_exports.discriminatedUnion("kind", [
4376
4388
  external_exports.object({ kind: external_exports.literal("string") }),
@@ -4398,7 +4410,9 @@ var fieldTypeSchema = external_exports.lazy(
4398
4410
  name: external_exports.string().regex(IDENT),
4399
4411
  label: external_exports.string().optional(),
4400
4412
  type: fieldTypeSchema,
4401
- optional: external_exports.boolean()
4413
+ optional: external_exports.boolean(),
4414
+ /** Nested image/file placements bind a Media profile like top-level ones. */
4415
+ asset: assetBindingSchema.optional()
4402
4416
  })
4403
4417
  )
4404
4418
  }),
@@ -4412,7 +4426,9 @@ var fieldSchema = external_exports.object({
4412
4426
  name: external_exports.string().regex(IDENT, "must be a valid identifier"),
4413
4427
  label: external_exports.string().optional(),
4414
4428
  type: fieldTypeSchema,
4415
- optional: external_exports.boolean()
4429
+ optional: external_exports.boolean(),
4430
+ /** Image/file fields may pin a Media profile; existence is enforced at upload/pick. */
4431
+ asset: assetBindingSchema.optional()
4416
4432
  });
4417
4433
  var indexSchema = external_exports.object({
4418
4434
  name: external_exports.string().regex(IDENT),
@@ -4429,6 +4445,8 @@ var tableSchema = external_exports.object({
4429
4445
  }),
4430
4446
  label: external_exports.string().optional(),
4431
4447
  color: tableColorSchema.optional(),
4448
+ /** Asset metadata tables are typed root tables but never ordinary user records. */
4449
+ kind: tableKindSchema.optional().default("record"),
4432
4450
  fields: external_exports.array(fieldSchema),
4433
4451
  indexes: external_exports.array(indexSchema).default([]),
4434
4452
  searchIndexes: external_exports.array(searchIndexSchema).default([]),
@@ -4438,6 +4456,7 @@ external_exports.object({
4438
4456
  tables: external_exports.array(tableSchema)
4439
4457
  }).superRefine((manifest, ctx) => {
4440
4458
  const tableNames = /* @__PURE__ */ new Set();
4459
+ const tablesByName = /* @__PURE__ */ new Map();
4441
4460
  for (const t of manifest.tables) {
4442
4461
  if (tableNames.has(t.name)) {
4443
4462
  ctx.addIssue({
@@ -4447,6 +4466,37 @@ external_exports.object({
4447
4466
  });
4448
4467
  }
4449
4468
  tableNames.add(t.name);
4469
+ tablesByName.set(t.name, t);
4470
+ if (tableKind(t) === "asset") {
4471
+ if (t.indexes.length > 0) {
4472
+ ctx.addIssue({
4473
+ code: external_exports.ZodIssueCode.custom,
4474
+ message: `${t.name} is an asset metadata table and cannot define indexes`,
4475
+ path: ["tables"]
4476
+ });
4477
+ }
4478
+ if (t.searchIndexes.length > 0) {
4479
+ ctx.addIssue({
4480
+ code: external_exports.ZodIssueCode.custom,
4481
+ message: `${t.name} is an asset metadata table and cannot define search indexes`,
4482
+ path: ["tables"]
4483
+ });
4484
+ }
4485
+ if (t.displayTemplates !== void 0) {
4486
+ ctx.addIssue({
4487
+ code: external_exports.ZodIssueCode.custom,
4488
+ message: `${t.name} is an asset metadata table and cannot define display templates`,
4489
+ path: ["tables"]
4490
+ });
4491
+ }
4492
+ if (t.color !== void 0) {
4493
+ ctx.addIssue({
4494
+ code: external_exports.ZodIssueCode.custom,
4495
+ message: `${t.name} is an asset metadata table and cannot define a color`,
4496
+ path: ["tables"]
4497
+ });
4498
+ }
4499
+ }
4450
4500
  const fieldNames = /* @__PURE__ */ new Set();
4451
4501
  for (const f of t.fields) {
4452
4502
  if (fieldNames.has(f.name)) {
@@ -4457,36 +4507,43 @@ external_exports.object({
4457
4507
  });
4458
4508
  }
4459
4509
  fieldNames.add(f.name);
4510
+ assertAssetBindingAllowed(ctx, t, f);
4460
4511
  }
4461
4512
  }
4462
4513
  for (const t of manifest.tables) {
4463
4514
  for (const f of t.fields) {
4464
- const danglingRef = findDanglingRef(f.type, tableNames);
4465
- if (danglingRef) {
4515
+ const invalidRef = findInvalidReference(f.type, tablesByName);
4516
+ if (invalidRef) {
4466
4517
  ctx.addIssue({
4467
4518
  code: external_exports.ZodIssueCode.custom,
4468
- message: `${t.name}.${f.name} references unknown table: ${danglingRef}`,
4519
+ message: `${t.name}.${f.name} ${invalidRef}`,
4469
4520
  path: ["tables"]
4470
4521
  });
4471
4522
  }
4472
4523
  }
4473
4524
  }
4474
4525
  });
4475
- function findDanglingRef(t, known) {
4526
+ function findInvalidReference(t, tablesByName) {
4476
4527
  switch (t.kind) {
4477
- case "id":
4478
- return known.has(t.table) ? null : t.table;
4528
+ case "id": {
4529
+ const target = tablesByName.get(t.table);
4530
+ if (!target) return `references unknown table: ${t.table}`;
4531
+ if (tableKind(target) === "asset") {
4532
+ return `cannot reference asset metadata table: ${t.table}`;
4533
+ }
4534
+ return null;
4535
+ }
4479
4536
  case "array":
4480
- return findDanglingRef(t.element, known);
4537
+ return findInvalidReference(t.element, tablesByName);
4481
4538
  case "object":
4482
4539
  for (const f of t.fields) {
4483
- const r = findDanglingRef(f.type, known);
4540
+ const r = findInvalidReference(f.type, tablesByName);
4484
4541
  if (r) return r;
4485
4542
  }
4486
4543
  return null;
4487
4544
  case "union":
4488
4545
  for (const o of t.options) {
4489
- const r = findDanglingRef(o, known);
4546
+ const r = findInvalidReference(o, tablesByName);
4490
4547
  if (r) return r;
4491
4548
  }
4492
4549
  return null;
@@ -4494,6 +4551,9 @@ function findDanglingRef(t, known) {
4494
4551
  return null;
4495
4552
  }
4496
4553
  }
4554
+ function tableKind(table) {
4555
+ return table.kind ?? "record";
4556
+ }
4497
4557
  function trimmedLabel(label) {
4498
4558
  const t = label?.trim();
4499
4559
  return t ? t : void 0;
@@ -4501,6 +4561,46 @@ function trimmedLabel(label) {
4501
4561
  function fieldDisplayName(f) {
4502
4562
  return trimmedLabel(f.label) ?? f.name;
4503
4563
  }
4564
+ function isFileFieldType(t) {
4565
+ return t.kind === "file" || t.kind === "image";
4566
+ }
4567
+ function isFileArrayFieldType(t) {
4568
+ return t.kind === "array" && (t.element.kind === "file" || t.element.kind === "image");
4569
+ }
4570
+ function isStorageFieldType(t) {
4571
+ return isFileFieldType(t) || isFileArrayFieldType(t);
4572
+ }
4573
+ function assertAssetBindingAllowed(ctx, table, field) {
4574
+ if (field.asset && tableKind(table) === "asset") {
4575
+ ctx.addIssue({
4576
+ code: external_exports.ZodIssueCode.custom,
4577
+ message: `${table.name}.${field.name} asset metadata fields cannot define a Media profile`,
4578
+ path: ["tables"]
4579
+ });
4580
+ } else if (field.asset && !isStorageFieldType(field.type)) {
4581
+ ctx.addIssue({
4582
+ code: external_exports.ZodIssueCode.custom,
4583
+ message: `${table.name}.${field.name} asset profile can only be set on image/file fields`,
4584
+ path: ["tables"]
4585
+ });
4586
+ }
4587
+ assertAssetBindingsInType(ctx, table, field.type);
4588
+ }
4589
+ function assertAssetBindingsInType(ctx, table, type) {
4590
+ switch (type.kind) {
4591
+ case "array":
4592
+ assertAssetBindingsInType(ctx, table, type.element);
4593
+ return;
4594
+ case "object":
4595
+ for (const field of type.fields) assertAssetBindingAllowed(ctx, table, field);
4596
+ return;
4597
+ case "union":
4598
+ for (const option of type.options) assertAssetBindingsInType(ctx, table, option);
4599
+ return;
4600
+ default:
4601
+ return;
4602
+ }
4603
+ }
4504
4604
 
4505
4605
  // packages/@columnus/shared/src/recordTableColumns.ts
4506
4606
  var RECORD_TABLE_DEFAULT_FIELD_COUNT = 5;