deepspace 0.3.2 → 0.3.3

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/dist/worker.d.ts CHANGED
@@ -2832,8 +2832,9 @@ declare const COST_RATES: {
2832
2832
  * `[[d1_databases]]` bindings.
2833
2833
  *
2834
2834
  * The auto-provisioner gives apps an empty D1; the app needs to create its
2835
- * own tables before using them. This helper runs ordered SQL fragments under
2836
- * a `PRAGMA user_version` gate so re-running is a no-op:
2835
+ * own tables before using them. This helper runs ordered SQL fragments and
2836
+ * tracks which have applied via a `_dpc_migrations` meta-table so re-running
2837
+ * is a no-op:
2837
2838
  *
2838
2839
  * ```ts
2839
2840
  * import { runMigrations } from 'deepspace/worker'
@@ -2844,16 +2845,22 @@ declare const COST_RATES: {
2844
2845
  * ])
2845
2846
  * ```
2846
2847
  *
2847
- * Each entry in the array is one migration. The runner reads the database's
2848
- * current `user_version`, applies migrations from that index onward, and
2849
- * advances the version. Adding a new migration means appending to the array;
2850
- * never reorder or delete entries.
2848
+ * Each entry in the array is one migration. The runner records the index of
2849
+ * each successfully-applied migration in `_dpc_migrations`; subsequent calls
2850
+ * skip rows already recorded. Adding a new migration means appending to the
2851
+ * array; never reorder or delete entries.
2852
+ *
2853
+ * Why a meta-table instead of `PRAGMA user_version`: D1's SQLite authorizer
2854
+ * rejects PRAGMA writes with `SQLITE_AUTH`, even though the same statements
2855
+ * work in raw SQLite. A real table works on any D1 database and stays a
2856
+ * trivial bootstrap (one CREATE TABLE IF NOT EXISTS).
2851
2857
  *
2852
2858
  * Concurrency: D1 serializes statements per database, but two simultaneous
2853
- * `runMigrations` callers could race on the read-then-write of `user_version`.
2854
- * In practice apps invoke this at module/DO startup which is single-threaded
2855
- * per worker isolate; the worst case across isolates is a CREATE TABLE
2856
- * IF NOT EXISTS-equivalent failure — caught and reported with context.
2859
+ * `runMigrations` callers could race the same migration index. The duplicate
2860
+ * INSERT collides on the primary key and the second caller sees the failure
2861
+ * — but the migration itself uses `IF NOT EXISTS` so the schema is correct
2862
+ * either way. Apps invoke this at startup which is single-threaded per
2863
+ * worker isolate; the cross-isolate race is rare and self-healing.
2857
2864
  *
2858
2865
  * This is the simplest possible migration story (option 1 in
2859
2866
  * docs/proposals/binding-auto-provisioning.md). Apps that outgrow it can
@@ -2861,7 +2868,7 @@ declare const COST_RATES: {
2861
2868
  * helper.
2862
2869
  */
2863
2870
  interface RunMigrationsResult {
2864
- /** Version before this run started. */
2871
+ /** Version before this run started. Equals the count of migrations already applied. */
2865
2872
  fromVersion: number;
2866
2873
  /** Version after migrations applied. Equals fromVersion if nothing ran. */
2867
2874
  toVersion: number;
@@ -2872,8 +2879,8 @@ interface RunMigrationsResult {
2872
2879
  * Apply ordered SQL migrations to a D1 database. Idempotent: the next call
2873
2880
  * with the same array is a no-op until the array grows.
2874
2881
  *
2875
- * Throws on any individual migration failure. The user_version is only
2876
- * advanced after a migration succeeds, so a partial failure leaves a
2882
+ * Throws on any individual migration failure. The migrations meta-row is
2883
+ * only inserted after a migration succeeds, so a partial failure leaves a
2877
2884
  * recoverable state — fix the SQL, redeploy, and the failed migration runs
2878
2885
  * on next startup.
2879
2886
  */
package/dist/worker.js CHANGED
@@ -6117,9 +6117,15 @@ var COST_RATES = {
6117
6117
  };
6118
6118
 
6119
6119
  // src/server/utils/d1-migrations.ts
6120
+ var META_TABLE = "_dpc_migrations";
6121
+ var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (
6122
+ idx INTEGER PRIMARY KEY,
6123
+ applied_at TEXT NOT NULL
6124
+ );`;
6120
6125
  async function runMigrations(db, migrations) {
6121
- const versionRow = await db.prepare("PRAGMA user_version").first();
6122
- const fromVersion = versionRow?.user_version ?? 0;
6126
+ await db.exec(META_BOOTSTRAP_SQL);
6127
+ const row = await db.prepare(`SELECT COUNT(*) AS n FROM ${META_TABLE}`).first();
6128
+ const fromVersion = row?.n ?? 0;
6123
6129
  if (fromVersion >= migrations.length) {
6124
6130
  return { fromVersion, toVersion: fromVersion, applied: 0 };
6125
6131
  }
@@ -6130,10 +6136,10 @@ async function runMigrations(db, migrations) {
6130
6136
  await db.exec(sql);
6131
6137
  } catch (err) {
6132
6138
  const msg = err instanceof Error ? err.message : String(err);
6133
- throw new Error(`Migration ${i} failed (db at user_version=${current}): ${msg}`);
6139
+ throw new Error(`Migration ${i} failed (db at version=${current}): ${msg}`);
6134
6140
  }
6141
+ await db.prepare(`INSERT INTO ${META_TABLE} (idx, applied_at) VALUES (?, ?)`).bind(i, (/* @__PURE__ */ new Date()).toISOString()).run();
6135
6142
  current = i + 1;
6136
- await db.exec(`PRAGMA user_version = ${current}`);
6137
6143
  }
6138
6144
  return { fromVersion, toVersion: current, applied: current - fromVersion };
6139
6145
  }