@owlmeans/client-resource 0.1.18-rc.1 → 0.1.18-rc.11

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/README.md CHANGED
@@ -33,9 +33,14 @@ import type { ClientResource } from '@owlmeans/client-resource'
33
33
  const res = context.resource<ClientResource<MyRecord>>('my-resource')
34
34
  await res.save({ id: 'key', ...data })
35
35
  const record = await res.load('key')
36
+ const { items } = await res.list({ status: 'draft' })
36
37
  await res.erase() // clear all stored data
37
38
  ```
38
39
 
40
+ Reads are unpaged: `list(where)` returns every match, and `list(where, { page })` without a `size`
41
+ throws `UnsupportedArgumentError('page-without-size')`. Criteria and sorting are the shared
42
+ vocabulary from [`@owlmeans/resource`](../resource), evaluated in memory over the stored records.
43
+
39
44
  ## API
40
45
 
41
46
  ### `appendClientResource<C, T>(context, alias): T`
@@ -51,7 +56,7 @@ Extends `Resource<T>` with:
51
56
  ### `ClientDb`
52
57
 
53
58
  Low-level browser storage interface:
54
- - `get<T>(id): Promise<T>`
59
+ - `get<T>(id): Promise<T | undefined>` — a miss is `undefined`, not an error at this level
55
60
  - `set<T>(id, value): Promise<void>`
56
61
  - `has(id): Promise<boolean>`
57
62
  - `del(id): Promise<boolean>`
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/client-resource",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.511Z",
4
+ "version": "0.1.18-rc.11",
5
+ "generatedAt": "2026-09-01T16:28:56.993Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,23 +8,52 @@ user-invocable: false
8
8
  # @owlmeans/client-resource
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client-resource": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-resource": "^0.1.18-rc.11"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
15
15
  | Export | Description |
16
16
  |--------|-------------|
17
- | `makeClientResource<T>()` | Factory for an in-memory client resource |
18
- | `ClientResource<T>` types | Resource interface |
19
- | Constants | Default cache aliases |
17
+ | `appendClientResource<C, T, R>(context, alias)` | Register a client-side `Resource<R>` on the context and return the context |
18
+ | `ClientResource<T>` | `Resource<T>` + `db` (the key-value handle) and `erase()` |
19
+ | `ClientDb`, `ClientDbService` | What a storage backend implements — `get`/`set`/`has`/`del`, and the service that hands out one store per alias |
20
+ | `DEFAULT_DB_ALIAS` (`client-db`), `LIST_KEY` (`_list`) | Constants |
20
21
 
21
22
  ## Usage
22
23
 
23
24
  ```typescript
24
- import { makeClientResource } from '@owlmeans/client-resource'
25
- context.registerResource(makeClientResource<Project>('projects'))
25
+ import { appendClientResource } from '@owlmeans/client-resource'
26
+
27
+ appendClientResource<Config, Context, Project>(context, 'projects')
28
+
29
+ const projects = context.resource<ClientResource<Project>>('projects')
30
+ await projects.list({ status: ['open', 'blocked'] }, { sort: ['createdAt'] })
26
31
  ```
27
32
 
33
+ The backing store comes from `cfg.dbs` — the entry whose `alias` matches the resource's, with
34
+ `schema` naming the store (`@owlmeans/web-db` supplies the browser one). Without an entry the
35
+ resource falls back to the default `client-db` service under its own alias.
36
+
37
+ ## A key-value store dressed as a resource
38
+
39
+ Records live under their own id and a list of those ids under `LIST_KEY` is the only index there
40
+ is. So:
41
+
42
+ - `load(id)` / `get(id)` / `delete(id)` / `take(id)` address the key directly, no walk.
43
+ - `load(where)`, `get(where)`, `list`, `count` and `purge` read the whole set and evaluate through
44
+ the shared in-memory engine from `@owlmeans/resource`, which is what makes a criteria object mean
45
+ here exactly what it means against a relational store. `{ sort }` orders the result the same way.
46
+ - **Unpaged**: `list(where)` with no `size` returns every match; `size: 0` says the same thing
47
+ explicitly, and `page` without `size` throws `UnsupportedArgumentError('page-without-size')`.
48
+ - `create` mints an id when the record carries none and throws `RecordExists` for one already
49
+ stored; `update` **replaces** the whole record and throws `UnknownRecordError` for an unknown id;
50
+ `save` creates or replaces. `take(id)` **deletes** the record it returns.
51
+ - `purge(where)` refuses an empty criteria object. `erase()` empties the store, index included.
52
+
28
53
  ## Depends On
29
54
 
30
- - `@owlmeans/resource`, `@owlmeans/client-context`
55
+ - `@owlmeans/resource`, `@owlmeans/client-context`, `@scure/base`, `@noble/hashes`
56
+
57
+ ## Related
58
+
59
+ - [[web-db]] — the browser IndexedDB backend behind `ClientDbService`
@@ -1,7 +1,14 @@
1
1
  import type { ClientConfig, ClientContext } from '@owlmeans/client-context';
2
+ import type { ResourceRecord } from '@owlmeans/resource';
2
3
  type Config = ClientConfig;
3
4
  interface Context<C extends Config = Config> extends ClientContext<C> {
4
5
  }
5
- export declare const appendClientResource: <C extends Config, T extends Context<C>>(context: T, alias: string) => T;
6
+ /**
7
+ * A key-value client store dressed as a resource: records live under their own id and a list of
8
+ * those ids under {@link LIST_KEY} is the only index there is. Queries therefore read the whole
9
+ * set and run through the shared in-memory engine, which is what makes a criteria object mean
10
+ * here exactly what it means against a relational store.
11
+ */
12
+ export declare const appendClientResource: <C extends Config, T extends Context<C>, R extends ResourceRecord = ResourceRecord>(context: T, alias: string) => T;
6
13
  export {};
7
14
  //# sourceMappingURL=resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAS3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,MAAM,KAAG,CA2LxG,CAAA"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAM3E,OAAO,KAAK,EAAuC,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAM7F,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,cAAc,GAAG,cAAc,EACjF,SAAS,CAAC,EAAE,OAAO,MAAM,KAAG,CAuJ7B,CAAA"}
package/build/resource.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import { appendContextual, assertContext } from '@owlmeans/context';
2
- import { RecordExists, ResourceError, UnknownRecordError } from '@owlmeans/resource';
2
+ import { applyQuery, filterRecords, firstMatch, MisshapedRecord, RecordExists, ResourceError, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource';
3
3
  import { DEFAULT_DB_ALIAS, LIST_KEY } from './consts.js';
4
4
  import { base58 } from '@scure/base';
5
5
  import { randomBytes } from '@noble/hashes/utils';
6
+ /**
7
+ * A key-value client store dressed as a resource: records live under their own id and a list of
8
+ * those ids under {@link LIST_KEY} is the only index there is. Queries therefore read the whole
9
+ * set and run through the shared in-memory engine, which is what makes a criteria object mean
10
+ * here exactly what it means against a relational store.
11
+ */
6
12
  export const appendClientResource = (context, alias) => {
7
13
  const location = `client-resource:${alias}`;
8
14
  const assert = () => {
@@ -11,6 +17,24 @@ export const appendClientResource = (context, alias) => {
11
17
  }
12
18
  return resource.db;
13
19
  };
20
+ const ids = async () => await assert().get(LIST_KEY) ?? [];
21
+ const readAll = async () => {
22
+ const db = assert();
23
+ const records = await Promise.all((await ids()).map(id => db.get(id)));
24
+ return records.filter((record) => record != null);
25
+ };
26
+ const dropId = async (id) => {
27
+ const list = await ids();
28
+ const idx = list.indexOf(id);
29
+ if (idx > -1) {
30
+ list.splice(idx, 1);
31
+ await assert().set(LIST_KEY, list);
32
+ }
33
+ };
34
+ /** An id is a direct key read; criteria have to walk the set. */
35
+ const first = async (idOrWhere, opts) => typeof idOrWhere === 'string'
36
+ ? await assert().get(idOrWhere) ?? null
37
+ : firstMatch(await readAll(), idOrWhere, opts);
14
38
  const resource = appendContextual(alias, {
15
39
  init: async () => {
16
40
  const context = assertContext(resource.ctx, location);
@@ -18,59 +42,27 @@ export const appendClientResource = (context, alias) => {
18
42
  const dbService = context.service(config.service);
19
43
  resource.db = await dbService.initialize(config.schema ?? alias);
20
44
  },
21
- get: async (id) => {
22
- const record = await resource.load(id);
45
+ get: async (idOrWhere, opts) => {
46
+ const record = await first(idOrWhere, opts);
23
47
  if (record == null) {
24
- throw new UnknownRecordError(id);
48
+ throw new UnknownRecordError(typeof idOrWhere === 'string' ? idOrWhere : 'criteria');
25
49
  }
26
50
  return record;
27
51
  },
28
- load: async (id) => {
29
- const db = assert();
30
- return await db.get(id);
31
- },
32
- list: async (criteria, opts) => {
33
- if (criteria == null) {
34
- criteria = {};
35
- opts = { pager: { page: 0, size: 10 } };
52
+ load: async (idOrWhere, opts) => await first(idOrWhere, opts),
53
+ /** Unpaged unless a size is asked for. */
54
+ list: async (where, opts) => {
55
+ if (opts?.page != null && opts.size == null) {
56
+ throw new UnsupportedArgumentError('page-without-size');
36
57
  }
37
- if ("pager" in criteria) {
38
- opts = criteria;
39
- criteria = criteria.criteria;
40
- }
41
- const db = assert();
42
- const list = await db.get(LIST_KEY);
43
- const pager = opts?.pager ?? { page: 0, size: 10 };
44
- const conditions = Object.entries(criteria ?? {});
45
- pager.page = pager.page ?? 0;
46
- pager.size = pager.size ?? 10;
47
- const result = [];
48
- let skip = pager.page * pager.size;
49
- let total = 0;
50
- let added = 0;
51
- for (const id of list) {
52
- const record = await db.get(id);
53
- if (record != null) {
54
- if (!(conditions.length === 0 || conditions.every(([key, value]) => record[key] === value))) {
55
- continue;
56
- }
57
- if (skip-- <= 0 && added < pager.size) {
58
- added++;
59
- result.push(record);
60
- }
61
- ++total;
62
- }
63
- }
64
- return { items: result, pager: { ...pager, total } };
58
+ return applyQuery(await readAll(), where, opts);
65
59
  },
60
+ count: async (where) => filterRecords(await readAll(), where).length,
66
61
  create: async (record) => {
67
62
  const db = assert();
68
63
  record.id = record.id ?? base58.encode(randomBytes(32));
69
- if (await db.has(record.id)) {
70
- throw new RecordExists(record.id);
71
- }
72
- const list = await db.get(LIST_KEY) ?? [];
73
- if (list.includes(record.id)) {
64
+ const list = await ids();
65
+ if (list.includes(record.id) || await db.has(record.id)) {
74
66
  throw new RecordExists(record.id);
75
67
  }
76
68
  list.push(record.id);
@@ -78,76 +70,57 @@ export const appendClientResource = (context, alias) => {
78
70
  await db.set(record.id, record);
79
71
  return record;
80
72
  },
73
+ /** Replaces the stored record rather than merging into it, as the contract says. */
81
74
  update: async (record) => {
82
75
  const db = assert();
83
76
  if (record.id == null) {
84
- throw new UnknownRecordError('update');
77
+ throw new MisshapedRecord('id');
85
78
  }
86
- const update = await resource.load(record.id);
87
- if (update == null) {
79
+ if (!await db.has(record.id)) {
88
80
  throw new UnknownRecordError(record.id);
89
81
  }
90
- Object.assign(update, record);
91
- await db.set(record.id, update);
82
+ await db.set(record.id, record);
92
83
  return record;
93
84
  },
85
+ save: async (record) => record.id == null || !await assert().has(record.id)
86
+ ? await resource.create(record)
87
+ : await resource.update(record),
94
88
  delete: async (id) => {
95
- if (typeof id === 'object') {
96
- if (id.id == null) {
97
- throw new UnknownRecordError('delete');
98
- }
99
- return resource.delete(id.id);
100
- }
101
89
  const db = assert();
102
- if (!await db.has(id)) {
103
- return null;
104
- }
105
90
  const record = await db.get(id);
106
91
  if (record == null) {
107
- throw new SyntaxError('We should not try to delete record that we know that not exists');
92
+ return null;
108
93
  }
109
94
  await db.del(id);
110
- const list = await db.get(LIST_KEY) ?? [];
111
- const idx = list.indexOf(id);
112
- if (idx > -1) {
113
- list.splice(idx, 1);
114
- await db.set(LIST_KEY, list);
115
- }
95
+ await dropId(id);
116
96
  return record;
117
97
  },
118
- pick: async (id) => {
119
- if (typeof id === 'object') {
120
- if (id.id == null) {
121
- throw new UnknownRecordError('pick');
122
- }
123
- return resource.pick(id.id);
124
- }
125
- const db = assert();
126
- const record = await db.get(id);
127
- if (record != null) {
128
- await db.del(id);
129
- }
130
- else {
98
+ take: async (id) => {
99
+ const record = await resource.delete(id);
100
+ if (record == null) {
131
101
  throw new UnknownRecordError(id);
132
102
  }
133
- const list = await db.get(LIST_KEY) ?? [];
134
- const idx = list.indexOf(id);
135
- if (idx > -1) {
136
- list.splice(idx, 1);
137
- await db.set(LIST_KEY, list);
138
- }
139
103
  return record;
140
104
  },
141
- save: async (record) => {
142
- if (record.id == null || (await resource.load(record.id) == null)) {
143
- return resource.create(record);
105
+ purge: async (where) => {
106
+ if (where == null || Object.keys(where).length < 1) {
107
+ throw new UnsupportedArgumentError('purge:empty-criteria');
108
+ }
109
+ const matched = filterRecords(await readAll(), where);
110
+ for (const record of matched) {
111
+ if (record.id != null) {
112
+ await resource.delete(record.id);
113
+ }
144
114
  }
145
- return resource.update(record);
115
+ return matched.length;
146
116
  },
147
117
  erase: async () => {
148
118
  const db = assert();
149
- const list = await db.get(LIST_KEY) ?? [];
150
- await Promise.all(list.map(id => resource.delete(id)));
119
+ /** The index is emptied in one write, so a failed pass cannot leave it naming dead ids. */
120
+ for (const id of await ids()) {
121
+ await db.del(id);
122
+ }
123
+ await db.set(LIST_KEY, []);
151
124
  }
152
125
  });
153
126
  context.registerResource(resource);
@@ -1 +1 @@
1
- {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -5,7 +5,8 @@ export interface ClientDbService extends InitializedService {
5
5
  erase: () => Promise<void>;
6
6
  }
7
7
  export interface ClientDb {
8
- get: <T>(id: string) => Promise<T>;
8
+ /** `undefined` for a key the store does not hold — a miss is not an error at this level. */
9
+ get: <T>(id: string) => Promise<T | undefined>;
9
10
  set: <T>(id: string, value: T) => Promise<void>;
10
11
  has: (id: string) => Promise<boolean>;
11
12
  del: (id: string) => Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAElE,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAClC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC5F,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAElE,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,4FAA4F;IAC5F,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IAC9C,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC5F,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-resource",
3
- "version": "0.1.18-rc.1",
3
+ "version": "0.1.18-rc.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,9 +22,9 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@noble/hashes": "^1.5.0",
25
- "@owlmeans/client-context": "^0.1.18-rc.1",
26
- "@owlmeans/context": "^0.1.18-rc.1",
27
- "@owlmeans/resource": "^0.1.18-rc.1",
25
+ "@owlmeans/client-context": "^0.1.18-rc.11",
26
+ "@owlmeans/context": "^0.1.18-rc.7",
27
+ "@owlmeans/resource": "^0.1.18-rc.8",
28
28
  "@scure/base": "^2.3.0"
29
29
  },
30
30
  "devDependencies": {
package/src/resource.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import type { ClientConfig, ClientContext } from '@owlmeans/client-context'
2
2
  import { appendContextual, assertContext } from '@owlmeans/context'
3
- import { RecordExists, ResourceError, UnknownRecordError } from '@owlmeans/resource'
4
- import type { ListCriteria, ListPager, ResourceRecord } from '@owlmeans/resource'
3
+ import {
4
+ applyQuery, filterRecords, firstMatch, MisshapedRecord, RecordExists, ResourceError,
5
+ UnknownRecordError, UnsupportedArgumentError
6
+ } from '@owlmeans/resource'
7
+ import type { Criteria, FirstOptions, ListOptions, ResourceRecord } from '@owlmeans/resource'
5
8
  import { DEFAULT_DB_ALIAS, LIST_KEY } from './consts.js'
6
9
  import type { ClientDb, ClientDbService, ClientResource } from './types.js'
7
10
  import { base58 } from '@scure/base'
@@ -10,7 +13,15 @@ import { randomBytes } from '@noble/hashes/utils'
10
13
  type Config = ClientConfig
11
14
  interface Context<C extends Config = Config> extends ClientContext<C> { }
12
15
 
13
- export const appendClientResource = <C extends Config, T extends Context<C>>(context: T, alias: string): T => {
16
+ /**
17
+ * A key-value client store dressed as a resource: records live under their own id and a list of
18
+ * those ids under {@link LIST_KEY} is the only index there is. Queries therefore read the whole
19
+ * set and run through the shared in-memory engine, which is what makes a criteria object mean
20
+ * here exactly what it means against a relational store.
21
+ */
22
+ export const appendClientResource = <
23
+ C extends Config, T extends Context<C>, R extends ResourceRecord = ResourceRecord
24
+ >(context: T, alias: string): T => {
14
25
 
15
26
  const location = `client-resource:${alias}`
16
27
 
@@ -22,7 +33,32 @@ export const appendClientResource = <C extends Config, T extends Context<C>>(con
22
33
  return resource.db
23
34
  }
24
35
 
25
- const resource: ClientResource<ResourceRecord> = appendContextual<ClientResource<ResourceRecord>>(alias, {
36
+ const ids = async (): Promise<string[]> => await assert().get<string[]>(LIST_KEY) ?? []
37
+
38
+ const readAll = async (): Promise<R[]> => {
39
+ const db = assert()
40
+ const records = await Promise.all((await ids()).map(id => db.get<R>(id)))
41
+
42
+ return records.filter((record): record is Awaited<R> => record != null) as R[]
43
+ }
44
+
45
+ const dropId = async (id: string): Promise<void> => {
46
+ const list = await ids()
47
+ const idx = list.indexOf(id)
48
+ if (idx > -1) {
49
+ list.splice(idx, 1)
50
+ await assert().set(LIST_KEY, list)
51
+ }
52
+ }
53
+
54
+ /** An id is a direct key read; criteria have to walk the set. */
55
+ const first = async (
56
+ idOrWhere: string | Criteria<R>, opts?: FirstOptions<R>
57
+ ): Promise<R | null> => typeof idOrWhere === 'string'
58
+ ? await assert().get<R>(idOrWhere) ?? null
59
+ : firstMatch(await readAll(), idOrWhere, opts)
60
+
61
+ const resource: ClientResource<R> = appendContextual<ClientResource<R>>(alias, {
26
62
  init: async () => {
27
63
  const context = assertContext<Config, Context>(resource.ctx as Context, location)
28
64
  const config = context.cfg.dbs?.find(db => db.alias === alias) ?? { service: DEFAULT_DB_ALIAS, host: [] }
@@ -30,167 +66,106 @@ export const appendClientResource = <C extends Config, T extends Context<C>>(con
30
66
  resource.db = await dbService.initialize(config.schema ?? alias)
31
67
  },
32
68
 
33
- get: async id => {
34
- const record = await resource.load(id)
35
-
69
+ get: async (idOrWhere: string | Criteria<R>, opts?: FirstOptions<R>): Promise<R> => {
70
+ const record = await first(idOrWhere, opts)
36
71
  if (record == null) {
37
- throw new UnknownRecordError(id)
72
+ throw new UnknownRecordError(typeof idOrWhere === 'string' ? idOrWhere : 'criteria')
38
73
  }
39
74
 
40
- return record as any
75
+ return record
41
76
  },
42
77
 
78
+ load: async (idOrWhere: string | Criteria<R>, opts?: FirstOptions<R>): Promise<R | null> =>
79
+ await first(idOrWhere, opts),
43
80
 
44
- load: async id => {
45
- const db = assert()
46
-
47
- return await db.get(id) as any | null
48
- },
49
-
50
- list: async (criteria, opts) => {
51
- if (criteria == null) {
52
- criteria = {}
53
- opts = { pager: { page: 0, size: 10 } }
54
- }
55
- if ("pager" in criteria) {
56
- opts = criteria
57
- criteria = criteria.criteria as ListCriteria
58
- }
59
- const db = assert()
60
- const list = await db.get<string[]>(LIST_KEY)
61
-
62
- const pager: ListPager = opts?.pager ?? { page: 0, size: 10 }
63
- const conditions = Object.entries(criteria as ListCriteria ?? {})
64
- pager.page = pager.page ?? 0
65
- pager.size = pager.size ?? 10
66
-
67
- const result: ResourceRecord[] = []
68
- let skip = pager.page * pager.size
69
- let total = 0
70
- let added = 0
71
- for (const id of list) {
72
- const record = await db.get<ResourceRecord>(id)
73
- if (record != null) {
74
- if (!(conditions.length === 0 || conditions.every(
75
- ([key, value]) => record[key as keyof typeof record] === value
76
- ))) {
77
- continue
78
- }
79
- if (skip-- <= 0 && added < pager.size) {
80
- added++
81
- result.push(record)
82
- }
83
- ++total
84
- }
81
+ /** Unpaged unless a size is asked for. */
82
+ list: async (where?: Criteria<R>, opts?: ListOptions<R>) => {
83
+ if (opts?.page != null && opts.size == null) {
84
+ throw new UnsupportedArgumentError('page-without-size')
85
85
  }
86
86
 
87
- return { items: result as any[], pager: { ...pager, total } }
87
+ return applyQuery(await readAll(), where, opts)
88
88
  },
89
89
 
90
- create: async record => {
90
+ count: async (where?: Criteria<R>) => filterRecords(await readAll(), where).length,
91
+
92
+ create: async (record: Partial<R>) => {
91
93
  const db = assert()
92
94
 
93
95
  record.id = record.id ?? base58.encode(randomBytes(32))
94
96
 
95
- if (await db.has(record.id)) {
96
- throw new RecordExists(record.id)
97
- }
98
-
99
- const list: string[] = await db.get(LIST_KEY) ?? []
100
- if (list.includes(record.id)) {
97
+ const list = await ids()
98
+ if (list.includes(record.id) || await db.has(record.id)) {
101
99
  throw new RecordExists(record.id)
102
100
  }
103
101
  list.push(record.id)
104
102
  await db.set(LIST_KEY, list)
105
103
  await db.set(record.id, record)
106
104
 
107
- return record as any
105
+ return record as R
108
106
  },
109
107
 
110
- update: async record => {
108
+ /** Replaces the stored record rather than merging into it, as the contract says. */
109
+ update: async (record: Partial<R>) => {
111
110
  const db = assert()
112
111
 
113
112
  if (record.id == null) {
114
- throw new UnknownRecordError('update')
113
+ throw new MisshapedRecord('id')
115
114
  }
116
-
117
- const update = await resource.load(record.id)
118
- if (update == null) {
115
+ if (!await db.has(record.id)) {
119
116
  throw new UnknownRecordError(record.id)
120
117
  }
118
+ await db.set(record.id, record)
121
119
 
122
- Object.assign(update, record)
123
-
124
- await db.set(record.id, update)
125
-
126
- return record as any
120
+ return record as R
127
121
  },
128
122
 
129
- delete: async id => {
130
- if (typeof id === 'object') {
131
- if (id.id == null) {
132
- throw new UnknownRecordError('delete')
133
- }
134
- return resource.delete(id.id)
135
- }
123
+ save: async (record: Partial<R>) => record.id == null || !await assert().has(record.id)
124
+ ? await resource.create(record)
125
+ : await resource.update(record),
136
126
 
127
+ delete: async (id: string) => {
137
128
  const db = assert()
138
- if (!await db.has(id)) {
139
- return null
140
- }
141
-
142
- const record = await db.get(id)
129
+ const record = await db.get<R>(id)
143
130
  if (record == null) {
144
- throw new SyntaxError('We should not try to delete record that we know that not exists')
131
+ return null
145
132
  }
146
133
  await db.del(id)
147
- const list: string[] = await db.get(LIST_KEY) ?? []
148
- const idx = list.indexOf(id)
149
- if (idx > -1) {
150
- list.splice(idx, 1)
151
- await db.set(LIST_KEY, list)
152
- }
134
+ await dropId(id)
153
135
 
154
- return record as any
136
+ return record
155
137
  },
156
138
 
157
- pick: async id => {
158
- if (typeof id === 'object') {
159
- if (id.id == null) {
160
- throw new UnknownRecordError('pick')
161
- }
162
- return resource.pick(id.id)
163
- }
164
-
165
- const db = assert()
166
- const record = await db.get(id)
167
- if (record != null) {
168
- await db.del(id)
169
- } else {
139
+ take: async (id: string) => {
140
+ const record = await resource.delete(id)
141
+ if (record == null) {
170
142
  throw new UnknownRecordError(id)
171
143
  }
172
- const list: string[] = await db.get(LIST_KEY) ?? []
173
- const idx = list.indexOf(id)
174
- if (idx > -1) {
175
- list.splice(idx, 1)
176
- await db.set(LIST_KEY, list)
177
- }
178
144
 
179
- return record as any
145
+ return record
180
146
  },
181
147
 
182
- save: async record => {
183
- if (record.id == null || (await resource.load(record.id) == null)) {
184
- return resource.create(record)
148
+ purge: async (where: Criteria<R>) => {
149
+ if (where == null || Object.keys(where).length < 1) {
150
+ throw new UnsupportedArgumentError('purge:empty-criteria')
151
+ }
152
+ const matched = filterRecords(await readAll(), where)
153
+ for (const record of matched) {
154
+ if (record.id != null) {
155
+ await resource.delete(record.id)
156
+ }
185
157
  }
186
158
 
187
- return resource.update(record)
159
+ return matched.length
188
160
  },
189
161
 
190
162
  erase: async () => {
191
163
  const db = assert()
192
- const list: string[] = await db.get(LIST_KEY) ?? []
193
- await Promise.all(list.map(id => resource.delete(id)))
164
+ /** The index is emptied in one write, so a failed pass cannot leave it naming dead ids. */
165
+ for (const id of await ids()) {
166
+ await db.del(id)
167
+ }
168
+ await db.set(LIST_KEY, [])
194
169
  }
195
170
  })
196
171
 
package/src/types.ts CHANGED
@@ -7,7 +7,8 @@ export interface ClientDbService extends InitializedService {
7
7
  }
8
8
 
9
9
  export interface ClientDb {
10
- get: <T>(id: string) => Promise<T>
10
+ /** `undefined` for a key the store does not hold — a miss is not an error at this level. */
11
+ get: <T>(id: string) => Promise<T | undefined>
11
12
  set: <T>(id: string, value: T) => Promise<void>
12
13
  has: (id: string) => Promise<boolean>
13
14
  del: (id: string) => Promise<boolean>
package/build/.gitkeep DELETED
File without changes