cursedbelt-server 4.17.0 → 4.17.1

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.
@@ -139,6 +139,16 @@ export async function pullD1ToSqlite(options) {
139
139
  // `exec`, not `run`: each is many statements and `run` would execute only the first,
140
140
  // leaving a file with a schema and no rows that looks perfectly valid.
141
141
  db.exec(options.schemaSql);
142
+ // 🔴 A table D1 holds that the schema does not declare is created from D1's OWN DDL, never
143
+ // dropped: measured on music's first post-cutover pull (2026-09-23), `art_lookups` — a
144
+ // cache its art job creates where it first needs it — made the whole pull refuse with
145
+ // `no such table`, because its rows were exported and its table was not in the schema.
146
+ const have = new Set(db.query("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((r) => r.name));
147
+ for (const row of rows) {
148
+ if (!tables.includes(row.name) || have.has(row.name) || !row.sql)
149
+ continue;
150
+ db.exec(row.sql.replace(/^\s*CREATE\s+TABLE\s+(?!IF\s+NOT\s+EXISTS)/i, 'CREATE TABLE IF NOT EXISTS '));
151
+ }
142
152
  db.exec(sql);
143
153
  if (options.afterLoadSql)
144
154
  db.exec(options.afterLoadSql);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-server",
3
- "version": "4.17.0",
3
+ "version": "4.17.1",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
@@ -144,6 +144,26 @@ describe('pulling D1 down — table by table, because a whole-database export re
144
144
  expect(hits.n).toBe(1);
145
145
  });
146
146
 
147
+ test('🔴 a table D1 holds that the schema does not declare is created from D1\'s own DDL', async () => {
148
+ const dir = scratch();
149
+ const master = [...MASTER, { name: 'art_lookups', sql: 'CREATE TABLE art_lookups (key TEXT PRIMARY KEY, art_key TEXT)' }];
150
+ const run: Wrangler = (args) => {
151
+ if (args[1] === 'execute') return { code: 0, stdout: JSON.stringify([{ results: master }]), stderr: '' };
152
+ const table = args[args.indexOf('--table') + 1] as string;
153
+ const out = args[args.indexOf('--output') + 1] as string;
154
+ writeFileSync(
155
+ out,
156
+ table === 'items' ? "INSERT INTO items (id) VALUES ('i1');" : table === 'art_lookups' ? "INSERT INTO art_lookups VALUES ('k', 'art/1');" : '',
157
+ );
158
+ return { code: 0, stdout: '', stderr: '' };
159
+ };
160
+ await pull(dir, run);
161
+ const db = new Database(join(dir, 'pulled.sqlite'), { readonly: true });
162
+ const row = db.query('select art_key from art_lookups').get() as { art_key: string } | null;
163
+ db.close();
164
+ expect(row?.art_key).toBe('art/1');
165
+ });
166
+
147
167
  test('🔴 a database without the required table is refused as the wrong one', async () => {
148
168
  const dir = scratch();
149
169
  const { run } = fake(() => '');
@@ -176,6 +176,17 @@ export async function pullD1ToSqlite(options: PullD1Options): Promise<void> {
176
176
  // `exec`, not `run`: each is many statements and `run` would execute only the first,
177
177
  // leaving a file with a schema and no rows that looks perfectly valid.
178
178
  db.exec(options.schemaSql);
179
+ // 🔴 A table D1 holds that the schema does not declare is created from D1's OWN DDL, never
180
+ // dropped: measured on music's first post-cutover pull (2026-09-23), `art_lookups` — a
181
+ // cache its art job creates where it first needs it — made the whole pull refuse with
182
+ // `no such table`, because its rows were exported and its table was not in the schema.
183
+ const have = new Set(
184
+ (db.query("SELECT name FROM sqlite_master WHERE type = 'table'").all() as { name: string }[]).map((r) => r.name),
185
+ );
186
+ for (const row of rows) {
187
+ if (!tables.includes(row.name) || have.has(row.name) || !row.sql) continue;
188
+ db.exec(row.sql.replace(/^\s*CREATE\s+TABLE\s+(?!IF\s+NOT\s+EXISTS)/i, 'CREATE TABLE IF NOT EXISTS '));
189
+ }
179
190
  db.exec(sql);
180
191
  if (options.afterLoadSql) db.exec(options.afterLoadSql);
181
192
  } finally {