@abloatai/cli 0.59.2 → 0.61.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 (2) hide show
  1. package/dist/cli.cjs +525 -228
  2. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -1443,6 +1443,7 @@ var init_bytes = __esm({
1443
1443
  // ../../node_modules/postgres/src/connection.js
1444
1444
  function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose = noop } = {}) {
1445
1445
  const {
1446
+ sslnegotiation,
1446
1447
  ssl,
1447
1448
  max,
1448
1449
  user,
@@ -1460,7 +1461,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1460
1461
  target_session_attrs
1461
1462
  } = options;
1462
1463
  const sent = queue_default(), id = uid++, backend = { pid: null, secret: null }, idleTimer = timer(end, options.idle_timeout), lifeTimer = timer(end, options.max_lifetime), connectTimer = timer(connectTimedOut, options.connect_timeout);
1463
- let socket = null, cancelMessage, result = new Result(), incoming = Buffer.alloc(0), needsTypes = options.fetch_types, backendParameters = {}, statements = {}, statementId = Math.random().toString(36).slice(2), statementCount = 1, closedDate = 0, remaining = 0, hostIndex = 0, retries = 0, length = 0, delay = 0, rows = 0, serverSignature = null, nextWriteTimer = null, terminated = false, incomings = null, results = null, initial = null, ending = null, stream = null, chunk = null, ended = null, nonce = null, query = null, final = null;
1464
+ let socket = null, cancelMessage, errorResponse = null, result = new Result(), incoming = Buffer.alloc(0), needsTypes = options.fetch_types, backendParameters = {}, statements = {}, statementId = Math.random().toString(36).slice(2), statementCount = 1, closedTime = 0, remaining = 0, hostIndex = 0, retries = 0, length = 0, delay = 0, rows = 0, serverSignature = null, nextWriteTimer = null, terminated = false, incomings = null, results = null, initial = null, ending = null, stream = null, chunk = null, ended = null, nonce = null, query = null, final = null;
1464
1465
  const connection2 = {
1465
1466
  queue: queues.closed,
1466
1467
  idleTimer,
@@ -1503,6 +1504,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1503
1504
  function execute(q2) {
1504
1505
  if (terminated)
1505
1506
  return queryError(q2, Errors.connection("CONNECTION_DESTROYED", options));
1507
+ if (stream)
1508
+ return queryError(q2, Errors.generic("COPY_IN_PROGRESS", "You cannot execute queries during copy"));
1506
1509
  if (q2.cancelled)
1507
1510
  return;
1508
1511
  try {
@@ -1572,16 +1575,24 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1572
1575
  socket.destroy();
1573
1576
  }
1574
1577
  async function secure() {
1575
- write(SSLRequest);
1576
- const canSSL = await new Promise((r2) => socket.once("data", (x2) => r2(x2[0] === 83)));
1577
- if (!canSSL && ssl === "prefer")
1578
- return connected();
1579
- socket.removeAllListeners();
1580
- socket = import_tls.default.connect({
1578
+ if (sslnegotiation !== "direct") {
1579
+ write(SSLRequest);
1580
+ const canSSL = await new Promise((r2) => socket.once("data", (x2) => r2(x2[0] === 83)));
1581
+ if (!canSSL && ssl === "prefer")
1582
+ return connected();
1583
+ }
1584
+ const options2 = {
1581
1585
  socket,
1582
- servername: import_net.default.isIP(socket.host) ? void 0 : socket.host,
1583
- ...ssl === "require" || ssl === "allow" || ssl === "prefer" ? { rejectUnauthorized: false } : ssl === "verify-full" ? {} : typeof ssl === "object" ? ssl : {}
1584
- });
1586
+ servername: import_net.default.isIP(socket.host) ? void 0 : socket.host
1587
+ };
1588
+ if (sslnegotiation === "direct")
1589
+ options2.ALPNProtocols = ["postgresql"];
1590
+ if (ssl === "require" || ssl === "allow" || ssl === "prefer")
1591
+ options2.rejectUnauthorized = false;
1592
+ else if (typeof ssl === "object")
1593
+ Object.assign(options2, ssl);
1594
+ socket.removeAllListeners();
1595
+ socket = import_tls.default.connect(options2);
1585
1596
  socket.on("secureConnect", connected);
1586
1597
  socket.on("error", error);
1587
1598
  socket.on("close", closed);
@@ -1635,7 +1646,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1635
1646
  hostIndex = (hostIndex + 1) % port.length;
1636
1647
  }
1637
1648
  function reconnect() {
1638
- setTimeout(connect2, closedDate ? closedDate + delay - import_perf_hooks.performance.now() : 0);
1649
+ setTimeout(connect2, closedTime ? Math.max(0, closedTime + delay - import_perf_hooks.performance.now()) : 0);
1639
1650
  }
1640
1651
  function connected() {
1641
1652
  try {
@@ -1708,7 +1719,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1708
1719
  if (initial)
1709
1720
  return reconnect();
1710
1721
  !hadError && (query || sent.length) && error(Errors.connection("CONNECTION_CLOSED", options, socket));
1711
- closedDate = import_perf_hooks.performance.now();
1722
+ closedTime = import_perf_hooks.performance.now();
1712
1723
  hadError && options.shared.retries++;
1713
1724
  delay = (typeof backoff2 === "function" ? backoff2(options.shared.retries) : backoff2) * 1e3;
1714
1725
  onclose(connection2, Errors.connection("CONNECTION_CLOSED", options, socket));
@@ -1813,8 +1824,16 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1813
1824
  }
1814
1825
  }
1815
1826
  function ReadyForQuery(x2) {
1816
- query && query.options.simple && query.resolve(results || result);
1817
- query = results = null;
1827
+ if (query) {
1828
+ if (errorResponse) {
1829
+ query.retried ? errored(query.retried) : query.prepared && retryRoutines.has(errorResponse.routine) ? retry(query, errorResponse) : errored(errorResponse);
1830
+ } else {
1831
+ query.resolve(results || result);
1832
+ }
1833
+ } else if (errorResponse) {
1834
+ errored(errorResponse);
1835
+ }
1836
+ query = results = errorResponse = null;
1818
1837
  result = new Result();
1819
1838
  connectTimer.cancel();
1820
1839
  if (initial) {
@@ -1859,7 +1878,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
1859
1878
  result.count && query.cursorFn(result);
1860
1879
  write(Sync);
1861
1880
  }
1862
- query.resolve(result);
1863
1881
  }
1864
1882
  function ParseComplete() {
1865
1883
  query.parsing = false;
@@ -2007,9 +2025,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
2007
2025
  query2.execute();
2008
2026
  }
2009
2027
  function ErrorResponse(x2) {
2010
- query && (query.cursorFn || query.describeFirst) && write(Sync);
2011
- const error2 = Errors.postgres(parseError(x2));
2012
- query && query.retried ? errored(query.retried) : query && query.prepared && retryRoutines.has(error2.routine) ? retry(query, error2) : errored(error2);
2028
+ if (query) {
2029
+ (query.cursorFn || query.describeFirst) && write(Sync);
2030
+ errorResponse = Errors.postgres(parseError(x2));
2031
+ } else {
2032
+ errored(Errors.postgres(parseError(x2)));
2033
+ }
2013
2034
  }
2014
2035
  function retry(q2, error2) {
2015
2036
  delete statements[q2.signature];
@@ -2054,6 +2075,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
2054
2075
  final(callback) {
2055
2076
  socket.write(bytes_default().c().end());
2056
2077
  final = callback;
2078
+ stream = null;
2057
2079
  }
2058
2080
  });
2059
2081
  query.resolve(stream);
@@ -2846,8 +2868,9 @@ function parseOptions(a, b4) {
2846
2868
  query.sslrootcert === "system" && (query.ssl = "verify-full");
2847
2869
  const ints = ["idle_timeout", "connect_timeout", "max_lifetime", "max_pipeline", "backoff", "keep_alive"];
2848
2870
  const defaults = {
2849
- max: 10,
2871
+ max: globalThis.Cloudflare ? 3 : 10,
2850
2872
  ssl: false,
2873
+ sslnegotiation: null,
2851
2874
  idle_timeout: null,
2852
2875
  connect_timeout: 30,
2853
2876
  max_lifetime,
@@ -3953,7 +3976,7 @@ var init_observeCliError = __esm({
3953
3976
  import_errorObservation = require("@abloatai/transaction/errorObservation");
3954
3977
  import_errors5 = require("@abloatai/transaction/errors");
3955
3978
  dsn = process.env.ABLO_CLI_SENTRY_DSN ?? "https://1ac154bff10b06836e1ea9de9e0d92f0@o4510928209772544.ingest.de.sentry.io/4511660691423312" ?? "";
3956
- release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.59.2";
3979
+ release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.61.0";
3957
3980
  initialized = false;
3958
3981
  nativeProcessExit = process.exit.bind(process);
3959
3982
  exitBoundaryInstalled = false;
@@ -4288,7 +4311,7 @@ var init_src2 = __esm({
4288
4311
 
4289
4312
  // src/cliEnvironment.ts
4290
4313
  function cliVersion() {
4291
- return "0.59.2";
4314
+ return "0.61.0";
4292
4315
  }
4293
4316
  function cliOs() {
4294
4317
  const value = (0, import_node_os.platform)();
@@ -5345,6 +5368,210 @@ var init_dbProvider = __esm({
5345
5368
  }
5346
5369
  });
5347
5370
 
5371
+ // src/connect/inspect.ts
5372
+ function inspectRegisteredConnection(validation) {
5373
+ if (!validation.ok) {
5374
+ return {
5375
+ object: "database_connection_reconcile",
5376
+ code: "operator_action_required",
5377
+ steps: {
5378
+ database: "pending",
5379
+ registration: "unchanged",
5380
+ snapshot: "pending",
5381
+ readiness: "action_required"
5382
+ },
5383
+ repairItems: [],
5384
+ needsDatabaseReconcile: false,
5385
+ needsSnapshotRequest: false,
5386
+ detail: validation.code ?? validation.message
5387
+ };
5388
+ }
5389
+ if (!validation.reachable) {
5390
+ return {
5391
+ object: "database_connection_reconcile",
5392
+ code: "operator_action_required",
5393
+ steps: {
5394
+ database: "action_required",
5395
+ registration: "unchanged",
5396
+ snapshot: "pending",
5397
+ readiness: "action_required"
5398
+ },
5399
+ repairItems: [],
5400
+ needsDatabaseReconcile: false,
5401
+ needsSnapshotRequest: false,
5402
+ detail: validation.reason ?? "source_unreachable"
5403
+ };
5404
+ }
5405
+ if (validation.failures.length > 0) {
5406
+ const repairItems = [...new Set(validation.failures.map((failure) => failure.item))].sort();
5407
+ const operatorItems = repairItems.filter((item) => !DATABASE_RECONCILABLE_ITEMS.has(item));
5408
+ if (operatorItems.length > 0) {
5409
+ return {
5410
+ object: "database_connection_reconcile",
5411
+ code: "operator_action_required",
5412
+ steps: {
5413
+ database: "action_required",
5414
+ registration: "unchanged",
5415
+ snapshot: "pending",
5416
+ readiness: "action_required"
5417
+ },
5418
+ repairItems,
5419
+ needsDatabaseReconcile: false,
5420
+ needsSnapshotRequest: false,
5421
+ detail: operatorItems.join(",")
5422
+ };
5423
+ }
5424
+ return {
5425
+ object: "database_connection_reconcile",
5426
+ code: "reconciling",
5427
+ steps: {
5428
+ database: "pending",
5429
+ registration: "unchanged",
5430
+ snapshot: "pending",
5431
+ readiness: "pending"
5432
+ },
5433
+ repairItems,
5434
+ needsDatabaseReconcile: true,
5435
+ needsSnapshotRequest: repairItems.some((item) => SNAPSHOT_SENSITIVE_ITEMS.has(item)) || validation.initialSnapshot?.status === "retrying"
5436
+ };
5437
+ }
5438
+ if (validation.initialSnapshot?.status === "retrying") {
5439
+ return {
5440
+ object: "database_connection_reconcile",
5441
+ code: "reconciling",
5442
+ steps: {
5443
+ database: "unchanged",
5444
+ registration: "unchanged",
5445
+ snapshot: "pending",
5446
+ readiness: "pending"
5447
+ },
5448
+ repairItems: [],
5449
+ needsDatabaseReconcile: false,
5450
+ needsSnapshotRequest: true,
5451
+ ...validation.initialSnapshot.detail ? { detail: validation.initialSnapshot.detail } : {}
5452
+ };
5453
+ }
5454
+ if (validation.initialSnapshot?.status === "loading") {
5455
+ return {
5456
+ object: "database_connection_reconcile",
5457
+ code: "loading",
5458
+ steps: {
5459
+ database: "unchanged",
5460
+ registration: "unchanged",
5461
+ snapshot: "loading",
5462
+ readiness: "pending"
5463
+ },
5464
+ repairItems: [],
5465
+ needsDatabaseReconcile: false,
5466
+ needsSnapshotRequest: false
5467
+ };
5468
+ }
5469
+ if (validation.ready) {
5470
+ return {
5471
+ object: "database_connection_reconcile",
5472
+ code: "ready",
5473
+ steps: {
5474
+ database: "unchanged",
5475
+ registration: "unchanged",
5476
+ snapshot: "ready",
5477
+ readiness: "ready"
5478
+ },
5479
+ repairItems: [],
5480
+ needsDatabaseReconcile: false,
5481
+ needsSnapshotRequest: false
5482
+ };
5483
+ }
5484
+ return {
5485
+ object: "database_connection_reconcile",
5486
+ code: "operator_action_required",
5487
+ steps: {
5488
+ database: "unchanged",
5489
+ registration: "unchanged",
5490
+ snapshot: "pending",
5491
+ readiness: "action_required"
5492
+ },
5493
+ repairItems: [],
5494
+ needsDatabaseReconcile: false,
5495
+ needsSnapshotRequest: false,
5496
+ detail: "unclassified_not_ready"
5497
+ };
5498
+ }
5499
+ function absentConnectionStatus() {
5500
+ return {
5501
+ object: "database_connection_reconcile",
5502
+ code: "absent",
5503
+ steps: {
5504
+ database: "pending",
5505
+ registration: "pending",
5506
+ snapshot: "pending",
5507
+ readiness: "pending"
5508
+ },
5509
+ repairItems: [],
5510
+ needsDatabaseReconcile: true,
5511
+ needsSnapshotRequest: false
5512
+ };
5513
+ }
5514
+ var SNAPSHOT_SENSITIVE_ITEMS, DATABASE_RECONCILABLE_ITEMS;
5515
+ var init_inspect = __esm({
5516
+ "src/connect/inspect.ts"() {
5517
+ "use strict";
5518
+ init_cjs_shims();
5519
+ SNAPSHOT_SENSITIVE_ITEMS = /* @__PURE__ */ new Set([
5520
+ "publication",
5521
+ "publication_drift",
5522
+ "replica_identity",
5523
+ "snapshot_row_security",
5524
+ "table_select"
5525
+ ]);
5526
+ DATABASE_RECONCILABLE_ITEMS = /* @__PURE__ */ new Set([
5527
+ "publication",
5528
+ "replication_role",
5529
+ "replica_identity",
5530
+ "table_select",
5531
+ "snapshot_row_security",
5532
+ "write_role",
5533
+ "row_security",
5534
+ "database_privileges",
5535
+ "schema_privileges",
5536
+ "idempotency_ledger",
5537
+ "table_privileges",
5538
+ "logical_marker",
5539
+ "publication_drift"
5540
+ ]);
5541
+ }
5542
+ });
5543
+
5544
+ // src/connect/snapshot.ts
5545
+ function requestInitialSnapshot(input) {
5546
+ return requestControlPlane({
5547
+ path: "/v1/datasources/resnapshot",
5548
+ method: "POST",
5549
+ ...input.apiUrl ? { baseUrl: input.apiUrl } : {},
5550
+ apiKey: input.apiKey,
5551
+ body: {},
5552
+ responseSchema: import_wire2.datasourceResnapshotResponseSchema
5553
+ });
5554
+ }
5555
+ var import_wire2;
5556
+ var init_snapshot = __esm({
5557
+ "src/connect/snapshot.ts"() {
5558
+ "use strict";
5559
+ init_cjs_shims();
5560
+ import_wire2 = require("@abloatai/transaction/wire");
5561
+ init_controlPlane();
5562
+ }
5563
+ });
5564
+
5565
+ // src/connect/index.ts
5566
+ var init_connect = __esm({
5567
+ "src/connect/index.ts"() {
5568
+ "use strict";
5569
+ init_cjs_shims();
5570
+ init_inspect();
5571
+ init_snapshot();
5572
+ }
5573
+ });
5574
+
5348
5575
  // src/remoteValidation.ts
5349
5576
  function dialFailureReason(err) {
5350
5577
  if (err === null || typeof err !== "object") return null;
@@ -5362,7 +5589,7 @@ function dialFailureReason(err) {
5362
5589
  return null;
5363
5590
  }
5364
5591
  function describeRemoteFailure(failure) {
5365
- const label = (0, import_wire2.isReadinessItem)(failure.item) ? READINESS_LABELS[failure.item](failure) : failure.item;
5592
+ const label = (0, import_wire3.isReadinessItem)(failure.item) ? READINESS_LABELS[failure.item](failure) : failure.item;
5366
5593
  return { label, fix: failure.fix };
5367
5594
  }
5368
5595
  async function requestRemoteValidation(input) {
@@ -5375,7 +5602,7 @@ async function requestRemoteValidation(input) {
5375
5602
  ...input.connectionString ? { connectionString: input.connectionString } : {},
5376
5603
  ...input.writeConnectionString ? { writeConnectionString: input.writeConnectionString } : {}
5377
5604
  },
5378
- responseSchema: import_wire2.datasourceValidationResponseSchema,
5605
+ responseSchema: import_wire3.datasourceValidationResponseSchema,
5379
5606
  ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
5380
5607
  });
5381
5608
  if (!result.ok) {
@@ -5396,12 +5623,12 @@ async function requestRemoteValidation(input) {
5396
5623
  failures: verdict.failures
5397
5624
  };
5398
5625
  }
5399
- var import_wire2, DIAL_FAILURE_CODES, withActual, READINESS_LABELS;
5626
+ var import_wire3, DIAL_FAILURE_CODES, withActual, READINESS_LABELS;
5400
5627
  var init_remoteValidation = __esm({
5401
5628
  "src/remoteValidation.ts"() {
5402
5629
  "use strict";
5403
5630
  init_cjs_shims();
5404
- import_wire2 = require("@abloatai/transaction/wire");
5631
+ import_wire3 = require("@abloatai/transaction/wire");
5405
5632
  init_controlPlane();
5406
5633
  DIAL_FAILURE_CODES = /* @__PURE__ */ new Set([
5407
5634
  "ENOTFOUND",
@@ -5754,19 +5981,21 @@ async function registerDirectDataSource(opts) {
5754
5981
  ...opts.replicationSlot ? { replicationSlot: opts.replicationSlot } : {},
5755
5982
  ...opts.publication ? { publication: opts.publication } : {}
5756
5983
  },
5757
- responseSchema: import_wire3.datasourceSummarySchema
5984
+ responseSchema: import_wire4.datasourceSummarySchema
5758
5985
  });
5759
5986
  if (result.ok) {
5760
5987
  const body = result.value;
5761
5988
  const statusNote = body.status === "active" ? `${opts.route}, active` : opts.route;
5762
- console.log(
5763
- `
5989
+ if (!opts.quiet) {
5990
+ console.log(
5991
+ `
5764
5992
  ${import_picocolors8.default.green("\u2713")} Registered${body.host ? ` ${import_picocolors8.default.dim(body.host)}` : ""}${body.id ? ` ${import_picocolors8.default.dim(`(${body.id})`)}` : ""} as a direct DataSource (${statusNote}).
5765
5993
  Your database is connected. Reads follow its replication stream; writes go through Ablo
5766
5994
  and land in your own tables. Rows that already exist load automatically \u2014 no manual
5767
5995
  backfill or row updates. Check their progress with ${import_picocolors8.default.cyan("ablo connect check")}.
5768
5996
  `
5769
- );
5997
+ );
5998
+ }
5770
5999
  return true;
5771
6000
  }
5772
6001
  const err = result.error;
@@ -5838,14 +6067,14 @@ async function registerDirectDataSource(opts) {
5838
6067
  console.error();
5839
6068
  return false;
5840
6069
  }
5841
- var import_picocolors8, import_zod3, import_wire3, import_source2, import_footprint, DIRECT_DATA_SOURCE_ROUTES, registerFailureDetailsSchema;
6070
+ var import_picocolors8, import_zod3, import_wire4, import_source2, import_footprint, DIRECT_DATA_SOURCE_ROUTES, registerFailureDetailsSchema;
5842
6071
  var init_connectSetup = __esm({
5843
6072
  "src/connectSetup.ts"() {
5844
6073
  "use strict";
5845
6074
  init_cjs_shims();
5846
6075
  import_picocolors8 = __toESM(require_picocolors(), 1);
5847
6076
  import_zod3 = require("zod");
5848
- import_wire3 = require("@abloatai/transaction/wire");
6077
+ import_wire4 = require("@abloatai/transaction/wire");
5849
6078
  init_controlPlane();
5850
6079
  init_remoteValidation();
5851
6080
  import_source2 = require("@abloatai/transaction/source");
@@ -5858,10 +6087,10 @@ var init_connectSetup = __esm({
5858
6087
  "vpn"
5859
6088
  ];
5860
6089
  registerFailureDetailsSchema = import_zod3.z.object({
5861
- failures: import_zod3.z.array(import_wire3.readinessFailureSchema).optional(),
6090
+ failures: import_zod3.z.array(import_wire4.readinessFailureSchema).optional(),
5862
6091
  reason: import_zod3.z.string().optional(),
5863
6092
  details: import_zod3.z.object({
5864
- failures: import_zod3.z.array(import_wire3.readinessFailureSchema).optional(),
6093
+ failures: import_zod3.z.array(import_wire4.readinessFailureSchema).optional(),
5865
6094
  reason: import_zod3.z.string().optional()
5866
6095
  }).loose().optional()
5867
6096
  }).loose();
@@ -5887,7 +6116,7 @@ async function deregisterDataSource(opts) {
5887
6116
  path: "/v1/datasources",
5888
6117
  method: "DELETE",
5889
6118
  apiKey: opts.apiKey,
5890
- responseSchema: import_wire4.datasourceDisconnectedResponseSchema,
6119
+ responseSchema: import_wire5.datasourceDisconnectedResponseSchema,
5891
6120
  ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
5892
6121
  ...opts.fetchImpl !== void 0 ? { fetchImpl: opts.fetchImpl } : {}
5893
6122
  });
@@ -6023,7 +6252,7 @@ async function disconnect(argv) {
6023
6252
  }
6024
6253
  renderDisconnected(outcome.response, project, branchLabel);
6025
6254
  }
6026
- var import_picocolors9, import_errors10, import_wire4, DISCONNECT_USAGE;
6255
+ var import_picocolors9, import_errors10, import_wire5, DISCONNECT_USAGE;
6027
6256
  var init_disconnect = __esm({
6028
6257
  "src/disconnect.ts"() {
6029
6258
  "use strict";
@@ -6031,7 +6260,7 @@ var init_disconnect = __esm({
6031
6260
  import_picocolors9 = __toESM(require_picocolors(), 1);
6032
6261
  init_dist2();
6033
6262
  import_errors10 = require("@abloatai/transaction/errors");
6034
- import_wire4 = require("@abloatai/transaction/wire");
6263
+ import_wire5 = require("@abloatai/transaction/wire");
6035
6264
  init_config();
6036
6265
  init_dbRole();
6037
6266
  init_controlPlane();
@@ -6184,7 +6413,7 @@ async function fetchPushedSchema(apiUrl3, apiKey) {
6184
6413
  signal: ctrl.signal
6185
6414
  });
6186
6415
  if (!res.ok) return null;
6187
- const parsed = import_wire5.schemaReadResponseSchema.safeParse(await res.json());
6416
+ const parsed = import_wire6.schemaReadResponseSchema.safeParse(await res.json());
6188
6417
  if (!parsed.success) return null;
6189
6418
  const read = parsed.data;
6190
6419
  return read.active ? {
@@ -6212,7 +6441,7 @@ async function fetchDeliveryState(apiUrl3, apiKey, timeoutMs = 4e3) {
6212
6441
  signal: ctrl.signal
6213
6442
  });
6214
6443
  if (!res.ok) return { kind: "unknown", detail: `HTTP ${res.status}` };
6215
- const parsed = import_wire5.logDeliveryResponseSchema.safeParse(await res.json());
6444
+ const parsed = import_wire6.logDeliveryResponseSchema.safeParse(await res.json());
6216
6445
  if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
6217
6446
  const { window_seconds, recorded, unroutable, sample } = parsed.data;
6218
6447
  return { kind: "known", window_seconds, recorded, unroutable, sample: sample ?? null };
@@ -6236,7 +6465,7 @@ async function fetchDataSourceState(apiUrl3, apiKey, timeoutMs = 4e3) {
6236
6465
  if (!res.ok) {
6237
6466
  return { kind: "unknown", detail: `HTTP ${res.status}` };
6238
6467
  }
6239
- const parsed = import_wire5.datasourceListResponseSchema.safeParse(await res.json());
6468
+ const parsed = import_wire6.datasourceListResponseSchema.safeParse(await res.json());
6240
6469
  if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
6241
6470
  const rows = parsed.data.data;
6242
6471
  if (rows.length === 0) return { kind: "none" };
@@ -6323,12 +6552,12 @@ function blockers(input) {
6323
6552
  }
6324
6553
  return found;
6325
6554
  }
6326
- var import_wire5, import_schema5, WRITE_READY_VERDICT;
6555
+ var import_wire6, import_schema5, WRITE_READY_VERDICT;
6327
6556
  var init_readiness = __esm({
6328
6557
  "src/readiness.ts"() {
6329
6558
  "use strict";
6330
6559
  init_cjs_shims();
6331
- import_wire5 = require("@abloatai/transaction/wire");
6560
+ import_wire6 = require("@abloatai/transaction/wire");
6332
6561
  init_push();
6333
6562
  init_dbProvider();
6334
6563
  init_remoteValidation();
@@ -6623,7 +6852,7 @@ async function locateExistingConnection(input) {
6623
6852
  connectionString: input.connectionString,
6624
6853
  ...input.schema ? { schema: input.schema } : {}
6625
6854
  },
6626
- responseSchema: import_wire6.datasourceLocationResponseSchema
6855
+ responseSchema: import_wire7.datasourceLocationResponseSchema
6627
6856
  });
6628
6857
  if (!result.ok) return null;
6629
6858
  if (result.value.held) return result.value.held;
@@ -6637,13 +6866,13 @@ function alreadyConnectedElsewhere(held) {
6637
6866
  const where = held.project ? `project ${held.project}, branch ${held.branch}` : `branch ${held.branch}`;
6638
6867
  return `This database schema is already connected to ${where}. A (database, schema) binding belongs to one plane at a time.`;
6639
6868
  }
6640
- var import_wire6, import_schema6;
6869
+ var import_wire7, import_schema6;
6641
6870
  var init_connectPreflight = __esm({
6642
6871
  "src/connectPreflight.ts"() {
6643
6872
  "use strict";
6644
6873
  init_cjs_shims();
6645
6874
  init_src();
6646
- import_wire6 = require("@abloatai/transaction/wire");
6875
+ import_wire7 = require("@abloatai/transaction/wire");
6647
6876
  init_controlPlane();
6648
6877
  init_push();
6649
6878
  import_schema6 = require("@abloatai/transaction/schema");
@@ -6666,6 +6895,26 @@ function postRegistrationOutcome(input) {
6666
6895
  notice: ROTATE_STRANDED_CREDENTIALS_NOTICE
6667
6896
  };
6668
6897
  }
6898
+ function emitReconcileStatus(status2, json) {
6899
+ if (json) {
6900
+ console.log(JSON.stringify(status2));
6901
+ return;
6902
+ }
6903
+ if (status2.code === "ready") {
6904
+ console.log(
6905
+ ` ${import_picocolors12.default.green("\u2713")} Already ready \u2014 database, credentials, and snapshot are unchanged.
6906
+ `
6907
+ );
6908
+ } else if (status2.code === "loading") {
6909
+ console.log(
6910
+ ` ${import_picocolors12.default.yellow("\u2014")} Existing rows are loading; re-run the same command to poll readiness.
6911
+ `
6912
+ );
6913
+ } else {
6914
+ console.log(` ${import_picocolors12.default.yellow("\u2014")} ${status2.code}
6915
+ `);
6916
+ }
6917
+ }
6669
6918
  function isPlaintextRefusal(err) {
6670
6919
  const message2 = err instanceof Error ? err.message : String(err);
6671
6920
  return /plaintext password/i.test(message2);
@@ -6697,15 +6946,10 @@ async function runConnectApply(args) {
6697
6946
  const rotating = args.rotate;
6698
6947
  const verb = rotating ? "connect rotate" : "connect apply";
6699
6948
  let adminUrl = args.url ?? readProjectAdminDatabaseUrl();
6700
- if (!adminUrl) {
6701
- throw new import_errors11.AbloValidationError(
6702
- "No admin connection string. Pass --url <admin-conn> (or set DATABASE_URL) and re-run.",
6703
- { code: "cli_database_url_missing" }
6704
- );
6705
- }
6706
6949
  const adminSource = args.url ? "--url" : "DATABASE_URL";
6707
6950
  let target = "your database";
6708
6951
  try {
6952
+ if (!adminUrl) throw new Error("not resolved yet");
6709
6953
  const parsed = new URL(adminUrl);
6710
6954
  target = `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
6711
6955
  } catch {
@@ -6729,15 +6973,62 @@ ${ambient}` : ""}`,
6729
6973
  { code: "cli_api_key_missing" }
6730
6974
  );
6731
6975
  }
6732
- console.log(
6733
- `
6976
+ const apiUrl3 = apiBaseUrl();
6977
+ const planeState = await fetchDataSourceState(apiUrl3, apiKey);
6978
+ const existingRegistration = !rotating && planeState.kind === "connected" && planeState.connections.includes("direct");
6979
+ let existingNeedsSnapshot = false;
6980
+ if (existingRegistration) {
6981
+ const inspected = inspectRegisteredConnection(
6982
+ await requestRemoteValidation({ apiUrl: apiUrl3, apiKey })
6983
+ );
6984
+ if (inspected.code === "ready") {
6985
+ emitReconcileStatus(inspected, args.json);
6986
+ return;
6987
+ }
6988
+ if (inspected.code === "loading") {
6989
+ emitReconcileStatus(inspected, args.json);
6990
+ return;
6991
+ }
6992
+ if (!inspected.needsDatabaseReconcile && inspected.needsSnapshotRequest) {
6993
+ const snapshot = await requestInitialSnapshot({ apiUrl: apiUrl3, apiKey });
6994
+ const resumed = {
6995
+ ...inspected,
6996
+ code: snapshot.replication_slot?.released === false ? "operator_action_required" : "loading",
6997
+ steps: {
6998
+ ...inspected.steps,
6999
+ snapshot: snapshot.replication_slot?.released === false ? "action_required" : "loading",
7000
+ readiness: snapshot.replication_slot?.released === false ? "action_required" : "pending"
7001
+ },
7002
+ ...snapshot.replication_slot?.released === false ? { detail: snapshot.replication_slot.detail ?? "replication_slot_active" } : {}
7003
+ };
7004
+ emitReconcileStatus(resumed, args.json);
7005
+ return;
7006
+ }
7007
+ if (inspected.code === "operator_action_required") {
7008
+ throw new import_errors11.AbloConnectionError(
7009
+ `The registered source needs operator action before it can be reconciled (${inspected.detail ?? "not ready"}).`,
7010
+ { code: "cli_database_unreachable", details: { ...inspected } }
7011
+ );
7012
+ }
7013
+ existingNeedsSnapshot = inspected.needsSnapshotRequest;
7014
+ }
7015
+ if (!adminUrl) {
7016
+ throw new import_errors11.AbloValidationError(
7017
+ "This connection needs database reconciliation. Pass the transient owner connection with --url <admin-conn> (or set DATABASE_URL) and re-run the same `ablo connect apply` operation.",
7018
+ { code: "cli_database_url_missing" }
7019
+ );
7020
+ }
7021
+ if (!args.json) {
7022
+ console.log(
7023
+ `
6734
7024
  ${brand("ablo")} ${import_picocolors12.default.dim(verb)} ${import_picocolors12.default.dim(rotating ? "re-key the scoped roles" : "set up your database for Ablo")}
6735
7025
  `
6736
- );
6737
- console.log(
6738
- ` ${import_picocolors12.default.dim("\u2192")} ${import_picocolors12.default.bold(target)}${adminSource === "DATABASE_URL" ? import_picocolors12.default.dim(" (admin via DATABASE_URL)") : ""}
7026
+ );
7027
+ console.log(
7028
+ ` ${import_picocolors12.default.dim("\u2192")} ${import_picocolors12.default.bold(target)}${adminSource === "DATABASE_URL" ? import_picocolors12.default.dim(" (admin via DATABASE_URL)") : ""}
6739
7029
  `
6740
- );
7030
+ );
7031
+ }
6741
7032
  const pooledAdmin = detectPooler(adminUrl);
6742
7033
  if (pooledAdmin?.confidence === "host") {
6743
7034
  if (pooledAdmin.direct) {
@@ -6748,7 +7039,7 @@ ${ambient}` : ""}`,
6748
7039
  target = `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
6749
7040
  } catch {
6750
7041
  }
6751
- console.log(
7042
+ if (!args.json) console.log(
6752
7043
  ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(pooledLabel)} is a connection pooler, so this run uses the direct host:
6753
7044
  ${import_picocolors12.default.bold(target)}
6754
7045
  ` + import_picocolors12.default.dim(
@@ -6775,7 +7066,7 @@ ${ambient}` : ""}`,
6775
7066
  }
6776
7067
  }
6777
7068
  if (pooledAdmin?.confidence === "port") {
6778
- console.log(
7069
+ if (!args.json) console.log(
6779
7070
  ` ${import_picocolors12.default.yellow("!")} Port ${import_picocolors12.default.bold(new URL(adminUrl).port)} is the one a connection pooler usually answers on.
6780
7071
  ` + import_picocolors12.default.dim(
6781
7072
  ` Replication cannot run over a pooler, so if that is what this is, point ${import_picocolors12.default.bold("--url")}
@@ -6791,7 +7082,7 @@ ${ambient}` : ""}`,
6791
7082
  }).catch(() => null);
6792
7083
  const mismatch = connectTarget ? describeMismatches(connectTarget.mismatches) : null;
6793
7084
  if (mismatch) {
6794
- console.log(` ${import_picocolors12.default.yellow("!")} ${mismatch}
7085
+ if (!args.json) console.log(` ${import_picocolors12.default.yellow("!")} ${mismatch}
6795
7086
  `);
6796
7087
  }
6797
7088
  const confirmed = connectTarget?.confirmed;
@@ -6836,7 +7127,7 @@ ${ambient}` : ""}`,
6836
7127
  );
6837
7128
  }
6838
7129
  if (args.tables.length === 0) {
6839
- console.log(
7130
+ if (!args.json) console.log(
6840
7131
  import_picocolors12.default.dim(
6841
7132
  ` publishing the ${tables.length} table${tables.length === 1 ? "" : "s"} declared by your Ablo schema in ${import_picocolors12.default.bold(args.schema)} (${import_picocolors12.default.bold("--tables")} to override)
6842
7133
  `
@@ -6927,6 +7218,13 @@ ${ambient}` : ""}`,
6927
7218
  publication
6928
7219
  });
6929
7220
  const existingRoles = await presentRoles(admin, [role, writeRole]).catch(() => []);
7221
+ if (existingRegistration && existingRoles.length !== 2) {
7222
+ await admin.end({ timeout: 2 });
7223
+ throw new import_errors11.AbloValidationError(
7224
+ `The registered source's scoped role pair is incomplete (${existingRoles.length}/2 present). Re-run with \`ablo connect rotate\` to repair credentials explicitly; no database changes were made.`,
7225
+ { code: "cli_invalid_arguments", details: { existingRoles: [...existingRoles] } }
7226
+ );
7227
+ }
6930
7228
  if (rotatePlane) {
6931
7229
  const refusal = rotateWithoutConnection({ rotating, ...rotatePlane, existingRoles });
6932
7230
  if (refusal) {
@@ -6940,11 +7238,14 @@ ${ambient}` : ""}`,
6940
7238
  }
6941
7239
  const blocker2 = reapplyBlocker({
6942
7240
  rotating,
6943
- existingRoles
7241
+ // A complete, registered pair keeps its current passwords: apply repairs
7242
+ // only non-secret invariants and never re-registers it. A partial pair is
7243
+ // still refused because it cannot be completed without credential repair.
7244
+ existingRoles: existingRegistration && existingRoles.length === 2 ? [] : existingRoles
6944
7245
  });
6945
7246
  if (blocker2) {
6946
7247
  await admin.end({ timeout: 2 });
6947
- console.log(
7248
+ if (!args.json) console.log(
6948
7249
  ` ${import_picocolors12.default.yellow("!")} ${blocker2.roles.map((r2) => import_picocolors12.default.bold(r2)).join(" and ")} ${blocker2.plural ? "are" : "is"} already set up here.
6949
7250
  `
6950
7251
  );
@@ -6991,17 +7292,23 @@ ${ambient}` : ""}`,
6991
7292
  });
6992
7293
  const steps = buildPlan("scram-verifier");
6993
7294
  if (pubReconcile.removed.length > 0 || pubReconcile.recreated) {
6994
- console.log(
6995
- ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(publication)} already publishes a different set; reconciling to your mapped tables:`
6996
- );
6997
- for (const t of pubReconcile.added) console.log(` ${import_picocolors12.default.green("+")} ${t}`);
6998
- for (const t of pubReconcile.removed)
6999
- console.log(` ${import_picocolors12.default.red("-")} ${t} ${import_picocolors12.default.dim("(stops replicating to Ablo)")}`);
7000
- if (pubReconcile.recreated && existingPublication.allTables)
7001
- console.log(` ${import_picocolors12.default.red("-")} ${import_picocolors12.default.dim("every other table (was FOR ALL TABLES)")}`);
7002
- console.log();
7295
+ if (!args.json) {
7296
+ console.log(
7297
+ ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(publication)} already publishes a different set; reconciling to your mapped tables:`
7298
+ );
7299
+ for (const t of pubReconcile.added) console.log(` ${import_picocolors12.default.green("+")} ${t}`);
7300
+ for (const t of pubReconcile.removed) {
7301
+ console.log(` ${import_picocolors12.default.red("-")} ${t} ${import_picocolors12.default.dim("(stops replicating to Ablo)")}`);
7302
+ }
7303
+ if (pubReconcile.recreated && existingPublication.allTables) {
7304
+ console.log(
7305
+ ` ${import_picocolors12.default.red("-")} ${import_picocolors12.default.dim("every other table (was FOR ALL TABLES)")}`
7306
+ );
7307
+ }
7308
+ console.log();
7309
+ }
7003
7310
  }
7004
- printPlan(steps, args.showSql);
7311
+ if (!args.json) printPlan(steps, args.showSql);
7005
7312
  if (!args.yes) {
7006
7313
  if (!process.stdout.isTTY) {
7007
7314
  await admin.end({ timeout: 2 });
@@ -7052,9 +7359,39 @@ ${ambient}` : ""}`,
7052
7359
  process.exit(1);
7053
7360
  }
7054
7361
  await admin.end({ timeout: 2 });
7362
+ if (existingRegistration) {
7363
+ const after = inspectRegisteredConnection(
7364
+ await requestRemoteValidation({ apiUrl: apiUrl3, apiKey })
7365
+ );
7366
+ if (after.needsDatabaseReconcile || after.code === "operator_action_required") {
7367
+ throw new import_errors11.AbloConnectionError(
7368
+ `Database reconciliation did not reach the snapshot boundary (${after.detail ?? after.repairItems.join(", ")}).`,
7369
+ { code: "cli_database_unreachable", details: { ...after } }
7370
+ );
7371
+ }
7372
+ if (existingNeedsSnapshot || after.needsSnapshotRequest) {
7373
+ const snapshot = await requestInitialSnapshot({ apiUrl: apiUrl3, apiKey });
7374
+ const reconciled = {
7375
+ ...after,
7376
+ code: snapshot.replication_slot?.released === false ? "operator_action_required" : "loading",
7377
+ steps: {
7378
+ ...after.steps,
7379
+ database: "changed",
7380
+ registration: "unchanged",
7381
+ snapshot: snapshot.replication_slot?.released === false ? "action_required" : "loading",
7382
+ readiness: snapshot.replication_slot?.released === false ? "action_required" : "pending"
7383
+ },
7384
+ ...snapshot.replication_slot?.released === false ? { detail: snapshot.replication_slot.detail ?? "replication_slot_active" } : {}
7385
+ };
7386
+ emitReconcileStatus(reconciled, args.json);
7387
+ return;
7388
+ }
7389
+ emitReconcileStatus(after, args.json);
7390
+ return;
7391
+ }
7055
7392
  const replicationUrl = rewriteDatabaseUrl(adminUrl, role, replicationPassword);
7056
7393
  const writeUrl = rewriteDatabaseUrl(adminUrl, writeRole, writePassword);
7057
- console.log(`
7394
+ if (!args.json) console.log(`
7058
7395
  ${import_picocolors12.default.green("\u2713")} Roles ${rotating ? "re-keyed" : "created"}.
7059
7396
  `);
7060
7397
  if (!walReady) {
@@ -7106,7 +7443,7 @@ ${ambient}` : ""}`,
7106
7443
  process.exit(1);
7107
7444
  }
7108
7445
  if (replicationProbe.items === null || writeProbe.items === null) {
7109
- console.log(import_picocolors12.default.dim(` Couldn't verify from here; Ablo will validate from its own network.
7446
+ if (!args.json) console.log(import_picocolors12.default.dim(` Couldn't verify from here; Ablo will validate from its own network.
7110
7447
  `));
7111
7448
  }
7112
7449
  const items = [...replicationProbe.items ?? [], ...writeProbe.items ?? []];
@@ -7130,7 +7467,6 @@ ${ambient}` : ""}`,
7130
7467
  `
7131
7468
  );
7132
7469
  }
7133
- const apiUrl3 = apiBaseUrl();
7134
7470
  const registered = await registerDirectDataSource({
7135
7471
  apiUrl: apiUrl3,
7136
7472
  apiKey,
@@ -7139,7 +7475,8 @@ ${ambient}` : ""}`,
7139
7475
  route: args.route,
7140
7476
  schema: args.schema,
7141
7477
  replicationSlot: footprint.slot,
7142
- publication
7478
+ publication,
7479
+ quiet: args.json
7143
7480
  });
7144
7481
  process.off("SIGINT", onRotateInterrupt);
7145
7482
  process.off("SIGTERM", onRotateInterrupt);
@@ -7149,6 +7486,22 @@ ${ambient}` : ""}`,
7149
7486
  ${import_picocolors12.default.red(outcome.notice.split("\n").join("\n "))}
7150
7487
  `);
7151
7488
  }
7489
+ if (args.json && registered) {
7490
+ const initial = absentConnectionStatus();
7491
+ emitReconcileStatus(
7492
+ {
7493
+ ...initial,
7494
+ code: "loading",
7495
+ steps: {
7496
+ database: "changed",
7497
+ registration: "changed",
7498
+ snapshot: "loading",
7499
+ readiness: "pending"
7500
+ }
7501
+ },
7502
+ true
7503
+ );
7504
+ }
7152
7505
  process.exit(outcome.exitCode);
7153
7506
  }
7154
7507
  var import_picocolors12, import_errors11, import_footprint2, ROTATE_STRANDED_CREDENTIALS_NOTICE;
@@ -7163,11 +7516,13 @@ var init_connectApply = __esm({
7163
7516
  import_footprint2 = require("@abloatai/transaction/footprint");
7164
7517
  init_connectSetup();
7165
7518
  init_connectOwnership();
7166
- init_connect();
7519
+ init_connect2();
7167
7520
  init_dbProvider();
7168
7521
  init_dbRole();
7169
7522
  init_config();
7170
7523
  init_readiness();
7524
+ init_remoteValidation();
7525
+ init_connect();
7171
7526
  init_push();
7172
7527
  init_controlPlane();
7173
7528
  init_theme();
@@ -7189,6 +7544,7 @@ function parseConnectArgs(argv) {
7189
7544
  let envFile;
7190
7545
  let yes = false;
7191
7546
  let showSql = false;
7547
+ let json = false;
7192
7548
  let scan = false;
7193
7549
  let locate = false;
7194
7550
  let manual = false;
@@ -7246,6 +7602,9 @@ function parseConnectArgs(argv) {
7246
7602
  case "--show-sql":
7247
7603
  showSql = true;
7248
7604
  break;
7605
+ case "--json":
7606
+ json = true;
7607
+ break;
7249
7608
  case "--manual":
7250
7609
  manual = true;
7251
7610
  break;
@@ -7293,6 +7652,7 @@ function parseConnectArgs(argv) {
7293
7652
  envFile,
7294
7653
  yes,
7295
7654
  showSql,
7655
+ json,
7296
7656
  scan,
7297
7657
  locate,
7298
7658
  tables,
@@ -7876,7 +8236,7 @@ ${ambient}` : ""}`,
7876
8236
  method: "POST",
7877
8237
  apiKey,
7878
8238
  body: { connectionString: url, schema: args.schema },
7879
- responseSchema: import_wire7.datasourceLocationResponseSchema
8239
+ responseSchema: import_wire8.datasourceLocationResponseSchema
7880
8240
  });
7881
8241
  if (answer.available === false && !answer.held) {
7882
8242
  console.log(
@@ -7919,13 +8279,7 @@ async function runResnapshot() {
7919
8279
  ${brand("ablo")} ${import_picocolors13.default.dim("connect resnapshot")} ${import_picocolors13.default.dim("reload existing rows")}
7920
8280
  `
7921
8281
  );
7922
- const result = await requestControlPlane({
7923
- path: "/v1/datasources/resnapshot",
7924
- method: "POST",
7925
- apiKey,
7926
- body: {},
7927
- responseSchema: import_wire7.datasourceResnapshotResponseSchema
7928
- });
8282
+ const result = await requestInitialSnapshot({ apiKey });
7929
8283
  if (result.replication_slot?.released === false) {
7930
8284
  console.log(` ${import_picocolors13.default.yellow("\u2014")} Snapshot reset recorded, but the old slot is still active.`);
7931
8285
  if (result.replication_slot.detail) console.log(` ${import_picocolors13.default.dim(result.replication_slot.detail)}`);
@@ -8016,8 +8370,8 @@ async function connect(argv) {
8016
8370
  })
8017
8371
  );
8018
8372
  }
8019
- var import_errors12, import_picocolors13, import_footprint3, import_wire7, FOOTPRINT_LOOKUP, CONNECT_USAGE;
8020
- var init_connect = __esm({
8373
+ var import_errors12, import_picocolors13, import_footprint3, import_wire8, FOOTPRINT_LOOKUP, CONNECT_USAGE;
8374
+ var init_connect2 = __esm({
8021
8375
  "src/connect.ts"() {
8022
8376
  "use strict";
8023
8377
  init_cjs_shims();
@@ -8029,7 +8383,8 @@ var init_connect = __esm({
8029
8383
  init_dbRole();
8030
8384
  init_config();
8031
8385
  init_controlPlane();
8032
- import_wire7 = require("@abloatai/transaction/wire");
8386
+ import_wire8 = require("@abloatai/transaction/wire");
8387
+ init_connect();
8033
8388
  init_theme();
8034
8389
  init_target();
8035
8390
  init_remoteValidation();
@@ -8075,6 +8430,7 @@ var init_connect = __esm({
8075
8430
  --manual Print the setup SQL instead of running it
8076
8431
  --yes Set up without the confirmation (non-interactive)
8077
8432
  --show-sql Show the exact statements before running them
8433
+ --json Emit stable result and step codes for automation
8078
8434
 
8079
8435
  apply registers with Ablo directly; the admin credential is used only on this
8080
8436
  machine and never persisted. Your app holds only ABLO_API_KEY.`;
@@ -219892,9 +220248,9 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
219892
220248
  }
219893
220249
  });
219894
220250
 
219895
- // ../../node_modules/@ts-morph/common/node_modules/balanced-match/dist/commonjs/index.js
220251
+ // ../../node_modules/balanced-match/dist/commonjs/index.js
219896
220252
  var require_commonjs = __commonJS({
219897
- "../../node_modules/@ts-morph/common/node_modules/balanced-match/dist/commonjs/index.js"(exports2) {
220253
+ "../../node_modules/balanced-match/dist/commonjs/index.js"(exports2) {
219898
220254
  "use strict";
219899
220255
  init_cjs_shims();
219900
220256
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -219955,9 +220311,9 @@ var require_commonjs = __commonJS({
219955
220311
  }
219956
220312
  });
219957
220313
 
219958
- // ../../node_modules/@ts-morph/common/node_modules/brace-expansion/dist/commonjs/index.js
220314
+ // ../../node_modules/brace-expansion/dist/commonjs/index.js
219959
220315
  var require_commonjs2 = __commonJS({
219960
- "../../node_modules/@ts-morph/common/node_modules/brace-expansion/dist/commonjs/index.js"(exports2) {
220316
+ "../../node_modules/brace-expansion/dist/commonjs/index.js"(exports2) {
219961
220317
  "use strict";
219962
220318
  init_cjs_shims();
219963
220319
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -220051,7 +220407,7 @@ var require_commonjs2 = __commonJS({
220051
220407
  }
220052
220408
  return out;
220053
220409
  }
220054
- function expandSequence(body, isAlphaSequence, max, maxLength) {
220410
+ function expandSequence(body, isAlphaSequence, max) {
220055
220411
  const n = body.split(/\.\./);
220056
220412
  const N2 = [];
220057
220413
  if (n[0] === void 0 || n[1] === void 0) {
@@ -220068,7 +220424,6 @@ var require_commonjs2 = __commonJS({
220068
220424
  test = gte;
220069
220425
  }
220070
220426
  const pad2 = n.some(isPadded);
220071
- let length = 0;
220072
220427
  for (let i = x2; test(i, y3) && N2.length < max; i += incr) {
220073
220428
  let c;
220074
220429
  if (isAlphaSequence) {
@@ -220090,10 +220445,7 @@ var require_commonjs2 = __commonJS({
220090
220445
  }
220091
220446
  }
220092
220447
  }
220093
- if (length + c.length > maxLength)
220094
- break;
220095
220448
  N2.push(c);
220096
- length += c.length;
220097
220449
  }
220098
220450
  return N2;
220099
220451
  }
@@ -220133,7 +220485,7 @@ var require_commonjs2 = __commonJS({
220133
220485
  }
220134
220486
  let values2;
220135
220487
  if (isSequence) {
220136
- values2 = expandSequence(m2.body, isAlphaSequence, max, maxLength);
220488
+ values2 = expandSequence(m2.body, isAlphaSequence, max);
220137
220489
  } else {
220138
220490
  let n = parseCommaParts(m2.body);
220139
220491
  if (n.length === 1 && n[0] !== void 0) {
@@ -220146,26 +220498,9 @@ var require_commonjs2 = __commonJS({
220146
220498
  continue;
220147
220499
  }
220148
220500
  }
220149
- let dropsEmpties = dropEmpties && !m2.post.length && !pre;
220150
- for (let d3 = 0; dropsEmpties && d3 < acc.length; d3++) {
220151
- if (acc[d3]) {
220152
- dropsEmpties = false;
220153
- }
220154
- }
220155
220501
  values2 = [];
220156
- let valuesLength = 0;
220157
- outer: for (let j2 = 0; j2 < n.length; j2++) {
220158
- const expanded = expand_(n[j2], max, maxLength, false);
220159
- for (let k3 = 0; k3 < expanded.length; k3++) {
220160
- const v2 = expanded[k3];
220161
- if (dropsEmpties && !v2)
220162
- continue;
220163
- if (values2.length >= max || valuesLength + v2.length > maxLength) {
220164
- break outer;
220165
- }
220166
- values2.push(v2);
220167
- valuesLength += v2.length;
220168
- }
220502
+ for (let j2 = 0; j2 < n.length; j2++) {
220503
+ values2.push.apply(values2, expand_(n[j2], max, maxLength, false));
220169
220504
  }
220170
220505
  }
220171
220506
  acc = combine(acc, pre, values2, max, maxLength, dropEmpties && !m2.post.length);
@@ -220178,9 +220513,9 @@ var require_commonjs2 = __commonJS({
220178
220513
  }
220179
220514
  });
220180
220515
 
220181
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js
220516
+ // ../../node_modules/minimatch/dist/commonjs/assert-valid-pattern.js
220182
220517
  var require_assert_valid_pattern = __commonJS({
220183
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js"(exports2) {
220518
+ "../../node_modules/minimatch/dist/commonjs/assert-valid-pattern.js"(exports2) {
220184
220519
  "use strict";
220185
220520
  init_cjs_shims();
220186
220521
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -220198,9 +220533,9 @@ var require_assert_valid_pattern = __commonJS({
220198
220533
  }
220199
220534
  });
220200
220535
 
220201
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/brace-expressions.js
220536
+ // ../../node_modules/minimatch/dist/commonjs/brace-expressions.js
220202
220537
  var require_brace_expressions = __commonJS({
220203
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/brace-expressions.js"(exports2) {
220538
+ "../../node_modules/minimatch/dist/commonjs/brace-expressions.js"(exports2) {
220204
220539
  "use strict";
220205
220540
  init_cjs_shims();
220206
220541
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -220316,9 +220651,9 @@ var require_brace_expressions = __commonJS({
220316
220651
  }
220317
220652
  });
220318
220653
 
220319
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/unescape.js
220654
+ // ../../node_modules/minimatch/dist/commonjs/unescape.js
220320
220655
  var require_unescape = __commonJS({
220321
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/unescape.js"(exports2) {
220656
+ "../../node_modules/minimatch/dist/commonjs/unescape.js"(exports2) {
220322
220657
  "use strict";
220323
220658
  init_cjs_shims();
220324
220659
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -220333,9 +220668,9 @@ var require_unescape = __commonJS({
220333
220668
  }
220334
220669
  });
220335
220670
 
220336
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/ast.js
220671
+ // ../../node_modules/minimatch/dist/commonjs/ast.js
220337
220672
  var require_ast = __commonJS({
220338
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/ast.js"(exports2) {
220673
+ "../../node_modules/minimatch/dist/commonjs/ast.js"(exports2) {
220339
220674
  "use strict";
220340
220675
  init_cjs_shims();
220341
220676
  var _a;
@@ -220988,9 +221323,9 @@ var require_ast = __commonJS({
220988
221323
  }
220989
221324
  });
220990
221325
 
220991
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/escape.js
221326
+ // ../../node_modules/minimatch/dist/commonjs/escape.js
220992
221327
  var require_escape = __commonJS({
220993
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/escape.js"(exports2) {
221328
+ "../../node_modules/minimatch/dist/commonjs/escape.js"(exports2) {
220994
221329
  "use strict";
220995
221330
  init_cjs_shims();
220996
221331
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -221005,9 +221340,9 @@ var require_escape = __commonJS({
221005
221340
  }
221006
221341
  });
221007
221342
 
221008
- // ../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/index.js
221343
+ // ../../node_modules/minimatch/dist/commonjs/index.js
221009
221344
  var require_commonjs3 = __commonJS({
221010
- "../../node_modules/@ts-morph/common/node_modules/minimatch/dist/commonjs/index.js"(exports2) {
221345
+ "../../node_modules/minimatch/dist/commonjs/index.js"(exports2) {
221011
221346
  "use strict";
221012
221347
  init_cjs_shims();
221013
221348
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -222528,9 +222863,9 @@ var require_is_glob = __commonJS({
222528
222863
  }
222529
222864
  });
222530
222865
 
222531
- // ../../node_modules/glob-parent/index.js
222866
+ // ../../node_modules/fast-glob/node_modules/glob-parent/index.js
222532
222867
  var require_glob_parent = __commonJS({
222533
- "../../node_modules/glob-parent/index.js"(exports2, module2) {
222868
+ "../../node_modules/fast-glob/node_modules/glob-parent/index.js"(exports2, module2) {
222534
222869
  "use strict";
222535
222870
  init_cjs_shims();
222536
222871
  var isGlob = require_is_glob();
@@ -223650,9 +223985,9 @@ var require_braces = __commonJS({
223650
223985
  }
223651
223986
  });
223652
223987
 
223653
- // ../../node_modules/picomatch/lib/constants.js
223988
+ // ../../node_modules/micromatch/node_modules/picomatch/lib/constants.js
223654
223989
  var require_constants2 = __commonJS({
223655
- "../../node_modules/picomatch/lib/constants.js"(exports2, module2) {
223990
+ "../../node_modules/micromatch/node_modules/picomatch/lib/constants.js"(exports2, module2) {
223656
223991
  "use strict";
223657
223992
  init_cjs_shims();
223658
223993
  var path = require("path");
@@ -223852,9 +224187,9 @@ var require_constants2 = __commonJS({
223852
224187
  }
223853
224188
  });
223854
224189
 
223855
- // ../../node_modules/picomatch/lib/utils.js
224190
+ // ../../node_modules/micromatch/node_modules/picomatch/lib/utils.js
223856
224191
  var require_utils2 = __commonJS({
223857
- "../../node_modules/picomatch/lib/utils.js"(exports2) {
224192
+ "../../node_modules/micromatch/node_modules/picomatch/lib/utils.js"(exports2) {
223858
224193
  "use strict";
223859
224194
  init_cjs_shims();
223860
224195
  var path = require("path");
@@ -223914,9 +224249,9 @@ var require_utils2 = __commonJS({
223914
224249
  }
223915
224250
  });
223916
224251
 
223917
- // ../../node_modules/picomatch/lib/scan.js
224252
+ // ../../node_modules/micromatch/node_modules/picomatch/lib/scan.js
223918
224253
  var require_scan = __commonJS({
223919
- "../../node_modules/picomatch/lib/scan.js"(exports2, module2) {
224254
+ "../../node_modules/micromatch/node_modules/picomatch/lib/scan.js"(exports2, module2) {
223920
224255
  "use strict";
223921
224256
  init_cjs_shims();
223922
224257
  var utils = require_utils2();
@@ -224245,9 +224580,9 @@ var require_scan = __commonJS({
224245
224580
  }
224246
224581
  });
224247
224582
 
224248
- // ../../node_modules/picomatch/lib/parse.js
224583
+ // ../../node_modules/micromatch/node_modules/picomatch/lib/parse.js
224249
224584
  var require_parse2 = __commonJS({
224250
- "../../node_modules/picomatch/lib/parse.js"(exports2, module2) {
224585
+ "../../node_modules/micromatch/node_modules/picomatch/lib/parse.js"(exports2, module2) {
224251
224586
  "use strict";
224252
224587
  init_cjs_shims();
224253
224588
  var constants = require_constants2();
@@ -225248,9 +225583,9 @@ var require_parse2 = __commonJS({
225248
225583
  }
225249
225584
  });
225250
225585
 
225251
- // ../../node_modules/picomatch/lib/picomatch.js
225586
+ // ../../node_modules/micromatch/node_modules/picomatch/lib/picomatch.js
225252
225587
  var require_picomatch = __commonJS({
225253
- "../../node_modules/picomatch/lib/picomatch.js"(exports2, module2) {
225588
+ "../../node_modules/micromatch/node_modules/picomatch/lib/picomatch.js"(exports2, module2) {
225254
225589
  "use strict";
225255
225590
  init_cjs_shims();
225256
225591
  var path = require("path");
@@ -225390,9 +225725,9 @@ var require_picomatch = __commonJS({
225390
225725
  }
225391
225726
  });
225392
225727
 
225393
- // ../../node_modules/picomatch/index.js
225728
+ // ../../node_modules/micromatch/node_modules/picomatch/index.js
225394
225729
  var require_picomatch2 = __commonJS({
225395
- "../../node_modules/picomatch/index.js"(exports2, module2) {
225730
+ "../../node_modules/micromatch/node_modules/picomatch/index.js"(exports2, module2) {
225396
225731
  "use strict";
225397
225732
  init_cjs_shims();
225398
225733
  module2.exports = require_picomatch();
@@ -284316,11 +284651,11 @@ async function migrate(argv) {
284316
284651
  }
284317
284652
 
284318
284653
  // src/index.ts
284319
- init_connect();
284654
+ init_connect2();
284320
284655
 
284321
284656
  // src/commands.ts
284322
284657
  init_cjs_shims();
284323
- init_connect();
284658
+ init_connect2();
284324
284659
 
284325
284660
  // src/docs.ts
284326
284661
  init_cjs_shims();
@@ -285037,7 +285372,7 @@ var import_path6 = require("path");
285037
285372
  var import_schema7 = require("@abloatai/transaction/schema");
285038
285373
  init_push();
285039
285374
  init_controlPlane();
285040
- var import_wire8 = require("@abloatai/transaction/wire");
285375
+ var import_wire9 = require("@abloatai/transaction/wire");
285041
285376
  init_config();
285042
285377
  init_readiness();
285043
285378
  init_theme();
@@ -285212,7 +285547,7 @@ async function registerLocalSource(args) {
285212
285547
  reverseChannel: true,
285213
285548
  metadata: { managed_by: "ablo dev --local" }
285214
285549
  },
285215
- responseSchema: import_wire8.datasourceSummarySchema
285550
+ responseSchema: import_wire9.datasourceSummarySchema
285216
285551
  });
285217
285552
  }
285218
285553
  function classifyKey(apiKey) {
@@ -288320,7 +288655,7 @@ var import_child_process2 = require("child_process");
288320
288655
  var import_picocolors23 = __toESM(require_picocolors(), 1);
288321
288656
  init_dist2();
288322
288657
  var import_errors21 = require("@abloatai/transaction/errors");
288323
- var import_wire9 = require("@abloatai/transaction/wire");
288658
+ var import_wire10 = require("@abloatai/transaction/wire");
288324
288659
  init_config();
288325
288660
  init_projects();
288326
288661
  init_theme();
@@ -288515,7 +288850,7 @@ ${import_picocolors23.default.dim(url)}`, "Approve in your browser");
288515
288850
  }
288516
288851
  process.exit(1);
288517
288852
  }
288518
- const parsedProv = import_wire9.provisionKeyResponseSchema.safeParse(
288853
+ const parsedProv = import_wire10.provisionKeyResponseSchema.safeParse(
288519
288854
  await provRes.json().catch(() => null)
288520
288855
  );
288521
288856
  if (!parsedProv.success) {
@@ -288865,7 +289200,7 @@ async function status(args = []) {
288865
289200
  // src/logs.ts
288866
289201
  init_cjs_shims();
288867
289202
  var import_errors22 = require("@abloatai/transaction/errors");
288868
- var import_wire10 = require("@abloatai/transaction/wire");
289203
+ var import_wire11 = require("@abloatai/transaction/wire");
288869
289204
  var import_picocolors25 = __toESM(require_picocolors(), 1);
288870
289205
  init_config();
288871
289206
  init_theme();
@@ -288976,7 +289311,7 @@ async function logs(argv) {
288976
289311
  const json = await res.json();
288977
289312
  return {
288978
289313
  events: json.data ?? json.events ?? [],
288979
- cursor: json.next_cursor ?? (json.cursor != null ? (0, import_wire10.formatFeedCursor)({ log: json.cursor, claims: 0 }) : (0, import_wire10.formatFeedCursor)(import_wire10.FEED_CURSOR_START))
289314
+ cursor: json.next_cursor ?? (json.cursor != null ? (0, import_wire11.formatFeedCursor)({ log: json.cursor, claims: 0 }) : (0, import_wire11.formatFeedCursor)(import_wire11.FEED_CURSOR_START))
288980
289315
  };
288981
289316
  }
288982
289317
  if (!args.json) {
@@ -289008,9 +289343,9 @@ async function logs(argv) {
289008
289343
  });
289009
289344
  if (!page) continue;
289010
289345
  for (const e2 of page.events) render2(e2, args.json);
289011
- const prev = (0, import_wire10.parseFeedCursor)(cursor);
289012
- const next = (0, import_wire10.parseFeedCursor)(page.cursor);
289013
- if (prev && next && (0, import_wire10.feedCursorAdvanced)(prev, next)) cursor = page.cursor;
289346
+ const prev = (0, import_wire11.parseFeedCursor)(cursor);
289347
+ const next = (0, import_wire11.parseFeedCursor)(page.cursor);
289348
+ if (prev && next && (0, import_wire11.feedCursorAdvanced)(prev, next)) cursor = page.cursor;
289014
289349
  }
289015
289350
  }
289016
289351
 
@@ -289203,10 +289538,14 @@ var import_contract = require("@abloatai/transaction/claims/contract");
289203
289538
  var import_routes = require("@abloatai/transaction/claims/routes");
289204
289539
  init_controlPlane();
289205
289540
  init_config();
289541
+ function writeStderr(message2) {
289542
+ process.stderr.write(`${message2}
289543
+ `);
289544
+ }
289206
289545
  function requireRuntimeKey() {
289207
289546
  const { key } = resolveRuntimeApiKey();
289208
289547
  if (!key) {
289209
- console.error(
289548
+ writeStderr(
289210
289549
  import_picocolors27.default.red(" No runtime credential.") + import_picocolors27.default.dim(
289211
289550
  ` Run ${import_picocolors27.default.bold("npx ablo login")}, or set ${import_picocolors27.default.bold("ABLO_API_KEY")} to a runtime key.`
289212
289551
  )
@@ -289306,10 +289645,10 @@ function describeAcquired(model, id, granted) {
289306
289645
  async function holdWhile(model, id, flags, apiKey, command) {
289307
289646
  const granted = await acquireClaim(model, id, flags, apiKey);
289308
289647
  if (granted.status === "queued") {
289309
- if (!flags.json) console.error(describeAcquired(model, id, granted));
289648
+ if (!flags.json) writeStderr(describeAcquired(model, id, granted));
289310
289649
  await waitForGrant(granted.id, flags.ttl, apiKey);
289311
289650
  }
289312
- if (!flags.json) console.error(` ${import_picocolors27.default.green("\u2713")} Holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289651
+ if (!flags.json) writeStderr(` ${import_picocolors27.default.green("\u2713")} Holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289313
289652
  const everyMs = Math.max(1e3, Math.floor((0, import_contract.claimTtlMs)(flags.ttl) / 3));
289314
289653
  const timer2 = setInterval(() => {
289315
289654
  void beat(model, id, flags.ttl, apiKey).catch(() => {
@@ -289349,7 +289688,7 @@ async function holdWhile(model, id, flags, apiKey, command) {
289349
289688
  });
289350
289689
  });
289351
289690
  await giveBack();
289352
- if (!flags.json) console.error(` ${import_picocolors27.default.dim("\xB7")} ${import_picocolors27.default.dim(`Released ${model} ${id}.`)}`);
289691
+ if (!flags.json) writeStderr(` ${import_picocolors27.default.dim("\xB7")} ${import_picocolors27.default.dim(`Released ${model} ${id}.`)}`);
289353
289692
  return code;
289354
289693
  }
289355
289694
  function renderList(rows) {
@@ -289398,7 +289737,7 @@ async function claims(argv = []) {
289398
289737
  }
289399
289738
  const [model, id] = rest;
289400
289739
  if (model === void 0 || id === void 0) {
289401
- console.error(` ${import_picocolors27.default.red("\u2717")} Name the row: ${import_picocolors27.default.bold(`ablo claims ${verb} <model> <id>`)}.`);
289740
+ writeStderr(` ${import_picocolors27.default.red("\u2717")} Name the row: ${import_picocolors27.default.bold(`ablo claims ${verb} <model> <id>`)}.`);
289402
289741
  process.exitCode = 1;
289403
289742
  return;
289404
289743
  }
@@ -289425,8 +289764,9 @@ async function claims(argv = []) {
289425
289764
  else console.log(` ${import_picocolors27.default.green("\u2713")} Still holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289426
289765
  return;
289427
289766
  }
289428
- console.error(` ${import_picocolors27.default.red("\u2717")} Unknown: ${import_picocolors27.default.bold(`ablo claims ${verb}`)}.`);
289429
- console.error(usageFor("claims"));
289767
+ writeStderr(` ${import_picocolors27.default.red("\u2717")} Unknown: ${import_picocolors27.default.bold(`ablo claims ${verb}`)}.`);
289768
+ const usage = usageFor("claims");
289769
+ if (usage) writeStderr(usage);
289430
289770
  process.exitCode = 1;
289431
289771
  }
289432
289772
 
@@ -289579,7 +289919,7 @@ async function upgrade(argv) {
289579
289919
  const stale = jsx.getAttributes().some(
289580
289920
  (a) => import_ts_morph.Node.isJsxAttribute(a) && ["schema", "teamIds", "authEndpoint", "scope", "apiKey"].includes(a.getNameNode().getText())
289581
289921
  );
289582
- if (stale) flag2(jsx, "<AbloProvider> props", "AbloProvider takes only `client` (+ userId/fallback/onError). Build `const ablo = Ablo({ schema, authEndpoint })` and pass `client={ablo}`.");
289922
+ if (stale) flag2(jsx, "<AbloProvider> props", "AbloProvider takes only `client` (+ userId/fallback/onError). Build `const ablo = Ablo({ schema, session: { endpoint } })` and pass `client={ablo}`.");
289583
289923
  }
289584
289924
  }
289585
289925
  const cwd = process.cwd();
@@ -290407,7 +290747,7 @@ import Ablo from '@abloatai/ablo';
290407
290747
  import { AbloProvider } from '@abloatai/ablo/react';
290408
290748
  import { schema } from '@/ablo/schema';
290409
290749
 
290410
- const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
290750
+ const ablo = Ablo({ schema, session: { endpoint: '/api/ablo-session' } });
290411
290751
 
290412
290752
  export function Providers({ children }: { children: React.ReactNode }) {
290413
290753
  return <AbloProvider client={ablo}>{children}</AbloProvider>;
@@ -290416,77 +290756,34 @@ export function Providers({ children }: { children: React.ReactNode }) {
290416
290756
  }
290417
290757
  function generateSessionRoute() {
290418
290758
  return `import { headers } from 'next/headers';
290419
- import {
290420
- credentialEndpointErrorSchema,
290421
- credentialEndpointSuccessSchema,
290422
- } from '@abloatai/ablo/auth';
290423
- import { sync } from '@/ablo';
290759
+ import Sessions from '@abloatai/ablo/sessions';
290760
+ import { schema } from '@/ablo/schema';
290424
290761
  import { auth } from '@/lib/auth';
290425
290762
 
290426
- const noStore = { 'Cache-Control': 'no-store' };
290763
+ const sessions = Sessions({ schema, apiKey: process.env.ABLO_API_KEY });
290427
290764
 
290428
- export async function POST(request: Request): Promise<Response> {
290429
- if (!(await isSameOrigin(request))) {
290430
- return Response.json(
290431
- credentialEndpointErrorSchema.parse({
290432
- error: { code: 'origin_mismatch', message: 'Cross-origin mint rejected' },
290433
- }),
290434
- { status: 403, headers: noStore },
290435
- );
290436
- }
290437
-
290438
- const user = await getCurrentUser();
290439
- if (!user) {
290440
- return Response.json(
290441
- credentialEndpointErrorSchema.parse({
290442
- error: { code: 'session_expired', message: 'Sign in again' },
290443
- }),
290444
- { status: 401, headers: noStore },
290445
- );
290446
- }
290447
-
290448
- const authorizedScope = await authorizeActiveWorkspace(user.id);
290449
- if (!authorizedScope) {
290450
- return Response.json(
290451
- credentialEndpointErrorSchema.parse({
290452
- error: { code: 'policy_denied', message: 'Workspace membership is stale or revoked' },
290453
- }),
290454
- { status: 403, headers: noStore },
290455
- );
290456
- }
290457
-
290458
- const { token, expiresAt } = await sync.sessions.create({
290459
- user: { id: user.id },
290460
- // These ids came from the server-side membership lookup below. Never take
290461
- // organization, workspace, team, or group ids from the request body.
290462
- syncGroups: authorizedScope.syncGroups,
290463
- can: { records: ['read', 'create', 'update'] },
290464
- });
290465
- return Response.json(
290466
- credentialEndpointSuccessSchema.parse({
290467
- token,
290468
- expiresAt,
290469
- credentialKind: 'ephemeral',
290470
- }),
290471
- { headers: noStore },
290472
- );
290473
- }
290474
-
290475
- async function isSameOrigin(request: Request): Promise<boolean> {
290476
- const origin = request.headers.get('origin');
290477
- if (!origin) return request.headers.get('sec-fetch-site') !== 'cross-site';
290478
- const host = (await headers()).get('host');
290479
- return host !== null && new URL(origin).host === host;
290480
- }
290765
+ export const POST = sessions.handler({
290766
+ async authenticate() {
290767
+ const session = await auth.api.getSession({ headers: await headers() });
290768
+ return session?.user ?? null;
290769
+ },
290770
+ async grant({ principal: user }) {
290771
+ const authorizedScope = await authorizeActiveWorkspace(user.id);
290772
+ if (!authorizedScope) return null;
290481
290773
 
290482
- async function getCurrentUser(): Promise<{ id: string } | null> {
290483
- const session = await auth.api.getSession({ headers: await headers() });
290484
- return session?.user ? { id: session.user.id } : null;
290485
- }
290774
+ return {
290775
+ user: { id: user.id },
290776
+ // These ids came from the server-side membership lookup below. Never take
290777
+ // organization, workspace, team, or group ids from the request body.
290778
+ groups: authorizedScope.groups,
290779
+ can: { records: ['read', 'create', 'update'] },
290780
+ };
290781
+ },
290782
+ });
290486
290783
 
290487
290784
  type AuthorizedWorkspace = {
290488
290785
  workspaceId: string;
290489
- syncGroups: readonly [\`workspace:\${string}\`, ...\`\${string}:\${string}\`[]];
290786
+ groups: readonly [\`workspace:\${string}\`, ...\`\${string}:\${string}\`[]];
290490
290787
  };
290491
290788
 
290492
290789
  async function authorizeActiveWorkspace(userId: string): Promise<AuthorizedWorkspace | null> {
@@ -290500,7 +290797,7 @@ async function authorizeActiveWorkspace(userId: string): Promise<AuthorizedWorks
290500
290797
  // Example after your membership query:
290501
290798
  // return {
290502
290799
  // workspaceId: membership.workspaceId,
290503
- // syncGroups: [\`workspace:\${membership.workspaceId}\`],
290800
+ // groups: [\`workspace:\${membership.workspaceId}\`],
290504
290801
  // };
290505
290802
  return null;
290506
290803
  }