@orkestrel/indexeddb 0.0.1 → 0.0.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.
@@ -1,3 +1,4 @@
1
+ import { isArray, isRecord } from "@orkestrel/contract";
1
2
  //#region src/browser/constants.ts
2
3
  /**
3
4
  * Native `DOMException.name` → our {@link IndexedDBErrorCode}.
@@ -13,9 +14,11 @@ var ERROR_CODES = Object.freeze({
13
14
  AbortError: "ABORTED",
14
15
  NotFoundError: "NOT_FOUND",
15
16
  DataError: "DATA",
17
+ DataCloneError: "DATA",
16
18
  VersionError: "UPGRADE",
17
19
  TransactionInactiveError: "INACTIVE",
18
- InvalidStateError: "INVALID"
20
+ InvalidStateError: "INVALID",
21
+ ReadOnlyError: "READONLY"
19
22
  });
20
23
  //#endregion
21
24
  //#region src/browser/errors.ts
@@ -54,99 +57,6 @@ var IndexedDBError = class extends Error {
54
57
  function isIndexedDBError(value) {
55
58
  return value instanceof IndexedDBError;
56
59
  }
57
- Object.freeze([
58
- "null",
59
- "boolean",
60
- "object",
61
- "array",
62
- "number",
63
- "integer",
64
- "string"
65
- ]);
66
- /**
67
- * Determine whether a value is a non-null object.
68
- *
69
- * @remarks
70
- * `true` for arrays, class instances, plain objects, `Map`, `Set`, etc. — use
71
- * {@link isRecord} when you need a plain-record check.
72
- */
73
- function isObject(value) {
74
- return typeof value === "object" && value !== null;
75
- }
76
- /**
77
- * Determine whether a value is a plain record (object literal or null-prototype),
78
- * not an array or class instance.
79
- *
80
- * @remarks
81
- * Use instead of {@link isObject} to distinguish a plain `{}` /
82
- * `Object.create(null)` from arrays, `Date`, `Map`, etc. The prototype-chain
83
- * test is realm-agnostic: rather than comparing against the current realm's
84
- * `Object.prototype` (which a plain object from another `vm.Context`, iframe,
85
- * or worker would fail), it accepts any value whose prototype is `null`, OR
86
- * whose prototype's own prototype is `null` — the shape every plain object
87
- * has in every realm, since `Object.prototype` itself always sits one step
88
- * above `null`. Arrays and class instances are still rejected: an array's
89
- * prototype chain runs through `Array.prototype` before `null`, and a class
90
- * instance's runs through the class's own prototype. The whole body runs
91
- * inside `attempt` (AGENTS §14) so a revoked `Proxy` or a hostile
92
- * `getPrototypeOf` trap cannot escape as a thrown error.
93
- */
94
- function isRecord(value) {
95
- const outcome = attempt(() => {
96
- if (!isObject(value) || isArray(value)) return false;
97
- const prototype = Object.getPrototypeOf(value);
98
- return prototype === null || Object.getPrototypeOf(prototype) === null;
99
- });
100
- return outcome.success && outcome.value;
101
- }
102
- /** Determine whether a value is an array. */
103
- function isArray(value) {
104
- return Array.isArray(value);
105
- }
106
- /**
107
- * Invoke a callback and capture its outcome as a {@link Result}, never letting
108
- * a throw escape.
109
- *
110
- * @remarks
111
- * The single sanctioned never-throw boundary for the guards (AGENTS §14). The
112
- * `whereOf`, `lazyOf`, and `transformOf` combinators invoke caller-supplied
113
- * callbacks *inside* a guard body, yet a guard must NEVER throw — it returns a
114
- * `boolean`. This converts a throwing callback into a `Failure` so the
115
- * surrounding guard can treat it as a non-match instead of propagating the
116
- * exception, written once and shared rather than copy-pasted as ad-hoc
117
- * `try`/`catch`.
118
- *
119
- * @param callback - The callback to invoke with no arguments
120
- * @returns A `Success` carrying the return value, or a `Failure` carrying the
121
- * thrown reason normalised to an `Error`
122
- *
123
- * @example
124
- * ```ts
125
- * const outcome = attempt(() => predicate(value))
126
- * return outcome.success && outcome.value
127
- * ```
128
- */
129
- function attempt(callback) {
130
- try {
131
- return {
132
- success: true,
133
- value: callback()
134
- };
135
- } catch (reason) {
136
- if (reason instanceof Error) return {
137
- success: false,
138
- error: reason
139
- };
140
- let message = "Unknown thrown value";
141
- try {
142
- message = String(reason);
143
- } catch {}
144
- return {
145
- success: false,
146
- error: new Error(message)
147
- };
148
- }
149
- }
150
60
  //#endregion
151
61
  //#region src/browser/helpers.ts
152
62
  /**
@@ -270,6 +180,26 @@ async function hasKey(source, key) {
270
180
  return await promisifyRequest(guardSync(() => source.count(key))) > 0;
271
181
  }
272
182
  /**
183
+ * Create a secondary index on a store from its {@link IndexDefinition}.
184
+ *
185
+ * @remarks
186
+ * The shared index-DDL leaf used both by the built-in create-missing-stores pass
187
+ * (`IndexedDBDatabase`'s internal store creation) and by an upgrade's
188
+ * `context.index`: translate the definition's `path` to a native key path and
189
+ * `unique` / `multiple` to `unique` / `multiEntry`, then issue `createIndex`.
190
+ * Versionchange-only — `store` must be inside an active upgrade transaction.
191
+ *
192
+ * @param store - The object store to add the index to
193
+ * @param definition - The index to create
194
+ */
195
+ function createIndex(store, definition) {
196
+ const keyPath = typeof definition.path === "string" ? definition.path : [...definition.path];
197
+ store.createIndex(definition.name, keyPath, {
198
+ unique: definition.unique ?? false,
199
+ multiEntry: definition.multiple ?? false
200
+ });
201
+ }
202
+ /**
273
203
  * Key-range builders — the wrapper's filter vocabulary.
274
204
  *
275
205
  * @remarks
@@ -717,14 +647,14 @@ var IndexedDBTransaction = class {
717
647
  return new IndexedDBTransactionStore(this.#transaction.objectStore(name));
718
648
  }
719
649
  abort() {
720
- if (this.#finished) throw new IndexedDBError("ABORTED", "Cannot abort an already-finished transaction");
721
- this.#transaction.abort();
650
+ if (this.#finished) throw new IndexedDBError("INACTIVE", "Cannot abort an already-finished transaction");
651
+ guardSync(() => this.#transaction.abort());
722
652
  this.#active = false;
723
653
  this.#finished = true;
724
654
  }
725
655
  commit() {
726
- if (this.#finished) throw new IndexedDBError("ABORTED", "Cannot commit an already-finished transaction");
727
- this.#transaction.commit();
656
+ if (this.#finished) throw new IndexedDBError("INACTIVE", "Cannot commit an already-finished transaction");
657
+ guardSync(() => this.#transaction.commit());
728
658
  }
729
659
  };
730
660
  //#endregion
@@ -814,13 +744,15 @@ var IndexedDBDatabase = class {
814
744
  const names = isArray(stores) ? [...stores] : [stores];
815
745
  const native = guardSync(() => database.transaction(names, mode));
816
746
  const tx = new IndexedDBTransaction(native);
747
+ const settled = promisifyTransaction(native);
817
748
  try {
818
749
  await scope(tx);
819
- await promisifyTransaction(native);
750
+ await settled;
820
751
  } catch (error) {
821
752
  if (tx.active) try {
822
753
  tx.abort();
823
754
  } catch {}
755
+ settled.catch(() => {});
824
756
  throw error;
825
757
  }
826
758
  }
@@ -835,6 +767,7 @@ var IndexedDBDatabase = class {
835
767
  }
836
768
  database.onclose = () => {
837
769
  this.#database = void 0;
770
+ this.#opening = void 0;
838
771
  };
839
772
  database.onversionchange = () => {
840
773
  database.close();
@@ -853,7 +786,7 @@ var IndexedDBDatabase = class {
853
786
  for (const [name, definition] of Object.entries(this.#stores)) if (!database.objectStoreNames.contains(name)) this.#createStore(database, name, definition);
854
787
  if (this.#upgrade !== void 0) {
855
788
  const transaction = request.transaction;
856
- if (transaction !== null) {
789
+ if (transaction !== null) try {
857
790
  const result = this.#upgrade(this.#context(database, transaction, event));
858
791
  if (result !== void 0) result.catch((error) => {
859
792
  upgradeError = error;
@@ -861,6 +794,11 @@ var IndexedDBDatabase = class {
861
794
  transaction.abort();
862
795
  } catch {}
863
796
  });
797
+ } catch (error) {
798
+ upgradeError = error;
799
+ try {
800
+ transaction.abort();
801
+ } catch {}
864
802
  }
865
803
  }
866
804
  };
@@ -879,25 +817,25 @@ var IndexedDBDatabase = class {
879
817
  version: event.newVersion ?? database.version,
880
818
  stores: Array.from(database.objectStoreNames),
881
819
  create: (name, definition) => {
882
- this.#createStore(database, name, definition);
820
+ guardSync(() => this.#createStore(database, name, definition));
883
821
  },
884
822
  drop: (name) => {
885
- database.deleteObjectStore(name);
823
+ guardSync(() => database.deleteObjectStore(name));
824
+ },
825
+ store: (name) => new IndexedDBTransactionStore(guardSync(() => transaction.objectStore(name))),
826
+ index: (store, definition) => {
827
+ guardSync(() => createIndex(transaction.objectStore(store), definition));
886
828
  },
887
- store: (name) => new IndexedDBTransactionStore(transaction.objectStore(name))
829
+ deindex: (store, name) => {
830
+ guardSync(() => transaction.objectStore(store).deleteIndex(name));
831
+ }
888
832
  };
889
833
  }
890
834
  #createStore(database, name, definition) {
891
835
  const options = { autoIncrement: definition.increment ?? false };
892
836
  if (definition.path !== void 0) options.keyPath = typeof definition.path === "string" ? definition.path : [...definition.path];
893
837
  const store = database.createObjectStore(name, options);
894
- for (const index of definition.indexes ?? []) {
895
- const keyPath = typeof index.path === "string" ? index.path : [...index.path];
896
- store.createIndex(index.name, keyPath, {
897
- unique: index.unique ?? false,
898
- multiEntry: index.multiple ?? false
899
- });
900
- }
838
+ for (const index of definition.indexes ?? []) createIndex(store, index);
901
839
  }
902
840
  };
903
841
  //#endregion
@@ -917,7 +855,7 @@ var IndexedDBDatabase = class {
917
855
  *
918
856
  * @example
919
857
  * ```ts
920
- * import { createIndexedDBDatabase, range } from '@src/browser'
858
+ * import { createIndexedDBDatabase, range } from '@orkestrel/indexeddb'
921
859
  *
922
860
  * const db = createIndexedDBDatabase({
923
861
  * name: 'app',
@@ -934,6 +872,6 @@ function createIndexedDBDatabase(options) {
934
872
  return new IndexedDBDatabase(options);
935
873
  }
936
874
  //#endregion
937
- export { ERROR_CODES, IndexedDBCursor, IndexedDBDatabase, IndexedDBError, IndexedDBIndex, IndexedDBStore, IndexedDBTransaction, IndexedDBTransactionStore, createIndexedDBDatabase, guardSync, hasKey, isIndexedDBError, isIndexedDBSupported, promisifyRequest, promisifyTransaction, range, readRecord, readRecords, wrapError };
875
+ export { ERROR_CODES, IndexedDBCursor, IndexedDBDatabase, IndexedDBError, IndexedDBIndex, IndexedDBStore, IndexedDBTransaction, IndexedDBTransactionStore, createIndex, createIndexedDBDatabase, guardSync, hasKey, isIndexedDBError, isIndexedDBSupported, promisifyRequest, promisifyTransaction, range, readRecord, readRecords, wrapError };
938
876
 
939
877
  //# sourceMappingURL=index.js.map