shelving 1.279.0 → 1.280.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.
@@ -119,7 +119,8 @@ export declare abstract class DBProvider<I extends Identifier = Identifier, T ex
119
119
  abstract getQuerySequence<II extends I, TT extends T>(collection: Collection<string, II, TT>, query?: Query<Item<II, TT>>): ItemsSequence<II, TT>;
120
120
  /**
121
121
  * Set (overwrite) the data for every item matching a query.
122
- * - Not guaranteed atomic: an implementation may resolve the matching items first and then write per item (two-step — see `ThroughDBProvider`), so wrap the call in `transact()` when atomicity matters.
122
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then sets each one concurrently with `setItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `UPDATE … WHERE`).
123
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
123
124
  *
124
125
  * @param collection Collection to write to.
125
126
  * @param query Query selecting the items to set.
@@ -127,10 +128,11 @@ export declare abstract class DBProvider<I extends Identifier = Identifier, T ex
127
128
  * @example await provider.setQuery(users, { age: 40 }, { active: true });
128
129
  * @see https://shelving.cc/db/DBProvider/setQuery
129
130
  */
130
- abstract setQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, data: TT): Promise<void>;
131
+ setQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, data: TT): Promise<void>;
131
132
  /**
132
133
  * Apply partial updates to every item matching a query.
133
- * - Not guaranteed atomic: an implementation may resolve the matching items first and then write per item (two-step — see `ThroughDBProvider`), so wrap the call in `transact()` when atomicity matters.
134
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then updates each one concurrently with `updateItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `UPDATE … WHERE`).
135
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
134
136
  *
135
137
  * @param collection Collection to write to.
136
138
  * @param query Query selecting the items to update.
@@ -138,17 +140,18 @@ export declare abstract class DBProvider<I extends Identifier = Identifier, T ex
138
140
  * @example await provider.updateQuery(users, { age: 40 }, { active: true });
139
141
  * @see https://shelving.cc/db/DBProvider/updateQuery
140
142
  */
141
- abstract updateQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
143
+ updateQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
142
144
  /**
143
145
  * Delete every item matching a query.
144
- * - Not guaranteed atomic: an implementation may resolve the matching items first and then delete per item (two-step — see `ThroughDBProvider`), so wrap the call in `transact()` when atomicity matters.
146
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then deletes each one concurrently with `deleteItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `DELETE … WHERE`).
147
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
145
148
  *
146
149
  * @param collection Collection to delete from.
147
150
  * @param query Query selecting the items to delete.
148
151
  * @example await provider.deleteQuery(users, { active: false });
149
152
  * @see https://shelving.cc/db/DBProvider/deleteQuery
150
153
  */
151
- abstract deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
154
+ deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
152
155
  /**
153
156
  * Get the first item matching a query, or `undefined` if there are none.
154
157
  *
@@ -1,6 +1,7 @@
1
1
  import { RequiredError } from "../../error/RequiredError.js";
2
2
  import { UnsupportedError } from "../../error/UnsupportedError.js";
3
3
  import { countArray, getFirst } from "../../util/array.js";
4
+ import { awaitValues } from "../../util/async.js";
4
5
  import { awaitDispose } from "../../util/dispose.js";
5
6
  /**
6
7
  * Provider with a fully asynchronous interface for database access.
@@ -49,6 +50,50 @@ export class DBProvider {
49
50
  async countQuery(collection, query) {
50
51
  return countArray(await this.getQuery(collection, query));
51
52
  }
53
+ /**
54
+ * Set (overwrite) the data for every item matching a query.
55
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then sets each one concurrently with `setItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `UPDATE … WHERE`).
56
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
57
+ *
58
+ * @param collection Collection to write to.
59
+ * @param query Query selecting the items to set.
60
+ * @param data Full data to store for each matching item.
61
+ * @example await provider.setQuery(users, { age: 40 }, { active: true });
62
+ * @see https://shelving.cc/db/DBProvider/setQuery
63
+ */
64
+ async setQuery(collection, query, data) {
65
+ const items = await this.getQuery(collection, query);
66
+ await awaitValues(...items.map(({ id }) => this.setItem(collection, id, data)));
67
+ }
68
+ /**
69
+ * Apply partial updates to every item matching a query.
70
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then updates each one concurrently with `updateItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `UPDATE … WHERE`).
71
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
72
+ *
73
+ * @param collection Collection to write to.
74
+ * @param query Query selecting the items to update.
75
+ * @param updates Updates to apply to each matching item.
76
+ * @example await provider.updateQuery(users, { age: 40 }, { active: true });
77
+ * @see https://shelving.cc/db/DBProvider/updateQuery
78
+ */
79
+ async updateQuery(collection, query, updates) {
80
+ const items = await this.getQuery(collection, query);
81
+ await awaitValues(...items.map(({ id }) => this.updateItem(collection, id, updates)));
82
+ }
83
+ /**
84
+ * Delete every item matching a query.
85
+ * - Two-step by default: resolves the query to its matching items with `getQuery()`, then deletes each one concurrently with `deleteItem()`. Engine providers override this with a native query write where one exists (e.g. SQL `DELETE … WHERE`).
86
+ * - Not guaranteed atomic: the resolve and the writes are separate steps, so wrap the call in `transact()` when atomicity matters.
87
+ *
88
+ * @param collection Collection to delete from.
89
+ * @param query Query selecting the items to delete.
90
+ * @example await provider.deleteQuery(users, { active: false });
91
+ * @see https://shelving.cc/db/DBProvider/deleteQuery
92
+ */
93
+ async deleteQuery(collection, query) {
94
+ const items = await this.getQuery(collection, query);
95
+ await awaitValues(...items.map(({ id }) => this.deleteItem(collection, id)));
96
+ }
52
97
  /**
53
98
  * Get the first item matching a query, or `undefined` if there are none.
54
99
  *
@@ -4,17 +4,17 @@ import type { Query } from "../../util/query.js";
4
4
  import type { Sourceable } from "../../util/source.js";
5
5
  import type { Updates } from "../../util/update.js";
6
6
  import type { Collection } from "../collection/Collection.js";
7
- import type { DBProvider } from "./DBProvider.js";
7
+ import { DBProvider } from "./DBProvider.js";
8
8
  /**
9
- * Database provider that passes every operation straight through to a wrapped `source` provider.
9
+ * Database provider that passes every core operation straight through to a wrapped `source` provider.
10
10
  *
11
11
  * - Base for the layered `Through*Provider` family (validation, caching, logging, change tracking); subclasses override individual methods to add behaviour and call `super` to delegate.
12
- * - Query writes (`setQuery()`, `updateQuery()`, `deleteQuery()`) are two-step by default — resolved to their matching items with `getQuery()`, then written per item through this provider's own item methods — so wrapper behaviour applies to every implied write. Subclasses that don't need per-item behaviour override them to pass through to `source` directly.
12
+ * - Only the core operations delegate to `source` — derived reads (`DBProvider.requireItem()`, `DBProvider.getFirst()`, `DBProvider.requireFirst()`) and two-step query writes are inherited from `DBProvider`, so they route through this provider's own overridden methods and wrapper behaviour applies to everything they do. Wrappers that don't need per-item behaviour override the query writes to pass through to `source` directly.
13
13
  * - Exposes `source` and implements `Sourceable`, so wrapped providers can be discovered with `getSource()` / `requireSource()`.
14
14
  *
15
15
  * @see https://shelving.cc/db/ThroughDBProvider
16
16
  */
17
- export declare class ThroughDBProvider<I extends Identifier, T extends Data> implements DBProvider<I, T>, Sourceable<DBProvider<I, T>> {
17
+ export declare class ThroughDBProvider<I extends Identifier, T extends Data> extends DBProvider<I, T> implements Sourceable<DBProvider<I, T>> {
18
18
  /**
19
19
  * The wrapped source provider that every operation is delegated to.
20
20
  *
@@ -23,28 +23,15 @@ export declare class ThroughDBProvider<I extends Identifier, T extends Data> imp
23
23
  readonly source: DBProvider<I, T>;
24
24
  constructor(source: DBProvider<I, T>);
25
25
  getItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<OptionalItem<II, TT>>;
26
- requireItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<Item<II, TT>>;
27
26
  getItemSequence<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): OptionalItemSequence<II, TT>;
28
27
  addItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, data: TT): Promise<II>;
29
28
  setItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II, data: TT): Promise<void>;
30
29
  updateItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II, updates: Updates<Item<II, TT>>): Promise<void>;
31
30
  deleteItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<void>;
31
+ /** Delegates to `source` so its native counting is kept (the base implementation would fetch the items and count them). */
32
32
  countQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query?: Query<Item<II, TT>>): Promise<number>;
33
33
  getQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query?: Query<Item<II, TT>>): Promise<Items<II, TT>>;
34
34
  getQuerySequence<II extends I, TT extends T>(collection: Collection<string, II, TT>, query?: Query<Item<II, TT>>): ItemsSequence<II, TT>;
35
- /**
36
- * Two-step: resolve the query to its matching items with `getQuery()`, then set each one with `setItem()`.
37
- * - Routes every implied write through this provider's own item methods, so wrapper behaviour applies to each item — the same theory as `transact()` re-wrapping the transaction provider.
38
- * - The per-item writes run concurrently (`awaitValues()`), so a batch over a remote source costs one round-trip of latency, not one per item.
39
- * - The resolve and the writes are separate steps, so this is only atomic inside `transact()`.
40
- */
41
- setQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, data: TT): Promise<void>;
42
- /** Two-step: resolve the query to its matching items with `getQuery()`, then update each one concurrently with `updateItem()` — see `ThroughDBProvider.setQuery()`. */
43
- updateQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
44
- /** Two-step: resolve the query to its matching items with `getQuery()`, then delete each one concurrently with `deleteItem()` — see `ThroughDBProvider.setQuery()`. */
45
- deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
46
- getFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<OptionalItem<II, TT>>;
47
- requireFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<Item<II, TT>>;
48
35
  transact<X>(callback: (provider: DBProvider<I, T>) => Promise<X>): Promise<X>;
49
36
  /** Clone this provider with different `source`. */
50
37
  cloneWith(source: DBProvider<I, T>): this;
@@ -1,15 +1,15 @@
1
- import { awaitValues } from "../../util/async.js";
2
1
  import { awaitDispose } from "../../util/dispose.js";
2
+ import { DBProvider } from "./DBProvider.js";
3
3
  /**
4
- * Database provider that passes every operation straight through to a wrapped `source` provider.
4
+ * Database provider that passes every core operation straight through to a wrapped `source` provider.
5
5
  *
6
6
  * - Base for the layered `Through*Provider` family (validation, caching, logging, change tracking); subclasses override individual methods to add behaviour and call `super` to delegate.
7
- * - Query writes (`setQuery()`, `updateQuery()`, `deleteQuery()`) are two-step by default — resolved to their matching items with `getQuery()`, then written per item through this provider's own item methods — so wrapper behaviour applies to every implied write. Subclasses that don't need per-item behaviour override them to pass through to `source` directly.
7
+ * - Only the core operations delegate to `source` — derived reads (`DBProvider.requireItem()`, `DBProvider.getFirst()`, `DBProvider.requireFirst()`) and two-step query writes are inherited from `DBProvider`, so they route through this provider's own overridden methods and wrapper behaviour applies to everything they do. Wrappers that don't need per-item behaviour override the query writes to pass through to `source` directly.
8
8
  * - Exposes `source` and implements `Sourceable`, so wrapped providers can be discovered with `getSource()` / `requireSource()`.
9
9
  *
10
10
  * @see https://shelving.cc/db/ThroughDBProvider
11
11
  */
12
- export class ThroughDBProvider {
12
+ export class ThroughDBProvider extends DBProvider {
13
13
  /**
14
14
  * The wrapped source provider that every operation is delegated to.
15
15
  *
@@ -17,14 +17,12 @@ export class ThroughDBProvider {
17
17
  */
18
18
  source;
19
19
  constructor(source) {
20
+ super();
20
21
  this.source = source;
21
22
  }
22
23
  getItem(collection, id) {
23
24
  return this.source.getItem(collection, id);
24
25
  }
25
- requireItem(collection, id) {
26
- return this.source.requireItem(collection, id);
27
- }
28
26
  getItemSequence(collection, id) {
29
27
  return this.source.getItemSequence(collection, id);
30
28
  }
@@ -40,6 +38,7 @@ export class ThroughDBProvider {
40
38
  deleteItem(collection, id) {
41
39
  return this.source.deleteItem(collection, id);
42
40
  }
41
+ /** Delegates to `source` so its native counting is kept (the base implementation would fetch the items and count them). */
43
42
  countQuery(collection, query) {
44
43
  return this.source.countQuery(collection, query);
45
44
  }
@@ -49,32 +48,6 @@ export class ThroughDBProvider {
49
48
  getQuerySequence(collection, query) {
50
49
  return this.source.getQuerySequence(collection, query);
51
50
  }
52
- /**
53
- * Two-step: resolve the query to its matching items with `getQuery()`, then set each one with `setItem()`.
54
- * - Routes every implied write through this provider's own item methods, so wrapper behaviour applies to each item — the same theory as `transact()` re-wrapping the transaction provider.
55
- * - The per-item writes run concurrently (`awaitValues()`), so a batch over a remote source costs one round-trip of latency, not one per item.
56
- * - The resolve and the writes are separate steps, so this is only atomic inside `transact()`.
57
- */
58
- async setQuery(collection, query, data) {
59
- const items = await this.getQuery(collection, query);
60
- await awaitValues(...items.map(({ id }) => this.setItem(collection, id, data)));
61
- }
62
- /** Two-step: resolve the query to its matching items with `getQuery()`, then update each one concurrently with `updateItem()` — see `ThroughDBProvider.setQuery()`. */
63
- async updateQuery(collection, query, updates) {
64
- const items = await this.getQuery(collection, query);
65
- await awaitValues(...items.map(({ id }) => this.updateItem(collection, id, updates)));
66
- }
67
- /** Two-step: resolve the query to its matching items with `getQuery()`, then delete each one concurrently with `deleteItem()` — see `ThroughDBProvider.setQuery()`. */
68
- async deleteQuery(collection, query) {
69
- const items = await this.getQuery(collection, query);
70
- await awaitValues(...items.map(({ id }) => this.deleteItem(collection, id)));
71
- }
72
- getFirst(collection, query) {
73
- return this.source.getFirst(collection, query);
74
- }
75
- requireFirst(collection, query) {
76
- return this.source.requireFirst(collection, query);
77
- }
78
51
  // Run the transaction against the wrapped `source` provider, keeping this provider's behaviour inside the transaction.
79
52
  transact(callback) {
80
53
  return this.source.transact(transaction => callback(this.cloneWith(transaction)));
@@ -85,6 +58,7 @@ export class ThroughDBProvider {
85
58
  }
86
59
  // Implement `AsyncDisposable`
87
60
  async [Symbol.asyncDispose]() {
88
- await awaitDispose(this.source);
61
+ await awaitDispose(this.source, // Dispose the source API provider.
62
+ super[Symbol.asyncDispose]());
89
63
  }
90
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.279.0",
3
+ "version": "1.280.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",