shelving 1.26.0 → 1.29.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/api/Resource.js CHANGED
@@ -1,4 +1,4 @@
1
- import { UNDEFINED, validate } from "../util/index.js";
1
+ import { GET_UNDEFINED, validate } from "../util/index.js";
2
2
  import { Feedback } from "../feedback/index.js";
3
3
  import { ResourceValidationError } from "./errors.js";
4
4
  /**
@@ -37,6 +37,6 @@ export class Resource {
37
37
  }
38
38
  }
39
39
  }
40
- export function RESOURCE(payload = UNDEFINED, result = UNDEFINED) {
40
+ export function RESOURCE(payload = GET_UNDEFINED, result = GET_UNDEFINED) {
41
41
  return new Resource(payload, result);
42
42
  }
package/db/Database.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { Entry, Observable, Observer, Result, Unsubscriber, ResultsMap, Validatable, Validator, Key, Data, Results, Datas, Validators, ValidatorType, Dispatcher } from "../util/index.js";
1
+ import { Entry, Observable, Observer, Result, Unsubscriber, ResultsMap, Validatable, Validator, Key, Data, Results, Datas, Validators, ValidatorType, Dispatcher, Nullish } from "../util/index.js";
2
2
  import { PropUpdates, Update } from "../update/index.js";
3
3
  import type { Provider } from "../provider/Provider.js";
4
4
  import { Filters, Sorts, Query } from "../query/index.js";
5
- import { DocumentDelete, DocumentSet, DocumentUpdate, Write, Writes } from "./Write.js";
5
+ import { DocumentDelete, DocumentSet, DocumentUpdate, Write } from "./Write.js";
6
6
  /**
7
7
  * Combines a database model and a provider.
8
8
  *
@@ -18,10 +18,10 @@ export declare class Database<V extends Validators<Datas> = Validators<Datas>> {
18
18
  query<K extends Key<V>>(collection: K, filters?: Filters<ValidatorType<V[K]>>, sorts?: Sorts<ValidatorType<V[K]>>, limit?: number | null): DataQuery<ValidatorType<V[K]>>;
19
19
  /** Reference a document in a collection in this model. */
20
20
  doc<K extends Key<V>>(collection: K, id: string): DataDocument<ValidatorType<V[K]>>;
21
- /** Perform a write on this database and return the `Write` instance representing the changes. */
22
- write(write: Write): Promise<Write>;
23
- /** Perform multiple writes on this database by combining them into a single `Writes` instance representing the changes. */
24
- writes(...writes: (Write | undefined)[]): Promise<Writes>;
21
+ /** Create a writer for this database from a set of separate writes. */
22
+ writer(...writes: Nullish<Write>[]): Write;
23
+ /** Perform one or more writes on this database and return the `Writes` instance representing the combined changes. */
24
+ write(...writes: Nullish<Write>[]): Promise<Write>;
25
25
  }
26
26
  /** A documents reference within a specific database. */
27
27
  export declare class DataQuery<T extends Data = Data> extends Query<T> implements Observable<Results<T>>, Validatable<Results<T>>, Iterable<Entry<T>> {
@@ -81,19 +81,19 @@ export declare class DataQuery<T extends Data = Data> extends Query<T> implement
81
81
  * @param data Complete data to set the document to.
82
82
  * @return Nothing (possibly promised).
83
83
  */
84
- set(data: T): void | PromiseLike<void>;
84
+ set(data: T): number | PromiseLike<number>;
85
85
  /**
86
86
  * Update all matching documents with the same partial value.
87
87
  *
88
88
  * @param updates `Update` instance or set of updates to apply to every matching document.
89
89
  * @return Nothing (possibly promised).
90
90
  */
91
- update(updates: Update<T> | PropUpdates<T>): void | PromiseLike<void>;
91
+ update(updates: Update<T> | PropUpdates<T>): number | PromiseLike<number>;
92
92
  /**
93
93
  * Delete all matching documents.
94
94
  * @return Nothing (possibly promised).
95
95
  */
96
- delete(): void | PromiseLike<void>;
96
+ delete(): number | PromiseLike<number>;
97
97
  /** Iterate over the resuls (will throw `Promise` if the results are asynchronous). */
98
98
  [Symbol.iterator](): Iterator<Entry<T>, void>;
99
99
  /** Validate a set of results for this query reference. */
package/db/Database.js CHANGED
@@ -1,4 +1,4 @@
1
- import { callAsync, getFirstItem, throwAsync, validate, toMap, countItems, TransformObserver, } from "../util/index.js";
1
+ import { callAsync, getFirstItem, throwAsync, validate, toMap, countItems, TransformObserver, NOT_NULLISH, } from "../util/index.js";
2
2
  import { DataUpdate, Update } from "../update/index.js";
3
3
  import { Feedback, InvalidFeedback } from "../feedback/index.js";
4
4
  import { Filters, Query, EqualFilter } from "../query/index.js";
@@ -25,14 +25,13 @@ export class Database {
25
25
  doc(collection, id) {
26
26
  return new DataDocument(this.provider, this.validators[collection], collection, id);
27
27
  }
28
- /** Perform a write on this database and return the `Write` instance representing the changes. */
29
- async write(write) {
30
- await write.transform(this);
31
- return write;
28
+ /** Create a writer for this database from a set of separate writes. */
29
+ writer(...writes) {
30
+ return new Writes(...writes.filter(NOT_NULLISH));
32
31
  }
33
- /** Perform multiple writes on this database by combining them into a single `Writes` instance representing the changes. */
34
- async writes(...writes) {
35
- const write = new Writes(...writes);
32
+ /** Perform one or more writes on this database and return the `Writes` instance representing the combined changes. */
33
+ async write(...writes) {
34
+ const write = new Writes(...writes.filter(NOT_NULLISH));
36
35
  await write.transform(this);
37
36
  return write;
38
37
  }
@@ -7,23 +7,15 @@ import { DataQuery } from "./Database.js";
7
7
  * - If you don't pass in initial values, it will autoload the first page.
8
8
  */
9
9
  export declare class Pagination<T extends Data> extends State<ResultsMap<T>> implements Iterable<Entry<T>> {
10
+ protected _pending: boolean;
10
11
  readonly ref: DataQuery<T>;
11
12
  readonly limit: number;
12
- readonly startLoading: boolean;
13
- readonly startDone: boolean;
14
- readonly endLoading: boolean;
15
- readonly endDone: boolean;
16
13
  constructor(ref: DataQuery<T>);
17
- /**
18
- * Load more results before the start.
19
- * - Promise that needs to be handled.
20
- */
21
- loadStart: () => Promise<void>;
22
14
  /**
23
15
  * Load more results after the end.
24
16
  * - Promise that needs to be handled.
25
17
  */
26
- loadEnd: () => Promise<void>;
18
+ more: () => Promise<void>;
27
19
  /**
28
20
  * Merge more results into this pagination.
29
21
  * @return The change in the number of results.
package/db/Pagination.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getFirstItem, getLastItem, assertLength, assertNumber, yieldMerged, toMap } from "../util/index.js";
1
+ import { getLastItem, assertLength, assertNumber, yieldMerged, toMap, LOADING } from "../util/index.js";
2
2
  import { State } from "../stream/index.js";
3
3
  import { ConditionError } from "../index.js";
4
4
  /**
@@ -9,56 +9,33 @@ import { ConditionError } from "../index.js";
9
9
  export class Pagination extends State {
10
10
  constructor(ref) {
11
11
  super();
12
- this.startLoading = false;
13
- this.startDone = false;
14
- this.endLoading = false;
15
- this.endDone = false;
16
- /**
17
- * Load more results before the start.
18
- * - Promise that needs to be handled.
19
- */
20
- this.loadStart = async () => {
21
- if (this.closed)
22
- throw new ConditionError();
23
- if (!this.startLoading) {
24
- this.startLoading = true;
25
- if (!this.loading) {
26
- const firstItem = getFirstItem(this.value);
27
- if (firstItem) {
28
- const next = await this.ref.after(firstItem[0], firstItem[1]).resultsMap;
29
- this.startDone = next.size < this.limit;
30
- this.startLoading = false;
31
- return this.merge(next);
32
- }
33
- }
34
- const next = await this.ref.resultsMap;
35
- this.startDone = next.size < this.limit;
36
- this.startLoading = false;
37
- return this.next(next);
38
- }
39
- };
12
+ this._pending = false; // Prevents double-loading.
40
13
  /**
41
14
  * Load more results after the end.
42
15
  * - Promise that needs to be handled.
43
16
  */
44
- this.loadEnd = async () => {
17
+ this.more = async () => {
45
18
  if (this.closed)
46
- throw new ConditionError();
47
- if (!this.endLoading) {
48
- this.endLoading = true;
19
+ throw new ConditionError("Pagination is closed");
20
+ if (!this._pending) {
21
+ this._pending = true;
49
22
  if (!this.loading) {
50
23
  const lastItem = getLastItem(this.value);
51
24
  if (lastItem) {
52
25
  const next = await this.ref.after(lastItem[0], lastItem[1]).resultsMap;
53
- this.endDone = next.size < this.limit;
54
- this.endLoading = false;
55
- return this.merge(next);
26
+ this.merge(next);
27
+ if (next.size < this.limit)
28
+ this.complete();
29
+ this._pending = false;
30
+ return;
56
31
  }
57
32
  }
33
+ this._value === LOADING;
58
34
  const next = await this.ref.resultsMap;
59
- this.endDone = next.size < this.limit;
60
- this.endLoading = false;
61
- return this.next(next);
35
+ this.next(next);
36
+ if (next.size < this.limit)
37
+ this.complete();
38
+ this._pending = false;
62
39
  }
63
40
  };
64
41
  this.ref = ref;
package/db/Write.d.ts CHANGED
@@ -12,7 +12,7 @@ export declare abstract class Write implements Transformable<Database, void | Pr
12
12
  */
13
13
  export declare class Writes extends Write {
14
14
  readonly writes: ImmutableArray<Write>;
15
- constructor(...writes: (Write | undefined)[]);
15
+ constructor(...writes: Write[]);
16
16
  transform(db: Database): Promise<void>;
17
17
  }
18
18
  /** Represent a write made to a single document in a database. */
package/db/Write.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DataUpdate, Update } from "../update/index.js";
2
- import { transform, IS_DEFINED } from "../util/index.js";
2
+ import { transform } from "../util/index.js";
3
3
  /** Represent a write made to a database. */
4
4
  export class Write {
5
5
  }
@@ -11,7 +11,7 @@ export class Write {
11
11
  export class Writes extends Write {
12
12
  constructor(...writes) {
13
13
  super();
14
- this.writes = writes.filter(IS_DEFINED);
14
+ this.writes = writes;
15
15
  }
16
16
  async transform(db) {
17
17
  for (const writes of this.writes)
@@ -17,7 +17,7 @@ export declare class FirestoreClientProvider extends Provider implements Asynchr
17
17
  delete<T extends Data>(ref: DataDocument<T>): Promise<void>;
18
18
  getQuery<T extends Data>(ref: DataQuery<T>): Promise<Results<T>>;
19
19
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
20
- setQuery<T extends Data>(ref: DataQuery<T>, data: T): Promise<void>;
21
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<void>;
22
- deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<void>;
20
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T): Promise<number>;
21
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<number>;
22
+ deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<number>;
23
23
  }
@@ -109,22 +109,18 @@ export class FirestoreClientProvider extends Provider {
109
109
  }
110
110
  async setQuery(ref, data) {
111
111
  const snapshot = await getDocs(getQuery(this.firestore, ref));
112
- if (data instanceof Update) {
113
- const updates = getFieldValues(data);
114
- await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, updates)));
115
- }
116
- else if (data)
117
- await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data)));
118
- else
119
- await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
112
+ await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data)));
113
+ return snapshot.size;
120
114
  }
121
115
  async updateQuery(ref, updates) {
122
116
  const snapshot = await getDocs(getQuery(this.firestore, ref));
123
117
  const fieldValues = getFieldValues(updates);
124
118
  await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, fieldValues)));
119
+ return snapshot.size;
125
120
  }
126
121
  async deleteQuery(ref) {
127
122
  const snapshot = await getDocs(getQuery(this.firestore, ref));
128
123
  await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
124
+ return snapshot.size;
129
125
  }
130
126
  }
@@ -17,7 +17,7 @@ export declare class FirestoreClientProvider extends Provider implements Asynchr
17
17
  delete<T extends Data>(ref: DataDocument<T>): Promise<void>;
18
18
  getQuery<T extends Data>(ref: DataQuery<T>): Promise<Results<T>>;
19
19
  subscribeQuery(): Unsubscriber;
20
- setQuery<T extends Data>(ref: DataQuery<T>, data: T | Update<T> | undefined): Promise<void>;
21
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<void>;
22
- deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<void>;
20
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T | Update<T> | undefined): Promise<number>;
21
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<number>;
22
+ deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<number>;
23
23
  }
@@ -110,14 +110,17 @@ export class FirestoreClientProvider extends Provider {
110
110
  async setQuery(ref, data) {
111
111
  const snapshot = await getDocs(getQuery(this.firestore, ref));
112
112
  await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data)));
113
+ return snapshot.size;
113
114
  }
114
115
  async updateQuery(ref, updates) {
115
116
  const snapshot = await getDocs(getQuery(this.firestore, ref));
116
117
  const fieldValues = getFieldValues(updates);
117
118
  await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, fieldValues)));
119
+ return snapshot.size;
118
120
  }
119
121
  async deleteQuery(ref) {
120
122
  const snapshot = await getDocs(getQuery(this.firestore, ref));
121
123
  await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
124
+ return snapshot.size;
122
125
  }
123
126
  }
@@ -15,7 +15,7 @@ export declare class FirestoreServerProvider extends Provider implements Asynchr
15
15
  delete<T extends Data>(ref: DataDocument<T>): Promise<void>;
16
16
  getQuery<T extends Data>(ref: DataQuery<T>): Promise<Iterable<Entry<T>>>;
17
17
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
18
- setQuery<T extends Data>(ref: DataQuery<T>, data: T | Update<T> | undefined): Promise<void>;
19
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<void>;
20
- deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<void>;
18
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T | Update<T> | undefined): Promise<number>;
19
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<number>;
20
+ deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<number>;
21
21
  }
@@ -104,28 +104,31 @@ export class FirestoreServerProvider extends Provider {
104
104
  return getQuery(this.firestore, ref).onSnapshot(snapshot => dispatchNext(observer, getResults(snapshot)), thrown => dispatchError(observer, thrown));
105
105
  }
106
106
  async setQuery(ref, data) {
107
- await bulkWrite(this.firestore, ref, (w, s) => void w.set(s.ref, data));
107
+ return await bulkWrite(this.firestore, ref, (w, s) => void w.set(s.ref, data));
108
108
  }
109
109
  async updateQuery(ref, updates) {
110
110
  const fieldValues = getFieldValues(updates);
111
- await bulkWrite(this.firestore, ref, (w, s) => void w.update(s.ref, fieldValues));
111
+ return await bulkWrite(this.firestore, ref, (w, s) => void w.update(s.ref, fieldValues));
112
112
  }
113
113
  async deleteQuery(ref) {
114
- await bulkWrite(this.firestore, ref, (w, s) => void w.delete(s.ref));
114
+ return await bulkWrite(this.firestore, ref, (w, s) => void w.delete(s.ref));
115
115
  }
116
116
  }
117
117
  /** Perform a bulk update on a set of documents using a `BulkWriter` */
118
118
  async function bulkWrite(firestore, ref, callback) {
119
+ let count = 0;
119
120
  const writer = firestore.bulkWriter();
120
121
  const query = getQuery(firestore, ref).limit(BATCH_SIZE).select(); // `select()` turs the query into a field mask query (with no field masks) which saves data transfer and memory.
121
122
  let current = query;
122
123
  while (current) {
123
- const snapshot = await current.get();
124
- for (const s of snapshot.docs)
124
+ const { docs, size } = await current.get();
125
+ count += size;
126
+ for (const s of docs)
125
127
  callback(writer, s);
126
- current = snapshot.size >= BATCH_SIZE && query.startAfter(snapshot.docs.pop()).select();
128
+ current = size >= BATCH_SIZE && query.startAfter(docs.pop()).select();
127
129
  void writer.flush();
128
130
  }
129
131
  await writer.close();
132
+ return count;
130
133
  }
131
134
  const BATCH_SIZE = 1000;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.26.0",
14
+ "version": "1.29.0",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -27,9 +27,9 @@ export declare class CacheProvider extends ThroughProvider implements Asynchrono
27
27
  private _cacheResults;
28
28
  getQuery<T extends Data>(ref: DataQuery<T>): Promise<Results<T>>;
29
29
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
30
- setQuery<T extends Data>(ref: DataQuery<T>, data: T): Promise<void>;
31
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<void>;
32
- deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<void>;
30
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T): Promise<number>;
31
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): Promise<number>;
32
+ deleteQuery<T extends Data>(ref: DataQuery<T>): Promise<number>;
33
33
  /** Reset this provider and clear all data. */
34
34
  reset(): void;
35
35
  }
@@ -79,16 +79,19 @@ export class CacheProvider extends ThroughProvider {
79
79
  return super.subscribeQuery(ref, new TransformObserver(results => this._cacheResults(ref, results), observer));
80
80
  }
81
81
  async setQuery(ref, data) {
82
- await super.setQuery(ref, data);
82
+ const count = await super.setQuery(ref, data);
83
83
  this.cache.setQuery(ref, data);
84
+ return count;
84
85
  }
85
86
  async updateQuery(ref, updates) {
86
- await super.updateQuery(ref, updates);
87
+ const count = await super.updateQuery(ref, updates);
87
88
  this.cache.updateQuery(ref, updates);
89
+ return count;
88
90
  }
89
91
  async deleteQuery(ref) {
90
- await super.deleteQuery(ref);
92
+ const count = await super.deleteQuery(ref);
91
93
  this.cache.deleteQuery(ref);
94
+ return count;
92
95
  }
93
96
  /** Reset this provider and clear all data. */
94
97
  reset() {
@@ -19,9 +19,9 @@ export declare class MemoryProvider extends Provider implements SynchronousProvi
19
19
  delete<T extends Data>(ref: DataDocument<T>): void;
20
20
  getQuery<T extends Data>(ref: DataQuery<T>): Results<T>;
21
21
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
22
- setQuery<T extends Data>(ref: DataQuery<T>, data: T): void;
23
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): void;
24
- deleteQuery<T extends Data>(ref: DataQuery<T>): void;
22
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T): number;
23
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): number;
24
+ deleteQuery<T extends Data>(ref: DataQuery<T>): number;
25
25
  /** Reset this provider and clear all data. */
26
26
  reset(): void;
27
27
  }
@@ -86,22 +86,34 @@ export class MemoryProvider extends Provider {
86
86
  const table = this._table(ref);
87
87
  // If there's a limit set: run the full query.
88
88
  // If there's no limit set: only need to run the filtering (more efficient because sort order doesn't matter).
89
- for (const [id] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data))
89
+ let count = 0;
90
+ for (const [id] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data)) {
90
91
  table.write(id, data);
92
+ count++;
93
+ }
94
+ return count;
91
95
  }
92
96
  updateQuery(ref, updates) {
93
97
  const table = this._table(ref);
94
98
  // If there's a limit set: run the full query.
95
99
  // If there's no limit set: only need to run the filtering (more efficient because sort order doesn't matter).
96
- for (const [id, existing] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data))
100
+ let count = 0;
101
+ for (const [id, existing] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data)) {
97
102
  table.write(id, updates.transform(existing));
103
+ count++;
104
+ }
105
+ return count;
98
106
  }
99
107
  deleteQuery(ref) {
100
108
  const table = this._table(ref);
101
109
  // If there's a limit set: run the full query.
102
110
  // If there's no limit set: only need to run the filtering (more efficient because sort order doesn't matter).
103
- for (const [id] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data))
111
+ let count = 0;
112
+ for (const [id] of ref.limit ? ref.transform(table.data) : ref.filters.transform(table.data)) {
104
113
  table.write(id, undefined);
114
+ count++;
115
+ }
116
+ return count;
105
117
  }
106
118
  /** Reset this provider and clear all data. */
107
119
  reset() {
@@ -77,20 +77,23 @@ export declare abstract class Provider {
77
77
  *
78
78
  * @param ref Documents reference specifying which collection to set.
79
79
  * @param value Value to set the document to.
80
+ * @return Number of documents that were set.
80
81
  */
81
- abstract setQuery<T extends Data>(ref: DataQuery<T>, data: T): void | PromiseLike<void>;
82
+ abstract setQuery<T extends Data>(ref: DataQuery<T>, data: T): number | PromiseLike<number>;
82
83
  /**
83
84
  * Update the data of all matching documents.
84
85
  *
85
86
  * @param ref Documents reference specifying which collection to set.
86
87
  * @param updates Update instance to set the document to.
88
+ * @return Number of documents that were updated.
87
89
  */
88
- abstract updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): void | PromiseLike<void>;
90
+ abstract updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): number | PromiseLike<number>;
89
91
  /**
90
92
  * Delete all matching documents.
91
93
  * @param ref Document reference specifying which document to delete.
94
+ * @return Number of documents that were deleted.
92
95
  */
93
- abstract deleteQuery<T extends Data>(ref: DataQuery<T>): void | PromiseLike<void>;
96
+ abstract deleteQuery<T extends Data>(ref: DataQuery<T>): number | PromiseLike<number>;
94
97
  }
95
98
  /** Provider with a fully synchronous interface */
96
99
  export interface SynchronousProvider extends Provider {
@@ -100,9 +103,9 @@ export interface SynchronousProvider extends Provider {
100
103
  update<T extends Data>(ref: DataDocument<T>, value: Update<T>): void;
101
104
  delete<T extends Data>(ref: DataDocument<T>): void;
102
105
  getQuery<T extends Data>(ref: DataQuery<T>): Results<T>;
103
- setQuery<T extends Data>(ref: DataQuery<T>, value: T): void;
104
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): void;
105
- deleteQuery<T extends Data>(ref: DataQuery<T>): void;
106
+ setQuery<T extends Data>(ref: DataQuery<T>, value: T): number;
107
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): number;
108
+ deleteQuery<T extends Data>(ref: DataQuery<T>): number;
106
109
  }
107
110
  /** Provider with a fully asynchronous interface */
108
111
  export interface AsynchronousProvider extends Provider {
@@ -112,7 +115,7 @@ export interface AsynchronousProvider extends Provider {
112
115
  update<T extends Data>(ref: DataDocument<T>, updates: Update<T>): PromiseLike<void>;
113
116
  delete<T extends Data>(ref: DataDocument<T>): PromiseLike<void>;
114
117
  getQuery<T extends Data>(ref: DataQuery<T>): PromiseLike<Results<T>>;
115
- setQuery<T extends Data>(ref: DataQuery<T>, value: T): PromiseLike<void>;
116
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): PromiseLike<void>;
117
- deleteQuery<T extends Data>(ref: DataQuery<T>): PromiseLike<void>;
118
+ setQuery<T extends Data>(ref: DataQuery<T>, value: T): PromiseLike<number>;
119
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): PromiseLike<number>;
120
+ deleteQuery<T extends Data>(ref: DataQuery<T>): PromiseLike<number>;
118
121
  }
@@ -16,9 +16,9 @@ export declare class ThroughProvider extends Provider {
16
16
  delete<T extends Data>(ref: DataDocument<T>): void | PromiseLike<void>;
17
17
  getQuery<T extends Data>(ref: DataQuery<T>): Results<T> | PromiseLike<Results<T>>;
18
18
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
19
- setQuery<T extends Data>(ref: DataQuery<T>, data: T): void | PromiseLike<void>;
20
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): void | PromiseLike<void>;
21
- deleteQuery<T extends Data>(ref: DataQuery<T>): void | PromiseLike<void>;
19
+ setQuery<T extends Data>(ref: DataQuery<T>, data: T): number | PromiseLike<number>;
20
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): number | PromiseLike<number>;
21
+ deleteQuery<T extends Data>(ref: DataQuery<T>): number | PromiseLike<number>;
22
22
  }
23
23
  /** Find a specific source provider in a database's provider stack. */
24
24
  export declare function findSourceProvider<P extends Provider>(provider: Provider, type: Class<P>): P;
@@ -11,6 +11,6 @@ export declare class ValidationProvider extends ThroughProvider {
11
11
  update<T extends Data>(ref: DataDocument<T>, updates: Update<T>): void | PromiseLike<void>;
12
12
  getQuery<T extends Data>(ref: DataQuery<T>): Results<T> | PromiseLike<Results<T>>;
13
13
  subscribeQuery<T extends Data>(ref: DataQuery<T>, observer: Observer<Results<T>>): Unsubscriber;
14
- setQuery<T extends Data>(ref: DataQuery<T>, value: T): void | PromiseLike<void>;
15
- updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): void | PromiseLike<void>;
14
+ setQuery<T extends Data>(ref: DataQuery<T>, value: T): number | PromiseLike<number>;
15
+ updateQuery<T extends Data>(ref: DataQuery<T>, updates: Update<T>): number | PromiseLike<number>;
16
16
  }
@@ -1,9 +1,24 @@
1
- import { Key, Data, PropTransformers } from "../util/index.js";
1
+ import { Key, Data, PropTransformers, Result } from "../util/index.js";
2
2
  import { State } from "./State.js";
3
- /** State that stores an array and has additional methods to help with that. */
3
+ /** State that stores a data object and has additional methods to help with that. */
4
4
  export declare class DataState<T extends Data> extends State<T> {
5
+ /** Get the data value of this state. */
6
+ get data(): T;
5
7
  /** Set a prop in this object to a new value. */
6
8
  set<K extends Key<T>>(key: K, value: T[K]): void;
7
9
  /** Update several props in this object. */
8
10
  update(updates: PropTransformers<T>): void;
9
11
  }
12
+ /** State that stores an optional data object and has additional methods to help with that. */
13
+ export declare class ResultState<T extends Data> extends State<Result<T>> {
14
+ /** Get the result value of this state. */
15
+ get result(): Result<T>;
16
+ /** Get current data value of this state (or throw `Promise` that resolves to the next required value). */
17
+ get data(): T;
18
+ /** Set a prop in this object to a new value. */
19
+ set<K extends Key<T>>(key: K, value: T[K]): void;
20
+ /** Update several props in this object. */
21
+ update(updates: PropTransformers<T>): void;
22
+ /** Delete this result. */
23
+ delete(): void;
24
+ }
@@ -1,13 +1,44 @@
1
- import { withProp, transformData } from "../util/index.js";
1
+ import { withProp, transformData, NOERROR, LOADING, awaitNext, getData } from "../util/index.js";
2
2
  import { State } from "./State.js";
3
- /** State that stores an array and has additional methods to help with that. */
3
+ /** State that stores a data object and has additional methods to help with that. */
4
4
  export class DataState extends State {
5
+ /** Get the data value of this state. */
6
+ get data() {
7
+ return this.value;
8
+ }
5
9
  /** Set a prop in this object to a new value. */
6
10
  set(key, value) {
7
- this.next(withProp(this.value, key, value));
11
+ this.next(withProp(this.data, key, value));
8
12
  }
9
13
  /** Update several props in this object. */
10
14
  update(updates) {
11
- this.next(transformData(this.value, updates));
15
+ this.next(transformData(this.data, updates));
16
+ }
17
+ }
18
+ /** State that stores an optional data object and has additional methods to help with that. */
19
+ export class ResultState extends State {
20
+ /** Get the result value of this state. */
21
+ get result() {
22
+ return this.value;
23
+ }
24
+ /** Get current data value of this state (or throw `Promise` that resolves to the next required value). */
25
+ get data() {
26
+ if (this.reason !== NOERROR)
27
+ throw this.reason;
28
+ if (this._value === LOADING)
29
+ throw awaitNext(this).then(getData);
30
+ return getData(this._value);
31
+ }
32
+ /** Set a prop in this object to a new value. */
33
+ set(key, value) {
34
+ this.next(withProp(this.data, key, value));
35
+ }
36
+ /** Update several props in this object. */
37
+ update(updates) {
38
+ this.next(transformData(this.data, updates));
39
+ }
40
+ /** Delete this result. */
41
+ delete() {
42
+ this.next(undefined);
12
43
  }
13
44
  }
package/stream/State.d.ts CHANGED
@@ -28,8 +28,6 @@ export declare class State<T> extends Stream<T> {
28
28
  /** Most recently dispatched value (or throw `Promise` that resolves to the next value). */
29
29
  get value(): T;
30
30
  protected _value: T | typeof LOADING;
31
- /** Get current required value (or throw `Promise` that resolves to the next required value). */
32
- get data(): Exclude<T, null | undefined>;
33
31
  /** Is there a current value, or is it still loading. */
34
32
  get loading(): boolean;
35
33
  /** Apply a transformer to this state. */
package/stream/State.js CHANGED
@@ -1,5 +1,5 @@
1
1
  var _a;
2
- import { LOADING, NOERROR, dispatchNext, dispatchError, dispatchComplete, transform, getRequired, awaitNext, } from "../util/index.js";
2
+ import { LOADING, NOERROR, dispatchNext, dispatchError, dispatchComplete, transform, awaitNext, } from "../util/index.js";
3
3
  import { Stream } from "./Stream.js";
4
4
  export class State extends Stream {
5
5
  constructor() {
@@ -22,14 +22,6 @@ export class State extends Stream {
22
22
  throw awaitNext(this);
23
23
  return this._value;
24
24
  }
25
- /** Get current required value (or throw `Promise` that resolves to the next required value). */
26
- get data() {
27
- if (this.reason !== NOERROR)
28
- throw this.reason;
29
- if (this._value === LOADING)
30
- throw awaitNext(this).then(getRequired);
31
- return getRequired(this._value);
32
- }
33
25
  /** Is there a current value, or is it still loading. */
34
26
  get loading() {
35
27
  return this._value === LOADING;
package/util/data.d.ts CHANGED
@@ -25,9 +25,6 @@ export declare const isData: <T extends Data>(value: unknown) => value is T;
25
25
  /** Turn a data object into an array of entries (if it isn't one already). */
26
26
  export declare function toProps<T extends Data>(input: T): ImmutableArray<Prop<T>>;
27
27
  export declare function toProps<T extends Data>(input: Partial<T>): ImmutableArray<Prop<T>>;
28
- /** Get a required value (returns value or throws `RequiredError` if value is `null` or `undefined`). */
29
- export declare function getRequired<T>(v: T): Exclude<T, null | undefined>;
30
- export declare function getRequired<T>(v: T | null | undefined): T;
31
28
  /** Get the data of a result (returns data or throws `RequiredError` if value is `null` or `undefined`). */
32
29
  export declare function getData<T extends Data>(result: Result<T>): T;
33
30
  /**
package/util/data.js CHANGED
@@ -4,11 +4,6 @@ export const isData = (value) => typeof value === "object" && value !== null;
4
4
  export function toProps(input) {
5
5
  return Object.entries(input);
6
6
  }
7
- export function getRequired(v) {
8
- if (v === undefined || v === null)
9
- throw new RequiredError(v === null ? "Required value is null" : "Required value is undefined");
10
- return v;
11
- }
12
7
  /** Get the data of a result (returns data or throws `RequiredError` if value is `null` or `undefined`). */
13
8
  export function getData(result) {
14
9
  if (!result)
package/util/null.d.ts CHANGED
@@ -3,4 +3,15 @@ export declare const IS_NULL: (v: unknown) => v is null;
3
3
  /** Is a value not null? */
4
4
  export declare const NOT_NULL: <T>(v: T | null) => v is T;
5
5
  /** Function that always returns null. */
6
- export declare const NULL: () => null;
6
+ export declare const GET_NULL: () => null;
7
+ /** Nullish is `null` or `undefined` */
8
+ export declare type Nullish<T> = T | null | undefined;
9
+ /** Nullish is `null` or `undefined` */
10
+ export declare type NotNullish<T> = Exclude<T, null | undefined>;
11
+ /** Is a value nullish? */
12
+ export declare const IS_NULLISH: <T>(v: Nullish<T>) => v is null | undefined;
13
+ /** Is a value nullish? */
14
+ export declare const NOT_NULLISH: <T>(v: Nullish<T>) => v is T;
15
+ /** Get a required value (returns value or throws `RequiredError` if value is `null` or `undefined`). */
16
+ export declare function getRequired<T>(v: T): NotNullish<T>;
17
+ export declare function getRequired<T>(v: Nullish<T>): T;
package/util/null.js CHANGED
@@ -1,6 +1,16 @@
1
+ import { RequiredError } from "../error/index.js";
1
2
  /** Is a value null? */
2
3
  export const IS_NULL = (v) => v === null;
3
4
  /** Is a value not null? */
4
5
  export const NOT_NULL = (v) => v !== null;
5
6
  /** Function that always returns null. */
6
- export const NULL = () => null;
7
+ export const GET_NULL = () => null;
8
+ /** Is a value nullish? */
9
+ export const IS_NULLISH = (v) => v === null || v === undefined;
10
+ /** Is a value nullish? */
11
+ export const NOT_NULLISH = (v) => v !== null && v !== undefined;
12
+ export function getRequired(v) {
13
+ if (v === undefined || v === null)
14
+ throw new RequiredError("Required value is missing");
15
+ return v;
16
+ }
package/util/object.d.ts CHANGED
@@ -87,3 +87,11 @@ export declare function deleteEntry<T>(obj: MutableObject<T>, key: string, value
87
87
  * @param entries Set of keys or entries to remove.
88
88
  */
89
89
  export declare function deleteEntries<T>(obj: MutableObject<T>, keys: ImmutableArray<string>): void;
90
+ /** Type that represents an empty object. */
91
+ export declare type EmptyObject = {
92
+ readonly [K in never]: never;
93
+ };
94
+ /** An empty object. */
95
+ export declare const EMPTY_OBJECT: EmptyObject;
96
+ /** Function that returns an an empty object. */
97
+ export declare const GET_EMPTY_OBJECT: () => EmptyObject;
package/util/object.js CHANGED
@@ -98,3 +98,7 @@ export function deleteEntries(obj, keys) {
98
98
  for (const key of keys)
99
99
  delete obj[key];
100
100
  }
101
+ /** An empty object. */
102
+ export const EMPTY_OBJECT = {};
103
+ /** Function that returns an an empty object. */
104
+ export const GET_EMPTY_OBJECT = () => EMPTY_OBJECT;
@@ -4,6 +4,4 @@ export declare const IS_UNDEFINED: (v: unknown) => v is undefined;
4
4
  export declare const IS_DEFINED: <T>(v: T | undefined) => v is T;
5
5
  export declare const NOT_UNDEFINED: <T>(v: T | undefined) => v is T;
6
6
  /** Function that always returns undefined. */
7
- export declare const UNDEFINED: () => undefined;
8
- /** Function that always returns void. */
9
- export declare const VOID: () => void;
7
+ export declare const GET_UNDEFINED: () => undefined;
package/util/undefined.js CHANGED
@@ -4,6 +4,4 @@ export const IS_UNDEFINED = (v) => v === undefined;
4
4
  export const IS_DEFINED = (v) => v !== undefined;
5
5
  export const NOT_UNDEFINED = IS_DEFINED;
6
6
  /** Function that always returns undefined. */
7
- export const UNDEFINED = () => undefined;
8
- /** Function that always returns void. */
9
- export const VOID = UNDEFINED;
7
+ export const GET_UNDEFINED = () => undefined;
package/util/url.d.ts CHANGED
@@ -1,10 +1,9 @@
1
+ /** Things that can be converted to a URL instance. */
2
+ export declare type PossibleURL = string | URL;
3
+ export declare type PossibleOptionalURL = PossibleURL | null;
4
+ /** Convert a possible URL to a URL or return `null` if conversion fails. */
5
+ export declare function toURL(url: PossibleURL, base?: PossibleOptionalURL): URL | null;
6
+ /** Convert a possible URL to a URL but throw `AssertionError` if conversion fails. */
7
+ export declare function getURL(input: PossibleURL, base?: PossibleOptionalURL): URL;
1
8
  /** Just get important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
2
- export declare const formatUrl: (url: string | URL) => string;
3
- /**
4
- * Convert a string to a URL instance or return `null` if we can't.
5
- * - Automatically prepend `https://` if there's no `:` anywhere.
6
- *
7
- * @param url Base
8
- * @param
9
- */
10
- export declare function toURL(url: string | URL, base?: URL | string | undefined): URL | null;
9
+ export declare const formatUrl: (url: PossibleURL) => string;
package/util/url.js CHANGED
@@ -1,25 +1,26 @@
1
- import { getRequired } from "./data.js";
2
- /** Just get important part of a URL, e.g. `http://shax.com/test?uid=129483` `shax.com/test` */
3
- export const formatUrl = (url) => {
4
- const { host, pathname } = getRequired(toURL(url));
5
- return `${host}${pathname.length > 1 ? pathname : ""}`;
6
- };
7
- /**
8
- * Convert a string to a URL instance or return `null` if we can't.
9
- * - Automatically prepend `https://` if there's no `:` anywhere.
10
- *
11
- * @param url Base
12
- * @param
13
- */
14
- export function toURL(url, base = typeof window === "object" ? window.location.href : undefined) {
1
+ import { AssertionError } from "../error/index.js";
2
+ /** Convert a possible URL to a URL or return `null` if conversion fails. */
3
+ export function toURL(url, base = typeof window === "object" ? window.location.href : null) {
15
4
  if (url instanceof URL)
16
5
  return url;
17
6
  if (!url)
18
7
  return null;
19
8
  try {
20
- return new URL(url, base);
9
+ return new URL(url, base || undefined);
21
10
  }
22
11
  catch (e) {
23
12
  return null;
24
13
  }
25
14
  }
15
+ /** Convert a possible URL to a URL but throw `AssertionError` if conversion fails. */
16
+ export function getURL(input, base) {
17
+ const url = toURL(input, base);
18
+ if (!url)
19
+ throw new AssertionError("Invalid URL", input);
20
+ return url;
21
+ }
22
+ /** Just get important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
23
+ export const formatUrl = (url) => {
24
+ const { host, pathname } = getURL(url);
25
+ return `${host}${pathname.length > 1 ? pathname : ""}`;
26
+ };