@abloatai/ablo 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +3 -3
  4. package/dist/BaseSyncedStore.d.ts +72 -195
  5. package/dist/BaseSyncedStore.js +240 -625
  6. package/dist/Database.d.ts +8 -9
  7. package/dist/Database.js +9 -14
  8. package/dist/LazyReferenceCollection.js +5 -0
  9. package/dist/Model.js +6 -8
  10. package/dist/ModelRegistry.d.ts +25 -6
  11. package/dist/ModelRegistry.js +12 -6
  12. package/dist/NetworkMonitor.js +1 -1
  13. package/dist/ObjectPool.js +9 -3
  14. package/dist/SyncClient.d.ts +15 -5
  15. package/dist/SyncClient.js +152 -66
  16. package/dist/SyncEngineContext.d.ts +1 -3
  17. package/dist/SyncEngineContext.js +1 -2
  18. package/dist/agent/Agent.d.ts +21 -8
  19. package/dist/agent/Agent.js +26 -13
  20. package/dist/ai-sdk/coordination-context.js +9 -3
  21. package/dist/ai-sdk/wrap.d.ts +1 -1
  22. package/dist/ai-sdk/wrap.js +1 -1
  23. package/dist/auth/credentialPolicy.d.ts +10 -2
  24. package/dist/auth/credentialSource.d.ts +1 -13
  25. package/dist/auth/credentialSource.js +6 -13
  26. package/dist/auth/index.js +3 -3
  27. package/dist/cli.cjs +328 -262
  28. package/dist/client/Ablo.d.ts +35 -764
  29. package/dist/client/Ablo.js +64 -688
  30. package/dist/client/ApiClient.d.ts +14 -1
  31. package/dist/client/ApiClient.js +63 -9
  32. package/dist/client/auth.d.ts +8 -17
  33. package/dist/client/auth.js +42 -6
  34. package/dist/client/consoleLogger.d.ts +36 -0
  35. package/dist/client/consoleLogger.js +45 -0
  36. package/dist/client/createInternalComponents.js +1 -1
  37. package/dist/client/createModelProxy.js +10 -6
  38. package/dist/client/credentialEndpoint.d.ts +63 -0
  39. package/dist/client/credentialEndpoint.js +87 -0
  40. package/dist/client/hostedEndpoints.d.ts +24 -0
  41. package/dist/client/hostedEndpoints.js +24 -0
  42. package/dist/client/httpClient.d.ts +1 -1
  43. package/dist/client/identity.js +2 -1
  44. package/dist/client/modelRegistration.d.ts +14 -0
  45. package/dist/client/modelRegistration.js +321 -0
  46. package/dist/client/options.d.ts +396 -0
  47. package/dist/client/options.js +10 -0
  48. package/dist/client/resourceTypes.d.ts +344 -0
  49. package/dist/client/resourceTypes.js +13 -0
  50. package/dist/client/schemaConfig.d.ts +56 -0
  51. package/dist/client/schemaConfig.js +188 -0
  52. package/dist/client/sessionMint.d.ts +1 -1
  53. package/dist/client/validateAbloOptions.js +1 -1
  54. package/dist/client/wsMutationExecutor.d.ts +33 -0
  55. package/dist/client/wsMutationExecutor.js +77 -0
  56. package/dist/context.js +0 -3
  57. package/dist/coordination/index.d.ts +8 -1
  58. package/dist/coordination/index.js +24 -1
  59. package/dist/coordination/trace.d.ts +0 -1
  60. package/dist/coordination/trace.js +0 -1
  61. package/dist/core/DatabaseManager.d.ts +0 -1
  62. package/dist/core/DatabaseManager.js +26 -24
  63. package/dist/core/QueryProcessor.js +7 -8
  64. package/dist/core/QueryView.d.ts +14 -2
  65. package/dist/core/QueryView.js +4 -5
  66. package/dist/core/StoreManager.d.ts +2 -2
  67. package/dist/core/StoreManager.js +5 -1
  68. package/dist/core/index.d.ts +1 -1
  69. package/dist/core/openIDBWithTimeout.js +15 -11
  70. package/dist/core/query-utils.js +1 -1
  71. package/dist/core/storeContract.d.ts +143 -0
  72. package/dist/core/storeContract.js +16 -0
  73. package/dist/errorCodes.d.ts +11 -2
  74. package/dist/errorCodes.js +155 -137
  75. package/dist/errors.d.ts +18 -7
  76. package/dist/errors.js +14 -1
  77. package/dist/index.js +5 -0
  78. package/dist/interfaces/index.d.ts +6 -20
  79. package/dist/keys/index.js +18 -9
  80. package/dist/mutators/UndoManager.js +6 -1
  81. package/dist/mutators/defineMutators.d.ts +1 -3
  82. package/dist/policy/types.d.ts +1 -1
  83. package/dist/query/client.d.ts +11 -0
  84. package/dist/query/client.js +81 -52
  85. package/dist/react/AbloProvider.d.ts +6 -10
  86. package/dist/react/AbloProvider.js +22 -15
  87. package/dist/react/context.d.ts +2 -118
  88. package/dist/react/useAblo.js +1 -1
  89. package/dist/react/useErrorListener.js +1 -1
  90. package/dist/react/useMutationFailureListener.js +1 -1
  91. package/dist/react/useMutators.js +7 -1
  92. package/dist/react/useReactive.js +0 -1
  93. package/dist/react/useUndoScope.js +2 -2
  94. package/dist/schema/ddlLock.d.ts +39 -0
  95. package/dist/schema/ddlLock.js +51 -0
  96. package/dist/schema/field.js +1 -1
  97. package/dist/schema/index.d.ts +4 -3
  98. package/dist/schema/index.js +15 -5
  99. package/dist/schema/model.d.ts +3 -3
  100. package/dist/schema/model.js +2 -2
  101. package/dist/schema/openapi.js +5 -1
  102. package/dist/schema/queries.d.ts +1 -1
  103. package/dist/schema/queries.js +1 -0
  104. package/dist/schema/residency.d.ts +35 -0
  105. package/dist/schema/residency.js +27 -0
  106. package/dist/schema/roles.d.ts +25 -0
  107. package/dist/schema/roles.js +37 -0
  108. package/dist/schema/schema.d.ts +3 -3
  109. package/dist/schema/schema.js +11 -7
  110. package/dist/schema/serialize.d.ts +8 -8
  111. package/dist/schema/sync-delta-row.d.ts +2 -2
  112. package/dist/schema/sync-delta-row.js +2 -2
  113. package/dist/server/storage-mode.d.ts +8 -1
  114. package/dist/server/storage-mode.js +14 -4
  115. package/dist/source/adapter.d.ts +1 -1
  116. package/dist/source/adapters/drizzle.js +4 -3
  117. package/dist/source/adapters/kysely.js +4 -3
  118. package/dist/source/adapters/memory.js +1 -1
  119. package/dist/source/adapters/prisma.js +5 -4
  120. package/dist/source/conformance.js +10 -8
  121. package/dist/source/connector.js +4 -2
  122. package/dist/source/factory.d.ts +106 -0
  123. package/dist/source/factory.js +273 -0
  124. package/dist/source/index.d.ts +11 -461
  125. package/dist/source/index.js +14 -416
  126. package/dist/source/next.d.ts +1 -1
  127. package/dist/source/next.js +1 -1
  128. package/dist/source/pushQueue.d.ts +1 -1
  129. package/dist/source/pushQueue.js +6 -3
  130. package/dist/source/signing.d.ts +59 -0
  131. package/dist/source/signing.js +142 -0
  132. package/dist/source/types.d.ts +334 -0
  133. package/dist/source/types.js +44 -0
  134. package/dist/stores/ObjectStore.d.ts +1 -1
  135. package/dist/stores/ObjectStore.js +23 -23
  136. package/dist/stores/SyncActionStore.d.ts +1 -1
  137. package/dist/stores/SyncActionStore.js +70 -35
  138. package/dist/surface.d.ts +1 -1
  139. package/dist/surface.js +1 -0
  140. package/dist/sync/AreaOfInterestManager.d.ts +2 -2
  141. package/dist/sync/AreaOfInterestManager.js +1 -1
  142. package/dist/sync/BootstrapHelper.d.ts +1 -3
  143. package/dist/sync/BootstrapHelper.js +29 -8
  144. package/dist/sync/ConnectionManager.d.ts +8 -0
  145. package/dist/sync/ConnectionManager.js +19 -4
  146. package/dist/sync/HydrationCoordinator.d.ts +12 -0
  147. package/dist/sync/HydrationCoordinator.js +25 -5
  148. package/dist/sync/NetworkProbe.d.ts +3 -2
  149. package/dist/sync/NetworkProbe.js +7 -6
  150. package/dist/sync/SyncWebSocket.d.ts +71 -120
  151. package/dist/sync/SyncWebSocket.js +324 -630
  152. package/dist/sync/awaitClaimGrant.js +27 -19
  153. package/dist/sync/bootstrapApply.d.ts +60 -0
  154. package/dist/sync/bootstrapApply.js +65 -0
  155. package/dist/sync/commitFrames.d.ts +43 -0
  156. package/dist/sync/commitFrames.js +94 -0
  157. package/dist/sync/createClaimStream.js +3 -2
  158. package/dist/sync/createSnapshot.d.ts +1 -3
  159. package/dist/sync/credentialLifecycle.d.ts +175 -0
  160. package/dist/sync/credentialLifecycle.js +329 -0
  161. package/dist/sync/deltaPipeline.d.ts +111 -0
  162. package/dist/sync/deltaPipeline.js +262 -0
  163. package/dist/sync/groupChange.d.ts +111 -0
  164. package/dist/sync/groupChange.js +246 -0
  165. package/dist/sync/heartbeat.d.ts +62 -0
  166. package/dist/sync/heartbeat.js +91 -0
  167. package/dist/sync/participants.d.ts +8 -8
  168. package/dist/sync/syncCursor.d.ts +44 -0
  169. package/dist/sync/syncCursor.js +59 -0
  170. package/dist/sync/syncPlan.d.ts +62 -0
  171. package/dist/sync/syncPlan.js +51 -0
  172. package/dist/sync/wsFrameHandlers.d.ts +117 -0
  173. package/dist/sync/wsFrameHandlers.js +378 -0
  174. package/dist/testing/fixtures/bootstrap.d.ts +9 -11
  175. package/dist/testing/fixtures/deltas.d.ts +1 -1
  176. package/dist/testing/fixtures/models.js +1 -0
  177. package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
  178. package/dist/testing/helpers/sync-engine-harness.js +2 -1
  179. package/dist/testing/index.d.ts +1 -1
  180. package/dist/testing/index.js +1 -1
  181. package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
  182. package/dist/testing/mocks/MockSyncContext.js +1 -32
  183. package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
  184. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  185. package/dist/transactions/TransactionQueue.d.ts +20 -56
  186. package/dist/transactions/TransactionQueue.js +101 -487
  187. package/dist/transactions/TransactionStore.d.ts +18 -0
  188. package/dist/transactions/TransactionStore.js +51 -0
  189. package/dist/transactions/coalesceRules.d.ts +34 -0
  190. package/dist/transactions/coalesceRules.js +117 -0
  191. package/dist/transactions/commitPayload.d.ts +134 -0
  192. package/dist/transactions/commitPayload.js +152 -0
  193. package/dist/transactions/deltaConfirmation.d.ts +60 -0
  194. package/dist/transactions/deltaConfirmation.js +223 -0
  195. package/dist/transactions/optimistic.d.ts +24 -0
  196. package/dist/transactions/optimistic.js +45 -0
  197. package/dist/transactions/persistedReplay.d.ts +93 -0
  198. package/dist/transactions/persistedReplay.js +105 -0
  199. package/dist/types/index.d.ts +3 -3
  200. package/dist/types/index.js +0 -5
  201. package/dist/types/modelData.d.ts +12 -0
  202. package/dist/types/modelData.js +11 -0
  203. package/dist/types/participant.d.ts +21 -0
  204. package/dist/types/participant.js +12 -0
  205. package/dist/types/streams.d.ts +8 -14
  206. package/dist/utils/mobx-setup.d.ts +1 -1
  207. package/dist/utils/mobx-setup.js +2 -2
  208. package/dist/wire/errorEnvelope.d.ts +11 -2
  209. package/dist/wire/errorEnvelope.js +14 -2
  210. package/dist/wire/frames.d.ts +90 -0
  211. package/dist/wire/frames.js +55 -1
  212. package/dist/wire/index.d.ts +4 -1
  213. package/dist/wire/index.js +20 -1
  214. package/dist/wire/protocol.d.ts +45 -0
  215. package/dist/wire/protocol.js +45 -0
  216. package/dist/wire/protocolVersion.d.ts +56 -0
  217. package/dist/wire/protocolVersion.js +63 -0
  218. package/docs/api-keys.md +4 -3
  219. package/docs/examples/existing-python-backend.md +3 -3
  220. package/docs/identity.md +4 -4
  221. package/docs/integration-guide.md +1 -1
  222. package/docs/react.md +1 -1
  223. package/docs/sessions.md +5 -7
  224. package/package.json +15 -13
  225. package/dist/client/index.d.ts +0 -36
  226. package/dist/client/index.js +0 -33
  227. package/dist/config/index.d.ts +0 -10
  228. package/dist/config/index.js +0 -12
  229. package/dist/interfaces/headless.d.ts +0 -95
  230. package/dist/interfaces/headless.js +0 -41
  231. package/dist/query/index.d.ts +0 -6
  232. package/dist/query/index.js +0 -5
  233. package/dist/realtime/index.d.ts +0 -10
  234. package/dist/realtime/index.js +0 -9
  235. package/dist/schema/plane.d.ts +0 -23
  236. package/dist/schema/plane.js +0 -19
  237. package/dist/server/next.d.ts +0 -51
  238. package/dist/server/next.js +0 -47
  239. package/dist/sync/OfflineFlush.d.ts +0 -9
  240. package/dist/sync/OfflineFlush.js +0 -22
  241. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  242. package/dist/sync/OfflineTransactionStore.js +0 -263
  243. package/dist/transactions/index.d.ts +0 -16
  244. package/dist/transactions/index.js +0 -7
package/dist/cli.cjs CHANGED
@@ -211978,9 +211978,9 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
211978
211978
  }
211979
211979
  });
211980
211980
 
211981
- // node_modules/@ts-morph/common/node_modules/balanced-match/dist/commonjs/index.js
211981
+ // node_modules/balanced-match/dist/commonjs/index.js
211982
211982
  var require_commonjs = __commonJS({
211983
- "node_modules/@ts-morph/common/node_modules/balanced-match/dist/commonjs/index.js"(exports2) {
211983
+ "node_modules/balanced-match/dist/commonjs/index.js"(exports2) {
211984
211984
  "use strict";
211985
211985
  init_cjs_shims();
211986
211986
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -212041,9 +212041,9 @@ var require_commonjs = __commonJS({
212041
212041
  }
212042
212042
  });
212043
212043
 
212044
- // node_modules/@ts-morph/common/node_modules/brace-expansion/dist/commonjs/index.js
212044
+ // node_modules/brace-expansion/dist/commonjs/index.js
212045
212045
  var require_commonjs2 = __commonJS({
212046
- "node_modules/@ts-morph/common/node_modules/brace-expansion/dist/commonjs/index.js"(exports2) {
212046
+ "node_modules/brace-expansion/dist/commonjs/index.js"(exports2) {
212047
212047
  "use strict";
212048
212048
  init_cjs_shims();
212049
212049
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -212215,9 +212215,9 @@ var require_commonjs2 = __commonJS({
212215
212215
  }
212216
212216
  });
212217
212217
 
212218
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js
212218
+ // node_modules/minimatch/dist/commonjs/assert-valid-pattern.js
212219
212219
  var require_assert_valid_pattern = __commonJS({
212220
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js"(exports2) {
212220
+ "node_modules/minimatch/dist/commonjs/assert-valid-pattern.js"(exports2) {
212221
212221
  "use strict";
212222
212222
  init_cjs_shims();
212223
212223
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -212235,9 +212235,9 @@ var require_assert_valid_pattern = __commonJS({
212235
212235
  }
212236
212236
  });
212237
212237
 
212238
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/brace-expressions.js
212238
+ // node_modules/minimatch/dist/commonjs/brace-expressions.js
212239
212239
  var require_brace_expressions = __commonJS({
212240
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/brace-expressions.js"(exports2) {
212240
+ "node_modules/minimatch/dist/commonjs/brace-expressions.js"(exports2) {
212241
212241
  "use strict";
212242
212242
  init_cjs_shims();
212243
212243
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -212353,9 +212353,9 @@ var require_brace_expressions = __commonJS({
212353
212353
  }
212354
212354
  });
212355
212355
 
212356
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/unescape.js
212356
+ // node_modules/minimatch/dist/commonjs/unescape.js
212357
212357
  var require_unescape = __commonJS({
212358
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/unescape.js"(exports2) {
212358
+ "node_modules/minimatch/dist/commonjs/unescape.js"(exports2) {
212359
212359
  "use strict";
212360
212360
  init_cjs_shims();
212361
212361
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -212370,9 +212370,9 @@ var require_unescape = __commonJS({
212370
212370
  }
212371
212371
  });
212372
212372
 
212373
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/ast.js
212373
+ // node_modules/minimatch/dist/commonjs/ast.js
212374
212374
  var require_ast = __commonJS({
212375
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/ast.js"(exports2) {
212375
+ "node_modules/minimatch/dist/commonjs/ast.js"(exports2) {
212376
212376
  "use strict";
212377
212377
  init_cjs_shims();
212378
212378
  var _a;
@@ -213025,9 +213025,9 @@ var require_ast = __commonJS({
213025
213025
  }
213026
213026
  });
213027
213027
 
213028
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/escape.js
213028
+ // node_modules/minimatch/dist/commonjs/escape.js
213029
213029
  var require_escape = __commonJS({
213030
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/escape.js"(exports2) {
213030
+ "node_modules/minimatch/dist/commonjs/escape.js"(exports2) {
213031
213031
  "use strict";
213032
213032
  init_cjs_shims();
213033
213033
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -213042,9 +213042,9 @@ var require_escape = __commonJS({
213042
213042
  }
213043
213043
  });
213044
213044
 
213045
- // node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/index.js
213045
+ // node_modules/minimatch/dist/commonjs/index.js
213046
213046
  var require_commonjs3 = __commonJS({
213047
- "node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/index.js"(exports2) {
213047
+ "node_modules/minimatch/dist/commonjs/index.js"(exports2) {
213048
213048
  "use strict";
213049
213049
  init_cjs_shims();
213050
213050
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -214565,9 +214565,9 @@ var require_is_glob = __commonJS({
214565
214565
  }
214566
214566
  });
214567
214567
 
214568
- // node_modules/glob-parent/index.js
214568
+ // node_modules/fast-glob/node_modules/glob-parent/index.js
214569
214569
  var require_glob_parent = __commonJS({
214570
- "node_modules/glob-parent/index.js"(exports2, module2) {
214570
+ "node_modules/fast-glob/node_modules/glob-parent/index.js"(exports2, module2) {
214571
214571
  "use strict";
214572
214572
  init_cjs_shims();
214573
214573
  var isGlob = require_is_glob();
@@ -276770,30 +276770,30 @@ var wire = (category, httpStatus, retryable, message, recovery) => ({ category,
276770
276770
  var client = (category, message) => ({ category, surface: "client", retryable: false, message });
276771
276771
  var ERROR_CODES = {
276772
276772
  // ── auth (401) ─────────────────────────────────────────────────────
276773
- apikey_invalid: wire("auth", 401, false, "API key is unknown or malformed."),
276774
- apikey_revoked: wire("auth", 401, false, "API key has been revoked."),
276773
+ apikey_invalid: wire("auth", 401, false, "This API key isn't one Ablo recognizes \u2014 it may be mistyped, truncated, or belong to a different environment. Check the key and try again."),
276774
+ apikey_revoked: wire("auth", 401, false, "This API key has been revoked and can no longer be used. Mint a new key from the dashboard."),
276775
276775
  // THE sync-engine access credential — the Stripe-style ephemeral key
276776
276776
  // (`ek_` for users, `rk_` for agents) minted server-side from the login and
276777
276777
  // presented as a Bearer. Its expiry is routine and re-mintable: get a fresh
276778
276778
  // key from the still-valid session and retry — NEVER a sign-out. (An agent's
276779
276779
  // expired `rk_` must not log a human out either.) This is the ONLY code on
276780
276780
  // the silent re-mint path; see RecoveryClass `access_credential_expiry`.
276781
- apikey_expired: wire("auth", 401, false, "API key has expired.", "access_credential_expiry"),
276782
- apikey_missing: wire("auth", 401, false, "No API key was supplied on the request."),
276783
- api_key_required: wire("auth", 401, false, "This operation requires an API key."),
276784
- capability_id_missing: wire("auth", 401, false, "A capability id was expected but not provided."),
276785
- exchange_failed: wire("auth", 401, false, "The API-key credential exchange was rejected."),
276786
- identity_resolve_failed: wire("auth", 401, false, "Identity resolution was rejected."),
276781
+ apikey_expired: wire("auth", 401, false, "This ephemeral API key has expired. Mint a fresh key from your still-valid session and retry the request.", "access_credential_expiry"),
276782
+ apikey_missing: wire("auth", 401, false, "The request arrived without an API key. Send one as `Authorization: Bearer <key>`."),
276783
+ api_key_required: wire("auth", 401, false, "This operation requires an API key, and none was presented. Send one as `Authorization: Bearer <key>`."),
276784
+ capability_id_missing: wire("auth", 401, false, "This request must name a capability id, but none was provided."),
276785
+ exchange_failed: wire("auth", 401, false, "The API key could not be exchanged for a working credential \u2014 the exchange was rejected. Check that the key is still valid."),
276786
+ identity_resolve_failed: wire("auth", 401, false, "The server could not resolve an identity for this credential \u2014 the identity lookup was rejected. Check that the credential is still valid."),
276787
276787
  auth_no_credentials: wire("auth", 401, false, "No recognized authentication credential was presented \u2014 no API key and no bearer JWT. Send `Authorization: Bearer <token>`."),
276788
- identity_missing_organization: wire("auth", 401, false, "Authentication succeeded but resolved to no organization context."),
276788
+ identity_missing_organization: wire("auth", 401, false, "Authentication succeeded, but the credential resolves to no organization, so requests cannot be scoped. Check that the key or token carries an organization."),
276789
276789
  // The long-lived login is gone — terminal, drives sign-out + re-auth.
276790
- session_expired: wire("auth", 401, false, "The session is invalid or expired; re-authenticate.", "session_expiry"),
276790
+ session_expired: wire("auth", 401, false, "Your session has expired or is no longer valid. Sign in again to continue.", "session_expiry"),
276791
276791
  // `jwt_invalid` is the residual fallback; the codes below split out the
276792
276792
  // specific failure modes so an integrating customer can tell "I registered
276793
276793
  // the wrong JWKS" from "my token has no org claim" from "wrong audience"
276794
276794
  // rather than getting one opaque code for all of them.
276795
- jwt_invalid: wire("auth", 401, false, "The bearer JWT could not be validated (unclassified)."),
276796
- jwt_malformed: wire("auth", 401, false, "The bearer JWT is not a well-formed JWT and could not be decoded."),
276795
+ jwt_invalid: wire("auth", 401, false, "The bearer JWT failed validation for a reason the server could not classify further. Check the token's issuer, signature, audience, and expiry."),
276796
+ jwt_malformed: wire("auth", 401, false, "The bearer token is not a well-formed JWT and could not be decoded. Check that the full, unmodified token was sent."),
276797
276797
  jwt_missing_issuer: wire("auth", 401, false, "The bearer JWT has no `iss` (issuer) claim, so it cannot be routed to a trusted issuer."),
276798
276798
  jwt_issuer_untrusted: wire("auth", 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Register it via POST /v1/trusted-issuers, or check the token's issuer claim."),
276799
276799
  jwt_signature_invalid: wire("auth", 401, false, "The bearer JWT's signature could not be verified against the issuer's JWKS (wrong key, rotated key, or forged token)."),
@@ -276805,24 +276805,24 @@ var ERROR_CODES = {
276805
276805
  // When a customer DOES present an external-IdP JWT, its expiry means
276806
276806
  // re-authenticate against that IdP, so it classifies as a session expiry
276807
276807
  // (which also keeps `isSessionErrorResponse` behaviour unchanged).
276808
- jwt_expired: wire("auth", 401, false, "The bearer JWT has expired; obtain a fresh token.", "session_expiry"),
276808
+ jwt_expired: wire("auth", 401, false, "The bearer JWT has expired. Obtain a fresh token from your identity provider and retry.", "session_expiry"),
276809
276809
  jwt_org_membership_denied: wire("auth", 403, false, "The bearer JWT's subject is not an active member of the organization in its `org_id` claim (removed, suspended, or the claim does not match a membership)."),
276810
- file_upload_auth_required: wire("auth", 401, false, "File upload requires an authenticated session."),
276811
- browser_apikey_blocked: client("auth", "Raw API keys must not be used from a browser context."),
276812
- browser_database_url_blocked: client("auth", "A database connection string must not be used from a browser context \u2014 it carries DB credentials."),
276813
- datasource_registration_failed: client("auth", "Failed to register the provided databaseUrl as a datasource."),
276814
- datasource_connection_unsupported: wire("validation", 400, false, "This deployment cannot register a direct (connection string) datasource \u2014 use the signed endpoint kind."),
276810
+ file_upload_auth_required: wire("auth", 401, false, "File uploads require an authenticated session. Sign in and retry."),
276811
+ browser_apikey_blocked: client("auth", "A raw API key was used from a browser, where anyone can read it. Keep secret keys server-side and hand the browser a short-lived ephemeral key instead."),
276812
+ browser_database_url_blocked: client("auth", "A database connection string was used from a browser context. It carries database credentials, so it must stay server-side."),
276813
+ datasource_registration_failed: client("auth", "The provided `databaseUrl` could not be registered as a data source. Check that the connection string is valid and the database is reachable."),
276814
+ datasource_connection_unsupported: wire("validation", 400, false, "This deployment does not accept direct connection-string data sources. Register a signed Data Source endpoint instead."),
276815
276815
  datasource_direct_deprecated: wire("validation", 410, false, "The direct (connection string) datasource is deprecated. Register a signed Data Source endpoint instead \u2014 your app owns the write and your credentials never leave it."),
276816
276816
  // ── permission / capability (403) ──────────────────────────────────
276817
- capability_scope_denied: wire("capability", 403, false, "The connection's resolved scope does not cover the attempted action."),
276818
- issuer_register_forbidden: wire("permission", 403, false, "Registering a trusted issuer requires a secret (sk_) API key."),
276819
- capability_invalid: wire("capability", 403, false, "The capability is unknown, revoked, or expired."),
276817
+ capability_scope_denied: wire("capability", 403, false, "This action falls outside the scope granted to the connection, so it was denied."),
276818
+ issuer_register_forbidden: wire("permission", 403, false, "Registering a trusted issuer requires a secret (`sk_`) API key. The key presented is not a secret key."),
276819
+ capability_invalid: wire("capability", 403, false, "This capability cannot be used \u2014 it is unknown, revoked, or expired. Request a fresh grant."),
276820
276820
  test_database_not_registered: wire("permission", 403, false, "Test mode requires a registered dev database for this org \u2014 run `npx ablo init`, or construct the client with `databaseUrl` using your test key."),
276821
276821
  tenant_routing_failed: wire("server", 500, true, "The org's registered database could not be resolved or dialed. Ablo never falls back to shared storage for a dedicated tenant \u2014 retry, and check the datasource status if it persists."),
276822
- database_role_cannot_enforce_rls: wire("permission", 403, false, "The connected database role cannot enforce row-level security (superuser or BYPASSRLS)."),
276823
- database_role_unreadable: wire("permission", 403, false, "The connected database role could not be introspected."),
276824
- database_tables_unforced_rls: wire("permission", 403, false, "Synced tables in the connected database do not have FORCE ROW LEVEL SECURITY applied."),
276825
- database_host_not_allowed: wire("permission", 403, false, "The connected database host resolves to a private, loopback, or link-local address and cannot be used."),
276822
+ database_role_cannot_enforce_rls: wire("permission", 403, false, "The database role Ablo connects with is a superuser or has `BYPASSRLS`, so Postgres will not enforce row-level security for it. Connect with a role that is subject to RLS."),
276823
+ database_role_unreadable: wire("permission", 403, false, "Ablo could not introspect the database role it connects with, so it cannot verify that row-level security is enforced."),
276824
+ database_tables_unforced_rls: wire("permission", 403, false, "Some synced tables do not have `FORCE ROW LEVEL SECURITY` applied, so the table owner can bypass row isolation. Run `ALTER TABLE ... FORCE ROW LEVEL SECURITY` on each synced table."),
276825
+ database_host_not_allowed: wire("permission", 403, false, "The database host resolves to a private, loopback, or link-local address, which Ablo's servers will not connect to. Use a publicly resolvable host."),
276826
276826
  // Deprecated spellings of the `database_*` codes above — still emitted by
276827
276827
  // older servers; kept so they classify identically. Do not use in new code.
276828
276828
  byo_role_cannot_enforce_rls: wire("permission", 403, false, "The direct Postgres connector role cannot enforce row-level security."),
@@ -276839,50 +276839,60 @@ var ERROR_CODES = {
276839
276839
  // behind the holder) or re-read and rebase. `retryable: true` here turned
276840
276840
  // every cross-client claim conflict into an infinite client resend loop
276841
276841
  // (~150ms storm — found by the claims journey, 2026-06-10).
276842
- claim_conflict: wire("claim", 409, false, "The target entity is claimed by another participant."),
276843
- claim_lost: wire("claim", 409, false, "A previously held claim was lost before the write applied."),
276844
- entity_claimed: wire("claim", 409, false, "The target entity is currently claimed; write was blocked."),
276845
- malformed_claim: wire("claim", 400, false, "The claim payload was malformed."),
276846
- malformed_subscription: wire("validation", 400, false, "The update_subscription payload was malformed; expected { syncGroups: string[] }."),
276847
- model_claimed: wire("claim", 409, false, "The model instance is claimed by another participant."),
276848
- model_claimed_timeout: wire("claim", 409, false, "Timed out waiting for a model claim to clear."),
276842
+ claim_conflict: wire("claim", 409, false, "Another participant holds a claim on this row, so the write was rejected. Take a claim with `ablo.<model>.claim` to queue fairly behind the holder, or re-read and rebase."),
276843
+ claim_lost: wire("claim", 409, false, "The claim held on this row was lost before the write could apply. Re-acquire the claim and retry."),
276844
+ entity_claimed: wire("claim", 409, false, "This row is currently claimed by another participant, so the write was blocked. Queue behind the holder with `ablo.<model>.claim`, or wait for the claim to clear."),
276845
+ malformed_claim: wire("claim", 400, false, "The claim payload could not be parsed. A claim must name the model and the entity it targets; check the payload shape and resend."),
276846
+ malformed_subscription: wire("validation", 400, false, "The `update_subscription` payload was malformed; expected `{ syncGroups: string[] }`."),
276847
+ model_claimed: wire("claim", 409, false, "Another participant holds a claim on this row. Read `claim.state` to see who holds it, or queue behind them with a claim of your own."),
276848
+ model_claimed_timeout: wire("claim", 409, false, "Another participant held a claim on this row and did not release it in time. Retry, or read `claim.state` to see who holds it."),
276849
276849
  model_claim_not_configured: client("claim", "Claiming requires the collaboration runtime, which the standard Ablo({ schema, apiKey }) client wires up for every model automatically \u2014 there is no per-model claim configuration to add. This appears only when a model proxy is constructed directly without that runtime (an internal/advanced path)."),
276850
276850
  model_watch_not_configured: client("claim", "watch() opens a presence/claim subscription and needs a live WebSocket, so it is unavailable on the HTTP transport and on model proxies built without a socket. Use the standard Ablo({ schema, apiKey }) client (default WebSocket transport)."),
276851
276851
  // ── stale context / idempotency (409) ──────────────────────────────
276852
- stale_context: wire("conflict", 409, true, "The write carried a readAt watermark that is now stale; re-read and retry."),
276852
+ stale_context: wire("conflict", 409, true, "The row changed after you read it \u2014 the write's `readAt` watermark is older than the current row version. Re-read the row and retry."),
276853
276853
  // Raised by the functional `update(id, current => next)` form once its
276854
276854
  // internal reconcile budget is exhausted — the row stayed continuously
276855
276855
  // contended. Client-side: the SDK already retried; the caller decides whether
276856
276856
  // to back off, raise `retries`, or move the row to the WebSocket transport.
276857
- contention_exhausted: client("conflict", "A functional update could not land after exhausting its reconcile budget; the row stayed continuously contended."),
276858
- update_aborted: client("conflict", "The functional update reconcile loop was aborted via its AbortSignal before the write landed."),
276859
- idempotency_conflict: wire("conflict", 409, false, "The same Idempotency-Key was reused with a different request body."),
276860
- idempotency_key_too_long: wire("validation", 400, false, "The supplied Idempotency-Key exceeds the maximum length."),
276857
+ contention_exhausted: client("conflict", "A functional update kept losing to concurrent writes and exhausted its reconcile budget. Back off and retry, raise `retries`, or move the row to the WebSocket transport."),
276858
+ update_aborted: client("conflict", "The functional update was aborted via its `AbortSignal` before the write landed; nothing was written."),
276859
+ idempotency_conflict: wire("conflict", 409, false, "This `Idempotency-Key` was already used with a different request body. Reuse a key only to retry an identical request; otherwise generate a new one."),
276860
+ idempotency_key_too_long: wire("validation", 400, false, "The supplied `Idempotency-Key` exceeds the maximum length. Use a shorter key \u2014 a UUID works well."),
276861
276861
  // ── validation (400 / 422) ─────────────────────────────────────────
276862
276862
  write_options_invalid: client("validation", "The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `claim`) failed validation against the write-options schema."),
276863
276863
  source_operation_id_required: client("validation", "A data-source operation arrived without the entity `id` it targets."),
276864
- source_adapter_misconfigured: client("validation", "The data-source ORM adapter could not map a schema model onto the backing client (missing delegate or model)."),
276865
- source_event_invalid: client("validation", "A data-source outbox event could not be built \u2014 the operation carries no entity id and none was supplied."),
276864
+ source_adapter_misconfigured: client("validation", "The data-source ORM adapter could not map a schema model onto the backing client \u2014 the client exposes no matching delegate or model. Check that the adapter and schema agree on model names."),
276865
+ // Wire since 2026-07-01: the sync-server validates every pushed/polled
276866
+ // source event before appending to the log and rejects the whole batch
276867
+ // with this code (`param` names the offending index + field path, e.g.
276868
+ // `events[3].entityId`). Also raised client-side by
276869
+ // `sourceEventForOperation` when an outbox event cannot be built.
276870
+ source_event_invalid: wire("validation", 400, false, "A data-source event was malformed \u2014 missing or invalid id, model, entityId, type, or field value. The whole event batch was rejected and nothing was ingested; fix the offending outbox row and re-send."),
276866
276871
  duration_invalid: client("validation", 'A duration value was not a number of seconds or a "500ms" | "30s" | "3m" | "24h" string.'),
276867
276872
  schema_definition_invalid: client("validation", "A schema definition value was invalid (bad column identifier, non-finite backfill, or unsupported schema-JSON version)."),
276868
276873
  cli_invalid_arguments: client("validation", "The CLI was invoked with an unknown flag or a malformed flag value."),
276869
- turn_validation_failed: wire("validation", 422, false, "The agent turn failed server-side validation."),
276874
+ turn_validation_failed: wire("validation", 422, false, "The agent turn payload failed server-side validation and was not applied."),
276870
276875
  commit_operation_required: wire("validation", 400, false, "A commit must carry `operation` or `operations`."),
276876
+ // Wire since 2026-07-01: both commit transports (WS `commit` frame and HTTP
276877
+ // `/v1/commits`) validate every operation against `commitOperationSchema`
276878
+ // (`wire/frames.ts`) and reject the whole batch with this code. `param`
276879
+ // names the offending index + field path (e.g. `operations[3].readAt`).
276880
+ commit_operation_invalid: wire("validation", 400, false, "A commit operation failed validation against the wire commit-operation schema \u2014 wrong field type (e.g. a string `readAt`), unknown `type`, or missing `model`. The whole batch was rejected; the error names the offending operation index and field path."),
276871
276881
  commit_operation_model_required: wire("validation", 400, false, "A commit operation is missing its `model`."),
276872
- commit_operations_ambiguous: wire("validation", 400, false, "A commit supplied both `operation` and `operations`."),
276882
+ commit_operations_ambiguous: wire("validation", 400, false, "A commit supplied both `operation` and `operations`. Send one or the other, not both."),
276873
276883
  commit_too_many_operations: wire("validation", 400, false, "A commit exceeded the per-commit operation limit; split it into smaller batches."),
276874
- model_required_field_missing: wire("validation", 400, false, "A required field was absent from the model payload."),
276875
- model_identifier_missing: wire("validation", 400, false, "The model payload is missing its identifier."),
276876
- snapshot_reserved_key: wire("validation", 400, false, "A snapshot used a reserved key name."),
276877
- mesh_message_invalid_input: wire("validation", 400, false, "The mesh message failed input validation."),
276878
- mesh_message_from_id_spoof: wire("validation", 403, false, "The mesh message `from` id does not match the authenticated sender."),
276879
- mesh_message_from_kind_mismatch: wire("validation", 403, false, "The mesh message `from` kind does not match the sender."),
276880
- agent_perception_missing_context: wire("validation", 422, false, "The agent perception request lacked required context."),
276884
+ model_required_field_missing: wire("validation", 400, false, "The write is missing a field the model marks as required. Include the field and retry."),
276885
+ model_identifier_missing: wire("validation", 400, false, "The payload is missing the model's identifier, so the target row cannot be determined. Include the `id` field."),
276886
+ snapshot_reserved_key: wire("validation", 400, false, "The snapshot uses a key name that is reserved by the runtime. Rename the key and retry."),
276887
+ mesh_message_invalid_input: wire("validation", 400, false, "The mesh message payload failed input validation and was not delivered."),
276888
+ mesh_message_from_id_spoof: wire("validation", 403, false, "The mesh message's `from` id does not match the authenticated sender, so it was rejected \u2014 participants may only send as themselves."),
276889
+ mesh_message_from_kind_mismatch: wire("validation", 403, false, "The mesh message's `from` kind does not match the kind of the authenticated sender, so it was rejected."),
276890
+ agent_perception_missing_context: wire("validation", 422, false, "The agent perception request is missing context it needs to run. Include the required context fields and retry."),
276881
276891
  // ── not found (404) ────────────────────────────────────────────────
276882
- entity_not_found: wire("not_found", 404, false, "The referenced entity does not exist."),
276883
- model_not_found: wire("not_found", 404, false, "The referenced model row does not exist."),
276884
- mutate_update_entity_not_found: wire("not_found", 404, false, "The entity targeted by an update does not exist."),
276885
- task_id_missing: wire("server", 502, true, "The task-create response did not include an id."),
276892
+ entity_not_found: wire("not_found", 404, false, "No row exists with the requested id. It may have been deleted, or the id may belong to a different environment."),
276893
+ model_not_found: wire("not_found", 404, false, "No row of this model exists with the requested id. It may have been deleted, or the id may belong to a different environment."),
276894
+ mutate_update_entity_not_found: wire("not_found", 404, false, "The row targeted by this update does not exist \u2014 it may have been deleted since you read it. Re-read before retrying."),
276895
+ task_id_missing: wire("server", 502, true, "The task-create response arrived without a task id, so the result cannot be used. Retry the request."),
276886
276896
  // ── data integrity / DB constraints ────────────────────────────────
276887
276897
  // Emitted when a write is rejected by a database integrity constraint
276888
276898
  // (Postgres class-23). All NON-retryable: the same payload re-sent
@@ -276890,83 +276900,83 @@ var ERROR_CODES = {
276890
276900
  // retry. The server normalizer maps SQLSTATE → these codes and tucks the
276891
276901
  // raw constraint/column/table detail into `details` rather than leaking
276892
276902
  // the driver's message text onto the wire.
276893
- not_null_violation: wire("validation", 400, false, "A required field was missing (database not-null constraint)."),
276894
- foreign_key_violation: wire("conflict", 409, false, "A referenced entity does not exist, or is still referenced (database foreign-key constraint)."),
276895
- unique_violation: wire("conflict", 409, false, "A value violates a uniqueness constraint."),
276896
- check_violation: wire("validation", 400, false, "A value violates a database check constraint."),
276897
- constraint_violation: wire("validation", 400, false, "A database integrity constraint was violated."),
276903
+ not_null_violation: wire("validation", 400, false, "The database rejected the write because a required column was left empty \u2014 a not-null constraint. The error details name the column; supply a value and retry."),
276904
+ foreign_key_violation: wire("conflict", 409, false, "The database rejected the write on a foreign-key constraint: a referenced row does not exist, or the row being deleted is still referenced by others. The error details name the constraint."),
276905
+ unique_violation: wire("conflict", 409, false, "The write duplicates a value that must be unique \u2014 another row already holds it. Choose a different value, or update the existing row."),
276906
+ check_violation: wire("validation", 400, false, "The database rejected a value that fails one of its check constraints. The error details name the constraint; adjust the value and retry."),
276907
+ constraint_violation: wire("validation", 400, false, "The database rejected the write on an integrity constraint. The error details identify the specific constraint."),
276898
276908
  column_type_mismatch: wire("validation", 400, false, "A structured (JSON) value was written to a column whose database type cannot hold it. Ablo adapts a json field to either a jsonb column (native) or a text column (serialized) \u2014 but a scalar column (integer, boolean, uuid, timestamp, \u2026) cannot store a JSON object or array. Use a jsonb or text column for this field. Ablo adapts to your column; it does not alter your schema."),
276899
276909
  // ── tenant / unknown model (400) ───────────────────────────────────
276900
276910
  server_execute_unknown_model: wire("tenant", 400, false, "Wrote to a model the server does not know. The server keeps its own copy of the schema \u2014 run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` before writing to new or changed models."),
276901
276911
  mutate_create_unknown_model: wire("tenant", 400, false, "Created a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` first \u2014 the server keeps its own copy of the schema."),
276902
- tenant_model_columns_unknown: wire("tenant", 400, false, "The tenant model's columns could not be resolved."),
276903
- tenant_model_missing_organization_id: wire("tenant", 400, false, "The tenant model is missing the organization_id column required for isolation."),
276912
+ tenant_model_columns_unknown: wire("tenant", 400, false, "The columns for this model could not be resolved in the tenant database, so the operation cannot be mapped onto its table."),
276913
+ tenant_model_missing_organization_id: wire("tenant", 400, false, "This model's table has no `organization_id` column, which Ablo requires to isolate rows by organization. Add the column before syncing this model."),
276904
276914
  // ── schema migration / declaration (validation) ────────────────────
276905
- schema_mutable_missing_meta: wire("schema", 400, false, "A mutable schema is missing its required meta block."),
276906
- schema_scope_kind_invalid: wire("schema", 400, false, "A scope kind in the schema is invalid."),
276907
- schema_field_not_camelcase: wire("schema", 400, false, "A schema field name is not camelCase."),
276908
- schema_field_consecutive_caps: wire("schema", 400, false, "A schema field name has consecutive capital letters."),
276915
+ schema_mutable_missing_meta: wire("schema", 400, false, "The schema is declared mutable but is missing its required `meta` block."),
276916
+ schema_scope_kind_invalid: wire("schema", 400, false, "A scope declaration in the schema uses a kind the engine does not recognize."),
276917
+ schema_field_not_camelcase: wire("schema", 400, false, "A schema field name is not camelCase. Rename the field (for example `dueDate`) \u2014 Ablo derives column names from camelCase field names."),
276918
+ schema_field_consecutive_caps: wire("schema", 400, false, "A schema field name contains consecutive capital letters, which cannot be mapped to a column name unambiguously. Write acronyms in lower case (`apiKey`, not `APIKey`)."),
276909
276919
  schema_reserved_field: client("schema", "A model redeclared a reserved base field (id, createdAt, updatedAt, organizationId, createdBy) that the SDK provides automatically."),
276910
- schema_grants_shape_invalid: wire("schema", 400, false, "A grants declaration has an invalid shape."),
276911
- schema_grants_identifier_unsafe: wire("schema", 400, false, "A grants declaration referenced an unsafe identifier."),
276912
- schema_grants_relation_kind: wire("schema", 400, false, "A grants relation referenced an invalid kind."),
276913
- schema_grants_relation_missing: wire("schema", 400, false, "A grants declaration referenced a missing relation."),
276914
- schema_grants_target_not_scope_root: wire("schema", 400, false, "A grants target is not a scope root."),
276915
- drop_field: client("schema", "Migration would drop a field (destructive classification)."),
276916
- drop_model: client("schema", "Migration would drop a model (destructive classification)."),
276917
- lossy_recreate: client("schema", "Migration would require a lossy table recreate."),
276918
- made_required: client("schema", "Migration would make an existing field required."),
276919
- required_field_added: client("schema", "Migration adds a new required field."),
276920
- enum_value_removed: client("schema", "Migration removes an enum value (destructive classification)."),
276921
- risky_cast: client("schema", "Migration would perform a risky column type cast."),
276920
+ schema_grants_shape_invalid: wire("schema", 400, false, "A `grants` declaration in the schema has an invalid shape and could not be parsed."),
276921
+ schema_grants_identifier_unsafe: wire("schema", 400, false, "A `grants` declaration references an identifier that is not safe to use in SQL. Use plain column and relation names."),
276922
+ schema_grants_relation_kind: wire("schema", 400, false, "A `grants` declaration references a relation of a kind it cannot traverse."),
276923
+ schema_grants_relation_missing: wire("schema", 400, false, "A `grants` declaration references a relation the model does not define. Check the relation name against the model."),
276924
+ schema_grants_target_not_scope_root: wire("schema", 400, false, "A `grants` declaration targets a model that is not a scope root, so access cannot be derived from it."),
276925
+ drop_field: client("schema", "This migration would drop an existing field, destroying the data stored in it."),
276926
+ drop_model: client("schema", "This migration would drop an entire model and its table, destroying the rows stored in it."),
276927
+ lossy_recreate: client("schema", "This migration can only apply by recreating the table, which would not preserve its existing rows."),
276928
+ made_required: client("schema", "This migration makes an existing optional field required, which rows without a value for it would violate."),
276929
+ required_field_added: client("schema", "This migration adds a new required field that existing rows have no value for."),
276930
+ enum_value_removed: client("schema", "This migration removes an enum value that existing rows may still hold."),
276931
+ risky_cast: client("schema", "This migration changes a column to a type its current values may not convert to cleanly."),
276922
276932
  // ── claim / lease (409 / transport) ───────────────────────────────
276923
- claim_lease_unavailable: wire("claim", 503, true, "The claim-lease coordination subsystem is unavailable; retry."),
276924
- claim_not_wired: client("claim", "Claim support was used but is not wired in this runtime."),
276925
- claim_queued: wire("claim", 409, true, "The claim was queued behind an active lease holder."),
276926
- claim_wait_aborted: wire("claim", 409, true, "Waiting for the claim lease was aborted."),
276927
- claim_wait_poll_interval_required: client("claim", "A poll interval is required when waiting on an claim."),
276928
- grant_timeout: wire("claim", 504, true, "Timed out waiting for a capability grant."),
276929
- slide_intent_missing_deck_id: wire("claim", 400, false, "A slide claim was missing its deck id."),
276930
- slide_intent_unknown_sibling: wire("claim", 400, false, "A slide claim referenced an unknown sibling slide."),
276933
+ claim_lease_unavailable: wire("claim", 503, true, "The claim-lease coordination subsystem is temporarily unavailable, so the claim could not be processed. Retry shortly."),
276934
+ claim_not_wired: client("claim", "Claims were used, but this runtime has no claim support wired in. The standard `Ablo({ schema, apiKey })` client wires it up automatically."),
276935
+ claim_queued: wire("claim", 409, true, "The claim was queued behind the current lease holder and will be granted in turn. Wait, or read `claim.queue` to see your position."),
276936
+ claim_wait_aborted: wire("claim", 409, true, "The wait for this claim lease was aborted before the lease was granted."),
276937
+ claim_wait_poll_interval_required: client("claim", "Waiting on a claim requires a poll interval, and none was provided."),
276938
+ grant_timeout: wire("claim", 504, true, "The wait for a capability grant timed out before one arrived. Retry the request."),
276939
+ slide_intent_missing_deck_id: wire("claim", 400, false, "This slide claim is missing the id of the deck it belongs to."),
276940
+ slide_intent_unknown_sibling: wire("claim", 400, false, "This slide claim references a sibling slide that does not exist in the deck."),
276931
276941
  // ── bootstrap (transport) ──────────────────────────────────────────
276932
- bootstrap_fetch_timeout: wire("bootstrap", 504, true, "The bootstrap fetch timed out."),
276933
- bootstrap_offline: wire("bootstrap", 503, true, "Bootstrap could not run because the client is offline."),
276934
- bootstrap_offline_no_cache: wire("bootstrap", 503, false, "Bootstrap is offline and no cached snapshot is available."),
276935
- bootstrap_response_invalid: wire("bootstrap", 502, true, "The bootstrap response was malformed."),
276936
- bootstrap_response_schema_invalid: wire("bootstrap", 502, true, "The bootstrap response failed schema validation."),
276942
+ bootstrap_fetch_timeout: wire("bootstrap", 504, true, "The initial bootstrap fetch timed out before the server responded. Retry shortly."),
276943
+ bootstrap_offline: wire("bootstrap", 503, true, "Bootstrap could not run because the client is offline. It can proceed once the network returns."),
276944
+ bootstrap_offline_no_cache: wire("bootstrap", 503, false, "The client is offline and no cached snapshot is available to start from, so there is no data to load until the network returns."),
276945
+ bootstrap_response_invalid: wire("bootstrap", 502, true, "The bootstrap response could not be parsed. Retrying may succeed."),
276946
+ bootstrap_response_schema_invalid: wire("bootstrap", 502, true, "The bootstrap response parsed but failed schema validation, so it was not applied. Retrying may succeed."),
276937
276947
  // ── transport / connection ─────────────────────────────────────────
276938
- exchange_malformed_response: wire("transport", 502, true, "The credential exchange returned a malformed response."),
276939
- exchange_network_error: wire("transport", 503, true, "A network error occurred during credential exchange."),
276940
- source_network_error: wire("transport", 503, true, "A network error occurred talking to the source."),
276941
- identity_network_error: wire("transport", 503, true, "A network error occurred resolving identity."),
276942
- commit_no_result: wire("transport", 504, true, "The commit was sent but no result frame arrived."),
276943
- commit_failed: wire("transport", 500, true, "The commit failed to apply."),
276944
- commit_offline_grace_expired: wire("transport", 503, false, "The offline grace window expired before the commit could be sent."),
276945
- queue_too_deep: wire("transport", 503, true, "The transaction queue exceeded its depth limit."),
276946
- flush_timeout: wire("transport", 504, true, "Timed out flushing the transaction queue."),
276947
- wait_for_timeout: wire("transport", 504, true, "A wait-for condition timed out."),
276948
+ exchange_malformed_response: wire("transport", 502, true, "The credential exchange returned a response that could not be parsed. Retrying may succeed."),
276949
+ exchange_network_error: wire("transport", 503, true, "A network error interrupted the credential exchange. Check connectivity and retry."),
276950
+ source_network_error: wire("transport", 503, true, "A network error occurred while talking to the data source. Check connectivity and retry."),
276951
+ identity_network_error: wire("transport", 503, true, "A network error occurred while resolving your identity. Check connectivity and retry."),
276952
+ commit_no_result: wire("transport", 504, true, "The commit was sent, but no result frame arrived, so its outcome is unknown. It is safe to retry."),
276953
+ commit_failed: wire("transport", 500, true, "The commit reached the server but failed to apply. Retrying may succeed."),
276954
+ commit_offline_grace_expired: wire("transport", 503, false, "The offline grace window expired before this commit could be sent, so it was not applied. Re-apply the change once the connection returns."),
276955
+ queue_too_deep: wire("transport", 503, true, "The transaction queue is over its depth limit, so new writes are being rejected until it drains. Retry shortly."),
276956
+ flush_timeout: wire("transport", 504, true, "Flushing the transaction queue timed out before every pending write was sent. Retry once connectivity stabilizes."),
276957
+ wait_for_timeout: wire("transport", 504, true, "A wait-for condition timed out before it was satisfied. Retry, or extend the timeout."),
276948
276958
  instance_at_capacity: wire("transport", 503, true, "The server is at connection capacity. Retry shortly \u2014 transient and not specific to your credentials."),
276949
- fetch_unavailable: client("transport", "No fetch implementation is available in this environment."),
276950
- base_url_missing: client("transport", "No base URL was configured for the client."),
276951
- sync_not_ready: client("transport", "A sync operation was attempted before the client was ready."),
276952
- ws_not_ready: client("transport", "A frame was sent before the WebSocket was connected."),
276959
+ fetch_unavailable: client("transport", "This environment provides no `fetch` implementation, so HTTP requests cannot be made. Run on a platform with `fetch` (Node 18+, modern browsers) or supply a polyfill."),
276960
+ base_url_missing: client("transport", "The client has no base URL configured, so it cannot address the server. Set the base URL when constructing the client."),
276961
+ sync_not_ready: client("transport", "A sync operation ran before the client finished initializing. Wait for the client to be ready before syncing."),
276962
+ ws_not_ready: client("transport", "A frame was sent before the WebSocket connection was established. Wait for the connection to open before sending."),
276953
276963
  // ── quota / rate limit (429) ──────────────────────────────────────
276954
- quota_exceeded: wire("rate_limit", 429, true, "The organization exceeded its configured usage quota."),
276964
+ quota_exceeded: wire("rate_limit", 429, true, "Your organization has used up its configured usage quota. Requests will succeed again once the quota resets or the limit is raised."),
276955
276965
  connection_limit_exceeded: wire("rate_limit", 429, true, "Too many concurrent WebSocket connections for this principal or organization. Close idle connections, or retry once others drain."),
276956
276966
  // Per-CREDENTIAL request-rate limit — the fast (RPS/burst) axis, distinct from
276957
276967
  // the slow-axis `quota_exceeded` (org daily/monthly usage). Keyed per API key,
276958
276968
  // so one noisy key backs off without affecting the rest of the org. The
276959
276969
  // `Retry-After` header carries the bucket-refill delay.
276960
- rate_limit_exceeded: wire("rate_limit", 429, true, "This API key is sending requests too quickly; slow down and retry after the indicated delay."),
276970
+ rate_limit_exceeded: wire("rate_limit", 429, true, "This API key is sending requests faster than its rate limit allows. Slow down and retry after the delay in the `Retry-After` header."),
276961
276971
  // ── server (5xx) ───────────────────────────────────────────────────
276962
- internal_error: wire("server", 500, true, "An unexpected server error occurred."),
276963
- quota_lookup_failed: wire("server", 503, true, "The quota decision could not be loaded."),
276972
+ internal_error: wire("server", 500, true, "Something went wrong on Ablo's side \u2014 an unexpected server error. It is safe to retry."),
276973
+ quota_lookup_failed: wire("server", 503, true, "The server could not load this organization's quota state, so the request was rejected rather than admitted unchecked. Retry shortly."),
276964
276974
  // The per-key rate-limiter backend (Redis) was unreachable and the API is
276965
276975
  // configured to FAIL CLOSED on that path, so the request was rejected rather
276966
276976
  // than admitted unchecked. Retryable: the next attempt re-probes the backend.
276967
276977
  rate_limiter_unavailable: wire("server", 503, true, "The rate-limiter backend is unavailable and this endpoint is configured to fail closed; retry shortly."),
276968
- turn_open_failed: wire("server", 500, true, "The agent turn failed to open."),
276969
- turn_close_failed: wire("server", 500, true, "The agent turn failed to close cleanly."),
276978
+ turn_open_failed: wire("server", 500, true, "The agent turn could not be opened on the server. It is safe to retry."),
276979
+ turn_close_failed: wire("server", 500, true, "The agent turn could not be closed cleanly on the server. It is safe to retry the close."),
276970
276980
  // ── client-only invariants (never serialized) ──────────────────────
276971
276981
  invalid_options: client("client", "The Ablo client was constructed with invalid or incomplete options."),
276972
276982
  no_ablo_provider: client("client", "An Ablo hook was used outside of an Ablo provider."),
@@ -277012,52 +277022,65 @@ var ERROR_CODES = {
277012
277022
  capability_id_required: wire("validation", 400, false, "A capability id is required for this request."),
277013
277023
  organization_mismatch: wire("permission", 403, false, "The request targeted an organization the caller is not scoped to."),
277014
277024
  project_scope_denied: wire("permission", 403, false, "The request targeted a project the caller's key is not scoped to."),
277015
- project_slug_taken: wire("validation", 409, false, "A project with this slug already exists in the organization."),
277025
+ project_slug_taken: wire("validation", 409, false, "A project with this slug already exists in the organization. Choose a different slug."),
277016
277026
  forbidden: wire("permission", 403, false, "The caller lacks permission for this operation."),
277017
- source_api_key_unresolved: wire("auth", 401, false, "The source API key could not be resolved."),
277027
+ source_api_key_unresolved: wire("auth", 401, false, "The API key presented for this data source could not be resolved to a known key. Check the key and its environment."),
277018
277028
  capability_auth_disabled: wire("server", 503, false, "Capability authentication is disabled on this server."),
277019
- provisioner_unavailable: wire("server", 503, false, "No database provisioner is configured."),
277020
- invalid_model: wire("validation", 400, false, "The request named an invalid model."),
277021
- invalid_id: wire("validation", 400, false, "The request carried an invalid id."),
277029
+ provisioner_unavailable: wire("server", 503, false, "This deployment has no database provisioner configured, so tables cannot be created here."),
277030
+ invalid_model: wire("validation", 400, false, "The model name in the request is not a valid model identifier."),
277031
+ invalid_id: wire("validation", 400, false, "The id in the request is not a valid identifier."),
277022
277032
  unknown_model: wire("tenant", 400, false, "Named a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` \u2014 the server keeps its own copy of the schema."),
277023
- model_not_tenant_scoped: wire("tenant", 400, false, "The model is not tenant-scoped and cannot be queried this way."),
277033
+ model_not_tenant_scoped: wire("tenant", 400, false, "This model is not tenant-scoped, so it cannot be queried through the tenant-scoped read path."),
277024
277034
  schema_table_invalid: wire("schema", 500, false, "The model's table identifier is invalid."),
277025
277035
  schema_scope_invalid: wire("schema", 500, false, "The model's scope predicate could not be built."),
277026
- entity_fetch_failed: wire("server", 500, true, "The entity fetch failed."),
277027
- events_required: wire("validation", 400, false, "The request must include a non-empty events array."),
277028
- ingest_failed: wire("validation", 400, false, "The source-event ingest failed."),
277029
- migration_failed: wire("server", 500, false, "The schema migration failed to apply."),
277030
- model_query_failed: wire("validation", 400, false, "The model query failed."),
277031
- queries_required: wire("validation", 400, false, "The request must include a non-empty queries array."),
277036
+ entity_fetch_failed: wire("server", 500, true, "The server failed to fetch the requested entity. It is safe to retry."),
277037
+ events_required: wire("validation", 400, false, "The request must include a non-empty `events` array."),
277038
+ ingest_failed: wire("validation", 400, false, "The source-event batch was rejected during ingest and nothing was appended. Check the events against the expected shape and re-send."),
277039
+ migration_failed: wire("server", 500, false, "The schema migration failed while applying and did not complete."),
277040
+ schema_provisioning_forbidden: wire(
277041
+ "permission",
277042
+ 403,
277043
+ false,
277044
+ "Schema registration could not create tables in the target database: the engine is not permitted to run DDL there."
277045
+ ),
277046
+ model_query_failed: wire("validation", 400, false, "The model query failed to execute. Check the query filters and operators."),
277047
+ queries_required: wire("validation", 400, false, "The request must include a non-empty `queries` array."),
277032
277048
  query_unsupported_operator: wire("validation", 400, false, "The query used an unsupported operator."),
277033
- query_unknown_relation: wire("validation", 400, false, "The query referenced an unknown relation."),
277034
- query_relation_target_unknown: wire("schema", 500, false, "A relation targets a model the schema does not define."),
277049
+ query_invalid_like_pattern: wire("validation", 400, false, "The `LIKE` pattern must not end with an escape character."),
277050
+ query_invalid_boolean: wire("validation", 400, false, "The query compared a boolean column against an invalid boolean literal."),
277051
+ protocol_version_unsupported: wire("transport", 426, false, "The client sync-protocol version is outside the range this server supports \u2014 upgrade the SDK (or the server was rolled back mid-fleet)."),
277052
+ database_unreachable: wire("validation", 400, false, "Ablo could not reach this database to check that it can stream replication. The connection string may be wrong, the host may not be reachable from Ablo's servers, or the credentials may not be accepted."),
277053
+ database_not_replication_ready: wire("validation", 400, false, "This database is not set up for logical replication yet. Every failing item \u2014 wal_level, the publication, the replication grant, a replica identity \u2014 is listed in the error details with its exact fix. `ablo connect` prints the one-time setup; `ablo connect --check` verifies it."),
277054
+ query_unknown_relation: wire("validation", 400, false, "The query references a relation the model does not define. Check the relation name against the schema."),
277055
+ query_relation_target_unknown: wire("schema", 500, false, "A relation in the query targets a model the schema does not define."),
277035
277056
  query_invalid_identifier: wire("validation", 400, false, "The query contained an invalid identifier."),
277036
277057
  org_id_required: wire("validation", 400, false, "An organization id is required for this request."),
277037
- presence_identity_required: wire("validation", 400, false, "Both userId and organizationId are required."),
277038
- upload_fields_required: wire("validation", 400, false, "A required upload field was missing."),
277039
- upload_items_required: wire("validation", 400, false, "The request must include a non-empty items array."),
277040
- presigned_url_failed: wire("server", 500, true, "Failed to generate a presigned upload URL."),
277058
+ presence_identity_required: wire("validation", 400, false, "Presence requests must carry both `userId` and `organizationId`."),
277059
+ upload_fields_required: wire("validation", 400, false, "The upload request is missing a required field."),
277060
+ upload_items_required: wire("validation", 400, false, "The request must include a non-empty `items` array."),
277061
+ presigned_url_failed: wire("server", 500, true, "The server could not generate a presigned upload URL. It is safe to retry."),
277062
+ upload_not_configured: wire("server", 503, false, "Uploads are not configured on this deployment: the upload storage bucket and CDN domain are unset."),
277041
277063
  task_id_required: wire("validation", 400, false, "A task id is required for this request."),
277042
- claim_id_required: wire("validation", 400, false, "An claim id is required for this request."),
277064
+ claim_id_required: wire("validation", 400, false, "A claim id is required for this request."),
277043
277065
  commit_operation_action_required: wire("validation", 400, false, "A commit operation is missing its `action`."),
277044
277066
  commit_operation_unsupported: wire("validation", 400, false, "A commit operation used an unsupported `action`."),
277045
277067
  usage_invalid: wire("validation", 400, false, "The usage request was invalid."),
277046
277068
  invalid_request: wire("validation", 400, false, "The request parameters were invalid."),
277047
277069
  capability_not_found: wire("not_found", 404, false, "No capability exists with the given id."),
277048
- invalid_participant_kind: wire("validation", 400, false, "The participant kind is invalid."),
277049
- invalid_sync_group: wire("validation", 400, false, 'Sync groups must be "default" or "<namespace>:<id>".'),
277050
- narrow_scope_required: wire("validation", 400, false, "A narrowed scope is required for this request."),
277051
- wide_scope_forbidden: wire("permission", 403, false, "A wide scope is not permitted for this caller."),
277052
- capability_required: wire("auth", 401, false, "This operation requires a capability."),
277070
+ invalid_participant_kind: wire("validation", 400, false, "The participant kind is not one the server recognizes."),
277071
+ invalid_sync_group: wire("validation", 400, false, "Sync groups must be `default` or `<namespace>:<id>`."),
277072
+ narrow_scope_required: wire("validation", 400, false, "This request requires a scope narrowed to specific resources; the presented scope is too broad."),
277073
+ wide_scope_forbidden: wire("permission", 403, false, "This caller may not use a wide scope. Request a scope narrowed to the resources you need."),
277074
+ capability_required: wire("auth", 401, false, "This operation requires a capability, and none was presented."),
277053
277075
  parent_turn_not_found: wire("not_found", 404, false, "The referenced parent turn does not exist."),
277054
- parent_turn_foreign_agent: wire("permission", 403, false, "The parent turn belongs to a different agent."),
277076
+ parent_turn_foreign_agent: wire("permission", 403, false, "The referenced parent turn belongs to a different agent, so it cannot be used here."),
277055
277077
  turn_not_found: wire("not_found", 404, false, "The referenced turn does not exist."),
277056
- turn_foreign_agent: wire("permission", 403, false, "The turn belongs to a different agent."),
277057
- invalid_intent: wire("validation", 400, false, "The claim request was invalid."),
277078
+ turn_foreign_agent: wire("permission", 403, false, "This turn belongs to a different agent, so the caller cannot act on it."),
277079
+ invalid_intent: wire("validation", 400, false, "The claim request was malformed and could not be processed."),
277058
277080
  schema_too_large: wire("validation", 413, false, "The submitted schema exceeds the maximum size."),
277081
+ request_too_large: wire("validation", 413, false, "The request body exceeds the maximum size."),
277059
277082
  invalid_schema: wire("validation", 400, false, "The submitted schema could not be parsed."),
277060
- incompatible_change: wire("conflict", 409, false, "The schema change is incompatible with the current schema.")
277083
+ incompatible_change: wire("conflict", 409, false, "The schema change is incompatible with the schema currently deployed and cannot be applied as-is.")
277061
277084
  };
277062
277085
  function errorCodeSpec(code) {
277063
277086
  return ERROR_CODES[code];
@@ -279600,7 +279623,7 @@ function readProjectDatabaseUrl(cwd = process.cwd()) {
279600
279623
  for (const name of [".env.local", ".env"]) {
279601
279624
  const path = (0, import_path.resolve)(cwd, name);
279602
279625
  if (!(0, import_fs2.existsSync)(path)) continue;
279603
- const match = (0, import_fs2.readFileSync)(path, "utf8").match(/^DATABASE_URL=(.+)$/m);
279626
+ const match = /^DATABASE_URL=(.+)$/m.exec((0, import_fs2.readFileSync)(path, "utf8"));
279604
279627
  if (match?.[1]) return match[1].trim().replace(/^["']|["']$/g, "");
279605
279628
  }
279606
279629
  return null;
@@ -279610,7 +279633,7 @@ function readProjectApiKey(cwd = process.cwd()) {
279610
279633
  for (const name of [".env.local", ".env"]) {
279611
279634
  const path = (0, import_path.resolve)(cwd, name);
279612
279635
  if (!(0, import_fs2.existsSync)(path)) continue;
279613
- const match = (0, import_fs2.readFileSync)(path, "utf8").match(/^ABLO_API_KEY=(.+)$/m);
279636
+ const match = /^ABLO_API_KEY=(.+)$/m.exec((0, import_fs2.readFileSync)(path, "utf8"));
279614
279637
  if (match?.[1]) return { key: match[1].trim().replace(/^["']|["']$/g, ""), source: name };
279615
279638
  }
279616
279639
  return null;
@@ -279645,6 +279668,12 @@ var import_path3 = require("path");
279645
279668
  var import_child_process = require("child_process");
279646
279669
  var import_schema2 = require("@abloatai/ablo/schema");
279647
279670
 
279671
+ // src/client/hostedEndpoints.ts
279672
+ init_cjs_shims();
279673
+ var ABLO_HOSTED_API_DOMAIN = "api.abloatai.com";
279674
+ var ABLO_HOSTED_HTTP_BASE_URL = `https://${ABLO_HOSTED_API_DOMAIN}`;
279675
+ var ABLO_DEFAULT_BASE_URL = ABLO_HOSTED_HTTP_BASE_URL;
279676
+
279648
279677
  // src/cli/config.ts
279649
279678
  init_cjs_shims();
279650
279679
  var import_os2 = require("os");
@@ -279876,12 +279905,18 @@ function guardActiveProjectKey() {
279876
279905
  const available = Object.entries(profiles).filter(([, keys]) => hasKey(keys)).map(([name]) => name);
279877
279906
  return { ok: hasKey(profiles[activeProfile]), activeProfile, available };
279878
279907
  }
279908
+ function resolveEffectiveApiKey(modeOverride, cwd) {
279909
+ const fromProject = readProjectApiKey(cwd);
279910
+ if (fromProject) return { key: fromProject.key, source: fromProject.source };
279911
+ const key = resolveApiKey(modeOverride);
279912
+ return key ? { key, source: "stored" } : { key: void 0, source: null };
279913
+ }
279879
279914
  function resolvePushPlan() {
279880
- const envKey = process.env.ABLO_API_KEY;
279881
- if (envKey) return { flow: modeFromKey(envKey) ?? getMode(), apiKey: envKey, source: "env" };
279882
- const mode2 = getMode();
279883
- const apiKey = resolveApiKey(mode2);
279884
- return { flow: mode2, apiKey, source: apiKey ? "stored" : null };
279915
+ const { key, source } = resolveEffectiveApiKey();
279916
+ if (key != null && source != null && source !== "stored") {
279917
+ return { flow: modeFromKey(key) ?? getMode(), apiKey: key, source };
279918
+ }
279919
+ return { flow: getMode(), apiKey: key, source };
279885
279920
  }
279886
279921
 
279887
279922
  // src/cli/theme.ts
@@ -279906,7 +279941,7 @@ function coerceBackfill(raw) {
279906
279941
  }
279907
279942
  var DEFAULT_SCHEMA_PATH = "ablo/schema.ts";
279908
279943
  var DEFAULT_EXPORT = "schema";
279909
- var DEFAULT_URL = "https://api.abloatai.com";
279944
+ var DEFAULT_URL = ABLO_DEFAULT_BASE_URL;
279910
279945
  function fmtSignal(s) {
279911
279946
  const sig = s;
279912
279947
  const where = sig.field ? `${sig.model}.${sig.field}` : sig.model;
@@ -280049,7 +280084,9 @@ function schemaGitState(schemaPath) {
280049
280084
  }
280050
280085
  async function fetchActiveSchema(url, apiKey) {
280051
280086
  const ctrl = new AbortController();
280052
- const t = setTimeout(() => ctrl.abort(), 3e3);
280087
+ const t = setTimeout(() => {
280088
+ ctrl.abort();
280089
+ }, 3e3);
280053
280090
  try {
280054
280091
  const res = await fetch(`${url}/api/schema`, {
280055
280092
  headers: { authorization: `Bearer ${apiKey}` },
@@ -280116,7 +280153,7 @@ async function confirmPush(args, env) {
280116
280153
  const project = getActiveProject();
280117
280154
  const expected = project?.slug ?? "production";
280118
280155
  const typed = await he({
280119
- message: `This deploys to ${import_picocolors3.default.red(import_picocolors3.default.bold("PRODUCTION"))}. Type ${import_picocolors3.default.bold(expected)} to confirm:`,
280156
+ message: `This deploys to ${import_picocolors3.default.bold("production")}. Type ${import_picocolors3.default.bold(expected)} to confirm:`,
280120
280157
  placeholder: expected
280121
280158
  });
280122
280159
  if (pD(typed) || String(typed).trim() !== expected) {
@@ -280126,7 +280163,7 @@ async function confirmPush(args, env) {
280126
280163
  return;
280127
280164
  }
280128
280165
  if (!isProd && !args.yes && tty) {
280129
- const ok = await ye({ message: `Apply to ${import_picocolors3.default.green("sandbox")}?` });
280166
+ const ok = await ye({ message: `Apply to ${import_picocolors3.default.bold("sandbox")}?` });
280130
280167
  if (pD(ok) || !ok) {
280131
280168
  xe("Aborted.");
280132
280169
  process.exit(1);
@@ -280135,11 +280172,11 @@ async function confirmPush(args, env) {
280135
280172
  }
280136
280173
  function printPushTarget(opts) {
280137
280174
  const env = modeFromKey(opts.apiKey);
280138
- const envLabel = env === "production" ? import_picocolors3.default.red(import_picocolors3.default.bold("production")) : env === "sandbox" ? import_picocolors3.default.green("sandbox") : import_picocolors3.default.yellow("unknown env");
280175
+ const envLabel = env === "production" ? import_picocolors3.default.bold("production") : env === "sandbox" ? import_picocolors3.default.bold("sandbox") : import_picocolors3.default.yellow("unknown env");
280139
280176
  const project = getActiveProject();
280140
- const projectLabel = project ? `${import_picocolors3.default.bold(project.slug)} ${import_picocolors3.default.dim(`(${project.id})`)}` : `${import_picocolors3.default.bold("default")} ${import_picocolors3.default.dim("(org-default \u2014 `ablo projects use <slug>` to scope)")}`;
280177
+ const projectLabel = project ? `${import_picocolors3.default.bold(project.slug)} ${import_picocolors3.default.dim(`(${project.id})`)}` : `${import_picocolors3.default.bold("default")} ${import_picocolors3.default.dim("(org-default)")}`;
280141
280178
  const cliMode = getMode();
280142
- const modeNote = env && env !== cliMode ? ` ${import_picocolors3.default.yellow(`(CLI mode is ${cliMode} \u2014 this key targets ${env})`)}` : "";
280179
+ const modeNote = env && env !== cliMode ? ` ${import_picocolors3.default.yellow(`(cli mode: ${cliMode})`)}` : "";
280143
280180
  console.log(`
280144
280181
  ${brand("ablo")} ${import_picocolors3.default.dim("push")} ${import_picocolors3.default.dim("\u2192")} ${envLabel}${modeNote}`);
280145
280182
  console.log(` ${import_picocolors3.default.dim("project")} ${projectLabel}`);
@@ -280148,20 +280185,20 @@ function printPushTarget(opts) {
280148
280185
  ` ${import_picocolors3.default.dim("key")} ${maskKey(opts.apiKey)} ${import_picocolors3.default.dim(`(${describeKeySource(opts.keySource)})`)}`
280149
280186
  );
280150
280187
  console.log(
280151
- ` ${import_picocolors3.default.dim("schema")} ${import_picocolors3.default.bold(opts.schemaPath)} ${import_picocolors3.default.dim(`\xB7 ${opts.modelCount} models \xB7 hash ${opts.hash}`)}
280188
+ ` ${import_picocolors3.default.dim("schema")} ${import_picocolors3.default.bold(opts.schemaPath)} ${import_picocolors3.default.dim(`${opts.modelCount} models, hash ${opts.hash}`)}
280152
280189
  `
280153
280190
  );
280154
280191
  }
280155
280192
  function describeKeySource(source) {
280156
280193
  switch (source) {
280157
280194
  case "env":
280158
- return "ABLO_API_KEY (environment)";
280195
+ return "ABLO_API_KEY";
280159
280196
  case ".env.local":
280160
280197
  return ".env.local";
280161
280198
  case ".env":
280162
280199
  return ".env";
280163
- case "login":
280164
- return "`ablo login` (stored sandbox config)";
280200
+ case "stored":
280201
+ return "ablo login";
280165
280202
  }
280166
280203
  }
280167
280204
  async function push(argv) {
@@ -280174,14 +280211,9 @@ async function push(argv) {
280174
280211
  }
280175
280212
  let keySource = "env";
280176
280213
  if (!args.apiKey) {
280177
- const fromProject = readProjectApiKey();
280178
- if (fromProject) {
280179
- args.apiKey = fromProject.key;
280180
- keySource = fromProject.source;
280181
- } else {
280182
- args.apiKey = resolveApiKey();
280183
- keySource = "login";
280184
- }
280214
+ const resolved = resolveEffectiveApiKey();
280215
+ args.apiKey = resolved.key;
280216
+ keySource = resolved.source ?? "stored";
280185
280217
  }
280186
280218
  if (!args.apiKey) {
280187
280219
  console.error(
@@ -280287,7 +280319,7 @@ async function push(argv) {
280287
280319
  } else {
280288
280320
  console.error(
280289
280321
  import_picocolors3.default.dim(
280290
- ` This key isn't authorized to push schema (needs ${import_picocolors3.default.bold("schema:push")}). ` + (keySource === "login" ? `It's your stored ${import_picocolors3.default.bold("ablo login")} sandbox key \u2014 a key in ${import_picocolors3.default.bold(".env.local")} or ${import_picocolors3.default.bold("ABLO_API_KEY")} takes precedence, so put a schema:push key there (sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}) and re-push. ` : `Use a schema:push key \u2014 a sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}. `) + `Manage keys at https://abloatai.com`
280322
+ ` This key isn't authorized to push schema (needs ${import_picocolors3.default.bold("schema:push")}). ` + (keySource === "stored" ? `It's your stored ${import_picocolors3.default.bold("ablo login")} sandbox key \u2014 a key in ${import_picocolors3.default.bold(".env.local")} or ${import_picocolors3.default.bold("ABLO_API_KEY")} takes precedence, so put a schema:push key there (sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}) and re-push. ` : `Use a schema:push key \u2014 a sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}. `) + `Manage keys at https://abloatai.com`
280291
280323
  )
280292
280324
  );
280293
280325
  }
@@ -280351,32 +280383,36 @@ function planFor(schema, targetSchema = "public") {
280351
280383
  return { statements: [...plan.statements, ...adapterTables], concurrent: plan.concurrent ?? [] };
280352
280384
  }
280353
280385
  var log = {
280354
- info: (msg, fields) => console.log(`[migrate] ${msg}`, fields),
280355
- warn: (msg, fields) => console.warn(import_picocolors4.default.yellow(`[migrate] ${msg}`), fields),
280356
- error: (msg, fields) => console.error(import_picocolors4.default.red(`[migrate] ${msg}`), fields)
280386
+ info: (msg, fields) => {
280387
+ console.log(`[migrate] ${msg}`, fields);
280388
+ },
280389
+ warn: (msg, fields) => {
280390
+ console.warn(import_picocolors4.default.yellow(`[migrate] ${msg}`), fields);
280391
+ },
280392
+ error: (msg, fields) => {
280393
+ console.error(import_picocolors4.default.red(`[migrate] ${msg}`), fields);
280394
+ }
280357
280395
  };
280358
- var PG_LOCK_NOT_AVAILABLE = "55P03";
280359
- var LOCK_TIMEOUT = process.env.ABLO_SCHEMA_LOCK_TIMEOUT ?? process.env.ABLO_DDL_LOCK_TIMEOUT ?? "5s";
280360
- var MAX_LOCK_ATTEMPTS = 5;
280361
280396
  async function applyStatements(dbUrl, targetSchema, statements, concurrent = []) {
280362
280397
  const sql = src_default(dbUrl, { max: 1, prepare: false, onnotice: () => {
280363
280398
  } });
280364
280399
  const total = statements.length;
280365
280400
  const startedAt = Date.now();
280401
+ const lockTimeout = (0, import_schema3.resolveDdlLockTimeout)();
280402
+ const maxLockAttempts = (0, import_schema3.resolveDdlMaxLockAttempts)();
280366
280403
  log.info("applying migration plan", { targetSchema, statements: total });
280367
280404
  try {
280368
280405
  for (let attempt = 1; ; attempt++) {
280369
280406
  try {
280370
280407
  await sql.begin(async (tx) => {
280371
- await tx.unsafe(`SET LOCAL lock_timeout = '${LOCK_TIMEOUT}'`);
280408
+ await tx.unsafe(`SET LOCAL lock_timeout = '${lockTimeout}'`);
280372
280409
  await tx`SELECT pg_advisory_xact_lock(hashtext(${`provision:${targetSchema}`}))`;
280373
- for (let index = 0; index < total; index++) {
280374
- const statement = statements[index];
280410
+ for (const [index, statement] of statements.entries()) {
280375
280411
  try {
280376
280412
  await tx.unsafe(statement);
280377
280413
  } catch (err) {
280378
280414
  const pg = err ?? {};
280379
- if (pg.code === PG_LOCK_NOT_AVAILABLE) throw err;
280415
+ if (pg.code === import_schema3.PG_LOCK_NOT_AVAILABLE) throw err;
280380
280416
  log.error("migration plan failed", {
280381
280417
  code: "migration_failed",
280382
280418
  durationMs: Date.now() - startedAt,
@@ -280394,8 +280430,8 @@ async function applyStatements(dbUrl, targetSchema, statements, concurrent = [])
280394
280430
  break;
280395
280431
  } catch (err) {
280396
280432
  const pg = err ?? {};
280397
- if (pg.code === PG_LOCK_NOT_AVAILABLE && attempt < MAX_LOCK_ATTEMPTS) {
280398
- const backoffMs = Math.min(6e4, 10 * 2 ** attempt) + Math.floor(Math.random() * 50);
280433
+ if (pg.code === import_schema3.PG_LOCK_NOT_AVAILABLE && attempt < maxLockAttempts) {
280434
+ const backoffMs = (0, import_schema3.ddlLockRetryBackoffMs)(attempt);
280399
280435
  log.warn("schema change blocked by a lock; backing off and retrying", { targetSchema, attempt, backoffMs });
280400
280436
  await new Promise((resolve6) => setTimeout(resolve6, backoffMs));
280401
280437
  continue;
@@ -280615,10 +280651,11 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
280615
280651
  `SELECT puballtables FROM pg_publication WHERE pubname = $1`,
280616
280652
  [publication]
280617
280653
  );
280654
+ const pubRow = pubRows[0];
280618
280655
  items.push(
280619
- pubRows.length > 0 ? {
280656
+ pubRow ? {
280620
280657
  ok: true,
280621
- label: `publication ${import_picocolors5.default.bold(publication)} exists ${import_picocolors5.default.dim(pubRows[0].puballtables ? "(all tables)" : "(table subset)")}`
280658
+ label: `publication ${import_picocolors5.default.bold(publication)} exists ${import_picocolors5.default.dim(pubRow.puballtables ? "(all tables)" : "(table subset)")}`
280622
280659
  } : {
280623
280660
  ok: false,
280624
280661
  label: `publication ${import_picocolors5.default.bold(publication)} not found`,
@@ -280776,22 +280813,32 @@ async function runRegister() {
280776
280813
  process.exit(0);
280777
280814
  }
280778
280815
  const body = await res.json().catch(() => ({}));
280779
- const code = body.error?.code;
280780
- const message = body.error?.message ?? `HTTP ${res.status}`;
280816
+ const code = body.code ?? body.error?.code;
280817
+ const message = body.message ?? body.error?.message ?? `HTTP ${res.status}`;
280781
280818
  console.error(import_picocolors5.default.red(`
280782
280819
  Registration failed: ${message}`));
280783
280820
  if (code === "forbidden") {
280784
280821
  console.error(import_picocolors5.default.dim(` Registering a database needs a ${import_picocolors5.default.bold("secret")} key (sk_\u2026). Run ${import_picocolors5.default.bold("ablo login")} for one.`));
280785
- } else if (code === "datasource_direct_deprecated") {
280786
- console.error(
280787
- import_picocolors5.default.dim(
280788
- ` This deployment doesn\u2019t accept new connection-string registrations yet. The operator must enable it (${import_picocolors5.default.bold("ABLO_ALLOW_DIRECT_DATASOURCE=true")}) \u2014 see ${import_picocolors5.default.bold("docs/runbooks/connect-customer-database-byo-replication.md")}.`
280789
- )
280790
- );
280791
280822
  } else if (code === "datasource_connection_unsupported") {
280792
280823
  console.error(
280793
280824
  import_picocolors5.default.dim(` This deployment can\u2019t accept connection strings \u2014 use a self-hosted/hosted engine, or the signed endpoint fallback.`)
280794
280825
  );
280826
+ } else if (code === "database_not_replication_ready") {
280827
+ for (const f of body.details?.failures ?? []) {
280828
+ console.error(` ${import_picocolors5.default.red("\u2717")} ${import_picocolors5.default.bold(f.item ?? "item")}${f.actual ? import_picocolors5.default.dim(` (${f.actual})`) : ""}`);
280829
+ if (f.fix) for (const line of f.fix.split("\n")) console.error(` ${import_picocolors5.default.red("\u2022")} ${line}`);
280830
+ }
280831
+ console.error(import_picocolors5.default.dim(`
280832
+ Apply the fixes, verify with ${import_picocolors5.default.bold("ablo connect --check")}, then re-run.`));
280833
+ } else if (code === "database_unreachable") {
280834
+ if (body.details?.reason) console.error(import_picocolors5.default.dim(` ${body.details.reason}`));
280835
+ console.error(
280836
+ import_picocolors5.default.dim(
280837
+ ` Ablo's servers must be able to reach this database \u2014 a localhost or private-network
280838
+ Postgres can't be replicated by the hosted service. Use a reachable host (or a tunnel),
280839
+ or the signed ${import_picocolors5.default.bold("dataSource()")} endpoint fallback.`
280840
+ )
280841
+ );
280795
280842
  }
280796
280843
  console.error();
280797
280844
  process.exit(1);
@@ -280989,16 +281036,17 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
280989
281036
  action = `Created ${import_picocolors7.default.bold(".env.local")} with ${import_picocolors7.default.bold("ABLO_API_KEY")}`;
280990
281037
  } else {
280991
281038
  const content = (0, import_fs7.readFileSync)(envPath, "utf8");
280992
- const match = content.match(/^ABLO_API_KEY=(.*)$/m);
281039
+ const match = /^ABLO_API_KEY=(.*)$/m.exec(content);
281040
+ const existing = match?.[1] ?? "";
280993
281041
  if (!match) {
280994
281042
  (0, import_fs7.appendFileSync)(envPath, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}${line}
280995
281043
  `);
280996
281044
  action = `Added ${import_picocolors7.default.bold("ABLO_API_KEY")} to ${import_picocolors7.default.bold(".env.local")}`;
280997
- } else if (match[1] === apiKey) {
281045
+ } else if (existing === apiKey) {
280998
281046
  action = `${import_picocolors7.default.bold(".env.local")} already has this key`;
280999
281047
  } else {
281000
281048
  (0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
281001
- action = `Updated ${import_picocolors7.default.bold("ABLO_API_KEY")} in ${import_picocolors7.default.bold(".env.local")} ${import_picocolors7.default.dim(`(was ${match[1].slice(0, 12)}\u2026)`)}`;
281049
+ action = `Updated ${import_picocolors7.default.bold("ABLO_API_KEY")} in ${import_picocolors7.default.bold(".env.local")} ${import_picocolors7.default.dim(`(was ${existing.slice(0, 12)}\u2026)`)}`;
281002
281050
  }
281003
281051
  }
281004
281052
  const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
@@ -281076,7 +281124,7 @@ async function dev(argv) {
281076
281124
  console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281077
281125
  process.exit(1);
281078
281126
  }
281079
- if (!args.apiKey) args.apiKey = resolveApiKey("sandbox");
281127
+ if (!args.apiKey) args.apiKey = resolveEffectiveApiKey("sandbox").key;
281080
281128
  const key = classifyKey(args.apiKey, getMode());
281081
281129
  if (!key.ok) {
281082
281130
  console.error(import_picocolors7.default.red(` ${key.reason}`));
@@ -281200,7 +281248,7 @@ async function deviceLogin(argv, deps = {}) {
281200
281248
  headers: { "content-type": "application/json" },
281201
281249
  body: JSON.stringify({ client_id: CLIENT_ID, scope: "openid profile email" })
281202
281250
  }).catch(() => null);
281203
- if (!codeRes || !codeRes.ok) {
281251
+ if (!codeRes?.ok) {
281204
281252
  M2.error(`Couldn't start login against ${AUTH_URL}. Is the dashboard reachable?`);
281205
281253
  process.exit(1);
281206
281254
  }
@@ -281270,7 +281318,7 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281270
281318
  ...targetProject ? { project_slug: targetProject } : {}
281271
281319
  })
281272
281320
  }).catch(() => null);
281273
- if (!provRes || !provRes.ok) {
281321
+ if (!provRes?.ok) {
281274
281322
  s.stop("Could not provision a key.");
281275
281323
  const reason = provRes ? (await provRes.json().catch(() => ({}))).error : void 0;
281276
281324
  if (reason) M2.error(reason);
@@ -281584,7 +281632,9 @@ function expiryLabel(iso) {
281584
281632
  }
281585
281633
  async function ping(apiUrl2) {
281586
281634
  const ctrl = new AbortController();
281587
- const t = setTimeout(() => ctrl.abort(), 3e3);
281635
+ const t = setTimeout(() => {
281636
+ ctrl.abort();
281637
+ }, 3e3);
281588
281638
  try {
281589
281639
  const res = await fetch(`${apiUrl2}/api/health`, { signal: ctrl.signal });
281590
281640
  return res.ok;
@@ -281597,7 +281647,9 @@ async function ping(apiUrl2) {
281597
281647
  async function fetchPushedSchema(apiUrl2, apiKey) {
281598
281648
  if (!apiKey) return null;
281599
281649
  const ctrl = new AbortController();
281600
- const t = setTimeout(() => ctrl.abort(), 3e3);
281650
+ const t = setTimeout(() => {
281651
+ ctrl.abort();
281652
+ }, 3e3);
281601
281653
  try {
281602
281654
  const res = await fetch(`${apiUrl2}/api/schema`, {
281603
281655
  headers: { authorization: `Bearer ${apiKey}` },
@@ -281613,7 +281665,9 @@ async function fetchPushedSchema(apiUrl2, apiKey) {
281613
281665
  }
281614
281666
  async function sampleRead(apiUrl2, apiKey, modelTypename, n) {
281615
281667
  const ctrl = new AbortController();
281616
- const t = setTimeout(() => ctrl.abort(), 4e3);
281668
+ const t = setTimeout(() => {
281669
+ ctrl.abort();
281670
+ }, 4e3);
281617
281671
  try {
281618
281672
  const res = await fetch(
281619
281673
  `${apiUrl2}/v1/models/${encodeURIComponent(modelTypename)}/__ablo_health_probe_${n}__`,
@@ -281664,12 +281718,19 @@ async function status(args = []) {
281664
281718
  const key2 = describeEffectiveKey(mode2, process.env.ABLO_API_KEY, entry);
281665
281719
  const plan2 = resolvePushPlan();
281666
281720
  const activeProject2 = getActiveProject();
281667
- const introspectKey = process.env.ABLO_API_KEY ?? entry?.apiKey;
281668
- const pushed = await fetchPushedSchema(apiUrl2, introspectKey);
281721
+ const effective2 = resolveEffectiveApiKey();
281722
+ const pushed = await fetchPushedSchema(apiUrl2, effective2.key);
281669
281723
  const out = {
281670
281724
  mode: mode2,
281671
281725
  // The locally-active project (`ablo projects use`); null = org-default.
281672
281726
  project: activeProject2 ?? null,
281727
+ // The credential the CLI resolves for requests, with its provenance —
281728
+ // 'env' | '.env.local' | '.env' | 'stored' (users debug key confusion
281729
+ // constantly; the SOURCE is usually the answer).
281730
+ effectiveKey: {
281731
+ prefix: effective2.key ? effective2.key.slice(0, 12) : null,
281732
+ source: effective2.source
281733
+ },
281673
281734
  keyPrefix: key2.keyPrefix,
281674
281735
  keySource: key2.keySource,
281675
281736
  keyMode: key2.keyMode,
@@ -281703,9 +281764,11 @@ async function status(args = []) {
281703
281764
  console.log(`
281704
281765
  ${brand("ablo")} ${import_picocolors11.default.dim("status")}
281705
281766
  `);
281706
- if (process.env.ABLO_API_KEY) {
281767
+ const effective = resolveEffectiveApiKey();
281768
+ if (effective.key && effective.source && effective.source !== "stored") {
281769
+ const label = effective.source === "env" ? "ABLO_API_KEY env" : effective.source;
281707
281770
  console.log(
281708
- ` ${import_picocolors11.default.dim("key")} ${process.env.ABLO_API_KEY.slice(0, 12)}\u2026 ${import_picocolors11.default.dim("(ABLO_API_KEY env \u2014 overrides stored)")}`
281771
+ ` ${import_picocolors11.default.dim("key")} ${effective.key.slice(0, 12)}\u2026 ${import_picocolors11.default.dim(`(${label} \u2014 overrides stored)`)}`
281709
281772
  );
281710
281773
  } else if (!cfg) {
281711
281774
  console.log(` ${import_picocolors11.default.yellow("!")} Not logged in \u2014 run ${import_picocolors11.default.bold("ablo login")}.`);
@@ -281740,9 +281803,9 @@ async function status(args = []) {
281740
281803
  const reachable = await ping(apiUrl2);
281741
281804
  console.log(reachable ? import_picocolors11.default.green("reachable") : import_picocolors11.default.red("unreachable"));
281742
281805
  if (reachable) {
281743
- const introspectKey = process.env.ABLO_API_KEY ?? activeEntry?.apiKey;
281806
+ const introspectKey = effective.key;
281744
281807
  const pushed = await fetchPushedSchema(apiUrl2, introspectKey);
281745
- if (pushed && pushed.active) {
281808
+ if (pushed?.active) {
281746
281809
  const when = pushed.pushedAt ? ` ${import_picocolors11.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
281747
281810
  const ver = pushed.version != null ? ` ${import_picocolors11.default.dim(`(rev ${pushed.version})`)}` : "";
281748
281811
  console.log(` ${import_picocolors11.default.dim("schema")} ${import_picocolors11.default.bold(`${pushed.models.length} models pushed`)}${ver}${when}`);
@@ -281755,8 +281818,9 @@ async function status(args = []) {
281755
281818
  } else if (pushed && !pushed.active) {
281756
281819
  console.log(` ${import_picocolors11.default.dim("schema")} ${import_picocolors11.default.yellow("none pushed")} ${import_picocolors11.default.dim(`(run ${import_picocolors11.default.bold("ablo push")} or ${import_picocolors11.default.bold("ablo dev")})`)}`);
281757
281820
  }
281758
- if (pushed && pushed.active && pushed.models.length > 0) {
281759
- const probe = await probeDataPlane(apiUrl2, introspectKey, pushed.models[0].typename);
281821
+ const firstPushedModel = pushed?.active ? pushed.models[0] : void 0;
281822
+ if (firstPushedModel !== void 0) {
281823
+ const probe = await probeDataPlane(apiUrl2, introspectKey, firstPushedModel.typename);
281760
281824
  if (probe.status === "no_database") {
281761
281825
  console.log(` ${import_picocolors11.default.dim("data")} ${import_picocolors11.default.red("\u2717 no database registered")}${org ? import_picocolors11.default.dim(` for org ${org}`) : ""}`);
281762
281826
  console.log(
@@ -281947,8 +282011,10 @@ function parseMode(args) {
281947
282011
  function positional(args) {
281948
282012
  for (let i = 0; i < args.length; i++) {
281949
282013
  const a = args[i];
282014
+ if (a === void 0) continue;
281950
282015
  if (a.startsWith("-")) {
281951
- if (!a.includes("=") && args[i + 1] && !args[i + 1].startsWith("-")) i++;
282016
+ const next = args[i + 1];
282017
+ if (!a.includes("=") && next && !next.startsWith("-")) i++;
281952
282018
  continue;
281953
282019
  }
281954
282020
  return a;
@@ -282269,8 +282335,8 @@ function verbRewrite(call, verb) {
282269
282335
  const params = VERB_ARGS[verb];
282270
282336
  if (!params) return null;
282271
282337
  const args = call.getArguments();
282272
- if (args.length === 0) return null;
282273
282338
  const first = args[0];
282339
+ if (first === void 0) return null;
282274
282340
  const calleeText = call.getExpression().getText();
282275
282341
  if (verb === "create") {
282276
282342
  if (import_ts_morph.Node.isObjectLiteralExpression(first) && hasKey2(first, "data")) return null;
@@ -282319,7 +282385,7 @@ async function upgrade(argv) {
282319
282385
  edits.push({ file, line: node.getStartLineNumber(), rule, before, after });
282320
282386
  };
282321
282387
  const flag2 = (node, rule, hint) => {
282322
- manual.push({ file, line: node.getStartLineNumber(), rule, snippet: node.getText().split("\n")[0].slice(0, 120), hint });
282388
+ manual.push({ file, line: node.getStartLineNumber(), rule, snippet: (node.getText().split("\n")[0] ?? "").slice(0, 120), hint });
282323
282389
  };
282324
282390
  for (const imp of sf.getImportDeclarations()) {
282325
282391
  if (!ABLO_REACT.has(imp.getModuleSpecifierValue())) continue;
@@ -282349,13 +282415,13 @@ async function upgrade(argv) {
282349
282415
  if (method in VERB_ARGS) {
282350
282416
  const next = verbRewrite(call, method);
282351
282417
  if (next) {
282352
- record(call, `${method}\u2192object-param`, call.getText().split("\n")[0].slice(0, 80), next.slice(0, 80));
282418
+ record(call, `${method}\u2192object-param`, (call.getText().split("\n")[0] ?? "").slice(0, 80), next.slice(0, 80));
282353
282419
  call.replaceWithText(next);
282354
282420
  }
282355
282421
  } else if (method === "load") {
282356
282422
  const next = loadRewrite(call);
282357
282423
  if (next) {
282358
- record(call, "load\u2192retrieve/list", call.getText().split("\n")[0].slice(0, 80), next.slice(0, 80));
282424
+ record(call, "load\u2192retrieve/list", (call.getText().split("\n")[0] ?? "").slice(0, 80), next.slice(0, 80));
282359
282425
  call.replaceWithText(next);
282360
282426
  }
282361
282427
  }
@@ -282665,7 +282731,7 @@ function stripComments(src) {
282665
282731
  let inStr = false;
282666
282732
  let res = "";
282667
282733
  for (let i = 0; i < line.length; i++) {
282668
- const c = line[i];
282734
+ const c = line.charAt(i);
282669
282735
  if (c === '"') {
282670
282736
  inStr = !inStr;
282671
282737
  res += c;
@@ -282693,8 +282759,10 @@ function parseBlocks(srcRaw) {
282693
282759
  }
282694
282760
  const body = src.slice(openIdx + 1, j2 - 1);
282695
282761
  const kw = m2[1];
282762
+ const name = m2[2];
282763
+ if (name === void 0) throw new Error("unparseable block header in the Prisma schema");
282696
282764
  const type = kw === "model" ? "model" : kw === "enum" ? "enum" : "other";
282697
- blocks.push({ type, name: m2[2], body });
282765
+ blocks.push({ type, name, body });
282698
282766
  headerRe.lastIndex = j2;
282699
282767
  }
282700
282768
  return blocks;
@@ -282705,7 +282773,7 @@ function parseEnumMembers(body) {
282705
282773
  const t = line.trim();
282706
282774
  if (!t || t.startsWith("@") || t.startsWith("@@")) continue;
282707
282775
  const first = t.split(/\s+/)[0];
282708
- if (/^\w+$/.test(first)) members.push(first);
282776
+ if (first !== void 0 && /^\w+$/.test(first)) members.push(first);
282709
282777
  }
282710
282778
  return members;
282711
282779
  }
@@ -282714,9 +282782,11 @@ function parseFieldLine(line) {
282714
282782
  if (!t || t.startsWith("@@") || t.startsWith("//")) return null;
282715
282783
  const m2 = /^(\w+)\s+([A-Za-z_]\w*)(\[\])?(\?)?\s*(.*)$/.exec(t);
282716
282784
  if (!m2) return null;
282785
+ const [, name, type] = m2;
282786
+ if (name === void 0 || type === void 0) return null;
282717
282787
  return {
282718
- name: m2[1],
282719
- type: m2[2],
282788
+ name,
282789
+ type,
282720
282790
  list: Boolean(m2[3]),
282721
282791
  optional: Boolean(m2[4]),
282722
282792
  attrs: m2[5] ?? ""
@@ -282729,11 +282799,11 @@ function blockMap(body) {
282729
282799
  return /@@map\("([^"]+)"\)/.exec(body)?.[1];
282730
282800
  }
282731
282801
  function relationFkFields(attrs) {
282732
- const rel = /@relation\(([^)]*)\)/.exec(attrs);
282733
- if (!rel) return null;
282734
- const fields = /fields:\s*\[([^\]]*)\]/.exec(rel[1]);
282735
- if (!fields) return null;
282736
- return fields[1].split(",").map((s) => s.trim()).filter(Boolean);
282802
+ const rel = /@relation\(([^)]*)\)/.exec(attrs)?.[1];
282803
+ if (rel === void 0) return null;
282804
+ const fields = /fields:\s*\[([^\]]*)\]/.exec(rel)?.[1];
282805
+ if (fields === void 0) return null;
282806
+ return fields.split(",").map((s) => s.trim()).filter(Boolean);
282737
282807
  }
282738
282808
  function parsePrismaSchema(src) {
282739
282809
  const blocks = parseBlocks(src);
@@ -282758,8 +282828,9 @@ function parsePrismaSchema(src) {
282758
282828
  const column = mapAttr(raw.attrs);
282759
282829
  if (modelNames.has(raw.type)) {
282760
282830
  const fks = relationFkFields(raw.attrs);
282761
- if (fks && fks.length === 1) {
282762
- relations.push({ name: raw.name, target: tableOf.get(raw.type) ?? raw.type, fkField: fks[0] });
282831
+ const fkField = fks && fks.length === 1 ? fks[0] : void 0;
282832
+ if (fkField !== void 0) {
282833
+ relations.push({ name: raw.name, target: tableOf.get(raw.type) ?? raw.type, fkField });
282763
282834
  }
282764
282835
  continue;
282765
282836
  }
@@ -282822,6 +282893,7 @@ function parsePrismaPullArgs(argv) {
282822
282893
  let force = false;
282823
282894
  for (let i = 0; i < argv.length; i++) {
282824
282895
  const arg = argv[i];
282896
+ if (arg === void 0) continue;
282825
282897
  switch (arg) {
282826
282898
  case "--schema":
282827
282899
  schema = argv[++i] ?? schema;
@@ -282971,8 +283043,8 @@ async function lowerDrizzleModule(mod) {
282971
283043
  const { foreignKeys } = getTableConfig(table);
282972
283044
  for (const fk of foreignKeys) {
282973
283045
  const ref = fk.reference();
282974
- if (ref.columns.length !== 1) continue;
282975
- const localCol = ref.columns[0];
283046
+ const [localCol, ...extraCols] = ref.columns;
283047
+ if (localCol === void 0 || extraCols.length > 0) continue;
282976
283048
  const fkField = fieldKeyByColumn.get(localCol) ?? fieldKeyByColName.get(localCol.name) ?? localCol.name;
282977
283049
  const target = getTableName(ref.foreignTable);
282978
283050
  relations.push({ name: stripIdSuffix(fkField), target, fkField });
@@ -283000,6 +283072,7 @@ function parseDrizzlePullArgs(argv) {
283000
283072
  let force = false;
283001
283073
  for (let i = 0; i < argv.length; i++) {
283002
283074
  const arg = argv[i];
283075
+ if (arg === void 0) continue;
283003
283076
  switch (arg) {
283004
283077
  case "--schema":
283005
283078
  schema = argv[++i] ?? schema;
@@ -283059,7 +283132,7 @@ async function drizzlePull(argv) {
283059
283132
  result = await buildSchemaSourceFromDrizzle({ mod, importPath: args.importPath });
283060
283133
  } catch (err) {
283061
283134
  const msg = err instanceof Error ? err.message : String(err);
283062
- const hint = /Cannot find package 'drizzle-orm'/.test(msg) ? import_picocolors18.default.dim(` (install ${import_picocolors18.default.bold("drizzle-orm")} in this project)`) : "";
283135
+ const hint = msg.includes("Cannot find package 'drizzle-orm'") ? import_picocolors18.default.dim(` (install ${import_picocolors18.default.bold("drizzle-orm")} in this project)`) : "";
283063
283136
  console.error(import_picocolors18.default.red(` Couldn't load the schema: ${msg}`) + hint);
283064
283137
  process.exit(1);
283065
283138
  }
@@ -283091,7 +283164,7 @@ var RECOVERY_HINT = {
283091
283164
  permission: "Your key isn't allowed to do this \u2014 check its scopes or role.",
283092
283165
  session_expiry: "Your session expired \u2014 sign in again.",
283093
283166
  access_credential_expiry: "Your access credential expired \u2014 refresh it and retry.",
283094
- auth_blocked: "Authentication was blocked."
283167
+ auth_blocked: "Authentication was blocked \u2014 the credential itself was rejected. Check that the key matches this environment."
283095
283168
  };
283096
283169
  function titleForType(type) {
283097
283170
  const core = type.replace(/^Ablo/, "").replace(/Error$/, "");
@@ -283127,7 +283200,9 @@ function renderKnownDetails(details, line) {
283127
283200
  }
283128
283201
  }
283129
283202
  function renderCliError(err, opts = {}) {
283130
- const line = opts.write ?? ((l2) => console.error(l2));
283203
+ const line = opts.write ?? ((l2) => {
283204
+ console.error(l2);
283205
+ });
283131
283206
  const verbose = opts.verbose ?? (process.argv.includes("--verbose") || process.env.ABLO_VERBOSE === "1");
283132
283207
  if (err instanceof AbloError) {
283133
283208
  const codeTag = err.code ? ` ${import_picocolors19.default.dim(`[${err.code}]`)}` : "";
@@ -283356,7 +283431,7 @@ function detectOrm(override) {
283356
283431
  try {
283357
283432
  const pkg = JSON.parse((0, import_fs12.readFileSync)("package.json", "utf-8"));
283358
283433
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
283359
- if (deps["@prisma/client"] || deps["prisma"]) return "prisma";
283434
+ if (deps["@prisma/client"] || deps.prisma) return "prisma";
283360
283435
  if (deps["drizzle-orm"]) return "drizzle";
283361
283436
  } catch {
283362
283437
  }
@@ -283564,7 +283639,9 @@ async function init(args = []) {
283564
283639
  `Set ${import_picocolors20.default.bold("DATABASE_URL")} in ${import_picocolors20.default.bold(envFile)} \u2014 your Postgres is the system of record; rows live there, never with Ablo`,
283565
283640
  `Run ${import_picocolors20.default.bold("npx ablo dev")} \u2014 pushes your schema definition and watches for changes`,
283566
283641
  ...storage === "replication" ? [
283567
- `Connect your database: ${import_picocolors20.default.bold("npx ablo connect")} prints the logical-replication setup SQL (run it once on your Postgres), then ${import_picocolors20.default.bold("npx ablo connect --register")} tells Ablo to replicate it \u2014 your app keeps writing through your own backend, Ablo tails the WAL`
283642
+ `Connect your database \u2014 ${import_picocolors20.default.bold("npx ablo connect")} prints the one-time logical-replication setup SQL to run on your Postgres`,
283643
+ `Verify it \u2014 ${import_picocolors20.default.bold("npx ablo connect --check")} walks wal_level, the publication, the role, and replica identity, with the exact fix for anything missing`,
283644
+ `Register it \u2014 ${import_picocolors20.default.bold("npx ablo connect --register")} tells Ablo to start replicating; your app keeps writing through your own backend while Ablo tails the WAL`
283568
283645
  ] : storage === "direct" ? [
283569
283646
  `Provision your DB: ${import_picocolors20.default.bold("npx ablo migrate")} (creates your synced-model tables with row-level security; keep your own migrations for everything else)`
283570
283647
  ] : [
@@ -283937,23 +284014,12 @@ import Ablo from '@abloatai/ablo';
283937
284014
  import { AbloProvider } from '@abloatai/ablo/react';
283938
284015
  import { schema } from '@/ablo/schema';
283939
284016
 
283940
- // The browser client holds NO secret. The \`apiKey\` resolver fetches the route
284017
+ // The browser client holds NO secret. The endpoint string points at the route
283941
284018
  // below, which mints a short-lived session token (already scoped to the org +
283942
- // user); the client keeps it fresh (refresh timer + wake/online/focus re-mint).
283943
- // Contract: return the token, return \`null\` when the user is signed out
283944
- // (\u2192 the client signs out), or throw on a transient failure (\u2192 it retries).
283945
- const ablo = Ablo({
283946
- schema,
283947
- apiKey: async () => {
283948
- const res = await fetch('/api/ablo-session', {
283949
- method: 'POST',
283950
- credentials: 'include',
283951
- });
283952
- if (!res.ok) return null;
283953
- const { token } = (await res.json()) as { token: string | null };
283954
- return token;
283955
- },
283956
- });
284019
+ // user); the SDK owns the exchange \u2014 it fetches the route (POST, same-origin
284020
+ // cookies) and keeps the token fresh (refresh timer + wake re-mint). A 401
284021
+ // from the route signs the user out; any other failure is retried.
284022
+ const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
283957
284023
 
283958
284024
  export function Providers({ children }: { children: React.ReactNode }) {
283959
284025
  return <AbloProvider client={ablo}>{children}</AbloProvider>;
@@ -283974,8 +284040,8 @@ export async function POST(): Promise<Response> {
283974
284040
  const user = await getCurrentUser();
283975
284041
  if (!user) return new Response('Unauthorized', { status: 401 });
283976
284042
 
283977
- const { token } = await sync.sessions.create({ user: { id: user.id } });
283978
- return Response.json({ token });
284043
+ const { token, expiresAt } = await sync.sessions.create({ user: { id: user.id } });
284044
+ return Response.json({ token, expiresAt });
283979
284045
  }
283980
284046
 
283981
284047
  // Validate the signed-in user SERVER-SIDE. This ships wired for Better Auth;