@stackbone/sdk 0.1.0-alpha.5 → 0.1.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -8870,7 +8870,8 @@ var journalTerminalStatusSchema = z.enum([
8870
8870
  ]);
8871
8871
  var journalJobStatusSchema = z.enum([
8872
8872
  ...journalTerminalStatusSchema.options,
8873
- "running"
8873
+ "running",
8874
+ "lost"
8874
8875
  ]);
8875
8876
  var jobStatusSchema = z.enum([
8876
8877
  ...bullmqJobStatusSchema.options,
@@ -8972,6 +8973,27 @@ z.object({
8972
8973
  queue: z.string(),
8973
8974
  status: jobStatusSchema
8974
8975
  });
8976
+ z.object({
8977
+ items: z.array(bullmqJobSchema)
8978
+ });
8979
+ z.object({
8980
+ purged: z.number().int().nonnegative()
8981
+ });
8982
+ z.object({
8983
+ payload: jsonObject(),
8984
+ retries: z.number().int().nonnegative().optional(),
8985
+ delay: z.number().int().nonnegative().optional()
8986
+ });
8987
+ z.object({
8988
+ message_id: z.string(),
8989
+ queue: queueNameSchema
8990
+ });
8991
+ z.object({
8992
+ name: queueNameSchema
8993
+ });
8994
+ z.object({
8995
+ limit: z.coerce.number().int().min(1).max(50).default(10)
8996
+ });
8975
8997
 
8976
8998
  // ../shared/validators/src/lib/agents/queue-jobs.ts
8977
8999
  var MAX_JOB_ATTEMPTS = 20;
@@ -9930,7 +9952,7 @@ var ApprovalFacade = class {
9930
9952
  const fallback = onTimeoutToFallback(options.onTimeout);
9931
9953
  const callbackUrl = options.onDecide;
9932
9954
  try {
9933
- const rows = await sql.data`
9955
+ const rows = await this._getDatabase().runShared((db) => db`
9934
9956
  INSERT INTO stackbone_platform.approvals (
9935
9957
  topic, payload, callback_url, idempotency_key,
9936
9958
  fallback, metadata, timeout_at
@@ -9949,7 +9971,7 @@ var ApprovalFacade = class {
9949
9971
  RETURNING id, callback_url,
9950
9972
  to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_at,
9951
9973
  to_char(timeout_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS timeout_at
9952
- `;
9974
+ `);
9953
9975
  const row = rows[0];
9954
9976
  if (!row) {
9955
9977
  return err({
@@ -9984,13 +10006,13 @@ var ApprovalFacade = class {
9984
10006
  const patch = reason ? {
9985
10007
  cancelReason: reason
9986
10008
  } : {};
9987
- await sql.data`
10009
+ await this._getDatabase().runShared((db) => db`
9988
10010
  UPDATE stackbone_platform.approvals
9989
10011
  SET status = 'cancelled',
9990
10012
  decided_at = now(),
9991
10013
  metadata = metadata || ${JSON.stringify(patch)}::jsonb
9992
10014
  WHERE id = ${approvalId}::uuid AND status = 'pending'
9993
- `;
10015
+ `);
9994
10016
  return ok(void 0);
9995
10017
  } catch (cause) {
9996
10018
  return err({
@@ -10010,14 +10032,14 @@ var ApprovalFacade = class {
10010
10032
  const sql = this.sql();
10011
10033
  if (sql.error) return err(sql.error);
10012
10034
  try {
10013
- const rows = await sql.data`
10035
+ const rows = await this._getDatabase().runShared((db) => db`
10014
10036
  SELECT id, topic, status, payload, metadata,
10015
10037
  to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_at,
10016
10038
  to_char(timeout_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS timeout_at
10017
10039
  FROM stackbone_platform.approvals
10018
10040
  WHERE id = ${approvalId}::uuid
10019
10041
  LIMIT 1
10020
- `;
10042
+ `);
10021
10043
  const row = rows[0];
10022
10044
  if (!row) {
10023
10045
  return err({
@@ -10042,7 +10064,7 @@ var ApprovalFacade = class {
10042
10064
  if (sql.error) return err(sql.error);
10043
10065
  const limit = clampLimit(options.limit);
10044
10066
  try {
10045
- const rows = await sql.data`
10067
+ const rows = await this._getDatabase().runShared((db) => db`
10046
10068
  SELECT id, topic, status, payload, metadata,
10047
10069
  to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_at,
10048
10070
  to_char(timeout_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS timeout_at
@@ -10051,7 +10073,7 @@ var ApprovalFacade = class {
10051
10073
  AND (${options.topic ?? null}::text IS NULL OR topic = ${options.topic ?? null})
10052
10074
  ORDER BY created_at DESC, id DESC
10053
10075
  LIMIT ${limit}
10054
- `;
10076
+ `);
10055
10077
  return ok({
10056
10078
  items: rows.map((row) => toRecord(row))
10057
10079
  });
@@ -10217,9 +10239,9 @@ var ConfigFacade = class {
10217
10239
  const sql = this.sql();
10218
10240
  if (sql.error) return err(sql.error);
10219
10241
  try {
10220
- const rows = await sql.data`
10242
+ const rows = await this._getDatabase().runShared((db) => db`
10221
10243
  SELECT payload FROM stackbone_platform.agent_config WHERE id = 1
10222
- `;
10244
+ `);
10223
10245
  return ok(rows[0]?.payload ?? null);
10224
10246
  } catch (cause) {
10225
10247
  return err({
@@ -10293,6 +10315,10 @@ function isPoolTerminatedError(error) {
10293
10315
  return typeof code === "string" && POOL_TERMINATED_ERROR_CODES.has(code);
10294
10316
  }
10295
10317
  __name(isPoolTerminatedError, "isPoolTerminatedError");
10318
+ function invalidatePoolHandle() {
10319
+ cachedHandle = null;
10320
+ }
10321
+ __name(invalidatePoolHandle, "invalidatePoolHandle");
10296
10322
  async function runWithColdStartRetry(firstAttempt, retryAttempt) {
10297
10323
  try {
10298
10324
  return await firstAttempt();
@@ -10461,6 +10487,48 @@ var DatabaseModule = class {
10461
10487
  // a half-baked tx. The contract gate fires before the first attempt.
10462
10488
  transaction = /* @__PURE__ */ __name((...args) => withColdStartRetry(() => getDatabaseHandle(this._databaseUrl).transaction(...args), this._gate), "transaction");
10463
10489
  /**
10490
+ * Cross-surface raw-SQL runner carrying the SAME cold-start resilience the
10491
+ * query-builder verbs above get. Sibling agent-local surfaces (`approval`,
10492
+ * `config`, `secrets`, `prompts`) that issue tagged-template queries over the
10493
+ * shared postgres-js `Sql` MUST execute them through this instead of caching
10494
+ * `shared().$client` once and calling it directly. Otherwise a dead pooled
10495
+ * socket — Fly Machines suspending the Machine between invokes, or a
10496
+ * WSL2/Docker localhost relay dropping an idle connection during a long LLM
10497
+ * step — surfaces as an unretried `CONNECTION_ENDED`/`ECONNRESET`. That is
10498
+ * exactly the asymmetry that let `client.approval.request` fail while the
10499
+ * `client.database.insert` right before it recovered.
10500
+ *
10501
+ * `fn` receives a FRESH `$client` on each attempt, so the single retry runs
10502
+ * against the rebuilt pool. Whole-operation granularity is replayed as a
10503
+ * unit — pass a multi-statement `sql.begin(...)` transaction or a helper that
10504
+ * fires several queries and the replay re-runs all of it; postgres-js never
10505
+ * commits a half-applied tx, so that is safe. SQLSTATE application errors
10506
+ * pass straight through without a retry. Like `shared()`/`raw()`, the
10507
+ * contract gate is intentionally NOT consulted here — gating surfaces gate at
10508
+ * their own call sites.
10509
+ */
10510
+ runShared(fn) {
10511
+ return withColdStartRetry(() => fn(getDatabaseHandle(this._databaseUrl).$client));
10512
+ }
10513
+ /**
10514
+ * `Result`-shaped sibling of `runShared` for surfaces whose driver swallows
10515
+ * the dead-socket error into a returned `Result.err.cause` instead of
10516
+ * throwing it — today only `client.rag`, whose `RagPipeline` returns a
10517
+ * `Result` rather than throwing. `attempt` runs once; if it comes back with a
10518
+ * pool-terminated `cause`, the pool is rebuilt and `attempt` replays exactly
10519
+ * once, then its result (success or the fresh error) is returned verbatim.
10520
+ * The `Result` contract is preserved end to end: a still-dead socket on the
10521
+ * replay surfaces as the surface's own `Result.err`, never as a raw throw.
10522
+ */
10523
+ async runSharedResult(attempt) {
10524
+ const first = await attempt();
10525
+ if (!first.error || !isPoolTerminatedError(first.error.cause)) return first;
10526
+ process.stderr.write(`[stackbone/sdk] database cold-start reconnect: rebuilding pool after ${first.error.cause.code ?? "unknown"}
10527
+ `);
10528
+ invalidatePoolHandle();
10529
+ return attempt();
10530
+ }
10531
+ /**
10464
10532
  * Canonical accessor for the **shared-handles pattern**. Returns the
10465
10533
  * process-wide Drizzle handle backing `client.database`. Cross-surface
10466
10534
  * SDK consumers (RAG today, memory / queues tomorrow) call this — never
@@ -10562,12 +10630,12 @@ var PromptsFacade = class {
10562
10630
  const sql = this.sql();
10563
10631
  if (sql.error) return err(sql.error);
10564
10632
  try {
10565
- const head = await fetchHead(sql.data, key);
10633
+ const head = await this._getDatabase().runShared((db) => fetchHead(db, key));
10566
10634
  if (!head) {
10567
10635
  return err(notFound(key));
10568
10636
  }
10569
10637
  if (options?.version !== void 0 && options.version !== head.current_version) {
10570
- const content = await fetchVersionContent(sql.data, key, options.version);
10638
+ const content = await this._getDatabase().runShared((db) => fetchVersionContent(db, key, options.version));
10571
10639
  if (content === null) {
10572
10640
  return err({
10573
10641
  code: "prompts_not_found",
@@ -10618,7 +10686,7 @@ var PromptsFacade = class {
10618
10686
  const sql = this.sql();
10619
10687
  if (sql.error) return err(sql.error);
10620
10688
  try {
10621
- const rows = await sql.data`
10689
+ const rows = await this._getDatabase().runShared((db) => db`
10622
10690
  SELECT p.key, p.name, p.description, p.current_version, p.metadata,
10623
10691
  p.created_at, p.updated_at, v.content
10624
10692
  FROM stackbone_platform.prompts p
@@ -10627,7 +10695,7 @@ var PromptsFacade = class {
10627
10695
  WHERE p.deleted_at IS NULL
10628
10696
  ORDER BY p.key
10629
10697
  LIMIT ${limit}
10630
- `;
10698
+ `);
10631
10699
  return ok({
10632
10700
  items: rows.map((row) => rowToPrompt(row, row.current_version, row.content))
10633
10701
  });
@@ -10654,7 +10722,7 @@ var PromptsFacade = class {
10654
10722
  if (sql.error) return err(sql.error);
10655
10723
  const variables = extractVars(request.template);
10656
10724
  try {
10657
- const head = await sql.data.begin(async (tx) => {
10725
+ const head = await this._getDatabase().runShared((db) => db.begin(async (tx) => {
10658
10726
  await tx`
10659
10727
  INSERT INTO stackbone_platform.prompts
10660
10728
  (key, name, description, current_version, metadata)
@@ -10682,7 +10750,7 @@ var PromptsFacade = class {
10682
10750
  WHERE p.key = ${request.key}
10683
10751
  `;
10684
10752
  return rows[0];
10685
- });
10753
+ }));
10686
10754
  if (!head) {
10687
10755
  return err({
10688
10756
  code: "prompts_unavailable",
@@ -10715,7 +10783,7 @@ var PromptsFacade = class {
10715
10783
  const sql = this.sql();
10716
10784
  if (sql.error) return err(sql.error);
10717
10785
  try {
10718
- const head = await sql.data.begin(async (tx) => {
10786
+ const head = await this._getDatabase().runShared((db) => db.begin(async (tx) => {
10719
10787
  await tx`SELECT pg_advisory_xact_lock(hashtext(${`prompts:${key}`}))`;
10720
10788
  const existing = await tx`
10721
10789
  SELECT p.key, p.name, p.description, p.current_version, p.metadata,
@@ -10759,7 +10827,7 @@ var PromptsFacade = class {
10759
10827
  WHERE p.key = ${key}
10760
10828
  `;
10761
10829
  return rows[0] ?? null;
10762
- });
10830
+ }));
10763
10831
  if (head === null) {
10764
10832
  return err(notFound(key));
10765
10833
  }
@@ -10774,12 +10842,12 @@ var PromptsFacade = class {
10774
10842
  const sql = this.sql();
10775
10843
  if (sql.error) return err(sql.error);
10776
10844
  try {
10777
- const rows = await sql.data`
10845
+ const rows = await this._getDatabase().runShared((db) => db`
10778
10846
  UPDATE stackbone_platform.prompts
10779
10847
  SET deleted_at = now(), updated_at = now()
10780
10848
  WHERE key = ${key} AND deleted_at IS NULL
10781
10849
  RETURNING key
10782
- `;
10850
+ `);
10783
10851
  const deleted = rows.length;
10784
10852
  if (deleted === 0) {
10785
10853
  return err(notFound(key));
@@ -11863,8 +11931,10 @@ var RagModule = class {
11863
11931
  const sql = this.sql();
11864
11932
  if (sql.error) return err(sql.error);
11865
11933
  try {
11866
- await sql.data`DROP TABLE IF EXISTS rag_chunks`;
11867
- await sql.data`DROP TABLE IF EXISTS _rag_meta`;
11934
+ await this._getDatabase().runShared(async (db) => {
11935
+ await db`DROP TABLE IF EXISTS rag_chunks`;
11936
+ await db`DROP TABLE IF EXISTS _rag_meta`;
11937
+ });
11868
11938
  return ok(void 0);
11869
11939
  } catch (cause) {
11870
11940
  return err(toRagError(cause, "Reset failed"));
@@ -11914,13 +11984,15 @@ var RagModule = class {
11914
11984
  }
11915
11985
  }
11916
11986
  async withPipeline(embedder, run) {
11917
- const pipeline = new RagPipeline({
11918
- sqlProvider: /* @__PURE__ */ __name(() => this.sql(), "sqlProvider"),
11919
- ...embedder && {
11920
- embedder
11921
- }
11987
+ return this._getDatabase().runSharedResult(async () => {
11988
+ const pipeline = new RagPipeline({
11989
+ sqlProvider: /* @__PURE__ */ __name(() => this.sql(), "sqlProvider"),
11990
+ ...embedder && {
11991
+ embedder
11992
+ }
11993
+ });
11994
+ return await run(pipeline);
11922
11995
  });
11923
- return await run(pipeline);
11924
11996
  }
11925
11997
  };
11926
11998
  function countChunks(request) {
@@ -15803,12 +15875,12 @@ var SecretsFacade = class {
15803
15875
  if (sql.error) return err(sql.error);
15804
15876
  let rows;
15805
15877
  try {
15806
- rows = await sql.data`
15878
+ rows = await this._getDatabase().runShared((db) => db`
15807
15879
  SELECT version, nonce, ciphertext
15808
15880
  FROM stackbone_platform.secrets
15809
15881
  WHERE name = ${name}
15810
15882
  LIMIT 1
15811
- `;
15883
+ `);
15812
15884
  } catch (cause) {
15813
15885
  return err({
15814
15886
  code: "secrets_unavailable",
@@ -15854,11 +15926,11 @@ var SecretsFacade = class {
15854
15926
  if (sql.error) return err(sql.error);
15855
15927
  let rows;
15856
15928
  try {
15857
- rows = await sql.data`
15929
+ rows = await this._getDatabase().runShared((db) => db`
15858
15930
  SELECT name, version, nonce, ciphertext
15859
15931
  FROM stackbone_platform.secrets
15860
15932
  WHERE name = ANY(${names})
15861
- `;
15933
+ `);
15862
15934
  } catch (cause) {
15863
15935
  return err({
15864
15936
  code: "secrets_unavailable",
@@ -17267,9 +17339,7 @@ var STORED_TO_SDK_ENV = {
17267
17339
  S3_REGION: "STACKBONE_S3_REGION",
17268
17340
  // OpenRouter (`client.ai`) — identity.
17269
17341
  OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
17270
- OPENROUTER_BASE_URL: "OPENROUTER_BASE_URL",
17271
- // Axiom (observability) — identity.
17272
- AXIOM_TOKEN: "AXIOM_TOKEN"
17342
+ OPENROUTER_BASE_URL: "OPENROUTER_BASE_URL"
17273
17343
  };
17274
17344
  function rehydrateSystemSecretsRows(env, rows, cipher, databaseUrl) {
17275
17345
  const applied = [];