@procedures/collections 0.3.0 → 0.5.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/CHANGELOG.md +27 -0
- package/README.md +53 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/storage/interface.d.ts +25 -4
- package/dist/storage/interface.d.ts.map +1 -1
- package/dist/storage/interface.js.map +1 -1
- package/dist/storage/memory.d.ts +39 -7
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +104 -53
- package/dist/storage/memory.js.map +1 -1
- package/dist/testing/check.d.ts +25 -0
- package/dist/testing/check.d.ts.map +1 -0
- package/dist/testing/check.js +85 -0
- package/dist/testing/check.js.map +1 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/storage-contract.d.ts +70 -0
- package/dist/testing/storage-contract.d.ts.map +1 -0
- package/dist/testing/storage-contract.js +527 -0
- package/dist/testing/storage-contract.js.map +1 -0
- package/package.json +5 -1
- package/src/storage/interface.ts +26 -4
- package/src/storage/memory.ts +120 -54
- package/src/testing/check.ts +81 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/storage-contract.ts +623 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @procedures/collections
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bc74091: The storages agree on the contract of `CollectionStorage`. `InMemoryStorage` changes in three points:
|
|
8
|
+
|
|
9
|
+
- It keeps copies. Each write takes a copy of the value (`structuredClone`), and each read gives a new copy. Before, it kept the values themselves. The option `copies: false` gives the old behavior for a caller that changes no value.
|
|
10
|
+
- A delta of `getChangesSince` follows the order of the last change of each id. Before, it followed the first write of each id.
|
|
11
|
+
- After `close()`, each operation rejects with an error that says "closed", and a second `close()` does nothing. Before, `close()` emptied the storage, and the storage kept working.
|
|
12
|
+
|
|
13
|
+
The contract suite of `@procedures/collections/testing` has no options `copies` and `nullValues` now. It tests the copies, a stored `null`, the order of each delta and the operations after `close()` for each storage. The helper `byId` is removed.
|
|
14
|
+
- 89481b0: The contract of `CollectionStorage` has the optional method `getIds()`. It gives the ids in the order of the entries, and it does not read or copy the values.
|
|
15
|
+
|
|
16
|
+
- `InMemoryStorage.getIds()` gives the keys of its map, without a `structuredClone` of the values.
|
|
17
|
+
- The contract suite of `@procedures/collections/testing` tests `getIds()`: the order, a new array for each call, the ids of `getEntries()`, and the rejection after `close()`. A storage without `getIds()` skips the case.
|
|
18
|
+
|
|
19
|
+
## 0.4.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- 0abf2ec: The new test-only entry `@procedures/collections/testing` holds one contract suite for `CollectionStorage`: `describeCollectionStorageContract(name, { create, createWithMaxTombstones, copies, nullValues }, { describe, it })`. It tests the reads, the writes, the batches, the order of the entries, the JSON values, `getChangesSince` and its versions, `flush`, `getMetadata`, `close` and the errors. A random sequence of writes runs against a `Map`. The suite imports no test framework, so the runtime entries never load vitest. `InMemoryStorage`, `SqliteStorage`, `ApiStorage` and `HybridStorage` run it.
|
|
24
|
+
|
|
25
|
+
The suite found two differences, and the contract fixes them:
|
|
26
|
+
|
|
27
|
+
- An error of a `find` predicate of `InMemoryStorage` threw at once. Each method of a storage gives a promise, so each method of `InMemoryStorage` is now `async`, and an error is a rejected promise.
|
|
28
|
+
- `getMetadata().size` of `HybridStorage` was the size of the local copy, which `remote-only` keeps empty. It is now the result of `size()`. The new `stats.localSize` gives the size of the local copy.
|
|
29
|
+
|
|
3
30
|
## 0.3.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -85,6 +85,9 @@ interface CollectionStorage<T> {
|
|
|
85
85
|
// Read Operations
|
|
86
86
|
get(id: string): Promise<T | undefined>;
|
|
87
87
|
getAll(): Promise<T[]>;
|
|
88
|
+
getEntries?(): Promise<Array<[string, T]>>;
|
|
89
|
+
getIds?(): Promise<string[]>;
|
|
90
|
+
getChangesSince?(since?: string): Promise<StorageChanges<T>>;
|
|
88
91
|
find(predicate: (item: T) => boolean): Promise<T[]>;
|
|
89
92
|
has(id: string): Promise<boolean>;
|
|
90
93
|
size(): Promise<number>;
|
|
@@ -105,20 +108,67 @@ interface CollectionStorage<T> {
|
|
|
105
108
|
}
|
|
106
109
|
```
|
|
107
110
|
|
|
111
|
+
Each storage agrees on these points:
|
|
112
|
+
|
|
113
|
+
- A storage keeps a copy of each value that a write takes, and each read gives a new copy. A change of a written or a read value does not change the stored item.
|
|
114
|
+
- A stored `null` reads back as `null`. A missing id reads back as `undefined`.
|
|
115
|
+
- The entries keep the order of the first set of each id, as a `Map` does.
|
|
116
|
+
- A delta of `getChangesSince` follows the order of the last change of each id. A full result follows the order of the entries.
|
|
117
|
+
- `getIds()` gives the ids in the order of the entries, and it does not read or copy the values. A storage that cannot give the ids cheaply does not have it. `HybridStorage` uses it for a full sync.
|
|
118
|
+
- After `close()`, each operation rejects with an error whose message says "closed". A second `close()` does nothing.
|
|
119
|
+
|
|
120
|
+
### The contract suite: `@procedures/collections/testing`
|
|
121
|
+
|
|
122
|
+
The test-only entry `@procedures/collections/testing` holds one test suite for the contract of `CollectionStorage`. A storage runs it with a factory:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { describe, it } from "vitest";
|
|
126
|
+
import { describeCollectionStorageContract, type ContractItem } from "@procedures/collections/testing";
|
|
127
|
+
|
|
128
|
+
describeCollectionStorageContract(
|
|
129
|
+
"MyStorage",
|
|
130
|
+
{
|
|
131
|
+
create: () => new MyStorage<ContractItem>(),
|
|
132
|
+
createWithMaxTombstones: (max) => new MyStorage<ContractItem>({ maxTombstones: max }),
|
|
133
|
+
},
|
|
134
|
+
{ describe, it },
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- The suite tests the reads, the writes and the batches, the order of the entries, `getIds`, and the results as new arrays.
|
|
139
|
+
- It tests the copies, the JSON values with `null`, and the writes at the same time.
|
|
140
|
+
- It tests `getChangesSince` with its versions and its order, `flush`, `getMetadata`, `close` and the errors.
|
|
141
|
+
- A case for an optional method skips a storage without the method.
|
|
142
|
+
- A random sequence of writes runs against a `Map`. Each saved version must give the right changes, in the order of the last change of each id.
|
|
143
|
+
- The suite imports no test framework. The caller gives `describe` and `it`, and a failed check throws `ContractViolation`. The runtime entries do not import this entry.
|
|
144
|
+
- The suite has no option that hides a difference of a storage. Before, the options `copies` and `nullValues` declared the differences, and the suite sorted each delta.
|
|
145
|
+
|
|
146
|
+
| Storage | Package | Notes |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `InMemoryStorage` | `@procedures/collections` | It copies with `structuredClone`. The run with `copies: false` skips only the case of the copies. |
|
|
149
|
+
| `SqliteStorage` (each engine, in memory and in a file) | `@procedures/sqlite` | It keeps JSON text, so it gives copies. |
|
|
150
|
+
| `ApiStorage` | `@procedures/store` | It reads `{ found, value }` from the procedure `get`, so a stored `null` stays `null`. It has no `getIds`. |
|
|
151
|
+
| `HybridStorage` (each mode, and a queue in IndexedDB) | `@procedures/store` | It has no `getChangesSince`. |
|
|
152
|
+
|
|
108
153
|
## Storage Backends
|
|
109
154
|
|
|
110
|
-
###
|
|
155
|
+
### InMemoryStorage
|
|
111
156
|
|
|
112
157
|
Fast, in-memory storage using JavaScript Map:
|
|
113
158
|
|
|
114
159
|
```typescript
|
|
115
|
-
import {
|
|
160
|
+
import { InMemoryStorage } from "@procedures/collections";
|
|
116
161
|
|
|
117
|
-
const storage = new
|
|
162
|
+
const storage = new InMemoryStorage<User>();
|
|
118
163
|
await storage.set("user-1", { name: "Alice", age: 30 });
|
|
119
164
|
const user = await storage.get("user-1");
|
|
120
165
|
```
|
|
121
166
|
|
|
167
|
+
- The storage keeps a copy of each written value, and each read gives a new copy (`structuredClone`). A `Date` stays a `Date`. A function in a value makes the write reject.
|
|
168
|
+
- The option `copies: false` turns the copies off: the storage keeps the values themselves, and the reads give them. It is faster. Give it only when no caller changes a value after a write or a read.
|
|
169
|
+
- `getIds()` gives the ids without a copy of the values.
|
|
170
|
+
- `close()` drops the items. After it, each operation rejects, and the methods of the `Map` interface (`keys()`, `values()`, `entries()`, `forEach()`) throw.
|
|
171
|
+
|
|
122
172
|
### ApiStorage
|
|
123
173
|
|
|
124
174
|
Remote storage via RPC client:
|