cursedops 0.13.0 → 0.13.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedops",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "The build-and-ops answers this generation's apps wrote independently and identically: finding a generation's roots — and printing a command that runs when pasted — without knowing a path, the generation's whole-tree laws run over one repo from a checkout or a worktree, macOS launchd agent install/replace/remove and the live port a job serves, the scaffolding and verdicts of a deployed smoke (origin probe, the smoke's own environment, a network that lies about DNS, a settled version), the static-serving helpers eight apps copied — the path-traversal guard among them — the API floor that keeps an unmatched /api/... from ever being answered with the app shell, the commit and dirty flag a checkout-served process reports, the Cloudflare Worker deploy toolkit four apps copied (the deploy sequence, exact-set secrets over a pipe, origin-first rollback, the curl edge fetch, the row-for-row D1 import proof, the billed-CPU tail check around a deploy's walk and smoke, and each app's worker:secrets and worker:smoke main as one function of its data), the relay a Worker fronts a Mac-bound app with (the Durable Object, the frames, the Mac's dialer and key rotation — lifted from station for roms — and the signed-in stage walk's skeleton and the relay app's whole worker:deploy), the Worker import-graph and await-port checks every Worker app's suite runs over its own source, and the public-surface ratchet three published libraries each carried a forked copy of. Mechanism only — no app knows its name from here. Bun, zero runtime dependencies (typescript is an optional peer, for public-surface only), ships source.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/d1Columns.ts CHANGED
@@ -58,21 +58,26 @@ export function schemaTables(schemaSql: string): SchemaTable[] {
58
58
  }
59
59
 
60
60
  /**
61
- * The read: `SELECT '<t>' AS t, name FROM pragma_table_info('<t>') UNION ALL …`, packed into as few
62
- * queries as `maxBytes` allows — one wrangler call for a whole schema, not one per table.
61
+ * The read: `SELECT m.name AS t, p.name AS name FROM sqlite_master m JOIN pragma_table_info(m.name) p
62
+ * WHERE m.type = 'table' AND m.name IN ('<t>', …)`, packed into as few queries as `maxBytes` allows —
63
+ * one wrangler call for a whole schema, not one per table.
64
+ *
65
+ * 🔴 Never a `… UNION ALL …` with a term per table: D1 refuses a compound SELECT with many terms
66
+ * (`too many terms in compound SELECT`, 2026-10-01 — 0.13.0's read stopped six apps' ships).
63
67
  */
64
68
  export function liveColumnsQueries(tables: readonly SchemaTable[], maxBytes: number): string[] {
69
+ const head = "SELECT m.name AS t, p.name AS name FROM sqlite_master m JOIN pragma_table_info(m.name) p WHERE m.type = 'table' AND m.name IN (";
70
+ const query = (names: string[]) => `${head}${names.join(", ")});`;
65
71
  const queries: string[] = [];
66
- let current = "";
72
+ let current: string[] = [];
67
73
  for (const { table } of tables) {
68
- const part = `SELECT '${table}' AS t, name FROM pragma_table_info('${table}')`;
69
- const next = current ? `${current} UNION ALL ${part}` : part;
70
- if (current && Buffer.byteLength(`${next};`) > maxBytes) {
71
- queries.push(`${current};`);
72
- current = part;
73
- } else current = next;
74
+ const name = `'${table}'`;
75
+ if (current.length > 0 && Buffer.byteLength(query([...current, name])) > maxBytes) {
76
+ queries.push(query(current));
77
+ current = [name];
78
+ } else current.push(name);
74
79
  }
75
- if (current) queries.push(`${current};`);
80
+ if (current.length > 0) queries.push(query(current));
76
81
  return queries;
77
82
  }
78
83
 
@@ -168,14 +168,40 @@ export interface ApiResult {
168
168
 
169
169
  export type CloudflareApi = (path: string, init?: RequestInit) => Promise<ApiResult>;
170
170
 
171
- /** A Cloudflare v4 API caller for `token`. `fetch` injectable for tests. */
172
- export function cloudflareApi(token: string, send: typeof fetch = fetch): CloudflareApi {
171
+ /** Tries per idempotent (GET/DELETE) call whose fetch THROWS — see {@link cloudflareApi}. */
172
+ export const API_IDEMPOTENT_ATTEMPTS = 3;
173
+
174
+ /**
175
+ * A Cloudflare v4 API caller for `token`. `fetch` and the backoff `pause` injectable for tests.
176
+ *
177
+ * 🔴 **A GET or DELETE whose fetch throws is tried again, up to {@link API_IDEMPOTENT_ATTEMPTS}
178
+ * times.** On 2026-10-04 (ship run 16-39-21) desk-stage and collections-stage both died in the
179
+ * same second on the first secrets list — `The socket connection was closed unexpectedly` — and
180
+ * were left uploaded with no secrets, serving nothing. Asking again changes nothing a GET or DELETE
181
+ * did not already change. An HTTP status is an ANSWER and is returned as-is; a thrown POST/PUT/PATCH
182
+ * is never retried, because it may have landed.
183
+ */
184
+ export function cloudflareApi(
185
+ token: string,
186
+ send: typeof fetch = fetch,
187
+ pause: (ms: number) => Promise<void> = (ms) => new Promise<void>((resolve) => setTimeout(resolve, ms)),
188
+ ): CloudflareApi {
173
189
  return async (path, init = {}) => {
174
- const response = await send(`https://api.cloudflare.com/client/v4${path}`, {
175
- ...init,
176
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
177
- signal: init.signal ?? AbortSignal.timeout(30_000),
178
- });
190
+ const method = (init.method ?? "GET").toUpperCase();
191
+ const attempts = method === "GET" || method === "DELETE" ? API_IDEMPOTENT_ATTEMPTS : 1;
192
+ let response: Response | undefined;
193
+ for (let tries = 1; !response; tries++) {
194
+ try {
195
+ response = await send(`https://api.cloudflare.com/client/v4${path}`, {
196
+ ...init,
197
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
198
+ signal: init.signal ?? AbortSignal.timeout(30_000),
199
+ });
200
+ } catch (error) {
201
+ if (tries >= attempts || init.signal?.aborted) throw error;
202
+ await pause(1_000 * tries);
203
+ }
204
+ }
179
205
  return (await response.json().catch(() => ({ success: false, errors: [`HTTP ${response.status}, not JSON`] }))) as ApiResult;
180
206
  };
181
207
  }