clivly 0.9.2 → 0.9.4

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 (64) hide show
  1. package/README.md +60 -6
  2. package/core-entity-config.cjs +1 -1
  3. package/core-entity-config.mjs +1 -1
  4. package/core-mappings-config.cjs +1 -1
  5. package/core-mappings-config.mjs +1 -1
  6. package/core-sync-engine.cjs +1 -1
  7. package/core-sync-engine.mjs +1 -1
  8. package/core-view-compiler.cjs +1 -1
  9. package/core-view-compiler.mjs +1 -1
  10. package/core.cjs +11 -6
  11. package/core.d.cts +3 -2
  12. package/core.d.mts +3 -2
  13. package/core.mjs +8 -7
  14. package/drizzle.cjs +74 -9
  15. package/drizzle.d.cts +83 -7
  16. package/drizzle.d.mts +83 -7
  17. package/drizzle.mjs +74 -10
  18. package/{entity-config-DtBsl2MQ.cjs → entity-config-7sGCk_nw.cjs} +1 -1
  19. package/{entity-config-kUHri9yA.mjs → entity-config-B74SPOUZ.mjs} +1 -1
  20. package/index.cjs +141 -29
  21. package/index.d.cts +2 -1
  22. package/index.d.mts +2 -1
  23. package/index.mjs +141 -30
  24. package/integration-state-BK-riZnm.d.cts +130 -0
  25. package/integration-state-BK-riZnm.d.mts +130 -0
  26. package/{internal-Df-PtOH_.mjs → internal-CykXpEGk.mjs} +2 -2
  27. package/{internal-CwnEG6Wl.cjs → internal-DqduZU3l.cjs} +2 -2
  28. package/internal-entity-config.cjs +1 -1
  29. package/internal-entity-config.mjs +1 -1
  30. package/internal-entity-heuristics.cjs +1 -1
  31. package/internal-entity-heuristics.mjs +1 -1
  32. package/internal-mappings-config.cjs +1 -1
  33. package/internal-mappings-config.mjs +1 -1
  34. package/{internal-CDY8g9Ic.d.mts → internal-o2M7FGlR.d.mts} +29 -1
  35. package/internal-sync-engine.cjs +1 -1
  36. package/internal-sync-engine.mjs +1 -1
  37. package/internal-view-compiler.cjs +1 -1
  38. package/internal-view-compiler.mjs +1 -1
  39. package/{internal-Da_dlcQ_.d.cts → internal-wCXSELDN.d.cts} +29 -1
  40. package/internal.cjs +11 -6
  41. package/internal.d.cts +3 -2
  42. package/internal.d.mts +3 -2
  43. package/internal.mjs +8 -7
  44. package/{mappings-config-B0RqFLxu.cjs → mappings-config-CLyEg0sf.cjs} +1 -1
  45. package/{mappings-config-B1b1HVBg.mjs → mappings-config-DQS_s4dg.mjs} +1 -1
  46. package/{namespace-probe-C6aHjedO.d.cts → namespace-probe-BoGI18dQ.d.cts} +15 -0
  47. package/{namespace-probe-Dj-0Q3Hb.d.mts → namespace-probe-Cn7voAd0.d.mts} +15 -0
  48. package/package.json +1 -1
  49. package/{projection-D7xmQP4S.mjs → projection-CSGJbZlZ.mjs} +25 -4
  50. package/{projection-C5244vGa.cjs → projection-Dz6_i1-w.cjs} +25 -4
  51. package/projection-sql-BgnfiGqF.mjs +65 -0
  52. package/projection-sql-Cl1qIn8m.cjs +94 -0
  53. package/{sdk-D4Wp_uxb.cjs → sdk-CM1UO-I7.cjs} +59 -15
  54. package/{sdk-DXV8M_Io.mjs → sdk-DYBDR939.mjs} +54 -16
  55. package/sdk.cjs +3 -2
  56. package/sdk.d.cts +5 -2
  57. package/sdk.d.mts +5 -2
  58. package/sdk.mjs +3 -3
  59. package/{sync-engine-abE94g6X.cjs → sync-engine-C4A5jgA4.cjs} +1 -1
  60. package/{sync-engine-SuxSzbZ4.mjs → sync-engine-Dt5QQSKO.mjs} +1 -1
  61. package/{view-compiler-Blu4OXJ7.cjs → view-compiler-B5nDal14.cjs} +21 -74
  62. package/{view-compiler-CQx8TRrW.mjs → view-compiler-CSYwEWf5.mjs} +2 -55
  63. package/vite.d.cts +1 -1
  64. package/vite.d.mts +1 -1
package/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { t as CANONICAL_FIELDS } from "./entity-config-kUHri9yA.mjs";
2
+ import { t as CANONICAL_FIELDS } from "./entity-config-B74SPOUZ.mjs";
3
3
  import "./mapping-score-C68uAqGB.mjs";
4
4
  import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
5
- import { t as createClivlySDK } from "./sdk-DXV8M_Io.mjs";
5
+ import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-DYBDR939.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { spawn } from "node:child_process";
8
8
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
@@ -291,10 +291,37 @@ function addCommand(pm, packages) {
291
291
  };
292
292
  }
293
293
  //#endregion
294
+ //#region src/tick-route.ts
295
+ /**
296
+ * The tick route's public URL path, and the template `clivly login` writes for
297
+ * it.
298
+ *
299
+ * Every framework the CLI scaffolds mounts the tick handler at the same URL —
300
+ * the *files* differ (`app/api/clivly/tick/route.ts`,
301
+ * `src/routes/api/clivly/tick.ts`, `app/routes/api.clivly.tick.ts`, …) but each
302
+ * one resolves to `/api/clivly/tick`. So this is a constant, not a per-framework
303
+ * lookup, and `clivly dev`, the generated config and the `.env` template can all
304
+ * read from one place instead of repeating the string and drifting.
305
+ */
306
+ const CLIVLY_TICK_PATH = "/api/clivly/tick";
307
+ /**
308
+ * What `clivly login` writes into `.env` for `CLIVLY_SYNC_TRIGGER_URL`.
309
+ *
310
+ * A visible template beats an empty line: the developer sees the shape they
311
+ * eventually need — a public origin plus the tick path — instead of an unhelpful
312
+ * `CLIVLY_SYNC_TRIGGER_URL=` they have to go and look up.
313
+ *
314
+ * It is safe to ship a truthy non-address here only because the SDK refuses to
315
+ * treat a bracketed value as configured (`isPlaceholderUrl` in
316
+ * `@clivly/sdk/trigger-url`). Change the brackets here and that guard stops
317
+ * matching, so the two must stay in step.
318
+ */
319
+ const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
320
+ //#endregion
294
321
  //#region src/dev-tunnel.ts
295
322
  const CLOUDFLARE_QUICK_TUNNEL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
296
323
  const DEFAULT_PORT = "3000";
297
- const DEFAULT_ROUTE_PATH = "/api/clivly/tick";
324
+ const DEFAULT_ROUTE_PATH = CLIVLY_TICK_PATH;
298
325
  function parseTunnelUrl(chunk) {
299
326
  const match = chunk.match(CLOUDFLARE_QUICK_TUNNEL_REGEX);
300
327
  return match ? match[0] : null;
@@ -1001,7 +1028,7 @@ Usage:
1001
1028
  clivly init [options] Scaffold Clivly (config, routes, env file) for your app
1002
1029
  clivly add widget [options] Scaffold the chat-session route
1003
1030
  clivly status [options] Check your setup, step by step
1004
- clivly push-schema [options] Report your schema to Clivly without deploying
1031
+ clivly push-schema [options] Push schema, custom objects and mappings to Clivly
1005
1032
  clivly dev [options] Tunnel localhost so remote sync can reach you
1006
1033
  clivly login [options] Pair this machine and write credentials to your env file
1007
1034
  clivly --version Print the installed clivly version
@@ -1057,7 +1084,9 @@ Options:
1057
1084
  Usage:
1058
1085
  clivly status [--verbose] [--config <path>] [--env-file <path>]
1059
1086
 
1060
- Runs the setup ladder: credential, reachability, config load, edge-safety,
1087
+ Prints two sections. "Workspace" is how far setup has got on Clivly's side,
1088
+ computed by the server so the terminal and the dashboard agree. "This project"
1089
+ runs the local ladder: credential, reachability, config load, edge-safety,
1061
1090
  setup checks, sync trigger. Read-only — writes nothing.
1062
1091
 
1063
1092
  Options:
@@ -1065,13 +1094,23 @@ Options:
1065
1094
  --config <path> Path to clivly.config.ts
1066
1095
  --env-file <path> Extra env file to load
1067
1096
  `,
1068
- "push-schema": `clivly push-schema — report your schema without deploying
1097
+ "push-schema": `clivly push-schema — push everything your config declares
1069
1098
 
1070
1099
  Usage:
1071
1100
  clivly push-schema [--config <path>]
1072
1101
 
1073
- Sends your discovered tables to Clivly so the dashboard can offer them for
1074
- mapping. Reads your config; writes nothing locally.
1102
+ Pushes three things to Clivly, without deploying your app:
1103
+ 1. Your table and column NAMES, so the dashboard can offer them for mapping.
1104
+ Never row data — discovery is names only.
1105
+ 2. The custom object types you declared with \`objectType\`.
1106
+ 3. The mappings that make those objects actually receive rows.
1107
+
1108
+ Reads your config; writes nothing locally. Safe to re-run — it reports what it
1109
+ created, updated, or left alone.
1110
+
1111
+ Exits 1 if Clivly refuses your config, most often because an entity would
1112
+ overwrite a mapping edited in the dashboard. That makes it usable in CI as a
1113
+ schema gate.
1075
1114
  `,
1076
1115
  dev: `clivly dev — tunnel localhost so remote sync can reach you
1077
1116
 
@@ -1387,15 +1426,23 @@ async function confirmWorkspace(deps, credentials) {
1387
1426
  return await deps.confirm("Write this workspace's credentials to .env? [y/N] ");
1388
1427
  }
1389
1428
  function credentialEntries(credentials) {
1390
- return [{
1391
- key: SECRET_KEY_ENV,
1392
- value: credentials.apiKey,
1393
- comment: `Org secret key — ${CREDENTIALS_LOCATION}`
1394
- }, {
1395
- key: "CLIVLY_SYNC_TRIGGER_SECRET",
1396
- value: credentials.triggerSecret,
1397
- comment: `Signing secret — ${SYNC_SECRET_LOCATION}`
1398
- }];
1429
+ return [
1430
+ {
1431
+ key: SECRET_KEY_ENV,
1432
+ value: credentials.apiKey,
1433
+ comment: `Org secret key — ${CREDENTIALS_LOCATION}`
1434
+ },
1435
+ {
1436
+ key: "CLIVLY_SYNC_TRIGGER_SECRET",
1437
+ value: credentials.triggerSecret,
1438
+ comment: `Signing secret — ${SYNC_SECRET_LOCATION}`
1439
+ },
1440
+ {
1441
+ key: "CLIVLY_SYNC_TRIGGER_URL",
1442
+ value: TRIGGER_URL_PLACEHOLDER,
1443
+ comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1444
+ }
1445
+ ];
1399
1446
  }
1400
1447
  function writeCredentials(options, credentials) {
1401
1448
  const { deps } = options;
@@ -1860,7 +1907,13 @@ export default createClivlySDK({
1860
1907
  ${sources.join(",\n")},
1861
1908
  },
1862
1909
  syncTrigger: {
1863
- // The public URL Clivly Cloud calls to trigger a sync — your mounted tick route.
1910
+ // Where Clivly Cloud calls to start a sync — your mounted tick route.
1911
+ //
1912
+ // \`path\` lets the SDK build its own public URL from the hosting platform's
1913
+ // environment (VERCEL_URL, CF_PAGES_URL, RAILWAY_PUBLIC_DOMAIN,
1914
+ // RENDER_EXTERNAL_URL, FLY_APP_NAME), so on those platforms you never have
1915
+ // to set the URL by hand. \`url\` overrides that when you do.
1916
+ path: "${CLIVLY_TICK_PATH}",
1864
1917
  url: process.env.CLIVLY_SYNC_TRIGGER_URL,
1865
1918
  secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
1866
1919
  },
@@ -2860,6 +2913,17 @@ function defaultReadConfigSource(path) {
2860
2913
  return null;
2861
2914
  }
2862
2915
  }
2916
+ /**
2917
+ * Ping Clivly at most once per command, memoised on the context.
2918
+ *
2919
+ * `clivly status` asks two questions of one response — "did this call reach
2920
+ * Clivly" and "how far has this workspace got" — and they must come from the
2921
+ * same answer, not two calls that could disagree.
2922
+ */
2923
+ function pingOnce(ctx) {
2924
+ ctx.pingResult ??= ctx.connect(ctx.apiKey, ctx.apiUrl);
2925
+ return ctx.pingResult;
2926
+ }
2863
2927
  const REASON_HINT = {
2864
2928
  ok: "",
2865
2929
  invalid_api_key: "The key was rejected (401). Check CLIVLY_SECRET_KEY (or legacy CLIVLY_API_KEY).",
@@ -2871,7 +2935,7 @@ const REASON_HINT = {
2871
2935
  network_error: "Could not reach Clivly. Check your network or --api-url."
2872
2936
  };
2873
2937
  const apiKeyStage = {
2874
- id: "credential",
2938
+ id: "api-key",
2875
2939
  label: "API key present",
2876
2940
  run(ctx) {
2877
2941
  if (ctx.apiKey) return Promise.resolve({
@@ -2886,11 +2950,11 @@ const apiKeyStage = {
2886
2950
  }
2887
2951
  };
2888
2952
  const reachabilityStage = {
2889
- id: "application",
2953
+ id: "reachable",
2890
2954
  label: "Clivly reachable",
2891
2955
  needs: [apiKeyStage.id],
2892
2956
  async run(ctx) {
2893
- const result = await ctx.connect(ctx.apiKey, ctx.apiUrl);
2957
+ const result = await pingOnce(ctx);
2894
2958
  if (result.ok) {
2895
2959
  const name = result.org?.name ?? "(unnamed org)";
2896
2960
  const id = result.org?.id ? ` (${result.org.id})` : "";
@@ -2962,14 +3026,15 @@ const triggerUrlStage = {
2962
3026
  needs: [configStage.id],
2963
3027
  run(ctx) {
2964
3028
  const url = ctx.sdk?.syncTrigger?.url;
2965
- if (url) return Promise.resolve({
3029
+ if (url && !isPlaceholderUrl(url)) return Promise.resolve({
2966
3030
  state: "pass",
2967
3031
  detail: url
2968
3032
  });
3033
+ const isPlaceholder = url !== void 0;
2969
3034
  return Promise.resolve({
2970
3035
  state: "warn",
2971
- detail: "Not set — remote sync cannot call back into this app.",
2972
- hint: "Set CLIVLY_SYNC_TRIGGER_URL once the app is deployed. For local development run `clivly dev` — it opens a tunnel and registers the URL for you. Push-only setups can ignore this."
3036
+ detail: isPlaceholder ? `Still the placeholder (${url}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
3037
+ hint: "Replace <yourDomain.com> with your deployed origin, or delete the line — on Vercel, Cloudflare Pages, Railway, Render and Fly the SDK derives the URL from the platform. For local development run `clivly dev`: it opens a tunnel and registers the URL for you, outranking whatever is in .env. Push-only setups can ignore this."
2973
3038
  });
2974
3039
  }
2975
3040
  };
@@ -3024,19 +3089,57 @@ function defaultLoadSdk(path) {
3024
3089
  return loadConfigSdk(path, "doctor");
3025
3090
  }
3026
3091
  //#endregion
3092
+ //#region src/workspace-section.ts
3093
+ const MARK$1 = {
3094
+ active: "⏳",
3095
+ blocked: "🔒",
3096
+ done: "✅",
3097
+ failed: "❌",
3098
+ skipped: "⏭️ "
3099
+ };
3100
+ /**
3101
+ * The workspace half of `clivly status`, rendered from what the server sent.
3102
+ *
3103
+ * This module decides nothing about a stage's state — it prints the verdict the
3104
+ * server computed. A CLI that computed its own would be a second ladder, which
3105
+ * is the drift this whole change removes.
3106
+ *
3107
+ * "Unavailable" and "nothing is set up" are deliberately different outcomes. An
3108
+ * old server, a rejected key and a dead network all mean we could not ask; none
3109
+ * of them means the workspace is empty.
3110
+ */
3111
+ function workspaceSection(result) {
3112
+ if (!result) return {
3113
+ kind: "unavailable",
3114
+ reason: "Could not reach Clivly."
3115
+ };
3116
+ if (!result.ok) return {
3117
+ kind: "unavailable",
3118
+ reason: result.status === null ? "Could not reach Clivly (no response)." : `Clivly rejected the request — ${result.reason} (HTTP ${result.status}).`
3119
+ };
3120
+ if (!result.workspace) return {
3121
+ kind: "unavailable",
3122
+ reason: "This Clivly server does not report setup progress. Upgrade the server to see it here."
3123
+ };
3124
+ return {
3125
+ kind: "ladder",
3126
+ lines: result.workspace.steps.map((step) => ` ${MARK$1[step.state]} ${step.label}${step.evidence ? ` ${step.evidence}` : ""}`)
3127
+ };
3128
+ }
3129
+ //#endregion
3027
3130
  //#region src/status.ts
3028
3131
  /** Stage ids each legacy command maps onto. */
3029
3132
  const ALIAS_STAGE_IDS = {
3030
3133
  status: STATUS_STAGES.map((stage) => stage.id),
3031
- ping: ["credential", "application"],
3134
+ ping: ["api-key", "reachable"],
3032
3135
  doctor: [
3033
3136
  "config",
3034
3137
  "runtime",
3035
3138
  "setup-checks"
3036
3139
  ],
3037
3140
  connect: [
3038
- "credential",
3039
- "application",
3141
+ "api-key",
3142
+ "reachable",
3040
3143
  "config",
3041
3144
  "trigger-url"
3042
3145
  ]
@@ -3092,6 +3195,13 @@ async function runStatus(options) {
3092
3195
  };
3093
3196
  out("\nClivly status\n");
3094
3197
  const results = await runLadder(stages, ctx);
3198
+ if (selected.has("reachable")) {
3199
+ const section = workspaceSection(ctx.pingResult ? await ctx.pingResult : null);
3200
+ out("\nWorkspace\n");
3201
+ if (section.kind === "ladder") for (const line of section.lines) out(`${line}\n`);
3202
+ else out(` ⚠️ ${section.reason}\n`);
3203
+ }
3204
+ out("\nThis project\n");
3095
3205
  for (const result of results) renderStage(result, options.verbose ?? false, out);
3096
3206
  const code = ladderExitCode(results);
3097
3207
  if (code !== 0 && results.some((result) => result.state === "pending")) out("\nSetup is incomplete — finish the steps above and re-run.\n");
@@ -3121,6 +3231,7 @@ const KNOWN_COMMANDS = [
3121
3231
  "init",
3122
3232
  "status",
3123
3233
  "push-schema",
3234
+ "add",
3124
3235
  "dev",
3125
3236
  "login",
3126
3237
  "help",
@@ -3477,8 +3588,8 @@ function previewMutations(deps, mutations, options) {
3477
3588
  function printNextSteps(out, authComplete, loginRan, envFile) {
3478
3589
  out("\nNext steps:\n");
3479
3590
  out(loginRan ? " 1. Credentials written by `clivly login`\n" : ` 1. Add CLIVLY_SECRET_KEY to ${envFile} — create one at\n ${CREDENTIALS_LOCATION}\n (the legacy CLIVLY_API_KEY is still accepted)\n`);
3480
- out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
3481
- out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
3591
+ out(" 2. Local dev: run `clivly dev` for dashboard-triggered sync\n");
3592
+ out(" It tunnels your app and registers the URL for you — remote sync is\n deploy-only otherwise, so nothing is broken if you skip it. Once deployed\n to Vercel/Cloudflare/Railway/Render/Fly the URL is detected automatically;\n set CLIVLY_SYNC_TRIGGER_URL by hand only for a custom domain.\n");
3482
3593
  const step = authComplete ? 3 : 4;
3483
3594
  if (!authComplete) out(" 3. Finish resolveUser in the auth/verify route\n");
3484
3595
  out(` ${step}. Run \`clivly status\` to see where you are\n`);
@@ -3888,4 +3999,4 @@ if (isEntrypoint(process.argv[1], import.meta.url)) main(process.argv.slice(2)).
3888
3999
  process.exitCode = code;
3889
4000
  });
3890
4001
  //#endregion
3891
- export { credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
4002
+ export { KNOWN_COMMANDS, credentialsMissing, main, nearestCommand, runAdd, runInit, runInitFromArgv };
@@ -0,0 +1,130 @@
1
+ //#region ../core/src/integration-state.d.ts
2
+ declare const INTEGRATION_STEPS: readonly ["credential", "application", "schema", "mapping", "trigger", "sync", "widget"];
3
+ type StepId = (typeof INTEGRATION_STEPS)[number];
4
+ /** `blocked` means "prerequisite unmet", never "broken" — see C-19. */
5
+ type StepState = "done" | "active" | "blocked" | "failed" | "skipped";
6
+ type IntegrationStatus = "not_configured" | "sdk_installed" | "awaiting_connection" | "connected" | "schema_discovered" | "mapping_required" | "trigger_required" | "ready_to_sync" | "syncing" | "healthy" | "warning" | "error" |
7
+ /**
8
+ * @deprecated No longer derived. Liveness is its own dimension — read
9
+ * `ConnectionHealth` from `deriveLiveness`. Setup progress does not move
10
+ * backwards just because an app stopped running.
11
+ */
12
+ "disconnected";
13
+ interface RunFact {
14
+ at: number;
15
+ message?: string;
16
+ ok: boolean;
17
+ }
18
+ /**
19
+ * Where the reporting app is running, as the app itself understands it.
20
+ *
21
+ * `unknown` is a real value, not an absence: `detectHostEnvironment` refuses to
22
+ * guess when nothing places the app, and a bare `node server.js` with no
23
+ * NODE_ENV must still be able to connect and be shown as itself. Defaulting
24
+ * such a host to `local_dev` would file a production box's faults under local
25
+ * development, where nobody would look for them.
26
+ */
27
+ type HostEnvironment = "local_dev" | "preview" | "production" | "unknown";
28
+ interface IntegrationFacts {
29
+ discoveredTableCount: number;
30
+ /** The environment the app reported on its last heartbeat. */
31
+ environment?: HostEnvironment | null;
32
+ /**
33
+ * Has this app ever checked in? Distinguishes "never installed" from "was
34
+ * working, now silent" — the same absent heartbeat means different things,
35
+ * and only one of them is diagnosed rather than set up.
36
+ */
37
+ everConnected: boolean;
38
+ hasCredential: boolean;
39
+ /**
40
+ * The cadence the app said it heartbeats at. Liveness windows are multiples
41
+ * of this rather than a fixed constant, so an app that deliberately reports
42
+ * slowly is not called dead for obeying its own configuration.
43
+ */
44
+ heartbeatIntervalMs?: number | null;
45
+ lastHeartbeatAt: number | null;
46
+ lastRun: RunFact | null;
47
+ mappedEntityCount: number;
48
+ now: number;
49
+ /**
50
+ * Deployment presence, when this deployment has one. Absent or `http`
51
+ * transport keeps the cadence-derived behaviour every SDK in the field uses.
52
+ */
53
+ presence?: DeploymentPresence | null;
54
+ /**
55
+ * Faults the running app diagnosed about itself on its last heartbeat — an
56
+ * unreadable database, a secret missing from the process environment. These
57
+ * are what make a reachable app `live_with_issues`; unfinished setup is not.
58
+ */
59
+ reportedIssues?: StepProblem[];
60
+ syncInFlight: boolean;
61
+ /** A trigger URL and secret both exist, so Clivly can ask the app to sync. */
62
+ triggerConfigured: boolean;
63
+ warnings: string[];
64
+ widgetInstalled: boolean;
65
+ }
66
+ interface StepProblem {
67
+ code: string;
68
+ fix: string;
69
+ message: string;
70
+ }
71
+ interface IntegrationStep {
72
+ blockedBy: StepId | null;
73
+ evidence: string | null;
74
+ id: StepId;
75
+ label: string;
76
+ problem: StepProblem | null;
77
+ state: StepState;
78
+ }
79
+ type ConnectionHealth = "live" | "live_with_issues" | "stale" | "offline";
80
+ /** Which algorithm decides whether this deployment is up. */
81
+ type PresenceTransport = "websocket" | "http";
82
+ type PresenceConnectionState = "connected" | "reconnecting" | "disconnected";
83
+ /**
84
+ * A deployment's connection state as its `PresenceRoom` last persisted it.
85
+ *
86
+ * This exists because a socket has an explicit state and a timestamp does not.
87
+ * Deriving WebSocket liveness from heartbeat age would leave a disconnected app
88
+ * shown as live for 150 seconds and offline only after ten minutes, which is
89
+ * the whole reason this transport exists.
90
+ */
91
+ interface DeploymentPresence {
92
+ /** Sockets currently accepted in the room. */
93
+ activeInstanceCount: number;
94
+ connectionState: PresenceConnectionState;
95
+ transport: PresenceTransport;
96
+ }
97
+ interface ConnectionLiveness {
98
+ /** `"8s ago"`, or `"No heartbeat yet"` when the app has never reported. */
99
+ ageLabel: string;
100
+ ageMs: number | null;
101
+ environment: HostEnvironment | null;
102
+ health: ConnectionHealth;
103
+ /**
104
+ * Faults on a reachable app. Empty unless `health` is `live_with_issues`.
105
+ */
106
+ issues: StepProblem[];
107
+ /** Present when a deployment-presence record supplied this liveness result. */
108
+ presence?: Pick<DeploymentPresence, "connectionState" | "transport">;
109
+ /** Is a heartbeat recent enough to speak for the app as it is right now? */
110
+ reachable: boolean;
111
+ }
112
+ interface NextAction {
113
+ command: string | null;
114
+ envFile: string | null;
115
+ label: string;
116
+ }
117
+ interface IntegrationState {
118
+ /**
119
+ * Is the app running right now. Deliberately alongside `status` rather than
120
+ * folded into it: a surface must be able to say "setup reached mapping" and
121
+ * "the app is offline" in the same breath without either overwriting the
122
+ * other.
123
+ */
124
+ liveness: ConnectionLiveness;
125
+ next: NextAction | null;
126
+ status: IntegrationStatus;
127
+ steps: IntegrationStep[];
128
+ }
129
+ //#endregion
130
+ export { IntegrationStep as a, StepProblem as c, IntegrationStatus as i, StepState as l, IntegrationFacts as n, NextAction as o, IntegrationState as r, StepId as s, ConnectionLiveness as t };
@@ -0,0 +1,130 @@
1
+ //#region ../core/src/integration-state.d.ts
2
+ declare const INTEGRATION_STEPS: readonly ["credential", "application", "schema", "mapping", "trigger", "sync", "widget"];
3
+ type StepId = (typeof INTEGRATION_STEPS)[number];
4
+ /** `blocked` means "prerequisite unmet", never "broken" — see C-19. */
5
+ type StepState = "done" | "active" | "blocked" | "failed" | "skipped";
6
+ type IntegrationStatus = "not_configured" | "sdk_installed" | "awaiting_connection" | "connected" | "schema_discovered" | "mapping_required" | "trigger_required" | "ready_to_sync" | "syncing" | "healthy" | "warning" | "error" |
7
+ /**
8
+ * @deprecated No longer derived. Liveness is its own dimension — read
9
+ * `ConnectionHealth` from `deriveLiveness`. Setup progress does not move
10
+ * backwards just because an app stopped running.
11
+ */
12
+ "disconnected";
13
+ interface RunFact {
14
+ at: number;
15
+ message?: string;
16
+ ok: boolean;
17
+ }
18
+ /**
19
+ * Where the reporting app is running, as the app itself understands it.
20
+ *
21
+ * `unknown` is a real value, not an absence: `detectHostEnvironment` refuses to
22
+ * guess when nothing places the app, and a bare `node server.js` with no
23
+ * NODE_ENV must still be able to connect and be shown as itself. Defaulting
24
+ * such a host to `local_dev` would file a production box's faults under local
25
+ * development, where nobody would look for them.
26
+ */
27
+ type HostEnvironment = "local_dev" | "preview" | "production" | "unknown";
28
+ interface IntegrationFacts {
29
+ discoveredTableCount: number;
30
+ /** The environment the app reported on its last heartbeat. */
31
+ environment?: HostEnvironment | null;
32
+ /**
33
+ * Has this app ever checked in? Distinguishes "never installed" from "was
34
+ * working, now silent" — the same absent heartbeat means different things,
35
+ * and only one of them is diagnosed rather than set up.
36
+ */
37
+ everConnected: boolean;
38
+ hasCredential: boolean;
39
+ /**
40
+ * The cadence the app said it heartbeats at. Liveness windows are multiples
41
+ * of this rather than a fixed constant, so an app that deliberately reports
42
+ * slowly is not called dead for obeying its own configuration.
43
+ */
44
+ heartbeatIntervalMs?: number | null;
45
+ lastHeartbeatAt: number | null;
46
+ lastRun: RunFact | null;
47
+ mappedEntityCount: number;
48
+ now: number;
49
+ /**
50
+ * Deployment presence, when this deployment has one. Absent or `http`
51
+ * transport keeps the cadence-derived behaviour every SDK in the field uses.
52
+ */
53
+ presence?: DeploymentPresence | null;
54
+ /**
55
+ * Faults the running app diagnosed about itself on its last heartbeat — an
56
+ * unreadable database, a secret missing from the process environment. These
57
+ * are what make a reachable app `live_with_issues`; unfinished setup is not.
58
+ */
59
+ reportedIssues?: StepProblem[];
60
+ syncInFlight: boolean;
61
+ /** A trigger URL and secret both exist, so Clivly can ask the app to sync. */
62
+ triggerConfigured: boolean;
63
+ warnings: string[];
64
+ widgetInstalled: boolean;
65
+ }
66
+ interface StepProblem {
67
+ code: string;
68
+ fix: string;
69
+ message: string;
70
+ }
71
+ interface IntegrationStep {
72
+ blockedBy: StepId | null;
73
+ evidence: string | null;
74
+ id: StepId;
75
+ label: string;
76
+ problem: StepProblem | null;
77
+ state: StepState;
78
+ }
79
+ type ConnectionHealth = "live" | "live_with_issues" | "stale" | "offline";
80
+ /** Which algorithm decides whether this deployment is up. */
81
+ type PresenceTransport = "websocket" | "http";
82
+ type PresenceConnectionState = "connected" | "reconnecting" | "disconnected";
83
+ /**
84
+ * A deployment's connection state as its `PresenceRoom` last persisted it.
85
+ *
86
+ * This exists because a socket has an explicit state and a timestamp does not.
87
+ * Deriving WebSocket liveness from heartbeat age would leave a disconnected app
88
+ * shown as live for 150 seconds and offline only after ten minutes, which is
89
+ * the whole reason this transport exists.
90
+ */
91
+ interface DeploymentPresence {
92
+ /** Sockets currently accepted in the room. */
93
+ activeInstanceCount: number;
94
+ connectionState: PresenceConnectionState;
95
+ transport: PresenceTransport;
96
+ }
97
+ interface ConnectionLiveness {
98
+ /** `"8s ago"`, or `"No heartbeat yet"` when the app has never reported. */
99
+ ageLabel: string;
100
+ ageMs: number | null;
101
+ environment: HostEnvironment | null;
102
+ health: ConnectionHealth;
103
+ /**
104
+ * Faults on a reachable app. Empty unless `health` is `live_with_issues`.
105
+ */
106
+ issues: StepProblem[];
107
+ /** Present when a deployment-presence record supplied this liveness result. */
108
+ presence?: Pick<DeploymentPresence, "connectionState" | "transport">;
109
+ /** Is a heartbeat recent enough to speak for the app as it is right now? */
110
+ reachable: boolean;
111
+ }
112
+ interface NextAction {
113
+ command: string | null;
114
+ envFile: string | null;
115
+ label: string;
116
+ }
117
+ interface IntegrationState {
118
+ /**
119
+ * Is the app running right now. Deliberately alongside `status` rather than
120
+ * folded into it: a surface must be able to say "setup reached mapping" and
121
+ * "the app is offline" in the same breath without either overwriting the
122
+ * other.
123
+ */
124
+ liveness: ConnectionLiveness;
125
+ next: NextAction | null;
126
+ status: IntegrationStatus;
127
+ steps: IntegrationStep[];
128
+ }
129
+ //#endregion
130
+ export { IntegrationStep as a, StepProblem as c, IntegrationStatus as i, StepState as l, IntegrationFacts as n, NextAction as o, IntegrationState as r, StepId as s, ConnectionLiveness as t };
@@ -1,5 +1,5 @@
1
- import { n as PROJECTION_KEY_PATTERN, t as BASE_REF } from "./projection-D7xmQP4S.mjs";
2
- import { h as fanOutJoins, m as validateProjection } from "./entity-config-kUHri9yA.mjs";
1
+ import { n as PROJECTION_KEY_PATTERN, t as BASE_REF } from "./projection-CSGJbZlZ.mjs";
2
+ import { h as fanOutJoins, m as validateProjection } from "./entity-config-B74SPOUZ.mjs";
3
3
  //#region ../core/src/projection-draft.ts
4
4
  function emptyProjectionDraft(baseTable) {
5
5
  return {
@@ -1,5 +1,5 @@
1
- const require_projection = require("./projection-C5244vGa.cjs");
2
- const require_entity_config = require("./entity-config-DtBsl2MQ.cjs");
1
+ const require_projection = require("./projection-Dz6_i1-w.cjs");
2
+ const require_entity_config = require("./entity-config-7sGCk_nw.cjs");
3
3
  //#region ../core/src/projection-draft.ts
4
4
  function emptyProjectionDraft(baseTable) {
5
5
  return {
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_entity_config = require("./entity-config-DtBsl2MQ.cjs");
2
+ const require_entity_config = require("./entity-config-7sGCk_nw.cjs");
3
3
  exports.CANONICAL_FIELDS = require_entity_config.CANONICAL_FIELDS;
4
4
  exports.CRM_CONCEPTS = require_entity_config.CRM_CONCEPTS;
5
5
  exports.CUSTOM_CONCEPT = require_entity_config.CUSTOM_CONCEPT;
@@ -1,2 +1,2 @@
1
- import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-kUHri9yA.mjs";
1
+ import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-B74SPOUZ.mjs";
2
2
  export { CANONICAL_FIELDS, CRM_CONCEPTS, CUSTOM_CONCEPT, ClivlyConfigError, MATERIALIZING_CONCEPTS, customObjectDeclarations, defineClivlyConfig, entitiesConfigSchema, filterSchema, projectionFields, relationshipSchema, relationshipsSchema, validateEntitiesConfig };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_entity_config = require("./entity-config-DtBsl2MQ.cjs");
2
+ const require_entity_config = require("./entity-config-7sGCk_nw.cjs");
3
3
  const require_mapping_score = require("./mapping-score-CaB3F4Uu.cjs");
4
4
  //#region ../core/src/entity-heuristics.ts
5
5
  const TARGET_CONCEPTS = [
@@ -1,4 +1,4 @@
1
- import { a as MATERIALIZING_CONCEPTS } from "./entity-config-kUHri9yA.mjs";
1
+ import { a as MATERIALIZING_CONCEPTS } from "./entity-config-B74SPOUZ.mjs";
2
2
  import { a as W, c as isTextLike, i as RELATIONSHIP_TIERS, l as isTimestampLike, n as CONCEPT_TIERS, o as isAmbiguous, r as FIELD_TIERS, s as isNumericLike, u as tierFor } from "./mapping-score-C68uAqGB.mjs";
3
3
  //#region ../core/src/entity-heuristics.ts
4
4
  const TARGET_CONCEPTS = [
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_mappings_config = require("./mappings-config-B0RqFLxu.cjs");
2
+ const require_mappings_config = require("./mappings-config-CLyEg0sf.cjs");
3
3
  exports.entitiesToMappings = require_mappings_config.entitiesToMappings;
4
4
  exports.mappingsToEntitiesConfig = require_mappings_config.mappingsToEntitiesConfig;
@@ -1,2 +1,2 @@
1
- import { n as mappingsToEntitiesConfig, t as entitiesToMappings } from "./mappings-config-B1b1HVBg.mjs";
1
+ import { n as mappingsToEntitiesConfig, t as entitiesToMappings } from "./mappings-config-DQS_s4dg.mjs";
2
2
  export { entitiesToMappings, mappingsToEntitiesConfig };
@@ -36,6 +36,34 @@ declare function describeCardinality(draft: ProjectionDraft, schema: DiscoveredS
36
36
  joinKey: string;
37
37
  }>;
38
38
  //#endregion
39
+ //#region ../core/src/projection-sql.d.ts
40
+ /**
41
+ * SQL generation for projection-backed entities.
42
+ *
43
+ * Base and joins are aliased by their ref key (`base`, then each join's `key`),
44
+ * which is what allows the same table to be joined twice and keeps generated
45
+ * SQL independent of host table names.
46
+ *
47
+ * Pure string generation — no DB access. Identifiers are escaped so output is
48
+ * deterministic and injection-safe.
49
+ */
50
+ /** Quote a single identifier part, escaping embedded double-quotes. */
51
+ declare function quoteIdent(name: string): string;
52
+ /**
53
+ * The identity expression. Composite identities concatenate their parts with
54
+ * `::`; validation guarantees every part is NOT NULL and non-text, which is
55
+ * what makes the concatenation collision-free.
56
+ *
57
+ * Exported unaliased because two paths need the same expression under different
58
+ * names: the view compiler aliases it `id`, and the SDK's projection push source
59
+ * also orders and keyset-paginates by it. Sharing one definition is what keeps
60
+ * a pushed row's `id` identical to the one the view would have produced.
61
+ */
62
+ declare function compileProjectionIdentity(projection: ProjectionMapping, basePkColumn: string): string;
63
+ declare function compileProjectionJoins(projection: ProjectionMapping): string[];
64
+ /** `FROM <baseTable> AS "base"` — the alias every ref resolves against. */
65
+ declare function compileProjectionFrom(projection: ProjectionMapping): string;
66
+ //#endregion
39
67
  //#region ../core/src/projection-validate.d.ts
40
68
  /**
41
69
  * Validate a projection against the discovered schema. Returns every error
@@ -69,4 +97,4 @@ declare function coversUniqueKey(table: DiscoveredSchemaTable, columns: string[]
69
97
  /** Is this table's payload rich enough to validate a projection against? */
70
98
  declare function hasRichMeta(table: DiscoveredSchemaTable): boolean;
71
99
  //#endregion
72
- export { validateProjection as a, draftErrors as c, suggestJoinConditions as d, fanOutJoins as f, primaryKeyColumns as i, draftToProjection as l, coversUniqueKey as n, ProjectionDraft as o, joinFansOut as p, hasRichMeta as r, describeCardinality as s, columnMetaFor as t, emptyProjectionDraft as u };
100
+ export { joinFansOut as _, validateProjection as a, compileProjectionJoins as c, describeCardinality as d, draftErrors as f, fanOutJoins as g, suggestJoinConditions as h, primaryKeyColumns as i, quoteIdent as l, emptyProjectionDraft as m, coversUniqueKey as n, compileProjectionFrom as o, draftToProjection as p, hasRichMeta as r, compileProjectionIdentity as s, columnMetaFor as t, ProjectionDraft as u };