shelving 1.274.0 → 1.275.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.274.0",
3
+ "version": "1.275.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/test/basics.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Collection } from "../db/collection/Collection.js";
1
2
  import type { Item } from "../util/item.js";
2
3
  import type { ValidatorType } from "../util/validate.js";
3
4
  /**
@@ -97,3 +98,22 @@ export declare const basics: ReadonlyArray<BasicItem>;
97
98
  * @see https://shelving.cc/test/basic999
98
99
  */
99
100
  export declare const basic999: BasicData;
101
+ /**
102
+ * Collection of `BasicData` items keyed by string id, for testing database providers.
103
+ *
104
+ * @see https://shelving.cc/test/BASICS_COLLECTION
105
+ */
106
+ export declare const BASICS_COLLECTION: Collection<"basics", string, {
107
+ str: string;
108
+ num: number;
109
+ group: "a" | "b" | "c";
110
+ tags: import("../index.js").ImmutableArray<string>;
111
+ odd: boolean;
112
+ even: boolean;
113
+ sub: {
114
+ str: /*elided*/ any;
115
+ num: /*elided*/ any;
116
+ odd: /*elided*/ any;
117
+ even: /*elided*/ any;
118
+ };
119
+ }>;
package/test/basics.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Collection } from "../db/collection/Collection.js";
1
2
  import { ARRAY } from "../schema/ArraySchema.js";
2
3
  import { BOOLEAN } from "../schema/BooleanSchema.js";
3
4
  import { CHOICE } from "../schema/ChoiceSchema.js";
@@ -173,3 +174,9 @@ export const basic999 = {
173
174
  tags: ["odd", "prime"],
174
175
  sub: { str: "zzz", num: 999, even: false, odd: true },
175
176
  };
177
+ /**
178
+ * Collection of `BasicData` items keyed by string id, for testing database providers.
179
+ *
180
+ * @see https://shelving.cc/test/BASICS_COLLECTION
181
+ */
182
+ export const BASICS_COLLECTION = new Collection("basics", STRING, BASIC_SCHEMA);
package/test/index.d.ts CHANGED
@@ -1,26 +1,5 @@
1
- import { Collection } from "../db/collection/Collection.js";
2
1
  export * from "./basics.js";
3
2
  export * from "./people.js";
4
3
  export * from "./TransactionTestDBProvider.js";
4
+ export * from "./testDBProvider.js";
5
5
  export * from "./util.js";
6
- export declare const BASICS_COLLECTION: Collection<"basics", string, {
7
- str: string;
8
- num: number;
9
- group: "a" | "b" | "c";
10
- tags: import("../index.js").ImmutableArray<string>;
11
- odd: boolean;
12
- even: boolean;
13
- sub: {
14
- str: /*elided*/ any;
15
- num: /*elided*/ any;
16
- odd: /*elided*/ any;
17
- even: /*elided*/ any;
18
- };
19
- }>;
20
- export declare const PEOPLE_COLLECTION: Collection<"people", string, {
21
- name: {
22
- first: /*elided*/ any;
23
- last: /*elided*/ any;
24
- };
25
- birthday: string | null;
26
- }>;
package/test/index.js CHANGED
@@ -1,10 +1,5 @@
1
- import { Collection } from "../db/collection/Collection.js";
2
- import { STRING } from "../schema/StringSchema.js";
3
- import { BASIC_SCHEMA } from "./basics.js";
4
- import { PERSON_SCHEMA } from "./people.js";
5
1
  export * from "./basics.js";
6
2
  export * from "./people.js";
7
3
  export * from "./TransactionTestDBProvider.js";
4
+ export * from "./testDBProvider.js";
8
5
  export * from "./util.js";
9
- export const BASICS_COLLECTION = new Collection("basics", STRING, BASIC_SCHEMA);
10
- export const PEOPLE_COLLECTION = new Collection("people", STRING, PERSON_SCHEMA);
package/test/people.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Collection } from "../db/collection/Collection.js";
1
2
  import type { Item } from "../util/item.js";
2
3
  import type { ValidatorType } from "../util/validate.js";
3
4
  /**
@@ -60,3 +61,15 @@ export declare const person5: PersonItem;
60
61
  * @see https://shelving.cc/test/people
61
62
  */
62
63
  export declare const people: ReadonlyArray<PersonItem>;
64
+ /**
65
+ * Collection of `PersonData` items keyed by string id, for testing database providers.
66
+ *
67
+ * @see https://shelving.cc/test/PEOPLE_COLLECTION
68
+ */
69
+ export declare const PEOPLE_COLLECTION: Collection<"people", string, {
70
+ name: {
71
+ first: /*elided*/ any;
72
+ last: /*elided*/ any;
73
+ };
74
+ birthday: string | null;
75
+ }>;
package/test/people.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { Collection } from "../db/collection/Collection.js";
1
2
  import { DATA } from "../schema/DataSchema.js";
2
3
  import { NULLABLE_DATE } from "../schema/DateSchema.js";
3
- import { REQUIRED_STRING } from "../schema/StringSchema.js";
4
+ import { REQUIRED_STRING, STRING } from "../schema/StringSchema.js";
4
5
  /**
5
6
  * Schema for a test "person" fixture, with a nested `name` and a nullable `birthday`.
6
7
  *
@@ -46,3 +47,9 @@ export const person5 = { id: "person5", name: { first: "Terry", last: "Times" },
46
47
  * @see https://shelving.cc/test/people
47
48
  */
48
49
  export const people = [person1, person2, person3, person4, person5];
50
+ /**
51
+ * Collection of `PersonData` items keyed by string id, for testing database providers.
52
+ *
53
+ * @see https://shelving.cc/test/PEOPLE_COLLECTION
54
+ */
55
+ export const PEOPLE_COLLECTION = new Collection("people", STRING, PERSON_SCHEMA);
@@ -0,0 +1,22 @@
1
+ import type { DBProvider } from "../db/provider/DBProvider.js";
2
+ import type { Data } from "../util/data.js";
3
+ /** Options for `testDBProvider()`, declaring the capabilities of the provider under test. */
4
+ export interface TestDBProviderOptions {
5
+ /** Whether the provider supports realtime sequences — when `false`, sequences are asserted to throw `UnsupportedError`. @default true */
6
+ readonly realtime?: boolean;
7
+ /** Whether the provider supports `transact()` — when `false`, it is asserted to throw `UnsupportedError`. @default false */
8
+ readonly transactions?: boolean;
9
+ }
10
+ /**
11
+ * Register the universal `DBProvider` contract test suite against a provider, so every backend proves the same behaviour.
12
+ *
13
+ * - Calls `createProvider()` fresh for every test and wipes `BASICS_COLLECTION` and `PEOPLE_COLLECTION` first, so persistent backends (e.g. an emulator) start each test clean.
14
+ * - Declare the provider's capabilities via options — unsupported capabilities are asserted to throw `UnsupportedError` rather than skipped.
15
+ *
16
+ * @param name Name for the provider used in the `describe` block.
17
+ * @param createProvider Create (or return) the provider instance to test.
18
+ * @param options Capability flags for the provider under test.
19
+ * @example testDBProvider("MemoryDBProvider", () => new MemoryDBProvider<string>());
20
+ * @see https://shelving.cc/test/testDBProvider
21
+ */
22
+ export declare function testDBProvider(name: string, createProvider: () => DBProvider<string, Data> | PromiseLike<DBProvider<string, Data>>, { realtime, transactions }?: TestDBProviderOptions): void;
@@ -0,0 +1,272 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { RequiredError } from "../error/RequiredError.js";
3
+ import { UnsupportedError } from "../error/UnsupportedError.js";
4
+ import { runMicrotasks } from "../util/async.js";
5
+ import { runSequence } from "../util/sequence.js";
6
+ import { BASICS_COLLECTION, basic1, basic2, basic3, basic999, basics } from "./basics.js";
7
+ import { PEOPLE_COLLECTION, person1 } from "./people.js";
8
+ import { expectOrderedItems, expectUnorderedItems } from "./util.js";
9
+ /**
10
+ * Register the universal `DBProvider` contract test suite against a provider, so every backend proves the same behaviour.
11
+ *
12
+ * - Calls `createProvider()` fresh for every test and wipes `BASICS_COLLECTION` and `PEOPLE_COLLECTION` first, so persistent backends (e.g. an emulator) start each test clean.
13
+ * - Declare the provider's capabilities via options — unsupported capabilities are asserted to throw `UnsupportedError` rather than skipped.
14
+ *
15
+ * @param name Name for the provider used in the `describe` block.
16
+ * @param createProvider Create (or return) the provider instance to test.
17
+ * @param options Capability flags for the provider under test.
18
+ * @example testDBProvider("MemoryDBProvider", () => new MemoryDBProvider<string>());
19
+ * @see https://shelving.cc/test/testDBProvider
20
+ */
21
+ export function testDBProvider(name, createProvider, { realtime = true, transactions = false } = {}) {
22
+ // Create the provider and wipe both fixture collections so each test starts clean.
23
+ async function init() {
24
+ const provider = await createProvider();
25
+ await provider.deleteQuery(BASICS_COLLECTION, {});
26
+ await provider.deleteQuery(PEOPLE_COLLECTION, {});
27
+ return provider;
28
+ }
29
+ describe(`DBProvider contract: ${name}`, () => {
30
+ test("sets, gets, and deletes items", async () => {
31
+ const db = await init();
32
+ // Set and get.
33
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
34
+ await db.setItem(BASICS_COLLECTION, "basic2", basic2);
35
+ await db.setItem(PEOPLE_COLLECTION, "person1", person1);
36
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject(basic1);
37
+ expect(await db.getItem(BASICS_COLLECTION, "basic2")).toMatchObject(basic2);
38
+ expect(await db.getItem(BASICS_COLLECTION, "basicNone")).toBe(undefined);
39
+ expect(await db.getItem(PEOPLE_COLLECTION, "person1")).toMatchObject(person1);
40
+ // Overwrite.
41
+ await db.setItem(BASICS_COLLECTION, "basic1", { ...basic1, str: "NEW" });
42
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject({ ...basic1, str: "NEW" });
43
+ // Require.
44
+ expect(await db.requireItem(BASICS_COLLECTION, "basic2")).toMatchObject(basic2);
45
+ try {
46
+ await db.requireItem(BASICS_COLLECTION, "basicNone");
47
+ expect.unreachable();
48
+ }
49
+ catch (thrown) {
50
+ expect(thrown).toBeInstanceOf(RequiredError);
51
+ }
52
+ // Delete (including a missing item, which is a no-op).
53
+ await db.deleteItem(BASICS_COLLECTION, "basic1");
54
+ await db.deleteItem(BASICS_COLLECTION, "basicNone");
55
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toBe(undefined);
56
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(1);
57
+ expect(await db.countQuery(PEOPLE_COLLECTION, {})).toBe(1);
58
+ });
59
+ test("adds items with generated ids", async () => {
60
+ const db = await init();
61
+ const id1 = await db.addItem(BASICS_COLLECTION, basic999);
62
+ const id2 = await db.addItem(BASICS_COLLECTION, basic999);
63
+ expect(typeof id1).toBe("string");
64
+ expect(typeof id2).toBe("string");
65
+ expect(id1).not.toBe(id2);
66
+ expect(await db.getItem(BASICS_COLLECTION, id1)).toMatchObject(basic999);
67
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(2);
68
+ });
69
+ test("updates items with set, sum, and array updates", async () => {
70
+ const db = await init();
71
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
72
+ // Set and sum updates.
73
+ await db.updateItem(BASICS_COLLECTION, "basic1", { str: "NEW", "+=num": 100 });
74
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject({ ...basic1, str: "NEW", num: basic1.num + 100 });
75
+ // Array with/omit updates.
76
+ await db.updateItem(BASICS_COLLECTION, "basic1", { "+[]tags": "extra" });
77
+ expect((await db.requireItem(BASICS_COLLECTION, "basic1")).tags).toEqual([...basic1.tags, "extra"]);
78
+ await db.updateItem(BASICS_COLLECTION, "basic1", { "-[]tags": "extra" });
79
+ expect((await db.requireItem(BASICS_COLLECTION, "basic1")).tags).toEqual(basic1.tags);
80
+ });
81
+ test("gets queries with filters", async () => {
82
+ const db = await init();
83
+ for (const { id, ...data } of basics)
84
+ await db.setItem(BASICS_COLLECTION, id, data);
85
+ // Equality filters.
86
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { str: "aaa" }), ["basic1"]);
87
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { str: "NOPE" }), []);
88
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { num: 300 }), ["basic3"]);
89
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { group: "a" }), ["basic1", "basic2", "basic3"]);
90
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { group: "b" }), ["basic4", "basic5", "basic6"]);
91
+ // ArrayContains filters.
92
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "tags[]": "odd" }), ["basic1", "basic3", "basic5", "basic7", "basic9"]);
93
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "tags[]": "NOPE" }), []);
94
+ // In filters.
95
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { num: [200, 600, 900, 999999] }), ["basic2", "basic6", "basic9"]);
96
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { id: ["basic1", "basic5", "basicNone"] }), ["basic1", "basic5"]);
97
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { num: [] }), []);
98
+ // Range filters.
99
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "num<": 300 }), ["basic1", "basic2"]);
100
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "num<=": 300 }), ["basic1", "basic2", "basic3"]);
101
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "num>": 700 }), ["basic8", "basic9"]);
102
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "num>=": 700 }), ["basic7", "basic8", "basic9"]);
103
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { "str<": "ccc" }), ["basic1", "basic2"]);
104
+ });
105
+ test("gets queries with sorts and limits", async () => {
106
+ const db = await init();
107
+ for (const { id, ...data } of basics)
108
+ await db.setItem(BASICS_COLLECTION, id, data);
109
+ const keysAsc = ["basic1", "basic2", "basic3", "basic4", "basic5", "basic6", "basic7", "basic8", "basic9"];
110
+ const keysDesc = [...keysAsc].reverse();
111
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "id" }), keysAsc);
112
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "!id" }), keysDesc);
113
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "str" }), keysAsc);
114
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "!str" }), keysDesc);
115
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "num" }), keysAsc);
116
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "!num" }), keysDesc);
117
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "id", $limit: 2 }), ["basic1", "basic2"]);
118
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { $order: "!id", $limit: 1 }), ["basic9"]);
119
+ expectOrderedItems(await db.getQuery(BASICS_COLLECTION, { "tags[]": "prime", $order: "!id", $limit: 2 }), ["basic7", "basic5"]);
120
+ });
121
+ test("counts queries", async () => {
122
+ const db = await init();
123
+ for (const { id, ...data } of basics)
124
+ await db.setItem(BASICS_COLLECTION, id, data);
125
+ expect(await db.countQuery(BASICS_COLLECTION)).toBe(9);
126
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(9);
127
+ expect(await db.countQuery(BASICS_COLLECTION, { group: "a" })).toBe(3);
128
+ expect(await db.countQuery(BASICS_COLLECTION, { str: "NOPE" })).toBe(0);
129
+ expect(await db.countQuery(BASICS_COLLECTION, { $limit: 4 })).toBe(4);
130
+ });
131
+ test("gets first items", async () => {
132
+ const db = await init();
133
+ for (const { id, ...data } of basics)
134
+ await db.setItem(BASICS_COLLECTION, id, data);
135
+ expect(await db.getFirst(BASICS_COLLECTION, { $order: "num" })).toMatchObject(basic1);
136
+ expect(await db.getFirst(BASICS_COLLECTION, { str: "NOPE", $order: "num" })).toBe(undefined);
137
+ expect(await db.requireFirst(BASICS_COLLECTION, { $order: "num" })).toMatchObject(basic1);
138
+ try {
139
+ await db.requireFirst(BASICS_COLLECTION, { str: "NOPE", $order: "num" });
140
+ expect.unreachable();
141
+ }
142
+ catch (thrown) {
143
+ expect(thrown).toBeInstanceOf(RequiredError);
144
+ }
145
+ });
146
+ test("sets, updates, and deletes queries", async () => {
147
+ const db = await init();
148
+ for (const { id, ...data } of basics)
149
+ await db.setItem(BASICS_COLLECTION, id, data);
150
+ // Set every matching item to the same data.
151
+ await db.setQuery(BASICS_COLLECTION, { group: "a" }, basic999);
152
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject(basic999);
153
+ expect(await db.getItem(BASICS_COLLECTION, "basic2")).toMatchObject(basic999);
154
+ expect(await db.getItem(BASICS_COLLECTION, "basic4")).toMatchObject({ id: "basic4", group: "b" }); // Non-matching items unchanged.
155
+ // Update every matching item.
156
+ await db.updateQuery(BASICS_COLLECTION, { group: "b" }, { str: "UPDATED" });
157
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { str: "UPDATED" }), ["basic4", "basic5", "basic6"]);
158
+ // Delete every matching item.
159
+ await db.deleteQuery(BASICS_COLLECTION, { group: "c" });
160
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(6);
161
+ await db.deleteQuery(BASICS_COLLECTION, {});
162
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(0);
163
+ });
164
+ if (realtime) {
165
+ test("subscribes to an item", async () => {
166
+ const db = await init();
167
+ const calls = [];
168
+ const stop = runSequence(db.getItemSequence(BASICS_COLLECTION, "basic1"), v => void calls.push(v));
169
+ await runMicrotasks();
170
+ expect(calls.length).toBe(1);
171
+ expect(calls[0]).toBe(undefined);
172
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
173
+ await runMicrotasks();
174
+ expect(calls.length).toBe(2);
175
+ expect(calls[1]).toMatchObject(basic1);
176
+ await db.deleteItem(BASICS_COLLECTION, "basic1");
177
+ await runMicrotasks();
178
+ expect(calls.length).toBe(3);
179
+ expect(calls[2]).toBe(undefined);
180
+ stop();
181
+ });
182
+ test("subscribes to a query", async () => {
183
+ const db = await init();
184
+ const calls = [];
185
+ const stop = runSequence(db.getQuerySequence(BASICS_COLLECTION, { $order: "id" }), v => void calls.push(v));
186
+ await runMicrotasks();
187
+ expectOrderedItems(calls[0] ?? [], []);
188
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
189
+ await runMicrotasks();
190
+ expectOrderedItems(calls[1] ?? [], ["basic1"]);
191
+ await db.setItem(BASICS_COLLECTION, "basic2", basic2);
192
+ await runMicrotasks();
193
+ expectOrderedItems(calls[2] ?? [], ["basic1", "basic2"]);
194
+ stop();
195
+ });
196
+ }
197
+ else {
198
+ test("sequences are not supported", async () => {
199
+ const db = await init();
200
+ expect(() => db.getItemSequence(BASICS_COLLECTION, "basic1")).toThrow(UnsupportedError);
201
+ expect(() => db.getQuerySequence(BASICS_COLLECTION, {})).toThrow(UnsupportedError);
202
+ });
203
+ }
204
+ if (transactions) {
205
+ test("transact(): commits reads and writes atomically", async () => {
206
+ const db = await init();
207
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
208
+ await db.setItem(BASICS_COLLECTION, "basic2", basic2);
209
+ const id = await db.transact(async (tx) => {
210
+ // Reads inside the transaction see committed items.
211
+ expect(await tx.getItem(BASICS_COLLECTION, "basic1")).toMatchObject(basic1);
212
+ expect(await tx.countQuery(BASICS_COLLECTION, {})).toBe(2);
213
+ expectUnorderedItems(await tx.getQuery(BASICS_COLLECTION, { group: "a" }), ["basic1", "basic2"]);
214
+ // Writes commit together when the callback resolves.
215
+ await tx.setItem(BASICS_COLLECTION, "basic3", basic3);
216
+ await tx.updateItem(BASICS_COLLECTION, "basic1", { str: "TX", "+=num": 1 });
217
+ await tx.deleteItem(BASICS_COLLECTION, "basic2");
218
+ return await tx.addItem(BASICS_COLLECTION, basic999);
219
+ });
220
+ expect(typeof id).toBe("string");
221
+ expect(await db.getItem(BASICS_COLLECTION, "basic3")).toMatchObject(basic3);
222
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject({ ...basic1, str: "TX", num: basic1.num + 1 });
223
+ expect(await db.getItem(BASICS_COLLECTION, "basic2")).toBe(undefined);
224
+ expect(await db.getItem(BASICS_COLLECTION, id)).toMatchObject(basic999);
225
+ });
226
+ test("transact(): commits nothing when the callback throws", async () => {
227
+ const db = await init();
228
+ await db.setItem(BASICS_COLLECTION, "basic1", basic1);
229
+ try {
230
+ await db.transact(async (tx) => {
231
+ await tx.setItem(BASICS_COLLECTION, "basic2", basic2);
232
+ await tx.deleteItem(BASICS_COLLECTION, "basic1");
233
+ throw new Error("nope");
234
+ });
235
+ expect.unreachable();
236
+ }
237
+ catch (thrown) {
238
+ expect(thrown).toBeInstanceOf(Error);
239
+ expect(thrown.message).toBe("nope");
240
+ }
241
+ expect(await db.getItem(BASICS_COLLECTION, "basic1")).toMatchObject(basic1);
242
+ expect(await db.getItem(BASICS_COLLECTION, "basic2")).toBe(undefined);
243
+ });
244
+ test("transact(): supports query writes", async () => {
245
+ const db = await init();
246
+ for (const { id, ...data } of basics)
247
+ await db.setItem(BASICS_COLLECTION, id, data);
248
+ await db.transact(async (tx) => {
249
+ await tx.updateQuery(BASICS_COLLECTION, { group: "a" }, { str: "TX" });
250
+ await tx.deleteQuery(BASICS_COLLECTION, { group: "c" });
251
+ });
252
+ expectUnorderedItems(await db.getQuery(BASICS_COLLECTION, { str: "TX" }), ["basic1", "basic2", "basic3"]);
253
+ expect(await db.countQuery(BASICS_COLLECTION, {})).toBe(6);
254
+ });
255
+ test("transact(): sequences and nested transactions are unsupported inside a transaction", async () => {
256
+ const db = await init();
257
+ expect(await db.transact(async () => 123)).toBe(123);
258
+ await db.transact(async (tx) => {
259
+ expect(() => tx.getItemSequence(BASICS_COLLECTION, "basic1")).toThrow(UnsupportedError);
260
+ expect(() => tx.getQuerySequence(BASICS_COLLECTION, {})).toThrow(UnsupportedError);
261
+ expect(() => tx.transact(async () => undefined)).toThrow(UnsupportedError);
262
+ });
263
+ });
264
+ }
265
+ else {
266
+ test("transactions are not supported", async () => {
267
+ const db = await init();
268
+ expect(() => db.transact(async () => undefined)).toThrow(UnsupportedError);
269
+ });
270
+ }
271
+ });
272
+ }