@entifix/core 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/deadline/deadline.d.ts +33 -0
- package/dist/deadline/deadline.d.ts.map +1 -0
- package/dist/deadline/deadline.js +33 -0
- package/dist/deadline/deadline.js.map +1 -0
- package/dist/entity-definition/links/entity-collection-link/entity-collection-link.d.ts +3 -3
- package/dist/entity-definition/links/entity-collection-link/entity-collection-link.d.ts.map +1 -1
- package/dist/entity-definition/links/entity-collection-link/entity-collection-link.js.map +1 -1
- package/dist/entity-definition/links/entity-link/entity-link.d.ts +4 -4
- package/dist/entity-definition/links/entity-link/entity-link.d.ts.map +1 -1
- package/dist/entity-definition/links/entity-link/entity-link.js.map +1 -1
- package/dist/entity-definition/links/entity-link-source/entity-link-source.d.ts +3 -3
- package/dist/entity-definition/links/entity-link-source/entity-link-source.d.ts.map +1 -1
- package/dist/entity-definition/links/entity-link-source/entity-link-source.js.map +1 -1
- package/dist/entity-definition/validate/validate-records.d.ts +23 -6
- package/dist/entity-definition/validate/validate-records.d.ts.map +1 -1
- package/dist/entity-definition/validate/validate-records.js +25 -10
- package/dist/entity-definition/validate/validate-records.js.map +1 -1
- package/dist/envelope/make-envelope.d.ts +7 -1
- package/dist/envelope/make-envelope.d.ts.map +1 -1
- package/dist/envelope/make-envelope.js +12 -0
- package/dist/envelope/make-envelope.js.map +1 -1
- package/dist/envelope/read-envelope.d.ts +14 -7
- package/dist/envelope/read-envelope.d.ts.map +1 -1
- package/dist/envelope/read-envelope.js +36 -25
- package/dist/envelope/read-envelope.js.map +1 -1
- package/dist/envelope/types.d.ts +3 -1
- package/dist/envelope/types.d.ts.map +1 -1
- package/dist/envelope/types.js.map +1 -1
- package/dist/failure/define.d.ts +39 -0
- package/dist/failure/define.d.ts.map +1 -0
- package/dist/failure/define.js +70 -0
- package/dist/failure/define.js.map +1 -0
- package/dist/failure/failure.d.ts +79 -0
- package/dist/failure/failure.d.ts.map +1 -0
- package/dist/failure/failure.js +39 -0
- package/dist/failure/failure.js.map +1 -0
- package/dist/failure/generic.d.ts +141 -0
- package/dist/failure/generic.d.ts.map +1 -0
- package/dist/failure/generic.js +101 -0
- package/dist/failure/generic.js.map +1 -0
- package/dist/failure/index.d.ts +50 -0
- package/dist/failure/index.d.ts.map +1 -0
- package/dist/failure/index.js +47 -0
- package/dist/failure/index.js.map +1 -0
- package/dist/failure/predicates.d.ts +56 -0
- package/dist/failure/predicates.d.ts.map +1 -0
- package/dist/failure/predicates.js +22 -0
- package/dist/failure/predicates.js.map +1 -0
- package/dist/failure/problem.d.ts +49 -0
- package/dist/failure/problem.d.ts.map +1 -0
- package/dist/failure/problem.js +87 -0
- package/dist/failure/problem.js.map +1 -0
- package/dist/failure/registry.d.ts +28 -0
- package/dist/failure/registry.d.ts.map +1 -0
- package/dist/failure/registry.js +36 -0
- package/dist/failure/registry.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/query/fold.d.ts +40 -0
- package/dist/query/fold.d.ts.map +1 -0
- package/dist/query/fold.js +49 -0
- package/dist/query/fold.js.map +1 -0
- package/dist/query/in-memory-query.d.ts +12 -1
- package/dist/query/in-memory-query.d.ts.map +1 -1
- package/dist/query/in-memory-query.js +33 -3
- package/dist/query/in-memory-query.js.map +1 -1
- package/dist/rsql/facet-query-url.d.ts +25 -0
- package/dist/rsql/facet-query-url.d.ts.map +1 -0
- package/dist/rsql/facet-query-url.js +63 -0
- package/dist/rsql/facet-query-url.js.map +1 -0
- package/dist/rsql/index.d.ts +1 -0
- package/dist/rsql/index.d.ts.map +1 -1
- package/dist/rsql/index.js +1 -0
- package/dist/rsql/index.js.map +1 -1
- package/dist/types/EntityFacets.d.ts +30 -0
- package/dist/types/EntityFacets.d.ts.map +1 -0
- package/dist/types/EntityFacets.js +10 -0
- package/dist/types/EntityFacets.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { Exit, Schema } from 'effect';
|
|
4
|
+
import { defaultStatus, FAILURE_CATEGORIES } from './failure.js';
|
|
5
|
+
import { ProtocolFailure } from './generic.js';
|
|
6
|
+
import { failureTypeUri, lookupFailure } from './registry.js';
|
|
7
|
+
/** The content type every error answer carries (RFC 9457). */ export const PROBLEM_CONTENT_TYPE = 'application/problem+json';
|
|
8
|
+
/**
|
|
9
|
+
* The envelope of an RFC 9457 problem, with entifix's members beside the
|
|
10
|
+
* standard ones. `code` is top-level because it is what a client translates;
|
|
11
|
+
* the failure's own fields ride as extension members.
|
|
12
|
+
*/ export const Problem = Schema.Struct({
|
|
13
|
+
type: Schema.String,
|
|
14
|
+
title: Schema.String,
|
|
15
|
+
status: Schema.Number,
|
|
16
|
+
code: Schema.String,
|
|
17
|
+
category: Schema.Literals(FAILURE_CATEGORIES),
|
|
18
|
+
detail: Schema.optionalKey(Schema.String),
|
|
19
|
+
instance: Schema.optionalKey(Schema.String),
|
|
20
|
+
traceId: Schema.optionalKey(Schema.String)
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* The failure's own fields, encoded by the class that registered its code.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ Encoded through the **registered** class, not the instance's
|
|
26
|
+
* constructor, so a failure decoded elsewhere encodes the same way. A failure
|
|
27
|
+
* whose code nothing registered — impossible through the definers — encodes
|
|
28
|
+
* with no fields rather than leaking whatever it carries.
|
|
29
|
+
*/ const encodedFields = (failure)=>{
|
|
30
|
+
const registered = lookupFailure(failure.code);
|
|
31
|
+
if (registered === undefined) return {};
|
|
32
|
+
const encoded = Schema.encodeUnknownExit(registered.failureClass)(failure);
|
|
33
|
+
if (Exit.isFailure(encoded)) return {};
|
|
34
|
+
const _encoded_value = encoded.value, { _tag: _ignored } = _encoded_value, fields = _object_without_properties_loose(_encoded_value, [
|
|
35
|
+
"_tag"
|
|
36
|
+
]);
|
|
37
|
+
return fields;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The problem body a failure answers with.
|
|
41
|
+
*
|
|
42
|
+
* `detail` is the failure's `message` when it has one, and is never
|
|
43
|
+
* `String(error)`: a failure's message is written by its author for a reader,
|
|
44
|
+
* a defect's text is not, and a defect never reaches this function.
|
|
45
|
+
*/ export const problemOf = (failure, options = {})=>{
|
|
46
|
+
var _options_status;
|
|
47
|
+
return _extends({}, encodedFields(failure), {
|
|
48
|
+
type: failureTypeUri(failure.code),
|
|
49
|
+
title: failure._tag,
|
|
50
|
+
status: (_options_status = options.status) != null ? _options_status : defaultStatus(failure.category),
|
|
51
|
+
code: failure.code,
|
|
52
|
+
category: failure.category
|
|
53
|
+
}, failure.message === '' ? {} : {
|
|
54
|
+
detail: failure.message
|
|
55
|
+
}, options.instance === undefined ? {} : {
|
|
56
|
+
instance: options.instance
|
|
57
|
+
}, options.traceId === undefined ? {} : {
|
|
58
|
+
traceId: options.traceId
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
62
|
+
/**
|
|
63
|
+
* The failure a problem body names, with its typed fields — or a
|
|
64
|
+
* {@link ProtocolFailure} when the body is not a problem, names a code nothing
|
|
65
|
+
* registered, or carries fields that do not decode.
|
|
66
|
+
*
|
|
67
|
+
* Never throws and never fails: whatever a peer sends, the caller gets a
|
|
68
|
+
* failure it can branch on.
|
|
69
|
+
*
|
|
70
|
+
* @param status the HTTP status the body arrived with, kept on a
|
|
71
|
+
* `ProtocolFailure` so the caller still knows what the peer meant.
|
|
72
|
+
*/ export const decodeProblem = (body, status)=>{
|
|
73
|
+
const protocol = (reason)=>new ProtocolFailure(_extends({
|
|
74
|
+
reason
|
|
75
|
+
}, status === undefined ? {} : {
|
|
76
|
+
upstreamStatus: status
|
|
77
|
+
}));
|
|
78
|
+
if (!isRecord(body) || typeof body['code'] !== 'string') return protocol('the error body is not a problem');
|
|
79
|
+
const registered = lookupFailure(body['code']);
|
|
80
|
+
if (registered === undefined) return protocol(`the problem names an unknown code '${body['code']}'`);
|
|
81
|
+
const decoded = Schema.decodeUnknownExit(registered.failureClass)(_extends({}, body, {
|
|
82
|
+
_tag: registered.tag
|
|
83
|
+
}));
|
|
84
|
+
return Exit.isSuccess(decoded) ? decoded.value : protocol(`the fields of '${body['code']}' do not decode`);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=problem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/failure/problem.ts"],"sourcesContent":["import { Exit, Schema } from 'effect';\n\nimport {\n defaultStatus,\n type EntifixFailure,\n FAILURE_CATEGORIES,\n} from './failure.js';\nimport { ProtocolFailure } from './generic.js';\nimport { failureTypeUri, lookupFailure } from './registry.js';\n\n/** The content type every error answer carries (RFC 9457). */\nexport const PROBLEM_CONTENT_TYPE = 'application/problem+json';\n\n/**\n * The envelope of an RFC 9457 problem, with entifix's members beside the\n * standard ones. `code` is top-level because it is what a client translates;\n * the failure's own fields ride as extension members.\n */\nexport const Problem = Schema.Struct({\n type: Schema.String,\n title: Schema.String,\n status: Schema.Number,\n code: Schema.String,\n category: Schema.Literals(FAILURE_CATEGORIES),\n detail: Schema.optionalKey(Schema.String),\n instance: Schema.optionalKey(Schema.String),\n traceId: Schema.optionalKey(Schema.String),\n});\n\n/** A problem body: the envelope plus the failure's own fields. */\nexport type Problem = typeof Problem.Type & Readonly<Record<string, unknown>>;\n\nexport interface ProblemOptions {\n /** The status to answer with; the category's default when absent. */\n readonly status?: number;\n readonly traceId?: string;\n /** The request path the failure answers. */\n readonly instance?: string;\n}\n\n/**\n * The failure's own fields, encoded by the class that registered its code.\n *\n * ⚠️ Encoded through the **registered** class, not the instance's\n * constructor, so a failure decoded elsewhere encodes the same way. A failure\n * whose code nothing registered — impossible through the definers — encodes\n * with no fields rather than leaking whatever it carries.\n */\nconst encodedFields = (\n failure: EntifixFailure,\n): Readonly<Record<string, unknown>> => {\n const registered = lookupFailure(failure.code);\n if (registered === undefined) return {};\n const encoded = Schema.encodeUnknownExit(\n registered.failureClass as unknown as Schema.Codec<unknown, unknown>,\n )(failure);\n if (Exit.isFailure(encoded)) return {};\n const { _tag: _ignored, ...fields } = encoded.value as Record<\n string,\n unknown\n >;\n return fields;\n};\n\n/**\n * The problem body a failure answers with.\n *\n * `detail` is the failure's `message` when it has one, and is never\n * `String(error)`: a failure's message is written by its author for a reader,\n * a defect's text is not, and a defect never reaches this function.\n */\nexport const problemOf = (\n failure: EntifixFailure,\n options: ProblemOptions = {},\n): Problem => ({\n ...encodedFields(failure),\n type: failureTypeUri(failure.code),\n title: failure._tag,\n status: options.status ?? defaultStatus(failure.category),\n code: failure.code,\n category: failure.category,\n ...(failure.message === '' ? {} : { detail: failure.message }),\n ...(options.instance === undefined ? {} : { instance: options.instance }),\n ...(options.traceId === undefined ? {} : { traceId: options.traceId }),\n});\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * The failure a problem body names, with its typed fields — or a\n * {@link ProtocolFailure} when the body is not a problem, names a code nothing\n * registered, or carries fields that do not decode.\n *\n * Never throws and never fails: whatever a peer sends, the caller gets a\n * failure it can branch on.\n *\n * @param status the HTTP status the body arrived with, kept on a\n * `ProtocolFailure` so the caller still knows what the peer meant.\n */\nexport const decodeProblem = (\n body: unknown,\n status?: number,\n): EntifixFailure => {\n const protocol = (reason: string) =>\n new ProtocolFailure({\n reason,\n ...(status === undefined ? {} : { upstreamStatus: status }),\n });\n\n if (!isRecord(body) || typeof body['code'] !== 'string')\n return protocol('the error body is not a problem');\n\n const registered = lookupFailure(body['code']);\n if (registered === undefined)\n return protocol(`the problem names an unknown code '${body['code']}'`);\n\n const decoded = Schema.decodeUnknownExit(\n registered.failureClass as unknown as Schema.Codec<unknown, unknown>,\n )({ ...body, _tag: registered.tag });\n\n return Exit.isSuccess(decoded)\n ? (decoded.value as EntifixFailure)\n : protocol(`the fields of '${body['code']}' do not decode`);\n};\n"],"names":["Exit","Schema","defaultStatus","FAILURE_CATEGORIES","ProtocolFailure","failureTypeUri","lookupFailure","PROBLEM_CONTENT_TYPE","Problem","Struct","type","String","title","status","Number","code","category","Literals","detail","optionalKey","instance","traceId","encodedFields","failure","registered","undefined","encoded","encodeUnknownExit","failureClass","isFailure","value","_tag","_ignored","fields","problemOf","options","message","isRecord","Array","isArray","decodeProblem","body","protocol","reason","upstreamStatus","decoded","decodeUnknownExit","tag","isSuccess"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,MAAM,QAAQ,SAAS;AAEtC,SACEC,aAAa,EAEbC,kBAAkB,QACb,eAAe;AACtB,SAASC,eAAe,QAAQ,eAAe;AAC/C,SAASC,cAAc,EAAEC,aAAa,QAAQ,gBAAgB;AAE9D,4DAA4D,GAC5D,OAAO,MAAMC,uBAAuB,2BAA2B;AAE/D;;;;CAIC,GACD,OAAO,MAAMC,UAAUP,OAAOQ,MAAM,CAAC;IACnCC,MAAMT,OAAOU,MAAM;IACnBC,OAAOX,OAAOU,MAAM;IACpBE,QAAQZ,OAAOa,MAAM;IACrBC,MAAMd,OAAOU,MAAM;IACnBK,UAAUf,OAAOgB,QAAQ,CAACd;IAC1Be,QAAQjB,OAAOkB,WAAW,CAAClB,OAAOU,MAAM;IACxCS,UAAUnB,OAAOkB,WAAW,CAAClB,OAAOU,MAAM;IAC1CU,SAASpB,OAAOkB,WAAW,CAAClB,OAAOU,MAAM;AAC3C,GAAG;AAaH;;;;;;;CAOC,GACD,MAAMW,gBAAgB,CACpBC;IAEA,MAAMC,aAAalB,cAAciB,QAAQR,IAAI;IAC7C,IAAIS,eAAeC,WAAW,OAAO,CAAC;IACtC,MAAMC,UAAUzB,OAAO0B,iBAAiB,CACtCH,WAAWI,YAAY,EACvBL;IACF,IAAIvB,KAAK6B,SAAS,CAACH,UAAU,OAAO,CAAC;IACrC,MAAsCA,iBAAAA,QAAQI,KAAK,EAA7C,EAAEC,MAAMC,QAAQ,EAAa,GAAGN,gBAAXO,0CAAWP;;;IAItC,OAAOO;AACT;AAEA;;;;;;CAMC,GACD,OAAO,MAAMC,YAAY,CACvBX,SACAY,UAA0B,CAAC,CAAC;QAKpBA;WAJK,aACVb,cAAcC;QACjBb,MAAML,eAAekB,QAAQR,IAAI;QACjCH,OAAOW,QAAQQ,IAAI;QACnBlB,MAAM,GAAEsB,kBAAAA,QAAQtB,MAAM,YAAdsB,kBAAkBjC,cAAcqB,QAAQP,QAAQ;QACxDD,MAAMQ,QAAQR,IAAI;QAClBC,UAAUO,QAAQP,QAAQ;OACtBO,QAAQa,OAAO,KAAK,KAAK,CAAC,IAAI;QAAElB,QAAQK,QAAQa,OAAO;IAAC,GACxDD,QAAQf,QAAQ,KAAKK,YAAY,CAAC,IAAI;QAAEL,UAAUe,QAAQf,QAAQ;IAAC,GACnEe,QAAQd,OAAO,KAAKI,YAAY,CAAC,IAAI;QAAEJ,SAASc,QAAQd,OAAO;IAAC;EACnE;AAEH,MAAMgB,WAAW,CAACP,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACQ,MAAMC,OAAO,CAACT;AAEhE;;;;;;;;;;CAUC,GACD,OAAO,MAAMU,gBAAgB,CAC3BC,MACA5B;IAEA,MAAM6B,WAAW,CAACC,SAChB,IAAIvC,gBAAgB;YAClBuC;WACI9B,WAAWY,YAAY,CAAC,IAAI;YAAEmB,gBAAgB/B;QAAO;IAG7D,IAAI,CAACwB,SAASI,SAAS,OAAOA,IAAI,CAAC,OAAO,KAAK,UAC7C,OAAOC,SAAS;IAElB,MAAMlB,aAAalB,cAAcmC,IAAI,CAAC,OAAO;IAC7C,IAAIjB,eAAeC,WACjB,OAAOiB,SAAS,CAAC,mCAAmC,EAAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvE,MAAMI,UAAU5C,OAAO6C,iBAAiB,CACtCtB,WAAWI,YAAY,EACvB,aAAKa;QAAMV,MAAMP,WAAWuB,GAAG;;IAEjC,OAAO/C,KAAKgD,SAAS,CAACH,WACjBA,QAAQf,KAAK,GACdY,SAAS,CAAC,eAAe,EAAED,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;AAC9D,EAAE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FailureCategory, FailureClass, FailureFamily } from './failure.js';
|
|
2
|
+
/** One registered failure: what a wire `code` decodes back into. */
|
|
3
|
+
export interface RegisteredFailure {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly tag: string;
|
|
6
|
+
readonly category: FailureCategory;
|
|
7
|
+
readonly family: FailureFamily;
|
|
8
|
+
/** The RFC 9457 `type` URI. */
|
|
9
|
+
readonly type: string;
|
|
10
|
+
readonly failureClass: FailureClass;
|
|
11
|
+
}
|
|
12
|
+
/** The RFC 9457 `type` a code answers with. */
|
|
13
|
+
export declare const failureTypeUri: (code: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Record a failure's code. A code is permanent (AIP-193): it never changes
|
|
16
|
+
* meaning and is never reused, so a second **tag** claiming one throws here,
|
|
17
|
+
* at module load, rather than putting two meanings on one wire.
|
|
18
|
+
*
|
|
19
|
+
* The same tag registering the same code again replaces the entry. That is a
|
|
20
|
+
* module evaluated twice — a dev server's hot reload, a test re-importing —
|
|
21
|
+
* and refusing it would turn every reload into a boot failure.
|
|
22
|
+
*/
|
|
23
|
+
export declare const registerFailure: (entry: RegisteredFailure) => void;
|
|
24
|
+
/** The failure a code names, if anything registered it. */
|
|
25
|
+
export declare const lookupFailure: (code: string) => RegisteredFailure | undefined;
|
|
26
|
+
/** Every registered failure, ordered by code. */
|
|
27
|
+
export declare const registeredFailures: () => readonly RegisteredFailure[];
|
|
28
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/failure/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACd,MAAM,cAAc,CAAC;AAEtB,oEAAoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC;AAsBD,+CAA+C;AAC/C,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MACf,CAAC;AAEhC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,iBAAiB,KAAG,IAS1D,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,iBAAiB,GAAG,SAC3C,CAAC;AAEvB,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,QAAO,SAAS,iBAAiB,EACO,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry is shared through `globalThis` under a registered symbol, not
|
|
3
|
+
* held in a module-level `Map`.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ Two copies of `@entifix/core` in one process — a bundler that duplicated
|
|
6
|
+
* it, a consumer's own pin beside a dependency's — would otherwise hold two
|
|
7
|
+
* registries, and a failure defined against one would not decode through the
|
|
8
|
+
* other. One table per process is the property the bus relies on when it
|
|
9
|
+
* decodes a failure a second module instance defined.
|
|
10
|
+
*/ const REGISTRY_KEY = Symbol.for('@entifix/core/failure-registry');
|
|
11
|
+
const registry = ()=>{
|
|
12
|
+
var _holder, _REGISTRY_KEY, _;
|
|
13
|
+
const holder = globalThis;
|
|
14
|
+
(_ = (_holder = holder)[_REGISTRY_KEY = REGISTRY_KEY]) != null ? _ : _holder[_REGISTRY_KEY] = new Map();
|
|
15
|
+
return holder[REGISTRY_KEY];
|
|
16
|
+
};
|
|
17
|
+
/** The RFC 9457 `type` a code answers with. */ export const failureTypeUri = (code)=>`urn:entifix:failure:${code}`;
|
|
18
|
+
/**
|
|
19
|
+
* Record a failure's code. A code is permanent (AIP-193): it never changes
|
|
20
|
+
* meaning and is never reused, so a second **tag** claiming one throws here,
|
|
21
|
+
* at module load, rather than putting two meanings on one wire.
|
|
22
|
+
*
|
|
23
|
+
* The same tag registering the same code again replaces the entry. That is a
|
|
24
|
+
* module evaluated twice — a dev server's hot reload, a test re-importing —
|
|
25
|
+
* and refusing it would turn every reload into a boot failure.
|
|
26
|
+
*/ export const registerFailure = (entry)=>{
|
|
27
|
+
const existing = registry().get(entry.code);
|
|
28
|
+
if (existing !== undefined && existing.tag !== entry.tag) throw new Error(`failure code '${entry.code}' is already registered by '${existing.tag}', ` + `and '${entry.tag}' cannot claim it. A code is permanent and names ` + 'one failure: give this one its own.');
|
|
29
|
+
registry().set(entry.code, entry);
|
|
30
|
+
};
|
|
31
|
+
/** The failure a code names, if anything registered it. */ export const lookupFailure = (code)=>registry().get(code);
|
|
32
|
+
/** Every registered failure, ordered by code. */ export const registeredFailures = ()=>[
|
|
33
|
+
...registry().values()
|
|
34
|
+
].sort((a, b)=>a.code.localeCompare(b.code));
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/failure/registry.ts"],"sourcesContent":["import type {\n FailureCategory,\n FailureClass,\n FailureFamily,\n} from './failure.js';\n\n/** One registered failure: what a wire `code` decodes back into. */\nexport interface RegisteredFailure {\n readonly code: string;\n readonly tag: string;\n readonly category: FailureCategory;\n readonly family: FailureFamily;\n /** The RFC 9457 `type` URI. */\n readonly type: string;\n readonly failureClass: FailureClass;\n}\n\n/**\n * The registry is shared through `globalThis` under a registered symbol, not\n * held in a module-level `Map`.\n *\n * ⚠️ Two copies of `@entifix/core` in one process — a bundler that duplicated\n * it, a consumer's own pin beside a dependency's — would otherwise hold two\n * registries, and a failure defined against one would not decode through the\n * other. One table per process is the property the bus relies on when it\n * decodes a failure a second module instance defined.\n */\nconst REGISTRY_KEY = Symbol.for('@entifix/core/failure-registry');\n\ntype Registry = Map<string, RegisteredFailure>;\n\nconst registry = (): Registry => {\n const holder = globalThis as { [REGISTRY_KEY]?: Registry };\n holder[REGISTRY_KEY] ??= new Map();\n return holder[REGISTRY_KEY];\n};\n\n/** The RFC 9457 `type` a code answers with. */\nexport const failureTypeUri = (code: string): string =>\n `urn:entifix:failure:${code}`;\n\n/**\n * Record a failure's code. A code is permanent (AIP-193): it never changes\n * meaning and is never reused, so a second **tag** claiming one throws here,\n * at module load, rather than putting two meanings on one wire.\n *\n * The same tag registering the same code again replaces the entry. That is a\n * module evaluated twice — a dev server's hot reload, a test re-importing —\n * and refusing it would turn every reload into a boot failure.\n */\nexport const registerFailure = (entry: RegisteredFailure): void => {\n const existing = registry().get(entry.code);\n if (existing !== undefined && existing.tag !== entry.tag)\n throw new Error(\n `failure code '${entry.code}' is already registered by '${existing.tag}', ` +\n `and '${entry.tag}' cannot claim it. A code is permanent and names ` +\n 'one failure: give this one its own.',\n );\n registry().set(entry.code, entry);\n};\n\n/** The failure a code names, if anything registered it. */\nexport const lookupFailure = (code: string): RegisteredFailure | undefined =>\n registry().get(code);\n\n/** Every registered failure, ordered by code. */\nexport const registeredFailures = (): readonly RegisteredFailure[] =>\n [...registry().values()].sort((a, b) => a.code.localeCompare(b.code));\n"],"names":["REGISTRY_KEY","Symbol","for","registry","holder","globalThis","Map","failureTypeUri","code","registerFailure","entry","existing","get","undefined","tag","Error","set","lookupFailure","registeredFailures","values","sort","a","b","localeCompare"],"mappings":"AAiBA;;;;;;;;;CASC,GACD,MAAMA,eAAeC,OAAOC,GAAG,CAAC;AAIhC,MAAMC,WAAW;QAEfC,SAAOJ,eAAPI;IADA,MAAMA,SAASC;KACfD,IAAAA,CAAAA,UAAAA,OAAM,CAACJ,gBAAAA,aAAa,YAApBI,IAAAA,OAAM,CAACJ,cAAa,GAAK,IAAIM;IAC7B,OAAOF,MAAM,CAACJ,aAAa;AAC7B;AAEA,6CAA6C,GAC7C,OAAO,MAAMO,iBAAiB,CAACC,OAC7B,CAAC,oBAAoB,EAAEA,MAAM,CAAC;AAEhC;;;;;;;;CAQC,GACD,OAAO,MAAMC,kBAAkB,CAACC;IAC9B,MAAMC,WAAWR,WAAWS,GAAG,CAACF,MAAMF,IAAI;IAC1C,IAAIG,aAAaE,aAAaF,SAASG,GAAG,KAAKJ,MAAMI,GAAG,EACtD,MAAM,IAAIC,MACR,CAAC,cAAc,EAAEL,MAAMF,IAAI,CAAC,4BAA4B,EAAEG,SAASG,GAAG,CAAC,GAAG,CAAC,GACzE,CAAC,KAAK,EAAEJ,MAAMI,GAAG,CAAC,iDAAiD,CAAC,GACpE;IAENX,WAAWa,GAAG,CAACN,MAAMF,IAAI,EAAEE;AAC7B,EAAE;AAEF,yDAAyD,GACzD,OAAO,MAAMO,gBAAgB,CAACT,OAC5BL,WAAWS,GAAG,CAACJ,MAAM;AAEvB,+CAA+C,GAC/C,OAAO,MAAMU,qBAAqB,IAChC;WAAIf,WAAWgB,MAAM;KAAG,CAACC,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEb,IAAI,CAACe,aAAa,CAACD,EAAEd,IAAI,GAAG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './base-entities/configuration-client/index.js';
|
|
2
2
|
export * from './base-entities/entifix-error/index.js';
|
|
3
3
|
export * from './command-palette/index.js';
|
|
4
|
+
export * from './deadline/deadline.js';
|
|
4
5
|
export * from './entity-definition/decorators/accessor/index.js';
|
|
5
6
|
export * from './entity-definition/decorators/entity/index.js';
|
|
6
7
|
export * from './entity-definition/decorators/method/index.js';
|
|
@@ -22,18 +23,21 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
23
|
export * from './entity-definition/serializer/index.js';
|
|
23
24
|
export * from './entity-definition/validate/index.js';
|
|
24
25
|
export * from './envelope/index.js';
|
|
26
|
+
export * from './failure/index.js';
|
|
25
27
|
export * from './hierarchy/index.js';
|
|
26
28
|
export * from './http/session-cookies.js';
|
|
27
29
|
export * from './i18n/format.js';
|
|
28
30
|
export * from './i18n/locales.js';
|
|
29
31
|
export * from './i18n/localized-text.js';
|
|
30
32
|
export * from './i18n/negotiate.js';
|
|
33
|
+
export * from './query/fold.js';
|
|
31
34
|
export * from './query/in-memory-query.js';
|
|
32
35
|
export * from './rsql/index.js';
|
|
33
36
|
export * from './types/BulkOutcome.js';
|
|
34
37
|
export * from './types/Entity.js';
|
|
35
38
|
export * from './types/EntityAction.js';
|
|
36
39
|
export * from './types/EntityDraft.js';
|
|
40
|
+
export * from './types/EntityFacets.js';
|
|
37
41
|
export * from './types/EntityFiltering.js';
|
|
38
42
|
export * from './types/EntityLoadRequest.js';
|
|
39
43
|
export * from './types/EntityPage.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './base-entities/configuration-client/index.js';
|
|
2
2
|
export * from './base-entities/entifix-error/index.js';
|
|
3
3
|
export * from './command-palette/index.js';
|
|
4
|
+
export * from './deadline/deadline.js';
|
|
4
5
|
export * from './entity-definition/decorators/accessor/index.js';
|
|
5
6
|
export * from './entity-definition/decorators/entity/index.js';
|
|
6
7
|
export * from './entity-definition/decorators/method/index.js';
|
|
@@ -22,18 +23,21 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
23
|
export * from './entity-definition/serializer/index.js';
|
|
23
24
|
export * from './entity-definition/validate/index.js';
|
|
24
25
|
export * from './envelope/index.js';
|
|
26
|
+
export * from './failure/index.js';
|
|
25
27
|
export * from './hierarchy/index.js';
|
|
26
28
|
export * from './http/session-cookies.js';
|
|
27
29
|
export * from './i18n/format.js';
|
|
28
30
|
export * from './i18n/locales.js';
|
|
29
31
|
export * from './i18n/localized-text.js';
|
|
30
32
|
export * from './i18n/negotiate.js';
|
|
33
|
+
export * from './query/fold.js';
|
|
31
34
|
export * from './query/in-memory-query.js';
|
|
32
35
|
export * from './rsql/index.js';
|
|
33
36
|
export * from './types/BulkOutcome.js';
|
|
34
37
|
export * from './types/Entity.js';
|
|
35
38
|
export * from './types/EntityAction.js';
|
|
36
39
|
export * from './types/EntityDraft.js';
|
|
40
|
+
export * from './types/EntityFacets.js';
|
|
37
41
|
export * from './types/EntityFiltering.js';
|
|
38
42
|
export * from './types/EntityLoadRequest.js';
|
|
39
43
|
export * from './types/EntityPage.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './hierarchy/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,uBAAuB;AACrC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './deadline/deadline.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './failure/index.js';\nexport * from './hierarchy/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/fold.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFacets.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,yBAAyB;AACvC,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,qBAAqB;AACnC,cAAc,uBAAuB;AACrC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,kBAAkB;AAChC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,0BAA0B;AACxC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `like` compares: text with its accents and its case taken off.
|
|
3
|
+
*
|
|
4
|
+
* One rule, stated once, because three engines answer `like` and each needs it
|
|
5
|
+
* in a different form — the in-memory matcher folds both sides, the Mongo
|
|
6
|
+
* adapter cannot fold a stored value and so widens the query instead, and the
|
|
7
|
+
* SQL adapter folds the column with `translate`. Every form is derived from
|
|
8
|
+
* {@link foldText} here, so none of them can drift into a different rule.
|
|
9
|
+
*
|
|
10
|
+
* The rule is Unicode's: decompose (NFD), drop the combining marks, lowercase.
|
|
11
|
+
* `é` is `e`, `ü` is `u`, and `ñ` is `n` — a buyer on a keyboard without the
|
|
12
|
+
* letter types `nino` for `niño`, and finding nothing is the worse answer. A
|
|
13
|
+
* letter that does not decompose (`ß`, `ø`, `æ`, `ł`) is left as it is, by
|
|
14
|
+
* every engine alike.
|
|
15
|
+
*/
|
|
16
|
+
/** `value` with its accents and case removed. */
|
|
17
|
+
export declare const foldText: (value: string) => string;
|
|
18
|
+
/**
|
|
19
|
+
* The precomposed letters each base letter stands for, both cases and the base
|
|
20
|
+
* itself included: `e` → `eEéÉèÈêÊëË…`.
|
|
21
|
+
*
|
|
22
|
+
* Built from Latin-1 Supplement and Latin Extended-A by {@link foldText}
|
|
23
|
+
* itself, rather than typed out, so a letter folds in the table exactly when
|
|
24
|
+
* it folds in the rule. Both cases are listed explicitly so a pattern built
|
|
25
|
+
* from it never depends on how a regex engine case-folds outside ASCII.
|
|
26
|
+
*/
|
|
27
|
+
export declare const FOLD_TABLE: Readonly<Record<string, string>>;
|
|
28
|
+
/** Escapes a string so it is matched literally inside a regular expression. */
|
|
29
|
+
export declare const escapeRegex: (value: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* A regular expression source that matches `value` as a substring the way
|
|
32
|
+
* `like` does, for an engine that cannot fold the text it searches: the value
|
|
33
|
+
* is folded, and each letter that has accented forms becomes a class of all of
|
|
34
|
+
* them, so `cafe` matches `Café` and `CAFÉ`. Everything else is escaped, so a
|
|
35
|
+
* `.` or a `(` in a search is matched literally.
|
|
36
|
+
*/
|
|
37
|
+
export declare const foldedPattern: (value: string) => string;
|
|
38
|
+
/** Whether `actual` contains `value`, both folded. */
|
|
39
|
+
export declare const containsFolded: (actual: string, value: string) => boolean;
|
|
40
|
+
//# sourceMappingURL=fold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/query/fold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,iDAAiD;AACjD,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,KAAG,MACoB,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUpD,CAAC;AAEL,+EAA+E;AAC/E,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MACE,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAMjC,CAAC;AAEd,sDAAsD;AACtD,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,OACnB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `like` compares: text with its accents and its case taken off.
|
|
3
|
+
*
|
|
4
|
+
* One rule, stated once, because three engines answer `like` and each needs it
|
|
5
|
+
* in a different form — the in-memory matcher folds both sides, the Mongo
|
|
6
|
+
* adapter cannot fold a stored value and so widens the query instead, and the
|
|
7
|
+
* SQL adapter folds the column with `translate`. Every form is derived from
|
|
8
|
+
* {@link foldText} here, so none of them can drift into a different rule.
|
|
9
|
+
*
|
|
10
|
+
* The rule is Unicode's: decompose (NFD), drop the combining marks, lowercase.
|
|
11
|
+
* `é` is `e`, `ü` is `u`, and `ñ` is `n` — a buyer on a keyboard without the
|
|
12
|
+
* letter types `nino` for `niño`, and finding nothing is the worse answer. A
|
|
13
|
+
* letter that does not decompose (`ß`, `ø`, `æ`, `ł`) is left as it is, by
|
|
14
|
+
* every engine alike.
|
|
15
|
+
*/ /** `value` with its accents and case removed. */ export const foldText = (value)=>value.normalize('NFD').replace(RegExp("[\\u0300-\\u036F\\u0483-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1-\\u05C2\\u05C4-\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7-\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u07FD\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0859-\\u085B\\u0897-\\u089F\\u08CA-\\u08E1\\u08E3-\\u0903\\u093A-\\u093C\\u093E-\\u094F\\u0951-\\u0957\\u0962-\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7-\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2-\\u09E3\\u09FE\\u0A01-\\u0A03\\u0A3C\\u0A3E-\\u0A42\\u0A47-\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70-\\u0A71\\u0A75\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AE2-\\u0AE3\\u0AFA-\\u0AFF\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B44\\u0B47-\\u0B48\\u0B4B-\\u0B4D\\u0B55-\\u0B57\\u0B62-\\u0B63\\u0B82\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C00-\\u0C04\\u0C3C\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55-\\u0C56\\u0C62-\\u0C63\\u0C81-\\u0C83\\u0CBC\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5-\\u0CD6\\u0CE2-\\u0CE3\\u0CF3\\u0D00-\\u0D03\\u0D3B-\\u0D3C\\u0D3E-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0D62-\\u0D63\\u0D81-\\u0D83\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DF2-\\u0DF3\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EBC\\u0EC8-\\u0ECE\\u0F18-\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E-\\u0F3F\\u0F71-\\u0F84\\u0F86-\\u0F87\\u0F8D-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102B-\\u103E\\u1056-\\u1059\\u105E-\\u1060\\u1062-\\u1064\\u1067-\\u106D\\u1071-\\u1074\\u1082-\\u108D\\u108F\\u109A-\\u109D\\u135D-\\u135F\\u1712-\\u1715\\u1732-\\u1734\\u1752-\\u1753\\u1772-\\u1773\\u17B4-\\u17D3\\u17DD\\u180B-\\u180D\\u180F\\u1885-\\u1886\\u18A9\\u1920-\\u192B\\u1930-\\u193B\\u1A17-\\u1A1B\\u1A55-\\u1A5E\\u1A60-\\u1A7C\\u1A7F\\u1AB0-\\u1ADD\\u1AE0-\\u1AEB\\u1B00-\\u1B04\\u1B34-\\u1B44\\u1B6B-\\u1B73\\u1B80-\\u1B82\\u1BA1-\\u1BAD\\u1BE6-\\u1BF3\\u1C24-\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE8\\u1CED\\u1CF4\\u1CF7-\\u1CF9\\u1DC0-\\u1DFF\\u20D0-\\u20F0\\u2CEF-\\u2CF1\\u2D7F\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099-\\u309A\\uA66F-\\uA672\\uA674-\\uA67D\\uA69E-\\uA69F\\uA6F0-\\uA6F1\\uA802\\uA806\\uA80B\\uA823-\\uA827\\uA82C\\uA880-\\uA881\\uA8B4-\\uA8C5\\uA8E0-\\uA8F1\\uA8FF\\uA926-\\uA92D\\uA947-\\uA953\\uA980-\\uA983\\uA9B3-\\uA9C0\\uA9E5\\uAA29-\\uAA36\\uAA43\\uAA4C-\\uAA4D\\uAA7B-\\uAA7D\\uAAB0\\uAAB2-\\uAAB4\\uAAB7-\\uAAB8\\uAABE-\\uAABF\\uAAC1\\uAAEB-\\uAAEF\\uAAF5-\\uAAF6\\uABE3-\\uABEA\\uABEC-\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE2F\\u{101FD}\\u{102E0}\\u{10376}-\\u{1037A}\\u{10A01}-\\u{10A03}\\u{10A05}-\\u{10A06}\\u{10A0C}-\\u{10A0F}\\u{10A38}-\\u{10A3A}\\u{10A3F}\\u{10AE5}-\\u{10AE6}\\u{10D24}-\\u{10D27}\\u{10D69}-\\u{10D6D}\\u{10EAB}-\\u{10EAC}\\u{10EFA}-\\u{10EFF}\\u{10F46}-\\u{10F50}\\u{10F82}-\\u{10F85}\\u{11000}-\\u{11002}\\u{11038}-\\u{11046}\\u{11070}\\u{11073}-\\u{11074}\\u{1107F}-\\u{11082}\\u{110B0}-\\u{110BA}\\u{110C2}\\u{11100}-\\u{11102}\\u{11127}-\\u{11134}\\u{11145}-\\u{11146}\\u{11173}\\u{11180}-\\u{11182}\\u{111B3}-\\u{111C0}\\u{111C9}-\\u{111CC}\\u{111CE}-\\u{111CF}\\u{1122C}-\\u{11237}\\u{1123E}\\u{11241}\\u{112DF}-\\u{112EA}\\u{11300}-\\u{11303}\\u{1133B}-\\u{1133C}\\u{1133E}-\\u{11344}\\u{11347}-\\u{11348}\\u{1134B}-\\u{1134D}\\u{11357}\\u{11362}-\\u{11363}\\u{11366}-\\u{1136C}\\u{11370}-\\u{11374}\\u{113B8}-\\u{113C0}\\u{113C2}\\u{113C5}\\u{113C7}-\\u{113CA}\\u{113CC}-\\u{113D0}\\u{113D2}\\u{113E1}-\\u{113E2}\\u{11435}-\\u{11446}\\u{1145E}\\u{114B0}-\\u{114C3}\\u{115AF}-\\u{115B5}\\u{115B8}-\\u{115C0}\\u{115DC}-\\u{115DD}\\u{11630}-\\u{11640}\\u{116AB}-\\u{116B7}\\u{1171D}-\\u{1172B}\\u{1182C}-\\u{1183A}\\u{11930}-\\u{11935}\\u{11937}-\\u{11938}\\u{1193B}-\\u{1193E}\\u{11940}\\u{11942}-\\u{11943}\\u{119D1}-\\u{119D7}\\u{119DA}-\\u{119E0}\\u{119E4}\\u{11A01}-\\u{11A0A}\\u{11A33}-\\u{11A39}\\u{11A3B}-\\u{11A3E}\\u{11A47}\\u{11A51}-\\u{11A5B}\\u{11A8A}-\\u{11A99}\\u{11B60}-\\u{11B67}\\u{11C2F}-\\u{11C36}\\u{11C38}-\\u{11C3F}\\u{11C92}-\\u{11CA7}\\u{11CA9}-\\u{11CB6}\\u{11D31}-\\u{11D36}\\u{11D3A}\\u{11D3C}-\\u{11D3D}\\u{11D3F}-\\u{11D45}\\u{11D47}\\u{11D8A}-\\u{11D8E}\\u{11D90}-\\u{11D91}\\u{11D93}-\\u{11D97}\\u{11EF3}-\\u{11EF6}\\u{11F00}-\\u{11F01}\\u{11F03}\\u{11F34}-\\u{11F3A}\\u{11F3E}-\\u{11F42}\\u{11F5A}\\u{13440}\\u{13447}-\\u{13455}\\u{1611E}-\\u{1612F}\\u{16AF0}-\\u{16AF4}\\u{16B30}-\\u{16B36}\\u{16F4F}\\u{16F51}-\\u{16F87}\\u{16F8F}-\\u{16F92}\\u{16FE4}\\u{16FF0}-\\u{16FF1}\\u{1BC9D}-\\u{1BC9E}\\u{1CF00}-\\u{1CF2D}\\u{1CF30}-\\u{1CF46}\\u{1D165}-\\u{1D169}\\u{1D16D}-\\u{1D172}\\u{1D17B}-\\u{1D182}\\u{1D185}-\\u{1D18B}\\u{1D1AA}-\\u{1D1AD}\\u{1D242}-\\u{1D244}\\u{1DA00}-\\u{1DA36}\\u{1DA3B}-\\u{1DA6C}\\u{1DA75}\\u{1DA84}\\u{1DA9B}-\\u{1DA9F}\\u{1DAA1}-\\u{1DAAF}\\u{1E000}-\\u{1E006}\\u{1E008}-\\u{1E018}\\u{1E01B}-\\u{1E021}\\u{1E023}-\\u{1E024}\\u{1E026}-\\u{1E02A}\\u{1E08F}\\u{1E130}-\\u{1E136}\\u{1E2AE}\\u{1E2EC}-\\u{1E2EF}\\u{1E4EC}-\\u{1E4EF}\\u{1E5EE}-\\u{1E5EF}\\u{1E6E3}\\u{1E6E6}\\u{1E6EE}-\\u{1E6EF}\\u{1E6F5}\\u{1E8D0}-\\u{1E8D6}\\u{1E944}-\\u{1E94A}\\u{E0100}-\\u{E01EF}]", "gu"), '').toLowerCase();
|
|
16
|
+
/**
|
|
17
|
+
* The precomposed letters each base letter stands for, both cases and the base
|
|
18
|
+
* itself included: `e` → `eEéÉèÈêÊëË…`.
|
|
19
|
+
*
|
|
20
|
+
* Built from Latin-1 Supplement and Latin Extended-A by {@link foldText}
|
|
21
|
+
* itself, rather than typed out, so a letter folds in the table exactly when
|
|
22
|
+
* it folds in the rule. Both cases are listed explicitly so a pattern built
|
|
23
|
+
* from it never depends on how a regex engine case-folds outside ASCII.
|
|
24
|
+
*/ export const FOLD_TABLE = (()=>{
|
|
25
|
+
const table = {};
|
|
26
|
+
for(let code = 0xc0; code <= 0x17f; code += 1){
|
|
27
|
+
var _table, _base, _;
|
|
28
|
+
const letter = String.fromCharCode(code);
|
|
29
|
+
const base = foldText(letter);
|
|
30
|
+
if (base === letter || !/^[a-z]$/.test(base)) continue;
|
|
31
|
+
(_ = (_table = table)[_base = base]) != null ? _ : _table[_base] = `${base}${base.toUpperCase()}`;
|
|
32
|
+
table[base] += letter;
|
|
33
|
+
}
|
|
34
|
+
return table;
|
|
35
|
+
})();
|
|
36
|
+
/** Escapes a string so it is matched literally inside a regular expression. */ export const escapeRegex = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
37
|
+
/**
|
|
38
|
+
* A regular expression source that matches `value` as a substring the way
|
|
39
|
+
* `like` does, for an engine that cannot fold the text it searches: the value
|
|
40
|
+
* is folded, and each letter that has accented forms becomes a class of all of
|
|
41
|
+
* them, so `cafe` matches `Café` and `CAFÉ`. Everything else is escaped, so a
|
|
42
|
+
* `.` or a `(` in a search is matched literally.
|
|
43
|
+
*/ export const foldedPattern = (value)=>Array.from(foldText(value)).map((char)=>{
|
|
44
|
+
const forms = FOLD_TABLE[char];
|
|
45
|
+
return forms === undefined ? escapeRegex(char) : `[${forms}]`;
|
|
46
|
+
}).join('');
|
|
47
|
+
/** Whether `actual` contains `value`, both folded. */ export const containsFolded = (actual, value)=>foldText(actual).includes(foldText(value));
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=fold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/query/fold.ts"],"sourcesContent":["/**\n * What `like` compares: text with its accents and its case taken off.\n *\n * One rule, stated once, because three engines answer `like` and each needs it\n * in a different form — the in-memory matcher folds both sides, the Mongo\n * adapter cannot fold a stored value and so widens the query instead, and the\n * SQL adapter folds the column with `translate`. Every form is derived from\n * {@link foldText} here, so none of them can drift into a different rule.\n *\n * The rule is Unicode's: decompose (NFD), drop the combining marks, lowercase.\n * `é` is `e`, `ü` is `u`, and `ñ` is `n` — a buyer on a keyboard without the\n * letter types `nino` for `niño`, and finding nothing is the worse answer. A\n * letter that does not decompose (`ß`, `ø`, `æ`, `ł`) is left as it is, by\n * every engine alike.\n */\n\n/** `value` with its accents and case removed. */\nexport const foldText = (value: string): string =>\n value.normalize('NFD').replace(/\\p{M}/gu, '').toLowerCase();\n\n/**\n * The precomposed letters each base letter stands for, both cases and the base\n * itself included: `e` → `eEéÉèÈêÊëË…`.\n *\n * Built from Latin-1 Supplement and Latin Extended-A by {@link foldText}\n * itself, rather than typed out, so a letter folds in the table exactly when\n * it folds in the rule. Both cases are listed explicitly so a pattern built\n * from it never depends on how a regex engine case-folds outside ASCII.\n */\nexport const FOLD_TABLE: Readonly<Record<string, string>> = (() => {\n const table: Record<string, string> = {};\n for (let code = 0xc0; code <= 0x17f; code += 1) {\n const letter = String.fromCharCode(code);\n const base = foldText(letter);\n if (base === letter || !/^[a-z]$/.test(base)) continue;\n table[base] ??= `${base}${base.toUpperCase()}`;\n table[base] += letter;\n }\n return table;\n})();\n\n/** Escapes a string so it is matched literally inside a regular expression. */\nexport const escapeRegex = (value: string): string =>\n value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * A regular expression source that matches `value` as a substring the way\n * `like` does, for an engine that cannot fold the text it searches: the value\n * is folded, and each letter that has accented forms becomes a class of all of\n * them, so `cafe` matches `Café` and `CAFÉ`. Everything else is escaped, so a\n * `.` or a `(` in a search is matched literally.\n */\nexport const foldedPattern = (value: string): string =>\n Array.from(foldText(value))\n .map(char => {\n const forms = FOLD_TABLE[char];\n return forms === undefined ? escapeRegex(char) : `[${forms}]`;\n })\n .join('');\n\n/** Whether `actual` contains `value`, both folded. */\nexport const containsFolded = (actual: string, value: string): boolean =>\n foldText(actual).includes(foldText(value));\n"],"names":["foldText","value","normalize","replace","toLowerCase","FOLD_TABLE","table","code","base","letter","String","fromCharCode","test","toUpperCase","escapeRegex","foldedPattern","Array","from","map","char","forms","undefined","join","containsFolded","actual","includes"],"mappings":"AAAA;;;;;;;;;;;;;;CAcC,GAED,+CAA+C,GAC/C,OAAO,MAAMA,WAAW,CAACC,QACvBA,MAAMC,SAAS,CAAC,OAAOC,OAAO,CAAC,27JAAW,IAAIC,WAAW,GAAG;AAE9D;;;;;;;;CAQC,GACD,OAAO,MAAMC,aAA+C,AAAC,CAAA;IAC3D,MAAMC,QAAgC,CAAC;IACvC,IAAK,IAAIC,OAAO,MAAMA,QAAQ,OAAOA,QAAQ,EAAG;YAI9CD,QAAME,OAANF;QAHA,MAAMG,SAASC,OAAOC,YAAY,CAACJ;QACnC,MAAMC,OAAOR,SAASS;QACtB,IAAID,SAASC,UAAU,CAAC,UAAUG,IAAI,CAACJ,OAAO;SAC9CF,IAAAA,CAAAA,SAAAA,MAAK,CAACE,QAAAA,KAAK,YAAXF,IAAAA,MAAK,CAACE,MAAK,GAAK,GAAGA,OAAOA,KAAKK,WAAW,IAAI;QAC9CP,KAAK,CAACE,KAAK,IAAIC;IACjB;IACA,OAAOH;AACT,CAAA,IAAK;AAEL,6EAA6E,GAC7E,OAAO,MAAMQ,cAAc,CAACb,QAC1BA,MAAME,OAAO,CAAC,uBAAuB,QAAQ;AAE/C;;;;;;CAMC,GACD,OAAO,MAAMY,gBAAgB,CAACd,QAC5Be,MAAMC,IAAI,CAACjB,SAASC,QACjBiB,GAAG,CAACC,CAAAA;QACH,MAAMC,QAAQf,UAAU,CAACc,KAAK;QAC9B,OAAOC,UAAUC,YAAYP,YAAYK,QAAQ,CAAC,CAAC,EAAEC,MAAM,CAAC,CAAC;IAC/D,GACCE,IAAI,CAAC,IAAI;AAEd,oDAAoD,GACpD,OAAO,MAAMC,iBAAiB,CAACC,QAAgBvB,QAC7CD,SAASwB,QAAQC,QAAQ,CAACzB,SAASC,QAAQ"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Entity } from '../types/Entity.js';
|
|
2
|
+
import type { EntityFacetCounts, EntityFacetQuery } from '../types/EntityFacets.js';
|
|
2
3
|
import type { EntityFiltering } from '../types/EntityFiltering.js';
|
|
3
4
|
import type { EntitySorting } from '../types/EntitySorting.js';
|
|
4
5
|
/**
|
|
@@ -16,7 +17,8 @@ import type { EntitySorting } from '../types/EntitySorting.js';
|
|
|
16
17
|
* - `in` and `nin` against an array match when **any element** does, as `$in`;
|
|
17
18
|
* - a property may be a dotted path into a member holding an object, which is
|
|
18
19
|
* how a filter reaches one locale of a `localized` member (`name.en`);
|
|
19
|
-
* - `like` is a
|
|
20
|
+
* - `like` is a substring match that ignores accents and case — `cafe` finds
|
|
21
|
+
* `Café` — by the one rule in `fold.ts` every adapter derives its form from;
|
|
20
22
|
* - a missing value sorts below a present one, numbers and dates compare as
|
|
21
23
|
* themselves and anything else as text;
|
|
22
24
|
* - every top-level filtering entry is combined with `and`.
|
|
@@ -46,4 +48,13 @@ export declare function applyFiltering<TEntity extends Entity>(records: readonly
|
|
|
46
48
|
* accident. No key leaves the order the records were given in.
|
|
47
49
|
*/
|
|
48
50
|
export declare function applySorting<TEntity extends Entity>(records: readonly TEntity[], sorting: readonly EntitySorting<TEntity>[] | undefined): TEntity[];
|
|
51
|
+
/**
|
|
52
|
+
* How many records hold each value of each queried member, each member counted
|
|
53
|
+
* over the records **its own** filtering keeps — the in-memory form of a Mongo
|
|
54
|
+
* `$facet` of `$match`, `$unwind` and `$group`, with its rules:
|
|
55
|
+
* - an array member counts each element, as `$unwind` does;
|
|
56
|
+
* - a missing or `null` value, or an empty array, is not counted;
|
|
57
|
+
* - only a string, number or boolean is a value; anything else is skipped.
|
|
58
|
+
*/
|
|
59
|
+
export declare function countFacets<TEntity extends Entity>(records: readonly TEntity[], queries: readonly EntityFacetQuery<TEntity>[]): EntityFacetCounts;
|
|
49
60
|
//# sourceMappingURL=in-memory-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-memory-query.d.ts","sourceRoot":"","sources":["../../src/query/in-memory-query.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"in-memory-query.d.ts","sourceRoot":"","sources":["../../src/query/in-memory-query.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAInD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CASjE;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAYzE;AAsJD,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,SAAS,eAAe,CAAC,OAAO,CAAC,EAAE,GAAG,SAAS,GACzD,OAAO,CAMT;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EACnD,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,SAAS,EAAE,SAAS,eAAe,CAAC,OAAO,CAAC,EAAE,GAAG,SAAS,GACzD,OAAO,EAAE,CAEX;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,EACjD,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,OAAO,EAAE,SAAS,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,SAAS,GACrD,OAAO,EAAE,CAqBX;AAWD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,EAChD,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,OAAO,EAAE,SAAS,gBAAgB,CAAC,OAAO,CAAC,EAAE,GAC5C,iBAAiB,CAgBnB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EntifixBuildError } from '../base-entities/entifix-error/index.js';
|
|
2
2
|
import { EntityCollectionLink } from '../entity-definition/links/entity-collection-link/index.js';
|
|
3
3
|
import { EntityLink } from '../entity-definition/links/entity-link/index.js';
|
|
4
|
+
import { containsFolded } from './fold.js';
|
|
4
5
|
/**
|
|
5
6
|
* An {@link EntityLoadRequest}'s filtering and sorting, answered over records
|
|
6
7
|
* held in memory, with the semantics of the Mongo adapter.
|
|
@@ -16,7 +17,8 @@ import { EntityLink } from '../entity-definition/links/entity-link/index.js';
|
|
|
16
17
|
* - `in` and `nin` against an array match when **any element** does, as `$in`;
|
|
17
18
|
* - a property may be a dotted path into a member holding an object, which is
|
|
18
19
|
* how a filter reaches one locale of a `localized` member (`name.en`);
|
|
19
|
-
* - `like` is a
|
|
20
|
+
* - `like` is a substring match that ignores accents and case — `cafe` finds
|
|
21
|
+
* `Café` — by the one rule in `fold.ts` every adapter derives its form from;
|
|
20
22
|
* - a missing value sorts below a present one, numbers and dates compare as
|
|
21
23
|
* themselves and anything else as text;
|
|
22
24
|
* - every top-level filtering entry is combined with `and`.
|
|
@@ -56,8 +58,7 @@ import { EntityLink } from '../entity-definition/links/entity-link/index.js';
|
|
|
56
58
|
const matches = (element)=>values.some((value)=>isEqual(element, value));
|
|
57
59
|
return Array.isArray(actual) ? actual.some(matches) : matches(actual);
|
|
58
60
|
};
|
|
59
|
-
/**
|
|
60
|
-
const matchesLike = (actual, value)=>actual != null && new RegExp(escapeRegex(value), 'i').test(String(actual));
|
|
61
|
+
/** `like`: a substring match with accents and case folded off (`fold.ts`). */ const matchesLike = (actual, value)=>actual != null && containsFolded(String(actual), value);
|
|
61
62
|
const isFilterGroup = (node)=>!('property' in node) && (node.operator === 'and' || node.operator === 'or');
|
|
62
63
|
const isBetween = (actual, start, end)=>actual != null && compareStoredValues(actual, start) >= 0 && compareStoredValues(actual, end) <= 0;
|
|
63
64
|
/**
|
|
@@ -174,5 +175,34 @@ function matchesNode(record, node) {
|
|
|
174
175
|
return 0;
|
|
175
176
|
});
|
|
176
177
|
}
|
|
178
|
+
const isFacetValue = (value)=>typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
179
|
+
/** Count first, highest first; a tie by value, so the order is stable. */ const byCountThenValue = (left, right)=>right.count - left.count || compareStoredValues(left.value, right.value);
|
|
180
|
+
/**
|
|
181
|
+
* How many records hold each value of each queried member, each member counted
|
|
182
|
+
* over the records **its own** filtering keeps — the in-memory form of a Mongo
|
|
183
|
+
* `$facet` of `$match`, `$unwind` and `$group`, with its rules:
|
|
184
|
+
* - an array member counts each element, as `$unwind` does;
|
|
185
|
+
* - a missing or `null` value, or an empty array, is not counted;
|
|
186
|
+
* - only a string, number or boolean is a value; anything else is skipped.
|
|
187
|
+
*/ export function countFacets(records, queries) {
|
|
188
|
+
const counts = {};
|
|
189
|
+
for (const { member, filtering } of queries){
|
|
190
|
+
const tally = new Map();
|
|
191
|
+
for (const record of applyFiltering(records, filtering)){
|
|
192
|
+
const stored = valueAt(record, member);
|
|
193
|
+
for (const value of Array.isArray(stored) ? stored : [
|
|
194
|
+
stored
|
|
195
|
+
]){
|
|
196
|
+
var _tally_get;
|
|
197
|
+
if (isFacetValue(value)) tally.set(value, ((_tally_get = tally.get(value)) != null ? _tally_get : 0) + 1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
counts[member] = Array.from(tally, ([value, count])=>({
|
|
201
|
+
value,
|
|
202
|
+
count
|
|
203
|
+
})).sort(byCountThenValue);
|
|
204
|
+
}
|
|
205
|
+
return counts;
|
|
206
|
+
}
|
|
177
207
|
|
|
178
208
|
//# sourceMappingURL=in-memory-query.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/query/in-memory-query.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\nimport { EntityCollectionLink } from '../entity-definition/links/entity-collection-link/index.js';\nimport { EntityLink } from '../entity-definition/links/entity-link/index.js';\nimport type { Entity } from '../types/Entity.js';\nimport type {\n EntityFilter,\n EntityFiltering,\n FilterGroup,\n} from '../types/EntityFiltering.js';\nimport type { EntitySorting } from '../types/EntitySorting.js';\n\n/**\n * An {@link EntityLoadRequest}'s filtering and sorting, answered over records\n * held in memory, with the semantics of the Mongo adapter.\n *\n * One implementation for everything that queries records it holds itself — the\n * in-memory test double, the static repository, a published view — so none of\n * them can drift into a more forgiving version of the others. They used to be\n * separate copies, and one of them compared an array member with `===` and so\n * never matched `in` (#34).\n *\n * The rules, each Mongo's:\n * - a link is its target's id, a collection link the list of their ids;\n * - `in` and `nin` against an array match when **any element** does, as `$in`;\n * - a property may be a dotted path into a member holding an object, which is\n * how a filter reaches one locale of a `localized` member (`name.en`);\n * - `like` is a case-insensitive substring match, as `$regex` with `i`;\n * - a missing value sorts below a present one, numbers and dates compare as\n * themselves and anything else as text;\n * - every top-level filtering entry is combined with `and`.\n */\n\n/**\n * A member as the Mongo adapter stores it, and so as its queries see it: a\n * link is its target's id, a collection link the list of their ids. Without\n * this no operator can compare a link, since `eq` against an id is never\n * `true` for an object.\n */\nexport function storedValue(value: unknown): unknown {\n if (value instanceof EntityLink) return value.id;\n if (value instanceof EntityCollectionLink) return value.ids;\n return value;\n}\n\n/**\n * The value a property names: a member, or a dotted path into one. A path\n * through something that is not an object ends in `undefined`, as a missing\n * field does in Mongo.\n */\nexport function valueAt(record: object, property: string): unknown {\n const [member, ...path] = property.split('.');\n return path.reduce<unknown>(\n (value, key) =>\n typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)[key]\n : undefined,\n storedValue((record as Record<string, unknown>)[member as string]),\n );\n}\n\n/** Ordering with Mongo's rules; `0` for two values it considers equal. */\nexport function compareStoredValues(left: unknown, right: unknown): number {\n if (left === right) return 0;\n // Mongo sorts missing values below present ones.\n if (left == null) return right == null ? 0 : -1;\n if (right == null) return 1;\n if (typeof left === 'number' && typeof right === 'number') {\n return left - right;\n }\n if (left instanceof Date && right instanceof Date) {\n return left.getTime() - right.getTime();\n }\n return String(left).localeCompare(String(right));\n}\n\n/** Equality by value, so two `Date`s naming one instant are equal. */\nconst isEqual = (actual: unknown, value: unknown): boolean =>\n actual instanceof Date && value instanceof Date\n ? actual.getTime() === value.getTime()\n : actual === value;\n\n/** `$in` semantics: a scalar matches a value, an array when any element does. */\nconst matchesAny = (actual: unknown, values: readonly unknown[]): boolean => {\n const matches = (element: unknown) =>\n values.some(value => isEqual(element, value));\n return Array.isArray(actual) ? actual.some(matches) : matches(actual);\n};\n\n/** Escapes a string so `like`/`nlike` match literally, as the Mongo adapter does. */\nconst escapeRegex = (value: string): string =>\n value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nconst matchesLike = (actual: unknown, value: string): boolean =>\n actual != null && new RegExp(escapeRegex(value), 'i').test(String(actual));\n\nconst isFilterGroup = <TEntity extends Entity>(\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): node is FilterGroup<TEntity> =>\n !('property' in node) && (node.operator === 'and' || node.operator === 'or');\n\nconst isBetween = (actual: unknown, start: unknown, end: unknown) =>\n actual != null &&\n compareStoredValues(actual, start) >= 0 &&\n compareStoredValues(actual, end) <= 0;\n\n/**\n * The values a dotted path reaches **through an array** — every element's\n * member — or `undefined` when the path crosses no array, so the caller reads\n * the single value as before.\n */\nfunction valuesThroughArray(\n record: object,\n property: string,\n): unknown[] | undefined {\n const dot = property.indexOf('.');\n if (dot === -1) return undefined;\n const elements = storedValue(\n (record as Record<string, unknown>)[property.slice(0, dot)],\n );\n if (!Array.isArray(elements)) return undefined;\n const member = property.slice(dot + 1);\n return elements.map(element =>\n typeof element === 'object' && element !== null\n ? valueAt(element as object, member)\n : undefined,\n );\n}\n\n/**\n * Splits a filter into its positive test and whether the operator negates it,\n * so a path into an array can ask \"any element\" of the positive form and\n * negate the answer — never \"any element is not equal\", which almost always\n * holds.\n */\nfunction positiveTest<TEntity extends Entity>(\n filter: EntityFilter<TEntity>,\n): { readonly test: (actual: unknown) => boolean; readonly negated: boolean } {\n switch (filter.operator) {\n case 'eq':\n case 'ne':\n return {\n test: actual => isEqual(actual, filter.value),\n negated: filter.operator === 'ne',\n };\n case 'gt':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) > 0,\n negated: false,\n };\n case 'gte':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) >= 0,\n negated: false,\n };\n case 'lt':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) < 0,\n negated: false,\n };\n case 'lte':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) <= 0,\n negated: false,\n };\n case 'in':\n case 'nin':\n return {\n test: actual => matchesAny(actual, filter.values),\n negated: filter.operator === 'nin',\n };\n case 'between':\n case 'nbetween':\n return {\n test: actual => isBetween(actual, filter.start, filter.end),\n negated: filter.operator === 'nbetween',\n };\n case 'like':\n case 'nlike':\n return {\n test: actual => matchesLike(actual, filter.value),\n negated: filter.operator === 'nlike',\n };\n case 'isNull':\n case 'isNotNull':\n return {\n test: actual => actual != null,\n negated: filter.operator === 'isNull',\n };\n default: {\n // Exhaustiveness guard — a new operator must be handled explicitly.\n const never: never = filter;\n throw new EntifixBuildError(\n `Unknown filter operator \"${String((never as { operator: unknown }).operator)}\"`,\n );\n }\n }\n}\n\nfunction matchesFilter<TEntity extends Entity>(\n record: TEntity,\n filter: EntityFilter<TEntity>,\n): boolean {\n const { test, negated } = positiveTest(filter);\n const property = String(filter.property);\n const elements = valuesThroughArray(record, property);\n const holds =\n elements === undefined\n ? test(valueAt(record, property))\n : elements.some(test);\n return negated ? !holds : holds;\n}\n\nfunction matchesNode<TEntity extends Entity>(\n record: TEntity,\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): boolean {\n if (!isFilterGroup(node)) return matchesFilter(record, node);\n return node.operator === 'or'\n ? node.values.some(child => matchesNode(record, child))\n : node.values.every(child => matchesNode(record, child));\n}\n\n/** Whether one record matches a request's filtering. */\nexport function matchesFiltering<TEntity extends Entity>(\n record: TEntity,\n filtering: readonly EntityFiltering<TEntity>[] | undefined,\n): boolean {\n return (filtering ?? []).every(entry =>\n Array.isArray(entry)\n ? entry.every(child => matchesNode(record, child))\n : matchesNode(record, entry),\n );\n}\n\n/** The records a request's filtering keeps, in the order they were given. */\nexport function applyFiltering<TEntity extends Entity>(\n records: readonly TEntity[],\n filtering: readonly EntityFiltering<TEntity>[] | undefined,\n): TEntity[] {\n return records.filter(record => matchesFiltering(record, filtering));\n}\n\n/**\n * The records in the order a request asks for, as a new array. `EntitySorting`\n * is a record keyed by priority rather than a list, so two keys cannot tie by\n * accident. No key leaves the order the records were given in.\n */\nexport function applySorting<TEntity extends Entity>(\n records: readonly TEntity[],\n sorting: readonly EntitySorting<TEntity>[] | undefined,\n): TEntity[] {\n const keys = (sorting ?? []).flatMap(entry =>\n Object.keys(entry)\n .map(Number)\n .sort((left, right) => left - right)\n .map(priority => entry[priority])\n .filter(key => key != null),\n );\n if (keys.length === 0) return [...records];\n\n return [...records].sort((left, right) => {\n for (const key of keys) {\n const property = String(key.property);\n const compared = compareStoredValues(\n valueAt(left, property),\n valueAt(right, property),\n );\n if (compared !== 0) return key.type === 'desc' ? -compared : compared;\n }\n return 0;\n });\n}\n"],"names":["EntifixBuildError","EntityCollectionLink","EntityLink","storedValue","value","id","ids","valueAt","record","property","member","path","split","reduce","key","undefined","compareStoredValues","left","right","Date","getTime","String","localeCompare","isEqual","actual","matchesAny","values","matches","element","some","Array","isArray","escapeRegex","replace","matchesLike","RegExp","test","isFilterGroup","node","operator","isBetween","start","end","valuesThroughArray","dot","indexOf","elements","slice","map","positiveTest","filter","negated","never","matchesFilter","holds","matchesNode","child","every","matchesFiltering","filtering","entry","applyFiltering","records","applySorting","sorting","keys","flatMap","Object","Number","sort","priority","length","compared","type"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/query/in-memory-query.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\nimport { EntityCollectionLink } from '../entity-definition/links/entity-collection-link/index.js';\nimport { EntityLink } from '../entity-definition/links/entity-link/index.js';\nimport type { Entity } from '../types/Entity.js';\nimport type {\n EntityFacetCounts,\n EntityFacetQuery,\n EntityFacetValue,\n} from '../types/EntityFacets.js';\nimport type {\n EntityFilter,\n EntityFiltering,\n FilterGroup,\n} from '../types/EntityFiltering.js';\nimport type { EntitySorting } from '../types/EntitySorting.js';\nimport { containsFolded } from './fold.js';\n\n/**\n * An {@link EntityLoadRequest}'s filtering and sorting, answered over records\n * held in memory, with the semantics of the Mongo adapter.\n *\n * One implementation for everything that queries records it holds itself — the\n * in-memory test double, the static repository, a published view — so none of\n * them can drift into a more forgiving version of the others. They used to be\n * separate copies, and one of them compared an array member with `===` and so\n * never matched `in` (#34).\n *\n * The rules, each Mongo's:\n * - a link is its target's id, a collection link the list of their ids;\n * - `in` and `nin` against an array match when **any element** does, as `$in`;\n * - a property may be a dotted path into a member holding an object, which is\n * how a filter reaches one locale of a `localized` member (`name.en`);\n * - `like` is a substring match that ignores accents and case — `cafe` finds\n * `Café` — by the one rule in `fold.ts` every adapter derives its form from;\n * - a missing value sorts below a present one, numbers and dates compare as\n * themselves and anything else as text;\n * - every top-level filtering entry is combined with `and`.\n */\n\n/**\n * A member as the Mongo adapter stores it, and so as its queries see it: a\n * link is its target's id, a collection link the list of their ids. Without\n * this no operator can compare a link, since `eq` against an id is never\n * `true` for an object.\n */\nexport function storedValue(value: unknown): unknown {\n if (value instanceof EntityLink) return value.id;\n if (value instanceof EntityCollectionLink) return value.ids;\n return value;\n}\n\n/**\n * The value a property names: a member, or a dotted path into one. A path\n * through something that is not an object ends in `undefined`, as a missing\n * field does in Mongo.\n */\nexport function valueAt(record: object, property: string): unknown {\n const [member, ...path] = property.split('.');\n return path.reduce<unknown>(\n (value, key) =>\n typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)[key]\n : undefined,\n storedValue((record as Record<string, unknown>)[member as string]),\n );\n}\n\n/** Ordering with Mongo's rules; `0` for two values it considers equal. */\nexport function compareStoredValues(left: unknown, right: unknown): number {\n if (left === right) return 0;\n // Mongo sorts missing values below present ones.\n if (left == null) return right == null ? 0 : -1;\n if (right == null) return 1;\n if (typeof left === 'number' && typeof right === 'number') {\n return left - right;\n }\n if (left instanceof Date && right instanceof Date) {\n return left.getTime() - right.getTime();\n }\n return String(left).localeCompare(String(right));\n}\n\n/** Equality by value, so two `Date`s naming one instant are equal. */\nconst isEqual = (actual: unknown, value: unknown): boolean =>\n actual instanceof Date && value instanceof Date\n ? actual.getTime() === value.getTime()\n : actual === value;\n\n/** `$in` semantics: a scalar matches a value, an array when any element does. */\nconst matchesAny = (actual: unknown, values: readonly unknown[]): boolean => {\n const matches = (element: unknown) =>\n values.some(value => isEqual(element, value));\n return Array.isArray(actual) ? actual.some(matches) : matches(actual);\n};\n\n/** `like`: a substring match with accents and case folded off (`fold.ts`). */\nconst matchesLike = (actual: unknown, value: string): boolean =>\n actual != null && containsFolded(String(actual), value);\n\nconst isFilterGroup = <TEntity extends Entity>(\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): node is FilterGroup<TEntity> =>\n !('property' in node) && (node.operator === 'and' || node.operator === 'or');\n\nconst isBetween = (actual: unknown, start: unknown, end: unknown) =>\n actual != null &&\n compareStoredValues(actual, start) >= 0 &&\n compareStoredValues(actual, end) <= 0;\n\n/**\n * The values a dotted path reaches **through an array** — every element's\n * member — or `undefined` when the path crosses no array, so the caller reads\n * the single value as before.\n */\nfunction valuesThroughArray(\n record: object,\n property: string,\n): unknown[] | undefined {\n const dot = property.indexOf('.');\n if (dot === -1) return undefined;\n const elements = storedValue(\n (record as Record<string, unknown>)[property.slice(0, dot)],\n );\n if (!Array.isArray(elements)) return undefined;\n const member = property.slice(dot + 1);\n return elements.map(element =>\n typeof element === 'object' && element !== null\n ? valueAt(element as object, member)\n : undefined,\n );\n}\n\n/**\n * Splits a filter into its positive test and whether the operator negates it,\n * so a path into an array can ask \"any element\" of the positive form and\n * negate the answer — never \"any element is not equal\", which almost always\n * holds.\n */\nfunction positiveTest<TEntity extends Entity>(\n filter: EntityFilter<TEntity>,\n): { readonly test: (actual: unknown) => boolean; readonly negated: boolean } {\n switch (filter.operator) {\n case 'eq':\n case 'ne':\n return {\n test: actual => isEqual(actual, filter.value),\n negated: filter.operator === 'ne',\n };\n case 'gt':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) > 0,\n negated: false,\n };\n case 'gte':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) >= 0,\n negated: false,\n };\n case 'lt':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) < 0,\n negated: false,\n };\n case 'lte':\n return {\n test: actual =>\n actual != null && compareStoredValues(actual, filter.value) <= 0,\n negated: false,\n };\n case 'in':\n case 'nin':\n return {\n test: actual => matchesAny(actual, filter.values),\n negated: filter.operator === 'nin',\n };\n case 'between':\n case 'nbetween':\n return {\n test: actual => isBetween(actual, filter.start, filter.end),\n negated: filter.operator === 'nbetween',\n };\n case 'like':\n case 'nlike':\n return {\n test: actual => matchesLike(actual, filter.value),\n negated: filter.operator === 'nlike',\n };\n case 'isNull':\n case 'isNotNull':\n return {\n test: actual => actual != null,\n negated: filter.operator === 'isNull',\n };\n default: {\n // Exhaustiveness guard — a new operator must be handled explicitly.\n const never: never = filter;\n throw new EntifixBuildError(\n `Unknown filter operator \"${String((never as { operator: unknown }).operator)}\"`,\n );\n }\n }\n}\n\nfunction matchesFilter<TEntity extends Entity>(\n record: TEntity,\n filter: EntityFilter<TEntity>,\n): boolean {\n const { test, negated } = positiveTest(filter);\n const property = String(filter.property);\n const elements = valuesThroughArray(record, property);\n const holds =\n elements === undefined\n ? test(valueAt(record, property))\n : elements.some(test);\n return negated ? !holds : holds;\n}\n\nfunction matchesNode<TEntity extends Entity>(\n record: TEntity,\n node: EntityFilter<TEntity> | FilterGroup<TEntity>,\n): boolean {\n if (!isFilterGroup(node)) return matchesFilter(record, node);\n return node.operator === 'or'\n ? node.values.some(child => matchesNode(record, child))\n : node.values.every(child => matchesNode(record, child));\n}\n\n/** Whether one record matches a request's filtering. */\nexport function matchesFiltering<TEntity extends Entity>(\n record: TEntity,\n filtering: readonly EntityFiltering<TEntity>[] | undefined,\n): boolean {\n return (filtering ?? []).every(entry =>\n Array.isArray(entry)\n ? entry.every(child => matchesNode(record, child))\n : matchesNode(record, entry),\n );\n}\n\n/** The records a request's filtering keeps, in the order they were given. */\nexport function applyFiltering<TEntity extends Entity>(\n records: readonly TEntity[],\n filtering: readonly EntityFiltering<TEntity>[] | undefined,\n): TEntity[] {\n return records.filter(record => matchesFiltering(record, filtering));\n}\n\n/**\n * The records in the order a request asks for, as a new array. `EntitySorting`\n * is a record keyed by priority rather than a list, so two keys cannot tie by\n * accident. No key leaves the order the records were given in.\n */\nexport function applySorting<TEntity extends Entity>(\n records: readonly TEntity[],\n sorting: readonly EntitySorting<TEntity>[] | undefined,\n): TEntity[] {\n const keys = (sorting ?? []).flatMap(entry =>\n Object.keys(entry)\n .map(Number)\n .sort((left, right) => left - right)\n .map(priority => entry[priority])\n .filter(key => key != null),\n );\n if (keys.length === 0) return [...records];\n\n return [...records].sort((left, right) => {\n for (const key of keys) {\n const property = String(key.property);\n const compared = compareStoredValues(\n valueAt(left, property),\n valueAt(right, property),\n );\n if (compared !== 0) return key.type === 'desc' ? -compared : compared;\n }\n return 0;\n });\n}\n\nconst isFacetValue = (value: unknown): value is EntityFacetValue['value'] =>\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean';\n\n/** Count first, highest first; a tie by value, so the order is stable. */\nconst byCountThenValue = (left: EntityFacetValue, right: EntityFacetValue) =>\n right.count - left.count || compareStoredValues(left.value, right.value);\n\n/**\n * How many records hold each value of each queried member, each member counted\n * over the records **its own** filtering keeps — the in-memory form of a Mongo\n * `$facet` of `$match`, `$unwind` and `$group`, with its rules:\n * - an array member counts each element, as `$unwind` does;\n * - a missing or `null` value, or an empty array, is not counted;\n * - only a string, number or boolean is a value; anything else is skipped.\n */\nexport function countFacets<TEntity extends Entity>(\n records: readonly TEntity[],\n queries: readonly EntityFacetQuery<TEntity>[],\n): EntityFacetCounts {\n const counts: Record<string, readonly EntityFacetValue[]> = {};\n for (const { member, filtering } of queries) {\n const tally = new Map<EntityFacetValue['value'], number>();\n for (const record of applyFiltering(records, filtering)) {\n const stored = valueAt(record, member);\n for (const value of Array.isArray(stored) ? stored : [stored]) {\n if (isFacetValue(value)) tally.set(value, (tally.get(value) ?? 0) + 1);\n }\n }\n counts[member] = Array.from(tally, ([value, count]) => ({\n value,\n count,\n })).sort(byCountThenValue);\n }\n return counts;\n}\n"],"names":["EntifixBuildError","EntityCollectionLink","EntityLink","containsFolded","storedValue","value","id","ids","valueAt","record","property","member","path","split","reduce","key","undefined","compareStoredValues","left","right","Date","getTime","String","localeCompare","isEqual","actual","matchesAny","values","matches","element","some","Array","isArray","matchesLike","isFilterGroup","node","operator","isBetween","start","end","valuesThroughArray","dot","indexOf","elements","slice","map","positiveTest","filter","test","negated","never","matchesFilter","holds","matchesNode","child","every","matchesFiltering","filtering","entry","applyFiltering","records","applySorting","sorting","keys","flatMap","Object","Number","sort","priority","length","compared","type","isFacetValue","byCountThenValue","count","countFacets","queries","counts","tally","Map","stored","set","get","from"],"mappings":"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"}
|