@owlmeans/resource 0.1.18-rc.2 → 0.1.18-rc.21
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 +355 -76
- package/agent-meta/manifest.json +9 -2
- package/agent-meta/skills/resource/SKILL.md +143 -11
- package/agent-meta/skills/resource-choice/SKILL.md +264 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +4 -27
- package/build/consts.js.map +1 -1
- package/build/helper.d.ts +0 -2
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +0 -24
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/types.d.ts +125 -44
- package/build/types.d.ts.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/name.d.ts +1 -1
- package/build/utils/name.d.ts.map +1 -1
- package/build/utils/name.js +1 -19
- package/build/utils/name.js.map +1 -1
- package/build/utils/query.d.ts +29 -0
- package/build/utils/query.d.ts.map +1 -0
- package/build/utils/query.js +270 -0
- package/build/utils/query.js.map +1 -0
- package/package.json +3 -5
- package/src/consts.ts +4 -27
- package/src/helper.ts +0 -28
- package/src/index.ts +1 -0
- package/src/types.ts +125 -46
- package/src/utils/index.ts +1 -0
- package/src/utils/name.ts +3 -25
- package/src/utils/query.ts +277 -0
- package/build/.gitkeep +0 -0
package/build/types.d.ts
CHANGED
|
@@ -1,54 +1,143 @@
|
|
|
1
1
|
import type { BasicConfig, BasicContext, BasicResource, LazyService } from '@owlmeans/context';
|
|
2
2
|
import type { MigrationStage } from './consts.js';
|
|
3
|
+
export interface ResourceRecord {
|
|
4
|
+
id?: string;
|
|
5
|
+
}
|
|
6
|
+
/** Operators a single field can be constrained by. */
|
|
7
|
+
export interface FieldOperators<V> {
|
|
8
|
+
$eq?: V;
|
|
9
|
+
$ne?: V;
|
|
10
|
+
$gt?: V;
|
|
11
|
+
$gte?: V;
|
|
12
|
+
$lt?: V;
|
|
13
|
+
$lte?: V;
|
|
14
|
+
$in?: Array<V | null>;
|
|
15
|
+
$nin?: Array<V | null>;
|
|
16
|
+
$exists?: boolean;
|
|
17
|
+
$null?: boolean;
|
|
18
|
+
$like?: string;
|
|
19
|
+
$ilike?: string;
|
|
20
|
+
$regex?: string;
|
|
21
|
+
$startsWith?: string;
|
|
22
|
+
$endsWith?: string;
|
|
23
|
+
$between?: [V, V];
|
|
24
|
+
$contains?: V | V[];
|
|
25
|
+
$contained?: V[];
|
|
26
|
+
$overlaps?: V[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How one field is constrained: a bare value is equality, a bare ARRAY is "any of these", `null`
|
|
30
|
+
* asks for the absence of a value, and `undefined` is SKIPPED — an untouched filter must never
|
|
31
|
+
* empty a list.
|
|
32
|
+
*/
|
|
33
|
+
export type FieldCriteria<V> = V | Array<V | null> | FieldOperators<NonNullable<V>> | null | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* A query over records of `T`. Keys are the record's own fields, so a typo is a compile error;
|
|
36
|
+
* a dotted key reaches into a nested value (or a jsonb column) and stays open.
|
|
37
|
+
*/
|
|
38
|
+
export type Criteria<T> = {
|
|
39
|
+
[K in keyof T & string]?: FieldCriteria<T[K]>;
|
|
40
|
+
} & {
|
|
41
|
+
[path: `${string}.${string}`]: FieldCriteria<unknown>;
|
|
42
|
+
} & {
|
|
43
|
+
$and?: Criteria<T>[];
|
|
44
|
+
$or?: Criteria<T>[];
|
|
45
|
+
$not?: Criteria<T>;
|
|
46
|
+
};
|
|
47
|
+
export type SortField<T> = (keyof T & string) | `${string}.${string}`;
|
|
48
|
+
/** A bare field name sorts ascending. */
|
|
49
|
+
export type Sort<T> = SortField<T> | {
|
|
50
|
+
field: SortField<T>;
|
|
51
|
+
order?: 'asc' | 'desc';
|
|
52
|
+
};
|
|
53
|
+
export interface FirstOptions<T> {
|
|
54
|
+
sort?: Sort<T>[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Paging is opt-in per call. A backend that cannot afford an unbounded read applies its own
|
|
58
|
+
* default size when none is asked for; the in-memory ones return everything.
|
|
59
|
+
*/
|
|
60
|
+
export interface ListOptions<T> extends FirstOptions<T> {
|
|
61
|
+
page?: number;
|
|
62
|
+
size?: number;
|
|
63
|
+
}
|
|
64
|
+
/** The same question in one object — the shape an API carries over the wire. */
|
|
65
|
+
export interface ListQuery<T> extends ListOptions<T> {
|
|
66
|
+
where?: Criteria<T>;
|
|
67
|
+
}
|
|
68
|
+
export interface ListResult<T extends ResourceRecord> {
|
|
69
|
+
items: T[];
|
|
70
|
+
total: number;
|
|
71
|
+
page?: number;
|
|
72
|
+
size?: number;
|
|
73
|
+
}
|
|
74
|
+
export type Ttl = number | Date;
|
|
75
|
+
export interface WriteOptions {
|
|
76
|
+
/** Seconds from now, or the instant to expire at. Backends without expiry refuse it. */
|
|
77
|
+
ttl?: Ttl;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Typed CRUD over records. Reads take either an id or a criteria object, so fetching one record by
|
|
81
|
+
* several fields is a single call rather than a list whose first element is taken.
|
|
82
|
+
*/
|
|
3
83
|
export interface Resource<T extends ResourceRecord> extends BasicResource {
|
|
4
84
|
/**
|
|
5
85
|
* @throws {UnknownRecordError}
|
|
6
86
|
*/
|
|
7
|
-
get
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
87
|
+
get(id: string): Promise<T>;
|
|
88
|
+
get(where: Criteria<T>, opts?: FirstOptions<T>): Promise<T>;
|
|
89
|
+
load(id: string): Promise<T | null>;
|
|
90
|
+
load(where: Criteria<T>, opts?: FirstOptions<T>): Promise<T | null>;
|
|
91
|
+
list(where?: Criteria<T>, opts?: ListOptions<T>): Promise<ListResult<T>>;
|
|
92
|
+
count(where?: Criteria<T>): Promise<number>;
|
|
11
93
|
/**
|
|
12
94
|
* @throws {RecordExists}
|
|
13
95
|
*/
|
|
14
|
-
create
|
|
96
|
+
create(record: Partial<T>, opts?: WriteOptions): Promise<T>;
|
|
15
97
|
/**
|
|
98
|
+
* Replaces the whole record.
|
|
16
99
|
* @throws {UnknownRecordError}
|
|
17
100
|
*/
|
|
18
|
-
update
|
|
19
|
-
|
|
101
|
+
update(record: Partial<T>, opts?: WriteOptions): Promise<T>;
|
|
102
|
+
/** Creates when the record carries no id, replaces otherwise. */
|
|
103
|
+
save(record: Partial<T>, opts?: WriteOptions): Promise<T>;
|
|
104
|
+
delete(id: string): Promise<T | null>;
|
|
20
105
|
/**
|
|
106
|
+
* Delete-and-return — the consume-once read. Absence is an error here; use `delete` when it is not.
|
|
21
107
|
* @throws {UnknownRecordError}
|
|
22
108
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
id?: string;
|
|
27
|
-
}
|
|
28
|
-
type MaybeArray<T> = T | T[];
|
|
29
|
-
export interface ListCriteria extends Record<string, MaybeArray<ListCriteria | number | string | undefined>> {
|
|
109
|
+
take(id: string): Promise<T>;
|
|
110
|
+
/** Bulk delete. Refuses an empty criteria object rather than emptying the resource. */
|
|
111
|
+
purge(where: Criteria<T>): Promise<number>;
|
|
30
112
|
}
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
113
|
+
export type Unsubscribe = () => Promise<void>;
|
|
114
|
+
export interface SubscribeOptions {
|
|
115
|
+
channel?: string;
|
|
116
|
+
once?: boolean;
|
|
117
|
+
ttl?: Ttl;
|
|
34
118
|
}
|
|
35
|
-
|
|
36
|
-
export interface
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
size?: number;
|
|
40
|
-
total?: number;
|
|
119
|
+
/** Optional capability: a backend that can carry messages beside its records. */
|
|
120
|
+
export interface PubSubResource<T> {
|
|
121
|
+
publish(value: T, channel?: string): Promise<void>;
|
|
122
|
+
subscribe(handler: (value: T) => void | Promise<void>, opts?: SubscribeOptions): Promise<Unsubscribe>;
|
|
41
123
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
124
|
+
/** Optional capability: watching ONE record for changes (redis keyspace notifications). */
|
|
125
|
+
export interface WatchableResource<T> {
|
|
126
|
+
watch(id: string, handler: (value: T | null) => void | Promise<void>, opts?: Omit<SubscribeOptions, 'channel'>): Promise<Unsubscribe>;
|
|
45
127
|
}
|
|
46
|
-
|
|
47
|
-
export interface
|
|
48
|
-
|
|
128
|
+
/** Optional capability: an append-only log with consumer groups. */
|
|
129
|
+
export interface StreamResource<T> {
|
|
130
|
+
stream(key: string, value: T): Promise<void>;
|
|
131
|
+
consume(key: string, opts?: {
|
|
132
|
+
group?: string;
|
|
133
|
+
consumer?: string;
|
|
134
|
+
block?: number;
|
|
135
|
+
}): AsyncGenerator<T>;
|
|
49
136
|
}
|
|
50
|
-
|
|
51
|
-
|
|
137
|
+
/** Optional capability: field level encryption at rest. */
|
|
138
|
+
export interface LockableResource<T extends ResourceRecord> {
|
|
139
|
+
lock: (record: Partial<T>, fields?: string[]) => Promise<T>;
|
|
140
|
+
unlock: (record: Partial<T>, fields?: string[]) => Promise<T>;
|
|
52
141
|
}
|
|
53
142
|
export interface ResourceMaker<R extends ResourceRecord, T extends Resource<R> = Resource<R>> {
|
|
54
143
|
(dbAlias?: string, serviceAlias?: string): T;
|
|
@@ -84,10 +173,6 @@ export interface DbLocker<T extends ResourceRecord> {
|
|
|
84
173
|
lock: (alias: string, record: Partial<T>, fields: string[]) => Promise<T>;
|
|
85
174
|
unlock: (alias: string, record: Partial<T>, fields: string[]) => Promise<T>;
|
|
86
175
|
}
|
|
87
|
-
export interface ResourceLocker<T extends ResourceRecord> {
|
|
88
|
-
lock: (record: Partial<T>, fields?: string[]) => Promise<T>;
|
|
89
|
-
unlock: (record: Partial<T>, fields?: string[]) => Promise<T>;
|
|
90
|
-
}
|
|
91
176
|
export interface DbConfig<P extends {} = {}> {
|
|
92
177
|
service: string;
|
|
93
178
|
alias?: string;
|
|
@@ -97,8 +182,6 @@ export interface DbConfig<P extends {} = {}> {
|
|
|
97
182
|
secret?: string;
|
|
98
183
|
schema?: string;
|
|
99
184
|
resourcePrefix?: string;
|
|
100
|
-
entitySensitive?: boolean;
|
|
101
|
-
serviceSensitive?: boolean;
|
|
102
185
|
encryptionKey?: string;
|
|
103
186
|
meta?: P;
|
|
104
187
|
}
|
|
@@ -113,16 +196,15 @@ export interface DbConfig<P extends {} = {}> {
|
|
|
113
196
|
* invoked. Backends with a durable structure also implement {@link MigrationStore}, the
|
|
114
197
|
* register that tracks which migrations have been applied.
|
|
115
198
|
*/
|
|
116
|
-
export interface MigratableResource<Tx = unknown> {
|
|
199
|
+
export interface MigratableResource<Tx = unknown, Self = unknown> {
|
|
117
200
|
/**
|
|
118
201
|
* Register a migration, applied once per database in declaration order.
|
|
119
202
|
*
|
|
120
|
-
* Chainable and idempotent: re-registering the same name with the same body is a no-op,
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* {@link MigrationConflict}.
|
|
203
|
+
* Chainable and idempotent: re-registering the same name with the same body is a no-op, which is
|
|
204
|
+
* what makes it safe to call from a resource maker that runs more than once for the same alias.
|
|
205
|
+
* Re-registering a *changed* body under a used name throws {@link MigrationConflict}.
|
|
124
206
|
*/
|
|
125
|
-
migration: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) =>
|
|
207
|
+
migration: (name: string, apply: (tx: Tx) => Promise<void>, stage?: MigrationStage) => Self;
|
|
126
208
|
/** The registered migrations for this resource's alias. Read-only; use {@link MigratableResource.migration}. */
|
|
127
209
|
migrations: () => MigrationRegistry<Tx>;
|
|
128
210
|
}
|
|
@@ -174,5 +256,4 @@ export interface Config extends BasicConfig {
|
|
|
174
256
|
}
|
|
175
257
|
export interface Context<C extends Config = Config> extends BasicContext<C> {
|
|
176
258
|
}
|
|
177
|
-
export {};
|
|
178
259
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,CAAC,CAAA;IACP,GAAG,CAAC,EAAE,CAAC,CAAA;IACP,GAAG,CAAC,EAAE,CAAC,CAAA;IACP,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,GAAG,CAAC,EAAE,CAAC,CAAA;IACP,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACjB,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,CAAC,EAAE,CAAA;IAChB,SAAS,CAAC,EAAE,CAAC,EAAE,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;AAEtG;;;GAGG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAClB;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACjD;IAAE,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;CAAE,GACzD;IAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAE,CAAA;AAErE,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;AAErE,yCAAyC;AACzC,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAA;AAEpF,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,gFAAgF;AAChF,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;IAClD,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CACpB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,cAAc;IAClD,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,IAAI,CAAA;AAE/B,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,GAAG,CAAC,EAAE,GAAG,CAAA;CACV;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,aAAa;IACvE;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3B,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACnE,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACxE,KAAK,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3C;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,iEAAiE;IACjE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACzD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5B,uFAAuF;IACvF,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC3C;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,GAAG,CAAA;CACV;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,SAAS,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACtG;AAED,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACtI;AAED,oEAAoE;AACpE,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;CACtG;AAED,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,cAAc;IACxD,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC1F,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,iBAAiB,CAAC,EAAE,EAAE,MAAM,CAAE,SAAQ,WAAW;IAChE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;;OAEG;IACH,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,CAAA;IAEnC;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAA;IAEpC;;OAEG;IACH,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7C;;OAEG;IACH,iBAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAExD;;OAEG;IACH,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAA;IAE3C;;OAEG;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,cAAc;IAChD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACzE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5E;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,CAAC,CAAA;CACT;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,GAAG,OAAO;IAC9D;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3F,gHAAgH;IAChH,UAAU,EAAE,MAAM,iBAAiB,CAAC,EAAE,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,SAAS,CAAC,EAAE,GAAG,OAAO;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB,CAAC,EAAE,GAAG,OAAO;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,cAAc,KAAK,iBAAiB,CAAC,EAAE,CAAC,CAAA;IAC3G,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC,EAAE,CAAC,EAAE,CAAA;IACjD,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,EAAE,GAAG,OAAO;IAC1C,oDAAoD;IACpD,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,6FAA6F;IAC7F,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;IAClE,gGAAgG;IAChG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,kDAAkD;IAClD,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,cAAc,CAAA;IACrB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,MAAO,SAAQ,WAAW;IACzC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CAC1E"}
|
package/build/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA"}
|
package/build/utils/index.js
CHANGED
package/build/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA"}
|
package/build/utils/name.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Context, Config, ResourceDbService, DbConfig } from '../types.js';
|
|
2
|
-
export declare const dbName: <C extends Config, T extends Context<C> = Context<C>>(
|
|
2
|
+
export declare const dbName: <C extends Config, T extends Context<C> = Context<C>>(_context: T, service: ResourceDbService<any, any>, config: DbConfig) => string;
|
|
3
3
|
//# sourceMappingURL=name.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAC,MAAM,aAAa,CAAA;AAE7E,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,YAC9D,CAAC,WAAW,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,QAAQ,WAClB,CAAA"}
|
package/build/utils/name.js
CHANGED
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { hex } from '@scure/base';
|
|
4
|
-
export const dbName = (context, service, config) => {
|
|
5
|
-
let name = config.schema ?? config.alias ?? service.alias;
|
|
6
|
-
if (context.cfg.layerId != null
|
|
7
|
-
&& [Layer.Entity, Layer.User].includes(context.cfg.layer)
|
|
8
|
-
&& service.layers?.includes(Layer.Entity)) {
|
|
9
|
-
name = format(name, context.cfg.layerId);
|
|
10
|
-
}
|
|
11
|
-
return name;
|
|
12
|
-
};
|
|
13
|
-
const format = (name, suffix) => {
|
|
14
|
-
name = (name + '_' + suffix).replace(/\W/g, '_');
|
|
15
|
-
if (name.length > 64) {
|
|
16
|
-
name = name.substring(0, 31) + '_' + hex.encode(sha256(name.substring(31)));
|
|
17
|
-
}
|
|
18
|
-
return name;
|
|
19
|
-
};
|
|
1
|
+
export const dbName = (_context, service, config) => config.schema ?? config.alias ?? service.alias;
|
|
20
2
|
//# sourceMappingURL=name.js.map
|
package/build/utils/name.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"name.js","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"name.js","sourceRoot":"","sources":["../../src/utils/name.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,QAAW,EAAE,OAAoC,EAAE,MAAgB,EACnE,EAAE,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Criteria, ListOptions, ListResult, ResourceRecord, Sort } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether one record satisfies the criteria.
|
|
4
|
+
*
|
|
5
|
+
* `undefined` as a criteria value is skipped rather than compared — it is what an optional filter
|
|
6
|
+
* looks like when nothing was chosen, and treating it as "must be undefined" makes an unset
|
|
7
|
+
* dropdown filter the list down to nothing.
|
|
8
|
+
*
|
|
9
|
+
* @throws {UnsupportedArgumentError} on an unknown operator.
|
|
10
|
+
*/
|
|
11
|
+
export declare const matchCriteria: <T extends ResourceRecord>(record: T, criteria?: Criteria<any>) => boolean;
|
|
12
|
+
/** Every record the criteria accepts, in insertion order. */
|
|
13
|
+
export declare const filterRecords: <T extends ResourceRecord>(records: T[], criteria?: Criteria<any>) => T[];
|
|
14
|
+
/**
|
|
15
|
+
* Sort a copy. A bare field name is ascending; `{ field, order: 'desc' }` reverses it — the same
|
|
16
|
+
* meaning every backend gives it, so a sort written for one store orders the same way against
|
|
17
|
+
* another.
|
|
18
|
+
*/
|
|
19
|
+
export declare const sortRecords: <T extends ResourceRecord>(records: T[], sort?: Sort<any>[]) => T[];
|
|
20
|
+
/** The first record the criteria accepts, honouring the sort. */
|
|
21
|
+
export declare const firstMatch: <T extends ResourceRecord>(records: T[], where?: Criteria<any>, opts?: {
|
|
22
|
+
sort?: Sort<any>[];
|
|
23
|
+
}) => T | null;
|
|
24
|
+
/**
|
|
25
|
+
* Filter, sort and page an in-memory record set into the shape every resource answers with.
|
|
26
|
+
* `size` is opt-in: an in-memory store returns everything when none is asked for.
|
|
27
|
+
*/
|
|
28
|
+
export declare const applyQuery: <T extends ResourceRecord>(records: T[], where?: Criteria<any>, opts?: ListOptions<any>) => ListResult<T>;
|
|
29
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/utils/query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAgK1F;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,cAAc,UAC5C,CAAC,aAAa,QAAQ,CAAC,GAAG,CAAC,KAClC,OA0CF,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,cAAc,WAC3C,CAAC,EAAE,aAAa,QAAQ,CAAC,GAAG,CAAC,KACrC,CAAC,EAE2D,CAAA;AAE/D;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,cAAc,WAAW,CAAC,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC,EAAE,KAAG,CAAC,EAuBzF,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,cAAc,WACxC,CAAC,EAAE,UAAU,QAAQ,CAAC,GAAG,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;CAAE,KACjE,CAAC,GAAG,IAAyE,CAAA;AAEhF;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,cAAc,WACxC,CAAC,EAAE,UAAU,QAAQ,CAAC,GAAG,CAAC,SAAS,WAAW,CAAC,GAAG,CAAC,KAC3D,UAAU,CAAC,CAAC,CAYd,CAAA"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { UnsupportedArgumentError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory evaluation of `Criteria<any>`.
|
|
4
|
+
*
|
|
5
|
+
* The operator set mirrors `@owlmeans/postgres-resource` on purpose: one criteria shape has to
|
|
6
|
+
* mean the same thing whether a list is filtered in SQL by an endpoint or locally by a screen
|
|
7
|
+
* reading the state resource. A client that learns `{ status: ['open', 'done'] }` means "either
|
|
8
|
+
* of these" must not meet a different answer when the same object reaches the server.
|
|
9
|
+
*
|
|
10
|
+
* Two deliberate differences, both because a state resource carries no schema:
|
|
11
|
+
*
|
|
12
|
+
* - An unknown key cannot be detected. Postgres throws on one (a typo silently widening a query
|
|
13
|
+
* to the whole table is worth being loud about); here the property is simply absent from the
|
|
14
|
+
* record, so the record does not match. There is nothing to compare it against.
|
|
15
|
+
* - There is no jsonb/column distinction, so a dotted key always reaches into the record.
|
|
16
|
+
*/
|
|
17
|
+
const COMPARISON = new Set(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte']);
|
|
18
|
+
/** Dates compare by instant; everything else compares as it is. */
|
|
19
|
+
const comparable = (value) => value instanceof Date ? value.getTime() : value;
|
|
20
|
+
/** Reach `profile.city` inside a record. A missing step yields `undefined`, never a throw. */
|
|
21
|
+
const reach = (record, path) => path.reduce((value, step) => value == null || typeof value !== 'object'
|
|
22
|
+
? undefined
|
|
23
|
+
: value[step], record);
|
|
24
|
+
/**
|
|
25
|
+
* An object naming at least one `$` key is a spec, not a value to compare against.
|
|
26
|
+
* A Date and an array are values even though both are objects.
|
|
27
|
+
*/
|
|
28
|
+
const isOperatorSpec = (value) => value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)
|
|
29
|
+
&& Object.keys(value).some(key => key.startsWith('$'));
|
|
30
|
+
const equal = (left, right) => {
|
|
31
|
+
if (left instanceof Date || right instanceof Date) {
|
|
32
|
+
return comparable(left) === comparable(right);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
35
|
+
return left.length === right.length && left.every((entry, index) => equal(entry, right[index]));
|
|
36
|
+
}
|
|
37
|
+
return left === right;
|
|
38
|
+
};
|
|
39
|
+
/** `$in` / `$nin`. `null` in the list matches an absent property, matching the SQL widening. */
|
|
40
|
+
const inList = (value, operand) => {
|
|
41
|
+
const values = Array.isArray(operand) ? operand : [operand];
|
|
42
|
+
return values.some(entry => entry == null ? value == null : equal(value, entry));
|
|
43
|
+
};
|
|
44
|
+
const text = (value) => value == null ? '' : `${value}`;
|
|
45
|
+
/** `%` and `_` are the SQL wildcards; `\` escapes them. */
|
|
46
|
+
const likeToRegExp = (pattern, flags) => {
|
|
47
|
+
let source = '';
|
|
48
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
49
|
+
const char = pattern[index];
|
|
50
|
+
if (char === '\\' && index + 1 < pattern.length) {
|
|
51
|
+
source += pattern[++index].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === '%') {
|
|
55
|
+
source += '.*';
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (char === '_') {
|
|
59
|
+
source += '.';
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
source += char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
63
|
+
}
|
|
64
|
+
return new RegExp(`^${source}$`, flags);
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* @throws {UnsupportedArgumentError} on an operator this store cannot answer.
|
|
68
|
+
*/
|
|
69
|
+
const applyOperators = (value, spec) => {
|
|
70
|
+
for (const [operator, operand] of Object.entries(spec)) {
|
|
71
|
+
if (COMPARISON.has(operator)) {
|
|
72
|
+
const left = comparable(value);
|
|
73
|
+
const right = comparable(operand);
|
|
74
|
+
const matched = operator === '$eq' ? equal(value, operand)
|
|
75
|
+
: operator === '$ne' ? !equal(value, operand)
|
|
76
|
+
: left == null || right == null ? false
|
|
77
|
+
: operator === '$gt' ? left > right
|
|
78
|
+
: operator === '$gte' ? left >= right
|
|
79
|
+
: operator === '$lt' ? left < right
|
|
80
|
+
: left <= right;
|
|
81
|
+
if (!matched)
|
|
82
|
+
return false;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
switch (operator) {
|
|
86
|
+
case '$in':
|
|
87
|
+
if (!inList(value, operand))
|
|
88
|
+
return false;
|
|
89
|
+
break;
|
|
90
|
+
case '$nin':
|
|
91
|
+
if (inList(value, operand))
|
|
92
|
+
return false;
|
|
93
|
+
break;
|
|
94
|
+
case '$exists':
|
|
95
|
+
if ((value != null) !== (operand !== false))
|
|
96
|
+
return false;
|
|
97
|
+
break;
|
|
98
|
+
case '$null':
|
|
99
|
+
if ((value == null) !== (operand !== false))
|
|
100
|
+
return false;
|
|
101
|
+
break;
|
|
102
|
+
case '$like':
|
|
103
|
+
if (!likeToRegExp(text(operand), '').test(text(value)))
|
|
104
|
+
return false;
|
|
105
|
+
break;
|
|
106
|
+
case '$ilike':
|
|
107
|
+
if (!likeToRegExp(text(operand), 'i').test(text(value)))
|
|
108
|
+
return false;
|
|
109
|
+
break;
|
|
110
|
+
case '$regex':
|
|
111
|
+
if (!new RegExp(text(operand)).test(text(value)))
|
|
112
|
+
return false;
|
|
113
|
+
break;
|
|
114
|
+
case '$startsWith':
|
|
115
|
+
if (!text(value).startsWith(text(operand)))
|
|
116
|
+
return false;
|
|
117
|
+
break;
|
|
118
|
+
case '$endsWith':
|
|
119
|
+
if (!text(value).endsWith(text(operand)))
|
|
120
|
+
return false;
|
|
121
|
+
break;
|
|
122
|
+
case '$between': {
|
|
123
|
+
if (!Array.isArray(operand) || operand.length !== 2) {
|
|
124
|
+
throw new UnsupportedArgumentError(`criteria:$between`);
|
|
125
|
+
}
|
|
126
|
+
const left = comparable(value);
|
|
127
|
+
if (left == null)
|
|
128
|
+
return false;
|
|
129
|
+
const from = comparable(operand[0]);
|
|
130
|
+
const to = comparable(operand[1]);
|
|
131
|
+
if (left < from || left > to)
|
|
132
|
+
return false;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case '$contains': {
|
|
136
|
+
const operands = Array.isArray(operand) ? operand : [operand];
|
|
137
|
+
const values = Array.isArray(value) ? value : [value];
|
|
138
|
+
if (!operands.every(entry => values.some(candidate => equal(candidate, entry))))
|
|
139
|
+
return false;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case '$contained': {
|
|
143
|
+
const operands = Array.isArray(operand) ? operand : [operand];
|
|
144
|
+
const values = Array.isArray(value) ? value : [value];
|
|
145
|
+
if (!values.every(candidate => operands.some(entry => equal(candidate, entry))))
|
|
146
|
+
return false;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case '$overlaps': {
|
|
150
|
+
const operands = Array.isArray(operand) ? operand : [operand];
|
|
151
|
+
const values = Array.isArray(value) ? value : [value];
|
|
152
|
+
if (!values.some(candidate => operands.some(entry => equal(candidate, entry))))
|
|
153
|
+
return false;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
throw new UnsupportedArgumentError(`criteria-operator:${operator}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Whether one record satisfies the criteria.
|
|
164
|
+
*
|
|
165
|
+
* `undefined` as a criteria value is skipped rather than compared — it is what an optional filter
|
|
166
|
+
* looks like when nothing was chosen, and treating it as "must be undefined" makes an unset
|
|
167
|
+
* dropdown filter the list down to nothing.
|
|
168
|
+
*
|
|
169
|
+
* @throws {UnsupportedArgumentError} on an unknown operator.
|
|
170
|
+
*/
|
|
171
|
+
export const matchCriteria = (record, criteria) => {
|
|
172
|
+
for (const [key, raw] of Object.entries(criteria ?? {})) {
|
|
173
|
+
if (raw === undefined) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (key === '$and' || key === '$or') {
|
|
177
|
+
const parts = (Array.isArray(raw) ? raw : [raw]);
|
|
178
|
+
if (parts.length < 1)
|
|
179
|
+
continue;
|
|
180
|
+
const matched = key === '$and'
|
|
181
|
+
? parts.every(part => matchCriteria(record, part))
|
|
182
|
+
: parts.some(part => matchCriteria(record, part));
|
|
183
|
+
if (!matched)
|
|
184
|
+
return false;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (key === '$not') {
|
|
188
|
+
if (matchCriteria(record, raw))
|
|
189
|
+
return false;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const value = reach(record, key.split('.'));
|
|
193
|
+
if (raw === null) {
|
|
194
|
+
if (value != null)
|
|
195
|
+
return false;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (isOperatorSpec(raw)) {
|
|
199
|
+
if (!applyOperators(value, raw))
|
|
200
|
+
return false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(raw)) {
|
|
204
|
+
/**
|
|
205
|
+
* A bare array means "any of these", exactly as it does against a relational store.
|
|
206
|
+
* Exact array equality stays reachable as `{ $eq: [...] }`.
|
|
207
|
+
*/
|
|
208
|
+
if (!inList(value, raw))
|
|
209
|
+
return false;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!equal(value, raw))
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
};
|
|
217
|
+
/** Every record the criteria accepts, in insertion order. */
|
|
218
|
+
export const filterRecords = (records, criteria) => criteria == null || Object.keys(criteria).length < 1
|
|
219
|
+
? [...records]
|
|
220
|
+
: records.filter(record => matchCriteria(record, criteria));
|
|
221
|
+
/**
|
|
222
|
+
* Sort a copy. A bare field name is ascending; `{ field, order: 'desc' }` reverses it — the same
|
|
223
|
+
* meaning every backend gives it, so a sort written for one store orders the same way against
|
|
224
|
+
* another.
|
|
225
|
+
*/
|
|
226
|
+
export const sortRecords = (records, sort) => {
|
|
227
|
+
if (sort == null || sort.length < 1) {
|
|
228
|
+
return [...records];
|
|
229
|
+
}
|
|
230
|
+
const rules = sort.map(entry => typeof entry === 'string'
|
|
231
|
+
? { field: entry, desc: false }
|
|
232
|
+
: { field: entry.field, desc: entry.order === 'desc' });
|
|
233
|
+
return [...records].sort((left, right) => {
|
|
234
|
+
for (const { field, desc } of rules) {
|
|
235
|
+
const a = comparable(reach(left, field.split('.')));
|
|
236
|
+
const b = comparable(reach(right, field.split('.')));
|
|
237
|
+
if (a == null && b == null)
|
|
238
|
+
continue;
|
|
239
|
+
/** An absent value sorts last ascending, which is where a reader expects "no value". */
|
|
240
|
+
if (a == null)
|
|
241
|
+
return 1;
|
|
242
|
+
if (b == null)
|
|
243
|
+
return -1;
|
|
244
|
+
if (a < b)
|
|
245
|
+
return desc ? 1 : -1;
|
|
246
|
+
if (a > b)
|
|
247
|
+
return desc ? -1 : 1;
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
/** The first record the criteria accepts, honouring the sort. */
|
|
253
|
+
export const firstMatch = (records, where, opts) => sortRecords(filterRecords(records, where), opts?.sort)[0] ?? null;
|
|
254
|
+
/**
|
|
255
|
+
* Filter, sort and page an in-memory record set into the shape every resource answers with.
|
|
256
|
+
* `size` is opt-in: an in-memory store returns everything when none is asked for.
|
|
257
|
+
*/
|
|
258
|
+
export const applyQuery = (records, where, opts) => {
|
|
259
|
+
const matched = sortRecords(filterRecords(records, where), opts?.sort);
|
|
260
|
+
const total = matched.length;
|
|
261
|
+
// `size: 0` is the explicit ask for everything — the same meaning the paged backends give it, so
|
|
262
|
+
// one query object means one thing whichever store answers it. An omitted size is unpaged here.
|
|
263
|
+
if (opts?.size == null || opts.size === 0) {
|
|
264
|
+
return { items: matched, total };
|
|
265
|
+
}
|
|
266
|
+
const page = opts.page ?? 0;
|
|
267
|
+
const from = page * opts.size;
|
|
268
|
+
return { items: matched.slice(from, from + opts.size), total, page, size: opts.size };
|
|
269
|
+
};
|
|
270
|
+
//# sourceMappingURL=query.js.map
|