@owlmeans/static-resource 0.1.18-rc.3 → 0.1.18-rc.30

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.28
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.33
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
2
  "schemaVersion": 2,
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.30",
5
+ "generatedAt": "2026-09-22T18:27:08.249Z",
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,62 @@ 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.30"` 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 and there is nothing here to mint one from, so **every write
44
+ needs an id**: `create`, `update` and `save` all throw `MisshapedRecord('id')` without one.
45
+ `create` additionally throws `RecordExists`, `update` `UnknownRecordError`; `save` is the upsert.
46
+ - `load(id)` / `get(id)` / `delete(id)` / `take(id)` hit the map directly. `take(id)` **deletes**
47
+ the record it returns and throws `UnknownRecordError` on a miss.
48
+ - `load(where)`, `get(where)`, `list`, `count` and `purge` go through the shared in-memory engine
49
+ from `@owlmeans/resource`, so criteria and `{ sort }` mean exactly what they mean against a
50
+ database. `purge(where)` refuses an empty criteria object
51
+ (`UnsupportedArgumentError('purge:empty-criteria')` — the database backends spell the same
52
+ refusal `purge:no-criteria`, so match on the error type rather than the message).
53
+ - **Unpaged**: `list(where)` with no `size` returns every match — the whole store is already in
54
+ memory; `size: 0` says the same explicitly, and `page` without `size` throws
55
+ `UnsupportedArgumentError('page-without-size')`.
56
+ - `opts.ttl` on a write arms a timer that deletes the id when it elapses — a number is seconds from
57
+ now, a `Date` the instant to expire at.
58
+ - **An armed expiry cannot be postponed.** Nothing ever clears a timer, so a later write — with a
59
+ ttl or without one — leaves the original running, and when it fires it deletes whatever record now
60
+ sits under that id. Passing the ttl again renews nothing; it only adds a second timer behind the
61
+ first. A record that has to outlive its first ttl needs a fresh id.
62
+ - **The map holds the caller's own object.** `create`/`update`/`save` store the object handed in and
63
+ `load`/`get`/`list` hand it straight back — no clone, no serialization. Mutating a record you
64
+ saved or read rewrites the store in place, bypassing `update` entirely, which is not how the
65
+ database backends behave. Copy before mutating, or treat what comes out of here as frozen.
66
+
32
67
  ## Depends On
33
68
 
34
- - `@owlmeans/resource`, `@owlmeans/server-context`
69
+ - `@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;AAK3B,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 { applyQuery, filterRecords, firstMatch, MisshapedRecord, RecordExists, 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
+ : 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 applyQuery(records(), where, opts);
65
59
  },
60
+ count: async (where) => 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 = 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.3",
3
+ "version": "0.1.18-rc.30",
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.3",
25
- "@owlmeans/error": "^0.1.18-rc.3",
26
- "@owlmeans/resource": "^0.1.18-rc.3"
24
+ "@owlmeans/context": "^0.1.18-rc.29",
25
+ "@owlmeans/error": "^0.1.18-rc.31",
26
+ "@owlmeans/resource": "^0.1.18-rc.31"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@owlmeans/dep-config": "workspace:*",
package/src/resource.ts CHANGED
@@ -1,167 +1,153 @@
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
+ applyQuery, filterRecords, firstMatch, MisshapedRecord, RecordExists, UnknownRecordError,
9
+ UnsupportedArgumentError
6
10
  } from '@owlmeans/resource'
7
- import { MisshapedRecord, RecordExists, UnknownRecordError, UnsupportedArgumentError } from '@owlmeans/resource'
8
11
  import type { Config, Context, StaticResourceAppend } from './types.js'
9
12
 
10
- type Getter = string | GetterOptions
11
- type ResourceType = Resource<ResourceRecord>
12
-
13
13
  const stores: Record<string, Map<string, ResourceRecord>> = {}
14
- export const createStaticResource = (alias: string = DEFAULT_ALIAS, key?: string) => {
15
- key = key ?? alias
16
14
 
17
- const getStore = () => {
18
- if (stores[key] == null) {
19
- stores[key] = new Map()
15
+ /**
16
+ * Milliseconds until the record expires, which is what `setTimeout` wants. A number is seconds
17
+ * from now and a Date is the instant to expire at — the two spellings have to land on the same
18
+ * moment, or the same ttl means an hour through one and a second through the other.
19
+ */
20
+ const expiresIn = (ttl: Ttl): number =>
21
+ ttl instanceof Date ? ttl.getTime() - Date.now() : ttl * 1000
22
+
23
+ export const createStaticResource = <T extends ResourceRecord = ResourceRecord>(
24
+ alias: string = DEFAULT_ALIAS, key?: string
25
+ ): Resource<T> => {
26
+ const storeKey = key ?? alias
27
+
28
+ const getStore = (): Map<string, T> => {
29
+ if (stores[storeKey] == null) {
30
+ stores[storeKey] = new Map()
20
31
  }
21
32
 
22
- return stores[key]
33
+ return stores[storeKey] as Map<string, T>
23
34
  }
24
35
 
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
- }
36
+ const records = (): T[] => [...getStore().values()]
31
37
 
32
- return record
33
- },
38
+ const expire = (id: string, opts?: WriteOptions): void => {
39
+ if (opts?.ttl == null) {
40
+ return
41
+ }
42
+ const store = getStore()
43
+ setTimeout(() => void store.delete(id), expiresIn(opts.ttl))
44
+ }
34
45
 
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
46
+ /** An id hits the map directly; criteria go through the shared in-memory engine. */
47
+ const first = (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): T | null =>
48
+ typeof idOrWhere === 'string'
49
+ ? getStore().get(idOrWhere) ?? null
50
+ : firstMatch(records(), idOrWhere, opts)
51
+
52
+ const write = (record: Partial<T>, opts?: WriteOptions): T => {
53
+ if (record.id == null) {
54
+ /**
55
+ * The store is a map keyed by id and there is nothing here to mint one from, so a record
56
+ * without an id is misshaped rather than new — `save` cannot create it.
57
+ */
58
+ throw new MisshapedRecord('id')
59
+ }
60
+ getStore().set(record.id, record as T)
61
+ expire(record.id, opts)
46
62
 
63
+ return record as T
64
+ }
65
+
66
+ const resource: Resource<T> = appendContextual<Resource<T>>(alias, {
67
+ get: async (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): Promise<T> => {
68
+ const record = first(idOrWhere, opts)
47
69
  if (record == null) {
48
- return null
70
+ throw new UnknownRecordError(typeof idOrWhere === 'string' ? idOrWhere : 'criteria')
49
71
  }
50
72
 
51
73
  return record
52
74
  },
53
75
 
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
- }
76
+ load: async (idOrWhere: string | Criteria<T>, opts?: FirstOptions<T>): Promise<T | null> =>
77
+ first(idOrWhere, opts),
61
78
 
62
- const result: ListResult<T> = {
63
- items: [...getStore().values()] as T[]
79
+ /** Unpaged unless a size is asked for — the whole store is already in memory. */
80
+ list: async (where?: Criteria<T>, opts?: ListOptions<T>) => {
81
+ if (opts?.page != null && opts.size == null) {
82
+ throw new UnsupportedArgumentError('page-without-size')
64
83
  }
65
84
 
66
- return result
85
+ return applyQuery(records(), where, opts)
67
86
  },
68
87
 
69
- save: async <T extends ResourceRecord>(record: Partial<T>, opts?: Getter) => {
70
- const store = getStore()
88
+ count: async (where?: Criteria<T>) => filterRecords(records(), where).length,
89
+
90
+ create: async (record: Partial<T>, opts?: WriteOptions) => {
91
+ /**
92
+ * A supplied id is honored (many callers key by a natural id, Redis-style) — but a missing
93
+ * one is minted here rather than rejected, the same as a real Mongo/Postgres `create()`
94
+ * auto-generates its own id when the caller supplies none. Only a supplied id that already
95
+ * exists is `RecordExists`.
96
+ */
71
97
  if (record.id == null) {
72
- throw new MisshapedRecord('id')
98
+ record = { ...record, id: randomUUID() }
73
99
  }
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)
100
+ if (getStore().has(record.id as string)) {
101
+ throw new RecordExists(record.id as string)
86
102
  }
87
103
 
88
- return record as T
104
+ return write(record, opts)
89
105
  },
90
106
 
91
- create: async (record, opts) => {
92
- const store = getStore()
107
+ update: async (record: Partial<T>, opts?: WriteOptions) => {
93
108
  if (record.id == null) {
94
- throw new UnknownRecordError('id')
95
- }
96
- if (store.has(record.id)) {
97
- throw new RecordExists(record.id)
109
+ throw new MisshapedRecord('id')
98
110
  }
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)
111
+ if (!getStore().has(record.id)) {
112
+ throw new UnknownRecordError(record.id)
110
113
  }
111
114
 
112
- return record as any
113
- },
114
-
115
- update: async (record, opts) => {
116
- return resource.save(record, opts)
115
+ return write(record, opts)
117
116
  },
118
117
 
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
- }
118
+ save: async (record: Partial<T>, opts?: WriteOptions) => write(record, opts),
131
119
 
120
+ delete: async (id: string) => {
121
+ const store = getStore()
122
+ const record = store.get(id) ?? null
132
123
  if (record == null) {
133
124
  return null
134
125
  }
126
+ store.delete(id)
135
127
 
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
- }
128
+ return record
129
+ },
149
130
 
131
+ take: async (id: string) => {
150
132
  const store = getStore()
151
- if (store.delete(id)) {
152
- return record as any
133
+ const record = store.get(id)
134
+ if (record == null) {
135
+ throw new UnknownRecordError(id)
153
136
  }
137
+ store.delete(id)
154
138
 
155
- return null
139
+ return record
156
140
  },
157
141
 
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')
142
+ purge: async (where: Criteria<T>) => {
143
+ if (where == null || Object.keys(where).length < 1) {
144
+ throw new UnsupportedArgumentError('purge:empty-criteria')
162
145
  }
146
+ const store = getStore()
147
+ const matched = filterRecords(records(), where)
148
+ matched.forEach(record => record.id != null && store.delete(record.id))
163
149
 
164
- return record as any
150
+ return matched.length
165
151
  }
166
152
  })
167
153
 
package/build/.gitkeep DELETED
File without changes