@scalar/workspace-store 0.47.0 → 0.48.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 (38) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +23 -4
  4. package/dist/entities/auth/schema.d.ts +20 -0
  5. package/dist/entities/auth/schema.d.ts.map +1 -1
  6. package/dist/events/bus.d.ts +14 -0
  7. package/dist/events/bus.d.ts.map +1 -1
  8. package/dist/events/bus.js +8 -0
  9. package/dist/events/definitions/ui.d.ts +2 -2
  10. package/dist/events/definitions/ui.d.ts.map +1 -1
  11. package/dist/persistence/index.d.ts +23 -21
  12. package/dist/persistence/index.d.ts.map +1 -1
  13. package/dist/persistence/index.js +37 -35
  14. package/dist/persistence/indexdb.d.ts +69 -49
  15. package/dist/persistence/indexdb.d.ts.map +1 -1
  16. package/dist/persistence/indexdb.js +100 -123
  17. package/dist/persistence/migrations/v1-initial.d.ts +19 -0
  18. package/dist/persistence/migrations/v1-initial.d.ts.map +1 -0
  19. package/dist/persistence/migrations/v1-initial.js +50 -0
  20. package/dist/persistence/migrations/v2-team-to-local.d.ts +33 -0
  21. package/dist/persistence/migrations/v2-team-to-local.d.ts.map +1 -0
  22. package/dist/persistence/migrations/v2-team-to-local.js +213 -0
  23. package/dist/schemas/extensions/document/x-scalar-registry-meta.d.ts +48 -0
  24. package/dist/schemas/extensions/document/x-scalar-registry-meta.d.ts.map +1 -1
  25. package/dist/schemas/extensions/document/x-scalar-registry-meta.js +33 -1
  26. package/dist/schemas/inmemory-workspace.d.ts +12 -0
  27. package/dist/schemas/inmemory-workspace.d.ts.map +1 -1
  28. package/dist/schemas/reference-config/index.d.ts +4 -0
  29. package/dist/schemas/reference-config/index.d.ts.map +1 -1
  30. package/dist/schemas/reference-config/settings.d.ts +4 -0
  31. package/dist/schemas/reference-config/settings.d.ts.map +1 -1
  32. package/dist/schemas/v3.1/openapi/index.d.ts +4 -0
  33. package/dist/schemas/v3.1/openapi/index.d.ts.map +1 -1
  34. package/dist/schemas/v3.1/strict/openapi-document.d.ts +144 -0
  35. package/dist/schemas/v3.1/strict/openapi-document.d.ts.map +1 -1
  36. package/dist/schemas/workspace.d.ts +12 -0
  37. package/dist/schemas/workspace.d.ts.map +1 -1
  38. package/package.json +7 -7
@@ -1,70 +1,90 @@
1
1
  import type { Static, TObject } from '@scalar/typebox';
2
+ /**
3
+ * Declarative shape for a table at its CURRENT (latest) version.
4
+ *
5
+ * This is used purely for TypeScript typing and runtime key serialization in
6
+ * the wrapper API returned by `get(name)`. IndexedDB schema (object stores,
7
+ * keyPaths, indexes) is NOT derived from this config — every schema change is
8
+ * expressed in a migration. That keeps fresh installs and upgraded installs on
9
+ * exactly the same code path and makes the TypeScript types safe to evolve
10
+ * without accidentally reshaping the underlying database.
11
+ */
2
12
  type TableEntry<S extends TObject, K extends readonly (keyof Static<S>)[]> = {
3
13
  schema: S;
4
14
  keyPath: K;
5
- indexes?: Record<string, readonly (keyof Static<S>)[]>;
6
15
  };
7
16
  /**
8
- * Initializes and manages an IndexedDB database connection for table-based persistence.
17
+ * Context passed to every migration. The upgrade `transaction` lives for as
18
+ * long as any IDB request on it is pending, so migrations may schedule async
19
+ * cursor / getAll work and still mutate the same transaction afterwards.
20
+ */
21
+ export type MigrationContext = {
22
+ db: IDBDatabase;
23
+ transaction: IDBTransaction;
24
+ oldVersion: number;
25
+ newVersion: number;
26
+ };
27
+ /**
28
+ * A single, atomic schema (and/or data) change.
9
29
  *
10
- * @param name - The database name. Defaults to 'scalar-workspace-store'.
11
- * @param tables - Table definitions: the tables to create and their key schemas.
12
- * @param version - The database version. Bump this to trigger upgrades (default: 1).
13
- * @param migrations - Optional migration steps to run for version upgrades.
14
- * @returns An object with the following methods:
15
- * - `get(tableName)` — Get a wrapper to interact with the object store for the given table name.
16
- * - `closeDatabase()` — Closes the database connection.
30
+ * Every structural change to the database creating an object store, adding
31
+ * or removing an index, renaming a field, re-keying records lives inside a
32
+ * migration. Fresh installs run the full chain from v1 up; existing installs
33
+ * run only the migrations whose position is past their current version.
17
34
  *
18
- * Example usage:
19
- * ```ts
20
- * import { Type } from '@scalar/typebox'
21
- * import { createIndexDbConnection } from './indexdb.js'
35
+ * The version of a migration is its 1-based position in the `migrations`
36
+ * array passed to `createIndexDbConnection` — there is no `version` field to
37
+ * keep in sync. Append to the end to add a new migration; never reorder or
38
+ * insert in the middle (each position represents a real schema state that
39
+ * shipped to users).
22
40
  *
23
- * // Define a schema for a user
24
- * const UserSchema = Type.Object({
25
- * id: Type.String(),
26
- * name: Type.String(),
27
- * age: Type.Number(),
28
- * })
41
+ * Migrations may run synchronously, or may return a Promise when they need
42
+ * to read existing data (via `getAll`, cursors, ...) before performing schema
43
+ * changes. The runner awaits each migration before starting the next, so a
44
+ * later migration always observes the fully-applied state of every earlier
45
+ * migration. To keep the upgrade transaction alive across awaits, every async
46
+ * migration must queue at least one IDB request before yielding.
47
+ */
48
+ export type Migration = {
49
+ /** Short human-readable summary surfaced in errors / logs. */
50
+ description?: string;
51
+ /** Runs inside the upgrade transaction. May be sync or async. */
52
+ up: (context: MigrationContext) => void | Promise<void>;
53
+ };
54
+ /**
55
+ * Initializes and manages an IndexedDB database connection for table-based persistence.
29
56
  *
30
- * // Define tables in the database
31
- * const dbConfig = {
32
- * users: {
33
- * schema: UserSchema,
34
- * index: ['id'] as const,
35
- * },
36
- * }
57
+ * The database version is derived from `migrations.length`, so callers cannot
58
+ * accidentally drift between the declared version and the migrations that
59
+ * define it. Every structural change — including the initial schema — must
60
+ * be expressed as a migration; append new ones to the end of the array.
37
61
  *
38
- * // Open the database connection and get table API
39
- * const { get, closeDatabase } = await createIndexDbConnection({
62
+ * Example:
63
+ * ```ts
64
+ * const connection = await createIndexDbConnection({
40
65
  * name: 'my-app-db',
41
- * tables: dbConfig,
42
- * version: 1,
66
+ * tables: {
67
+ * users: { schema: UserSchema, keyPath: ['id'] as const },
68
+ * },
69
+ * migrations: [
70
+ * {
71
+ * description: 'Initial schema',
72
+ * up: ({ db }) => {
73
+ * if (!db.objectStoreNames.contains('users')) {
74
+ * db.createObjectStore('users', { keyPath: 'id' })
75
+ * }
76
+ * },
77
+ * },
78
+ * ],
43
79
  * })
44
- *
45
- * // Get a strongly-typed users table API
46
- * const usersTable = get('users')
47
- *
48
- * // Add a user
49
- * await usersTable.addItem({ id: 'user-1' }, { name: 'Alice', age: 25 })
50
- *
51
- * // Retrieve a user by id
52
- * const user = await usersTable.getItem({ id: 'user-1' })
53
- *
54
- * // Don't forget to close the database when done!
55
- * closeDatabase()
56
80
  * ```
57
81
  */
58
- export declare const createIndexDbConnection: <T extends Record<string, TableEntry<any, readonly (keyof any)[]>>>({ name, tables, version, migrations, }: {
82
+ export declare const createIndexDbConnection: <T extends Record<string, TableEntry<any, readonly (keyof any)[]>>>({ name, tables, migrations, }: {
59
83
  name: string;
60
84
  tables: T;
61
- version: number;
62
- migrations?: {
63
- version: number;
64
- exec: (db: IDBDatabase, event: IDBVersionChangeEvent) => {};
65
- }[];
85
+ migrations: readonly Migration[];
66
86
  }) => Promise<{
67
- get: <Name extends keyof T>(name: Name) => {
87
+ get: <Name extends keyof T>(tableName: Name) => {
68
88
  addItem: (key: Record<T[Name]["keyPath"][number], IDBValidKey>, value: Omit<(T[Name]["schema"] & {
69
89
  params: [];
70
90
  })["static"], T[Name]["keyPath"][number]>) => Promise<(T[Name]["schema"] & {
@@ -1 +1 @@
1
- {"version":3,"file":"indexdb.d.ts","sourceRoot":"","sources":["../../src/persistence/indexdb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAW,MAAM,iBAAiB,CAAA;AAE/D,KAAK,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,SAAS,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI;IAC3E,MAAM,EAAE,CAAC,CAAA;IACT,OAAO,EAAE,CAAC,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;CACvD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,uBAAuB,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,wCAK9G;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,CAAC,CAAA;IACT,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,qBAAqB,KAAK,EAAE,CAAA;KAAE,EAAE,CAAA;CAChG;UAqCS,IAAI,SAAS,MAAM,CAAC,QAAQ,IAAI;;;;;;;;;+BA8FV,WAAW,EAAE,cAAc,MAAM;;;8EA+BP,OAAO,CAAC,IAAI,CAAC;kCAmBpC,WAAW,EAAE,KAAG,OAAO,CAAC,MAAM,CAAC;;;;yBA6BpC,OAAO,CAAC,IAAI,CAAC;;;EAtK1C,CAAA"}
1
+ {"version":3,"file":"indexdb.d.ts","sourceRoot":"","sources":["../../src/persistence/indexdb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAW,MAAM,iBAAiB,CAAA;AAE/D;;;;;;;;;GASG;AACH,KAAK,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,SAAS,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI;IAC3E,MAAM,EAAE,CAAC,CAAA;IACT,OAAO,EAAE,CAAC,CAAA;CACX,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,WAAW,CAAA;IACf,WAAW,EAAE,cAAc,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iEAAiE;IACjE,EAAE,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,uBAAuB,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,+BAI9G;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,CAAC,CAAA;IACT,UAAU,EAAE,SAAS,SAAS,EAAE,CAAA;CACjC;UAuFS,IAAI,SAAS,MAAM,CAAC,aAAa,IAAI;;;;;;;;;+BAiEf,WAAW,EAAE,cAAc,MAAM;;;8EA6BP,OAAO,CAAC,IAAI,CAAC;kCAWpC,WAAW,EAAE,KAAG,OAAO,CAAC,MAAM,CAAC;;;;yBA4BpC,OAAO,CAAC,IAAI,CAAC;;;EAxH1C,CAAA"}
@@ -1,127 +1,123 @@
1
1
  /**
2
2
  * Initializes and manages an IndexedDB database connection for table-based persistence.
3
3
  *
4
- * @param name - The database name. Defaults to 'scalar-workspace-store'.
5
- * @param tables - Table definitions: the tables to create and their key schemas.
6
- * @param version - The database version. Bump this to trigger upgrades (default: 1).
7
- * @param migrations - Optional migration steps to run for version upgrades.
8
- * @returns An object with the following methods:
9
- * - `get(tableName)` — Get a wrapper to interact with the object store for the given table name.
10
- * - `closeDatabase()` — Closes the database connection.
4
+ * The database version is derived from `migrations.length`, so callers cannot
5
+ * accidentally drift between the declared version and the migrations that
6
+ * define it. Every structural change including the initial schema must
7
+ * be expressed as a migration; append new ones to the end of the array.
11
8
  *
12
- * Example usage:
9
+ * Example:
13
10
  * ```ts
14
- * import { Type } from '@scalar/typebox'
15
- * import { createIndexDbConnection } from './indexdb.js'
16
- *
17
- * // Define a schema for a user
18
- * const UserSchema = Type.Object({
19
- * id: Type.String(),
20
- * name: Type.String(),
21
- * age: Type.Number(),
22
- * })
23
- *
24
- * // Define tables in the database
25
- * const dbConfig = {
26
- * users: {
27
- * schema: UserSchema,
28
- * index: ['id'] as const,
29
- * },
30
- * }
31
- *
32
- * // Open the database connection and get table API
33
- * const { get, closeDatabase } = await createIndexDbConnection({
11
+ * const connection = await createIndexDbConnection({
34
12
  * name: 'my-app-db',
35
- * tables: dbConfig,
36
- * version: 1,
13
+ * tables: {
14
+ * users: { schema: UserSchema, keyPath: ['id'] as const },
15
+ * },
16
+ * migrations: [
17
+ * {
18
+ * description: 'Initial schema',
19
+ * up: ({ db }) => {
20
+ * if (!db.objectStoreNames.contains('users')) {
21
+ * db.createObjectStore('users', { keyPath: 'id' })
22
+ * }
23
+ * },
24
+ * },
25
+ * ],
37
26
  * })
38
- *
39
- * // Get a strongly-typed users table API
40
- * const usersTable = get('users')
41
- *
42
- * // Add a user
43
- * await usersTable.addItem({ id: 'user-1' }, { name: 'Alice', age: 25 })
44
- *
45
- * // Retrieve a user by id
46
- * const user = await usersTable.getItem({ id: 'user-1' })
47
- *
48
- * // Don't forget to close the database when done!
49
- * closeDatabase()
50
27
  * ```
51
28
  */
52
- export const createIndexDbConnection = async ({ name = 'scalar-workspace-store', tables, version = 1, migrations = [], }) => {
53
- const db = indexedDB.open(name, version);
54
- db.onupgradeneeded = (e) => {
55
- // Initial setup of object stores
56
- if (e.oldVersion < 1) {
57
- const database = db.result;
58
- // Initialize all the tables
59
- Object.entries(tables).forEach(([name, options]) => {
60
- if (!database.objectStoreNames.contains(name)) {
61
- const objectStore = database.createObjectStore(name, {
62
- keyPath: options.keyPath.length === 1 ? options.keyPath[0] : options.keyPath,
63
- });
64
- // Create any indexes for the object store
65
- Object.entries(options.indexes ?? {}).forEach(([indexName, indexPath]) => {
66
- objectStore.createIndex(indexName, indexPath);
67
- });
68
- }
69
- });
29
+ export const createIndexDbConnection = async ({ name = 'scalar-workspace-store', tables, migrations, }) => {
30
+ if (migrations.length === 0) {
31
+ throw new Error(`createIndexDbConnection("${name}"): at least one migration is required. The initial schema must be defined as the first migration.`);
32
+ }
33
+ // The 1-based array position is the schema version. `migrations[0]` is v1,
34
+ // `migrations[1]` is v2, and so on. The latest version is just the length.
35
+ const latestVersion = migrations.length;
36
+ const request = indexedDB.open(name, latestVersion);
37
+ // Captured here so the descriptive error from a failing migration can be
38
+ // surfaced through the `open` promise instead of the generic IDB
39
+ // `AbortError` that follows `transaction.abort()`.
40
+ let migrationError;
41
+ request.onupgradeneeded = (event) => {
42
+ const transaction = request.transaction;
43
+ if (!transaction) {
44
+ // IDB always provides the upgrade transaction here; this is a guard for
45
+ // exotic environments and keeps types honest.
46
+ return;
70
47
  }
71
- // Run any future migrations here
72
- migrations.forEach((migration) => {
73
- if (e.oldVersion < migration.version) {
74
- migration.exec(db.result, e);
48
+ const context = {
49
+ db: request.result,
50
+ transaction,
51
+ oldVersion: event.oldVersion,
52
+ newVersion: event.newVersion ?? latestVersion,
53
+ };
54
+ // Run pending migrations sequentially, awaiting any async work before
55
+ // starting the next one. This is important when a migration reads
56
+ // existing data via IDB requests (e.g. `getAll`) and only performs the
57
+ // real schema changes inside the request callback — the next migration
58
+ // would otherwise execute against the pre-migration state.
59
+ //
60
+ // The upgrade transaction stays alive across awaits because every async
61
+ // migration in the codebase queues at least one IDB request before
62
+ // yielding, and microtasks complete before IDB checks for transaction
63
+ // commit at the next task boundary.
64
+ const runMigrations = async () => {
65
+ for (const [index, migration] of migrations.entries()) {
66
+ const version = index + 1;
67
+ if (version <= event.oldVersion) {
68
+ continue;
69
+ }
70
+ try {
71
+ await migration.up(context);
72
+ }
73
+ catch (error) {
74
+ const label = migration.description ? `v${version} (${migration.description})` : `v${version}`;
75
+ throw new Error(`Migration ${label} failed: ${error?.message ?? error}`, { cause: error });
76
+ }
77
+ }
78
+ };
79
+ runMigrations().catch((error) => {
80
+ migrationError = error;
81
+ // Abort the upgrade transaction so we do not leave the DB in a half-
82
+ // migrated state. Aborting fires `request.onerror`; the captured
83
+ // `migrationError` takes precedence over the resulting `AbortError`.
84
+ try {
85
+ transaction.abort();
86
+ }
87
+ catch {
88
+ // The transaction may already be in a finished state (e.g. when the
89
+ // failing migration itself triggered an abort). Nothing to do.
75
90
  }
76
91
  });
77
92
  };
78
93
  await new Promise((resolve, reject) => {
79
- db.onsuccess = () => resolve(true);
80
- db.onerror = () => reject(db.error);
94
+ request.onsuccess = () => resolve(true);
95
+ request.onerror = () => reject(migrationError ?? request.error);
96
+ // If another tab holds an older-version connection open we would otherwise
97
+ // hang forever waiting for the upgrade. Surface it as a clear rejection so
98
+ // the app can react (reload, notify the user, ...) instead of freezing.
99
+ request.onblocked = () => reject(new Error(`IndexedDB upgrade for "${name}" is blocked by another open connection. Close other tabs and try again.`));
81
100
  });
82
101
  return {
83
- get: (name) => {
84
- return createTableWrapper(name, db.result);
102
+ get: (tableName) => {
103
+ // Surface a helpful error if a caller asks for a table that is not in
104
+ // the typed config — the underlying IDB call would otherwise throw a
105
+ // generic `NotFoundError` from a lazy `transaction()`.
106
+ if (!Object.hasOwn(tables, tableName)) {
107
+ throw new Error(`Unknown table "${String(tableName)}". Add it to the \`tables\` config of "${name}".`);
108
+ }
109
+ return createTableWrapper(tableName, request.result);
85
110
  },
86
111
  closeDatabase: () => {
87
- db.result.close();
112
+ request.result.close();
88
113
  },
89
114
  };
90
115
  };
91
116
  /**
92
117
  * Utility wrapper for interacting with an IndexedDB object store, typed by the schema.
93
118
  *
94
- * Usage example:
95
- * ```
96
- * // Define a TypeBox schema for users
97
- * const UserSchema = Type.Object({
98
- * id: Type.String(),
99
- * name: Type.String(),
100
- * age: Type.Number(),
101
- * })
102
- *
103
- * // Open or create the users table
104
- * const usersTable = createTableWrapper<typeof UserSchema, 'id'>('users', openDatabase)
105
- *
106
- * // Add a user
107
- await usersTable.addItem({ id: 'user-1' }, { name: 'Alice', age: 24 })
108
- *
109
- * // Get a user by id
110
- * const alic = await usersTable.getItem({ id: 'user-1' })
111
- *
112
- * // Get users with a partial key (use [] if no composite key)
113
- * const users = await usersTable.getRange(['user-1'])
114
- *
115
- * // Get all users
116
- * const allUsers = await usersTable.getAll()
117
- * ```
118
- *
119
119
  * @template T TypeBox schema type for objects in the store
120
120
  * @template K Key property names that compose the primary key
121
- *
122
- * @param name - Object store name
123
- * @param getDb - Function returning a Promise for the IDBDatabase
124
- * @returns Methods to interact with the object store
125
121
  */
126
122
  function createTableWrapper(name, db) {
127
123
  /**
@@ -159,12 +155,6 @@ function createTableWrapper(name, db) {
159
155
  * Returns all records matching a partial (prefix) key. Use for composite keys.
160
156
  * For non-compound keys, pass single-element array: getRange(['some-id'])
161
157
  * For prefix search, pass subset of key parts.
162
- * @param partialKey - Array of partial key values
163
- * @returns Matching objects
164
- *
165
- * Example (composite [a,b]):
166
- * getRange(['foo']) // All with a === 'foo'
167
- * getRange(['foo', 'bar']) // All with a === 'foo' and b === 'bar'
168
158
  */
169
159
  function getRange(partialKey, indexName) {
170
160
  const store = getStore('readonly');
@@ -175,9 +165,9 @@ function createTableWrapper(name, db) {
175
165
  upperBound.push([]); // ensures upper bound includes all keys with this prefix
176
166
  const range = IDBKeyRange.bound(partialKey, upperBound, false, true);
177
167
  return new Promise((resolve, reject) => {
178
- const request = objectStoreOrIndex.openCursor(range);
179
- request.onerror = () => reject(request.error);
180
- request.onsuccess = (event) => {
168
+ const req = objectStoreOrIndex.openCursor(range);
169
+ req.onerror = () => reject(req.error);
170
+ req.onsuccess = (event) => {
181
171
  const cursor = event.target.result;
182
172
  if (cursor) {
183
173
  results.push(cursor.value);
@@ -191,8 +181,6 @@ function createTableWrapper(name, db) {
191
181
  }
192
182
  /**
193
183
  * Deletes an item from the store by its composite key.
194
- * @param key - Key values. For a single key: { id: '...' }
195
- * @returns void
196
184
  */
197
185
  async function deleteItem(key) {
198
186
  const store = getStore('readwrite');
@@ -203,14 +191,6 @@ function createTableWrapper(name, db) {
203
191
  }
204
192
  /**
205
193
  * Deletes all records matching a partial (prefix) key. Use for composite keys.
206
- * For non-compound keys, pass single-element array: deleteRange(['some-id'])
207
- * For prefix deletion, pass subset of key parts.
208
- * @param partialKey - Array of partial key values
209
- * @returns Number of deleted items
210
- *
211
- * Example (composite [a,b]):
212
- * deleteRange(['foo']) // Delete all with a === 'foo'
213
- * deleteRange(['foo', 'bar']) // Delete all with a === 'foo' and b === 'bar'
214
194
  */
215
195
  function deleteRange(partialKey) {
216
196
  const store = getStore('readwrite');
@@ -220,9 +200,9 @@ function createTableWrapper(name, db) {
220
200
  upperBound.push([]); // ensures upper bound includes all keys with this prefix
221
201
  const range = IDBKeyRange.bound(partialKey, upperBound, false, true);
222
202
  return new Promise((resolve, reject) => {
223
- const request = store.openCursor(range);
224
- request.onerror = () => reject(request.error);
225
- request.onsuccess = (event) => {
203
+ const req = store.openCursor(range);
204
+ req.onerror = () => reject(req.error);
205
+ req.onsuccess = (event) => {
226
206
  const cursor = event.target.result;
227
207
  if (cursor) {
228
208
  cursor.delete();
@@ -237,7 +217,6 @@ function createTableWrapper(name, db) {
237
217
  }
238
218
  /**
239
219
  * Deletes all items from the table.
240
- * @returns void
241
220
  */
242
221
  async function deleteAll() {
243
222
  const store = getStore('readwrite');
@@ -245,7 +224,6 @@ function createTableWrapper(name, db) {
245
224
  }
246
225
  /**
247
226
  * Retrieves all items from the table.
248
- * @returns Array of all objects in the store
249
227
  */
250
228
  function getAll() {
251
229
  const store = getStore('readonly');
@@ -261,7 +239,6 @@ function createTableWrapper(name, db) {
261
239
  deleteAll,
262
240
  };
263
241
  }
264
- // ---- Utility ----
265
242
  function requestAsPromise(req) {
266
243
  return new Promise((resolve, reject) => {
267
244
  req.onsuccess = () => resolve(req.result);
@@ -0,0 +1,19 @@
1
+ import type { Migration } from '../../persistence/indexdb.js';
2
+ /**
3
+ * v1 — initial schema for the workspace store.
4
+ *
5
+ * This migration defines the database as it first shipped: a `workspace`
6
+ * object store keyed by `[namespace, slug]` with a `teamUid` index, a `meta`
7
+ * store keyed by `workspaceId`, and six per-document chunk stores keyed by
8
+ * `[workspaceId, documentName]`.
9
+ *
10
+ * Every installation — fresh or upgraded — runs this migration. Fresh installs
11
+ * execute the full chain starting here, then each subsequent migration
12
+ * transforms the schema into the latest shape. Upgraded installs are already
13
+ * past v1 and skip it.
14
+ *
15
+ * Intentionally uses the ORIGINAL field names (including `teamUid`); later
16
+ * migrations are free to rename, drop, or reshape them.
17
+ */
18
+ export declare const v1InitialMigration: Migration;
19
+ //# sourceMappingURL=v1-initial.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1-initial.d.ts","sourceRoot":"","sources":["../../../src/persistence/migrations/v1-initial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAmBtD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAoBhC,CAAA"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tables that store per-workspace chunks keyed by `workspaceId` (single key).
3
+ */
4
+ const SINGLE_KEY_CHUNK_TABLES = ['meta'];
5
+ /**
6
+ * Tables that store per-document chunks keyed by `[workspaceId, documentName]`.
7
+ */
8
+ const COMPOSITE_KEY_CHUNK_TABLES = [
9
+ 'documents',
10
+ 'originalDocuments',
11
+ 'intermediateDocuments',
12
+ 'overrides',
13
+ 'history',
14
+ 'auth',
15
+ ];
16
+ /**
17
+ * v1 — initial schema for the workspace store.
18
+ *
19
+ * This migration defines the database as it first shipped: a `workspace`
20
+ * object store keyed by `[namespace, slug]` with a `teamUid` index, a `meta`
21
+ * store keyed by `workspaceId`, and six per-document chunk stores keyed by
22
+ * `[workspaceId, documentName]`.
23
+ *
24
+ * Every installation — fresh or upgraded — runs this migration. Fresh installs
25
+ * execute the full chain starting here, then each subsequent migration
26
+ * transforms the schema into the latest shape. Upgraded installs are already
27
+ * past v1 and skip it.
28
+ *
29
+ * Intentionally uses the ORIGINAL field names (including `teamUid`); later
30
+ * migrations are free to rename, drop, or reshape them.
31
+ */
32
+ export const v1InitialMigration = {
33
+ description: 'Initial schema: workspace + chunk tables',
34
+ up: ({ db }) => {
35
+ if (!db.objectStoreNames.contains('workspace')) {
36
+ const workspace = db.createObjectStore('workspace', { keyPath: ['namespace', 'slug'] });
37
+ workspace.createIndex('teamUid', ['teamUid']);
38
+ }
39
+ for (const tableName of SINGLE_KEY_CHUNK_TABLES) {
40
+ if (!db.objectStoreNames.contains(tableName)) {
41
+ db.createObjectStore(tableName, { keyPath: 'workspaceId' });
42
+ }
43
+ }
44
+ for (const tableName of COMPOSITE_KEY_CHUNK_TABLES) {
45
+ if (!db.objectStoreNames.contains(tableName)) {
46
+ db.createObjectStore(tableName, { keyPath: ['workspaceId', 'documentName'] });
47
+ }
48
+ }
49
+ },
50
+ };
@@ -0,0 +1,33 @@
1
+ import type { Migration } from '../../persistence/indexdb.js';
2
+ type WorkspaceRecordV1 = {
3
+ name: string;
4
+ /** Old field — dropped entirely in v2. */
5
+ teamUid?: string;
6
+ namespace: string;
7
+ slug: string;
8
+ };
9
+ type WorkspaceRecordV2 = {
10
+ name: string;
11
+ teamSlug: string;
12
+ slug: string;
13
+ };
14
+ /**
15
+ * Picks a slug that does not collide with anything in `taken`.
16
+ * Falls back to `<slug>-2`, `<slug>-3`, ... when the desired slug is already used.
17
+ */
18
+ export declare const pickUniqueSlug: (desired: string, taken: ReadonlySet<string>) => string;
19
+ /**
20
+ * Computes the new shape for every workspace, preserving local entries under
21
+ * their existing slug and relocating team entries into the local team with a
22
+ * unique slug when needed.
23
+ */
24
+ export declare const planWorkspaceMigration: (workspaces: readonly WorkspaceRecordV1[]) => Array<{
25
+ before: {
26
+ namespace: string;
27
+ slug: string;
28
+ };
29
+ after: WorkspaceRecordV2;
30
+ }>;
31
+ export declare const v2TeamToLocalMigration: Migration;
32
+ export {};
33
+ //# sourceMappingURL=v2-team-to-local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-team-to-local.d.ts","sourceRoot":"","sources":["../../../src/persistence/migrations/v2-team-to-local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AA2CtD,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,OAAO,WAAW,CAAC,MAAM,CAAC,KAAG,MAU5E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,iBAAiB,EAAE,KACvC,KAAK,CAAC;IAAE,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,CAqCjF,CAAA;AAqHD,eAAO,MAAM,sBAAsB,EAAE,SAqCpC,CAAA"}