@owlmeans/static-resource 0.1.18-rc.4 → 0.1.18-rc.40

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
@@ -12,7 +12,7 @@ In-memory `Resource<T>` backed by static config data — for read-only data load
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/static-resource
15
+ bun add @owlmeans/static-resource@^0.1.18-rc.40
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -26,8 +26,13 @@ appendStaticResource<C, T>(context, 'products')
26
26
  // Access from context
27
27
  const resource = context.getStaticResource<Product>('products')
28
28
  const product = await resource.load('product-sku')
29
+ const active = await resource.list({ status: 'active' })
29
30
  ```
30
31
 
32
+ Reads are unpaged: `list(where)` returns every match, and `list(where, { page })` without a `size`
33
+ throws `UnsupportedArgumentError('page-without-size')`. Criteria and sorting are the shared
34
+ vocabulary from [`@owlmeans/resource`](../resource), evaluated in memory.
35
+
31
36
  ## API
32
37
 
33
38
  ### `createStaticResource(alias?, key?): Resource<ResourceRecord>`
@@ -58,7 +63,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
58
63
  your project's skill store (`.agents/skills/`):
59
64
 
60
65
  ```sh
61
- npx @owlmeans/agent-skills
66
+ npx @owlmeans/agent-skills@^0.1.18-rc.57
62
67
  ```
63
68
 
64
69
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": "@owlmeans/static-resource",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.518Z",
4
+ "version": "0.1.18-rc.40",
5
+ "generatedAt": "2026-10-20T23:19:58.650Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: static-resource
3
- description: How to use @owlmeans/static-resource — Resource for serving static files from a server (e.g. uploaded assets, generated reports). Auto-invoked when serving static files via the framework.
3
+ description: How to use @owlmeans/static-resource — in-process Resource over a module-scope Map, for records an app holds in memory (fixtures, caches, short-lived handshake state). Auto-invoked when registering an in-memory resource on a context.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,27 +8,64 @@ user-invocable: false
8
8
  # @owlmeans/static-resource
9
9
 
10
10
  **Layer:** Infra
11
- **Install:** `"@owlmeans/static-resource": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/static-resource": "^0.1.18-rc.40"` in `dependencies`
12
+
13
+ The whole `Resource` contract over a `Map` held at module scope. Use it where records must be
14
+ reachable through the context like any other resource but have no database behind them — fixtures,
15
+ a process-local cache, short-lived handshake state.
12
16
 
13
17
  ## Key Exports
14
18
 
15
19
  | Export | Description |
16
20
  |--------|-------------|
17
- | `makeStaticResource(options)` | Static-file Resource factory |
18
- | `StaticResource` types | Resource interface |
19
- | Constants | Default cache headers |
21
+ | `appendStaticResource(context, alias?, key?)` | Register one on the context and add `getStaticResource(alias?)` to it |
22
+ | `createStaticResource<T>(alias?, key?)` | The bare `Resource<T>`, when you register it yourself |
23
+ | `StaticResourceAppend` | The `getStaticResource` accessor the append adds |
24
+ | `DEFAULT_ALIAS` (`static`) | Constant |
25
+
26
+ `key` names the underlying store, defaulting to the alias — two resources sharing a `key` share
27
+ records, which is how two contexts in one process see the same set.
20
28
 
21
29
  ## Usage
22
30
 
23
31
  ```typescript
24
- import { makeStaticResource } from '@owlmeans/static-resource'
32
+ import { appendStaticResource } from '@owlmeans/static-resource'
33
+
34
+ appendStaticResource(context, 'fixtures')
25
35
 
26
- context.registerResource(makeStaticResource({
27
- alias: 'public',
28
- root: '/var/lib/app/public',
29
- }))
36
+ const fixtures = context.getStaticResource<Fixture>('fixtures')
37
+ await fixtures.save({ id: 'seed', name: 'Owl' })
38
+ await fixtures.list({ name: { $startsWith: 'Owl' } }, { sort: ['name'] })
30
39
  ```
31
40
 
41
+ ## Semantics
42
+
43
+ - The store is a map keyed by id. **`create` mints one (`randomUUID()`) when the record carries
44
+ none** — what a Mongo or Postgres `create()` does, so code written against those backends runs
45
+ unchanged over this one — and honours a supplied id, throwing `RecordExists` only when that id is
46
+ already stored. `update` and `save` need an id and throw `MisshapedRecord('id')` without one;
47
+ `update` also throws `UnknownRecordError`, and `save` is the upsert.
48
+ - `load(id)` / `get(id)` / `delete(id)` / `take(id)` hit the map directly. `take(id)` **deletes**
49
+ the record it returns and throws `UnknownRecordError` on a miss.
50
+ - `load(where)`, `get(where)`, `list`, `count` and `purge` go through the shared in-memory engine
51
+ from `@owlmeans/resource`, so criteria and `{ sort }` mean exactly what they mean against a
52
+ database. `purge(where)` refuses an empty criteria object
53
+ (`UnsupportedArgumentError('purge:empty-criteria')` — the database backends spell the same
54
+ refusal `purge:no-criteria`, so match on the error type rather than the message).
55
+ - **Unpaged**: `list(where)` with no `size` returns every match — the whole store is already in
56
+ memory; `size: 0` says the same explicitly, and `page` without `size` throws
57
+ `UnsupportedArgumentError('page-without-size')`.
58
+ - `opts.ttl` on a write arms a timer that deletes the id when it elapses — a number is seconds from
59
+ now, a `Date` the instant to expire at.
60
+ - **An armed expiry cannot be postponed.** Nothing ever clears a timer, so a later write — with a
61
+ ttl or without one — leaves the original running, and when it fires it deletes whatever record now
62
+ sits under that id. Passing the ttl again renews nothing; it only adds a second timer behind the
63
+ first. A record that has to outlive its first ttl needs a fresh id.
64
+ - **The map holds the caller's own object.** `create`/`update`/`save` store the object handed in and
65
+ `load`/`get`/`list` hand it straight back — no clone, no serialization. Mutating a record you
66
+ saved or read rewrites the store in place, bypassing `update` entirely, which is not how the
67
+ database backends behave. Copy before mutating, or treat what comes out of here as frozen.
68
+
32
69
  ## Depends On
33
70
 
34
- - `@owlmeans/resource`, `@owlmeans/server-context`
71
+ - `@owlmeans/resource`, `@owlmeans/context`
@@ -1,5 +1,5 @@
1
1
  import type { Resource, ResourceRecord } from '@owlmeans/resource';
2
2
  import type { Config, Context, StaticResourceAppend } from './types.js';
3
- export declare const createStaticResource: (alias?: string, key?: string) => Resource<ResourceRecord>;
3
+ export declare const createStaticResource: <T extends ResourceRecord = ResourceRecord>(alias?: string, key?: string) => Resource<T>;
4
4
  export declare const appendStaticResource: <C extends Config, T extends Context<C>>(ctx: T, alias?: string, key?: string) => T & StaticResourceAppend;
5
5
  //# sourceMappingURL=resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAC8D,QAAQ,EAChF,cAAc,EACf,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAMvE,eAAO,MAAM,oBAAoB,WAAW,MAAM,QAAwB,MAAM,6BA2J/E,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OACpE,CAAC,UAAS,MAAM,QAAwB,MAAM,KAClD,CAAC,GAAG,oBAWN,CAAA"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAC2B,QAAQ,EAAE,cAAc,EAC9D,MAAM,oBAAoB,CAAA;AAI3B,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAYvE,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,cAAc,GAAG,cAAc,UACrE,MAAM,QAAwB,MAAM,KAC1C,QAAQ,CAAC,CAAC,CAkIZ,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OACpE,CAAC,UAAS,MAAM,QAAwB,MAAM,KAClD,CAAC,GAAG,oBAWN,CAAA"}
package/build/resource.js CHANGED
@@ -1,134 +1,114 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { appendContextual } from '@owlmeans/context';
2
3
  import { DEFAULT_ALIAS } from './consts.js';
3
- import { MisshapedRecord, RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource';
4
+ import { MisshapedRecord, RecordExists, recordQueryHelper, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource';
4
5
  const stores = {};
6
+ /**
7
+ * Milliseconds until the record expires, which is what `setTimeout` wants. A number is seconds
8
+ * from now and a Date is the instant to expire at — the two spellings have to land on the same
9
+ * moment, or the same ttl means an hour through one and a second through the other.
10
+ */
11
+ const expiresIn = (ttl) => ttl instanceof Date ? ttl.getTime() - Date.now() : ttl * 1000;
5
12
  export const createStaticResource = (alias = DEFAULT_ALIAS, key) => {
6
- key = key ?? alias;
13
+ const storeKey = key ?? alias;
7
14
  const getStore = () => {
8
- if (stores[key] == null) {
9
- stores[key] = new Map();
15
+ if (stores[storeKey] == null) {
16
+ stores[storeKey] = new Map();
10
17
  }
11
- return stores[key];
18
+ return stores[storeKey];
19
+ };
20
+ const records = () => [...getStore().values()];
21
+ const expire = (id, opts) => {
22
+ if (opts?.ttl == null) {
23
+ return;
24
+ }
25
+ const store = getStore();
26
+ setTimeout(() => void store.delete(id), expiresIn(opts.ttl));
27
+ };
28
+ /** An id hits the map directly; criteria go through the shared in-memory engine. */
29
+ const first = (idOrWhere, opts) => typeof idOrWhere === 'string'
30
+ ? getStore().get(idOrWhere) ?? null
31
+ : recordQueryHelper.firstMatch(records(), idOrWhere, opts);
32
+ const write = (record, opts) => {
33
+ if (record.id == null) {
34
+ /**
35
+ * The store is a map keyed by id and there is nothing here to mint one from, so a record
36
+ * without an id is misshaped rather than new — `save` cannot create it.
37
+ */
38
+ throw new MisshapedRecord('id');
39
+ }
40
+ getStore().set(record.id, record);
41
+ expire(record.id, opts);
42
+ return record;
12
43
  };
13
44
  const resource = appendContextual(alias, {
14
- get: async (id, field, opts) => {
15
- const record = await resource.load(id, field, opts);
45
+ get: async (idOrWhere, opts) => {
46
+ const record = first(idOrWhere, opts);
16
47
  if (record == null) {
17
- throw new UnknownRecordError(id);
48
+ throw new UnknownRecordError(typeof idOrWhere === 'string' ? idOrWhere : 'criteria');
18
49
  }
19
50
  return record;
20
51
  },
21
- load: async (id, field, opts) => {
22
- field = field ?? 'id';
23
- if (opts != null) {
24
- throw new UnsupportedArgumentError('static:get:opts');
52
+ load: async (idOrWhere, opts) => first(idOrWhere, opts),
53
+ /** Unpaged unless a size is asked for — the whole store is already in memory. */
54
+ list: async (where, opts) => {
55
+ if (opts?.page != null && opts.size == null) {
56
+ throw new UnsupportedArgumentError('page-without-size');
25
57
  }
26
- const store = getStore();
27
- const record = field === 'id'
28
- ? store.get(id)
29
- : [...store.values()].find(record => record[field] === id);
30
- if (record == null) {
31
- return null;
32
- }
33
- return record;
34
- },
35
- list: async (criteria, opts) => {
36
- if (criteria != null) {
37
- throw new UnsupportedArgumentError('static:list:criteria');
38
- }
39
- if (opts != null) {
40
- throw new UnsupportedArgumentError('static:list:opts');
41
- }
42
- const result = {
43
- items: [...getStore().values()]
44
- };
45
- return result;
46
- },
47
- save: async (record, opts) => {
48
- const store = getStore();
49
- if (record.id == null) {
50
- throw new MisshapedRecord('id');
51
- }
52
- store.set(record.id, record);
53
- if (typeof opts === 'object' && opts?.ttl != null) {
54
- const ttl = opts.ttl instanceof Date
55
- ? (opts.ttl.getTime() - Date.now()) / 1000
56
- : typeof opts.ttl === 'number'
57
- ? opts.ttl * 1000
58
- : null;
59
- if (ttl == null) {
60
- throw new UnsupportedArgumentError('static:opts:ttl-string');
61
- }
62
- setTimeout(() => record.id != null && void store.delete(record.id), ttl);
63
- }
64
- return record;
58
+ return recordQueryHelper.applyQuery(records(), where, opts);
65
59
  },
60
+ count: async (where) => recordQueryHelper.filterRecords(records(), where).length,
66
61
  create: async (record, opts) => {
67
- const store = getStore();
62
+ /**
63
+ * A supplied id is honored (many callers key by a natural id, Redis-style) — but a missing
64
+ * one is minted here rather than rejected, the same as a real Mongo/Postgres `create()`
65
+ * auto-generates its own id when the caller supplies none. Only a supplied id that already
66
+ * exists is `RecordExists`.
67
+ */
68
68
  if (record.id == null) {
69
- throw new UnknownRecordError('id');
69
+ record = { ...record, id: randomUUID() };
70
70
  }
71
- if (store.has(record.id)) {
71
+ if (getStore().has(record.id)) {
72
72
  throw new RecordExists(record.id);
73
73
  }
74
- store.set(record.id, record);
75
- if (opts?.ttl != null) {
76
- const ttl = opts.ttl instanceof Date
77
- ? (opts.ttl.getTime() - Date.now()) / 1000
78
- : typeof opts.ttl === 'number'
79
- ? opts.ttl * 1000
80
- : null;
81
- if (ttl == null) {
82
- throw new UnsupportedArgumentError('static:opts:ttl-string');
83
- }
84
- setTimeout(() => record.id != null && void store.delete(record.id), ttl);
85
- }
86
- return record;
74
+ return write(record, opts);
87
75
  },
88
76
  update: async (record, opts) => {
89
- return resource.save(record, opts);
90
- },
91
- delete: async (id, opts) => {
92
- let record = null;
93
- if (typeof id === 'object') {
94
- if (id.id == null) {
95
- throw new MisshapedRecord('id');
96
- }
97
- record = await resource.load(id.id);
98
- }
99
- else if (typeof opts === 'string') {
100
- record = await resource.load(id, opts);
77
+ if (record.id == null) {
78
+ throw new MisshapedRecord('id');
101
79
  }
102
- else if (typeof id === 'string') {
103
- record = await resource.load(id);
80
+ if (!getStore().has(record.id)) {
81
+ throw new UnknownRecordError(record.id);
104
82
  }
83
+ return write(record, opts);
84
+ },
85
+ save: async (record, opts) => write(record, opts),
86
+ delete: async (id) => {
87
+ const store = getStore();
88
+ const record = store.get(id) ?? null;
105
89
  if (record == null) {
106
90
  return null;
107
91
  }
108
- let field = 'id';
109
- if (typeof opts === 'string') {
110
- field = opts;
111
- opts = undefined;
112
- }
113
- else if (typeof opts === 'object') {
114
- field = opts.field ?? field;
115
- }
116
- id = record.id ?? '';
117
- if (id === '') {
118
- throw new MisshapedRecord('id');
119
- }
120
- const store = getStore();
121
- if (store.delete(id)) {
122
- return record;
123
- }
124
- return null;
92
+ store.delete(id);
93
+ return record;
125
94
  },
126
- pick: async (id, opts) => {
127
- const record = await resource.delete(id, opts);
95
+ take: async (id) => {
96
+ const store = getStore();
97
+ const record = store.get(id);
128
98
  if (record == null) {
129
- throw new UnknownRecordError((typeof id === 'string' ? id : id.id) ?? 'unknown');
99
+ throw new UnknownRecordError(id);
130
100
  }
101
+ store.delete(id);
131
102
  return record;
103
+ },
104
+ purge: async (where) => {
105
+ if (where == null || Object.keys(where).length < 1) {
106
+ throw new UnsupportedArgumentError('purge:empty-criteria');
107
+ }
108
+ const store = getStore();
109
+ const matched = recordQueryHelper.filterRecords(records(), where);
110
+ matched.forEach(record => record.id != null && store.delete(record.id));
111
+ return matched.length;
132
112
  }
133
113
  });
134
114
  return 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/static-resource",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.40",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,9 +21,9 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/context": "^0.1.18-rc.4",
25
- "@owlmeans/error": "^0.1.18-rc.4",
26
- "@owlmeans/resource": "^0.1.18-rc.4"
24
+ "@owlmeans/context": "^0.1.18-rc.35",
25
+ "@owlmeans/error": "^0.1.18-rc.39",
26
+ "@owlmeans/resource": "^0.1.18-rc.41"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@owlmeans/dep-config": "workspace:*",
package/src/resource.ts CHANGED
@@ -1,167 +1,152 @@
1
+ import { randomUUID } from 'node:crypto'
1
2
  import { appendContextual } from '@owlmeans/context'
2
3
  import { DEFAULT_ALIAS } from './consts.js'
3
4
  import type {
4
- GetterOptions, ListOptions, ListResult, LifecycleOptions, ListCriteria, Resource,
5
- ResourceRecord
5
+ Criteria, FirstOptions, ListOptions, Resource, ResourceRecord, Ttl, WriteOptions
6
+ } from '@owlmeans/resource'
7
+ import {
8
+ MisshapedRecord, RecordExists, recordQueryHelper, UnknownRecordError, UnsupportedArgumentError
6
9
  } from '@owlmeans/resource'
7
- import { MisshapedRecord, RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource'
8
10
  import type { Config, Context, StaticResourceAppend } from './types.js'
9
11
 
10
- type Getter = string | GetterOptions
11
- type ResourceType = Resource<ResourceRecord>
12
-
13
12
  const stores: Record<string, Map<string, ResourceRecord>> = {}
14
- export const createStaticResource = (alias: string = DEFAULT_ALIAS, key?: string) => {
15
- key = key ?? alias
16
13
 
17
- const getStore = () => {
18
- if (stores[key] == null) {
19
- stores[key] = new Map()
14
+ /**
15
+ * Milliseconds until the record expires, which is what `setTimeout` wants. A number is seconds
16
+ * from now and a Date is the instant to expire at — the two spellings have to land on the same
17
+ * moment, or the same ttl means an hour through one and a second through the other.
18
+ */
19
+ const expiresIn = (ttl: Ttl): number =>
20
+ ttl instanceof Date ? ttl.getTime() - Date.now() : ttl * 1000
21
+
22
+ export const createStaticResource = <T extends ResourceRecord = ResourceRecord>(
23
+ alias: string = DEFAULT_ALIAS, key?: string
24
+ ): Resource<T> => {
25
+ const storeKey = key ?? alias
26
+
27
+ const getStore = (): Map<string, T> => {
28
+ if (stores[storeKey] == null) {
29
+ stores[storeKey] = new Map()
20
30
  }
21
31
 
22
- return stores[key]
32
+ return stores[storeKey] as Map<string, T>
23
33
  }
24
34
 
25
- const resource: Resource<ResourceRecord> = appendContextual<ResourceType>(alias, {
26
- get: async <T extends ResourceRecord>(id: string, field?: Getter, opts?: LifecycleOptions) => {
27
- const record = await resource.load<T>(id, field, opts)
28
- if (record == null) {
29
- throw new UnknownRecordError(id)
30
- }
35
+ const records = (): T[] => [...getStore().values()]
31
36
 
32
- return record
33
- },
37
+ const expire = (id: string, opts?: WriteOptions): void => {
38
+ if (opts?.ttl == null) {
39
+ return
40
+ }
41
+ const store = getStore()
42
+ setTimeout(() => void store.delete(id), expiresIn(opts.ttl))
43
+ }
34
44
 
35
- load: async <T extends ResourceRecord>(id: string, field?: Getter, opts?: LifecycleOptions) => {
36
- field = field ?? 'id'
37
- if (opts != null) {
38
- throw new UnsupportedArgumentError('static:get:opts')
39
- }
40
- const store = getStore()
41
- const record = field === 'id'
42
- ? store.get(id) as T | null
43
- : [...store.values()].find(
44
- record => record[field as keyof typeof record] === id
45
- ) as T | undefined
45
+ /** An id hits the map directly; criteria go through the shared in-memory engine. */
46
+ const first = (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): T | null =>
47
+ typeof idOrWhere === 'string'
48
+ ? getStore().get(idOrWhere) ?? null
49
+ : recordQueryHelper.firstMatch(records(), idOrWhere, opts)
50
+
51
+ const write = (record: Partial<T>, opts?: WriteOptions): T => {
52
+ if (record.id == null) {
53
+ /**
54
+ * The store is a map keyed by id and there is nothing here to mint one from, so a record
55
+ * without an id is misshaped rather than new — `save` cannot create it.
56
+ */
57
+ throw new MisshapedRecord('id')
58
+ }
59
+ getStore().set(record.id, record as T)
60
+ expire(record.id, opts)
46
61
 
62
+ return record as T
63
+ }
64
+
65
+ const resource: Resource<T> = appendContextual<Resource<T>>(alias, {
66
+ get: async (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): Promise<T> => {
67
+ const record = first(idOrWhere, opts)
47
68
  if (record == null) {
48
- return null
69
+ throw new UnknownRecordError(typeof idOrWhere === 'string' ? idOrWhere : 'criteria')
49
70
  }
50
71
 
51
72
  return record
52
73
  },
53
74
 
54
- list: async <T extends ResourceRecord>(criteria?: ListOptions | ListCriteria, opts?: ListOptions) => {
55
- if (criteria != null) {
56
- throw new UnsupportedArgumentError('static:list:criteria')
57
- }
58
- if (opts != null) {
59
- throw new UnsupportedArgumentError('static:list:opts')
60
- }
75
+ load: async (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): Promise<T | null> =>
76
+ first(idOrWhere, opts),
61
77
 
62
- const result: ListResult<T> = {
63
- items: [...getStore().values()] as T[]
78
+ /** Unpaged unless a size is asked for — the whole store is already in memory. */
79
+ list: async (where?: Criteria<T>, opts?: ListOptions<T>) => {
80
+ if (opts?.page != null && opts.size == null) {
81
+ throw new UnsupportedArgumentError('page-without-size')
64
82
  }
65
83
 
66
- return result
84
+ return recordQueryHelper.applyQuery(records(), where, opts)
67
85
  },
68
86
 
69
- save: async <T extends ResourceRecord>(record: Partial<T>, opts?: Getter) => {
70
- const store = getStore()
87
+ count: async (where?: Criteria<T>) => recordQueryHelper.filterRecords(records(), where).length,
88
+
89
+ create: async (record: Partial<T>, opts?: WriteOptions) => {
90
+ /**
91
+ * A supplied id is honored (many callers key by a natural id, Redis-style) — but a missing
92
+ * one is minted here rather than rejected, the same as a real Mongo/Postgres `create()`
93
+ * auto-generates its own id when the caller supplies none. Only a supplied id that already
94
+ * exists is `RecordExists`.
95
+ */
71
96
  if (record.id == null) {
72
- throw new MisshapedRecord('id')
97
+ record = { ...record, id: randomUUID() }
73
98
  }
74
- store.set(record.id, record as ResourceRecord)
75
-
76
- if (typeof opts === 'object' && opts?.ttl != null) {
77
- const ttl = opts.ttl instanceof Date
78
- ? (opts.ttl.getTime() - Date.now()) / 1000
79
- : typeof opts.ttl === 'number'
80
- ? opts.ttl * 1000
81
- : null
82
- if (ttl == null) {
83
- throw new UnsupportedArgumentError('static:opts:ttl-string')
84
- }
85
- setTimeout(() => record.id != null && void store.delete(record.id), ttl)
99
+ if (getStore().has(record.id as string)) {
100
+ throw new RecordExists(record.id as string)
86
101
  }
87
102
 
88
- return record as T
103
+ return write(record, opts)
89
104
  },
90
105
 
91
- create: async (record, opts) => {
92
- const store = getStore()
106
+ update: async (record: Partial<T>, opts?: WriteOptions) => {
93
107
  if (record.id == null) {
94
- throw new UnknownRecordError('id')
95
- }
96
- if (store.has(record.id)) {
97
- throw new RecordExists(record.id)
108
+ throw new MisshapedRecord('id')
98
109
  }
99
- store.set(record.id, record)
100
- if (opts?.ttl != null) {
101
- const ttl = opts.ttl instanceof Date
102
- ? (opts.ttl.getTime() - Date.now()) / 1000
103
- : typeof opts.ttl === 'number'
104
- ? opts.ttl * 1000
105
- : null
106
- if (ttl == null) {
107
- throw new UnsupportedArgumentError('static:opts:ttl-string')
108
- }
109
- setTimeout(() => record.id != null && void store.delete(record.id), ttl)
110
+ if (!getStore().has(record.id)) {
111
+ throw new UnknownRecordError(record.id)
110
112
  }
111
113
 
112
- return record as any
113
- },
114
-
115
- update: async (record, opts) => {
116
- return resource.save(record, opts)
114
+ return write(record, opts)
117
115
  },
118
116
 
119
- delete: async (id, opts) => {
120
- let record: ResourceRecord | null = null
121
- if (typeof id === 'object') {
122
- if (id.id == null) {
123
- throw new MisshapedRecord('id')
124
- }
125
- record = await resource.load(id.id)
126
- } else if (typeof opts === 'string') {
127
- record = await resource.load(id, opts)
128
- } else if (typeof id === 'string') {
129
- record = await resource.load(id)
130
- }
117
+ save: async (record: Partial<T>, opts?: WriteOptions) => write(record, opts),
131
118
 
119
+ delete: async (id: string) => {
120
+ const store = getStore()
121
+ const record = store.get(id) ?? null
132
122
  if (record == null) {
133
123
  return null
134
124
  }
125
+ store.delete(id)
135
126
 
136
- let field = 'id'
137
- if (typeof opts === 'string') {
138
- field = opts
139
- opts = undefined
140
- } else if (typeof opts === 'object') {
141
- field = opts.field ?? field
142
- }
143
-
144
- id = record.id ?? ''
145
-
146
- if (id === '') {
147
- throw new MisshapedRecord('id')
148
- }
127
+ return record
128
+ },
149
129
 
130
+ take: async (id: string) => {
150
131
  const store = getStore()
151
- if (store.delete(id)) {
152
- return record as any
132
+ const record = store.get(id)
133
+ if (record == null) {
134
+ throw new UnknownRecordError(id)
153
135
  }
136
+ store.delete(id)
154
137
 
155
- return null
138
+ return record
156
139
  },
157
140
 
158
- pick: async (id, opts) => {
159
- const record = await resource.delete(id, opts)
160
- if (record == null) {
161
- throw new UnknownRecordError((typeof id === 'string' ? id : id.id) ?? 'unknown')
141
+ purge: async (where: Criteria<T>) => {
142
+ if (where == null || Object.keys(where).length < 1) {
143
+ throw new UnsupportedArgumentError('purge:empty-criteria')
162
144
  }
145
+ const store = getStore()
146
+ const matched = recordQueryHelper.filterRecords(records(), where)
147
+ matched.forEach(record => record.id != null && store.delete(record.id))
163
148
 
164
- return record as any
149
+ return matched.length
165
150
  }
166
151
  })
167
152
 
package/build/.gitkeep DELETED
File without changes