@dxos/echo 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a
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/dist/lib/neutral/Annotation.mjs +22 -8
- package/dist/lib/neutral/Database.mjs +3 -3
- package/dist/lib/neutral/Entity.mjs +11 -11
- package/dist/lib/neutral/Err.mjs +7 -3
- package/dist/lib/neutral/Feed.mjs +15 -19
- package/dist/lib/neutral/Filter.mjs +13 -11
- package/dist/lib/neutral/Format.mjs +3 -7
- package/dist/lib/neutral/JsonSchema.mjs +7 -7
- package/dist/lib/neutral/Migration.mjs +11 -11
- package/dist/lib/neutral/Obj.mjs +15 -13
- package/dist/lib/neutral/Order.mjs +1 -1
- package/dist/lib/neutral/Query.mjs +17 -17
- package/dist/lib/neutral/QueryResult.mjs +1 -1
- package/dist/lib/neutral/Ref.mjs +6 -6
- package/dist/lib/neutral/Registry.mjs +1 -1
- package/dist/lib/neutral/Relation.mjs +12 -12
- package/dist/lib/neutral/Tag.mjs +15 -15
- package/dist/lib/neutral/Text.mjs +25 -0
- package/dist/lib/neutral/Text.mjs.map +7 -0
- package/dist/lib/neutral/Type.mjs +10 -10
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs +1394 -0
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-O6BH7EPN.mjs → chunk-2ZHECXUR.mjs} +5 -4
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-AFANXSGV.mjs → chunk-3KTE2Y7Q.mjs} +20 -20
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WZ32BF53.mjs → chunk-3TN2VMI4.mjs} +49 -37
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-E7IOTOZD.mjs → chunk-42L4HJ3Z.mjs} +11 -15
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-K3S7D5PR.mjs → chunk-4A2NWKRJ.mjs} +5 -5
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +1531 -0
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-Z3WMRRAZ.mjs → chunk-BEFR7LEA.mjs} +11 -9
- package/dist/lib/neutral/{chunk-Z3WMRRAZ.mjs.map → chunk-BEFR7LEA.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs → chunk-BXIKXT7Q.mjs} +2 -6
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs.map → chunk-BXIKXT7Q.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-3JXQUMUI.mjs → chunk-C57XMMBY.mjs} +21 -20
- package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-JZ6LFQU5.mjs → chunk-C62JYFAD.mjs} +82 -8
- package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TF5BSLF6.mjs → chunk-FSGX2R62.mjs} +3 -3
- package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6CEISA3V.mjs → chunk-HBXT3GSB.mjs} +29 -15
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WU3GIANS.mjs → chunk-IQR45HAD.mjs} +1 -1
- package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TFEWTY5A.mjs → chunk-KCBW4BR6.mjs} +4 -8
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-4TPQZPLY.mjs → chunk-L3E5IXAE.mjs} +198 -30
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-EVK6XBXO.mjs → chunk-PRC4FEKJ.mjs} +2 -2
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-NOL7O3BL.mjs → chunk-SCT6FFCR.mjs} +9 -4
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BBFJWWAV.mjs → chunk-TOCVVFK2.mjs} +26 -2
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-A53NTBHE.mjs → chunk-U53LSQQA.mjs} +3 -3
- package/dist/lib/neutral/{chunk-A53NTBHE.mjs.map → chunk-U53LSQQA.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-PLDMJJJC.mjs → chunk-UGFFV2FR.mjs} +2 -2
- package/dist/lib/neutral/{chunk-35INCYOE.mjs → chunk-UWLP4RFM.mjs} +1 -1
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SZ5WB6HF.mjs → chunk-UZHCQ6NI.mjs} +13 -11
- package/dist/lib/neutral/{chunk-SZ5WB6HF.mjs.map → chunk-UZHCQ6NI.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-L3OBWBV2.mjs → chunk-WKIT2YY5.mjs} +2 -2
- package/dist/lib/neutral/{chunk-HGWQR5KH.mjs → chunk-XB6ISRAO.mjs} +27 -15
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs +84 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-5GAIWRQB.mjs → chunk-XXWK4J53.mjs} +27 -42
- package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +30 -26
- package/dist/lib/neutral/internal/index.mjs +91 -69
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +79 -60
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/types/src/Annotation.d.ts +35 -1
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Collection.d.ts +9 -8
- package/dist/types/src/Collection.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +16 -0
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +1 -16
- package/dist/types/src/Dataset.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +67 -0
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +56 -80
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +9 -1
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +2 -9
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Migration.d.ts +1 -1
- package/dist/types/src/Migration.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +31 -6
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +1 -1
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +1 -1
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/QueryResult.d.ts +2 -2
- package/dist/types/src/QueryResult.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +8 -4
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Registry.d.ts +1 -1
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Tag.d.ts +11 -13
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Text.d.ts +41 -0
- package/dist/types/src/Text.d.ts.map +1 -0
- package/dist/types/src/Text.test.d.ts +2 -0
- package/dist/types/src/Text.test.d.ts.map +1 -0
- package/dist/types/src/Type.d.ts +33 -20
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +8 -9
- package/dist/types/src/View.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +9 -0
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/atoms.d.ts +14 -0
- package/dist/types/src/internal/Annotation/atoms.d.ts.map +1 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts +13 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/guard.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +1 -1
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/object.d.ts +5 -1
- package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/relation.d.ts +7 -0
- package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/type-uri.d.ts.map +1 -1
- package/dist/types/src/internal/Filter/index.d.ts +2 -0
- package/dist/types/src/internal/Filter/index.d.ts.map +1 -0
- package/dist/types/src/internal/Filter/match.d.ts +22 -0
- package/dist/types/src/internal/Filter/match.d.ts.map +1 -0
- package/dist/types/src/internal/Format/format.d.ts +0 -2
- package/dist/types/src/internal/Format/format.d.ts.map +1 -1
- package/dist/types/src/internal/Format/index.d.ts +2 -2
- package/dist/types/src/internal/Format/index.d.ts.map +1 -1
- package/dist/types/src/internal/Format/string.d.ts +0 -8
- package/dist/types/src/internal/Format/string.d.ts.map +1 -1
- package/dist/types/src/internal/Format/types.d.ts +2 -1
- package/dist/types/src/internal/Format/types.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts +7 -3
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/clone.d.ts +2 -1
- package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/create-object.d.ts +3 -4
- package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Query/index.d.ts +1 -0
- package/dist/types/src/internal/Query/index.d.ts.map +1 -1
- package/dist/types/src/internal/Query/query-result-effect.d.ts +9 -0
- package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/atoms.d.ts +1 -2
- package/dist/types/src/internal/Ref/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +13 -6
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/common/atom-snapshot.d.ts +8 -0
- package/dist/types/src/internal/common/atom-snapshot.d.ts.map +1 -0
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts +12 -0
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts +25 -0
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts +5 -0
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts +17 -0
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/entity.d.ts +4 -4
- package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/meta.d.ts +2 -4
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/src/internal/index.d.ts +1 -0
- package/dist/types/src/internal/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +42 -171
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -13
- package/src/Annotation.test.ts +257 -10
- package/src/Annotation.ts +55 -6
- package/src/Collection.ts +10 -12
- package/src/Database.ts +22 -20
- package/src/Err.ts +22 -0
- package/src/Feed.ts +114 -141
- package/src/Filter.test.ts +38 -0
- package/src/Filter.ts +17 -3
- package/src/Hypergraph.ts +2 -11
- package/src/Migration.ts +1 -1
- package/src/Obj.test.ts +92 -12
- package/src/Obj.ts +36 -5
- package/src/Order.ts +3 -3
- package/src/Query.test.ts +2 -2
- package/src/Query.ts +16 -16
- package/src/QueryResult.ts +2 -2
- package/src/Ref.ts +12 -4
- package/src/Registry.ts +1 -1
- package/src/Relation.ts +1 -1
- package/src/Tag.ts +12 -16
- package/src/Text.test.ts +186 -0
- package/src/Text.ts +116 -0
- package/src/Type.test.ts +52 -24
- package/src/Type.ts +69 -31
- package/src/View.ts +22 -30
- package/src/index.ts +1 -1
- package/src/internal/Annotation/annotations.ts +15 -2
- package/src/internal/Annotation/atoms.ts +107 -0
- package/src/internal/Annotation/entity-dictionary.ts +40 -37
- package/src/internal/Entity/api.ts +1 -1
- package/src/internal/Entity/guard.ts +2 -1
- package/src/internal/Entity/model.ts +2 -2
- package/src/internal/Entity/object.ts +14 -1
- package/src/internal/Entity/relation.ts +19 -0
- package/src/internal/Entity/type-uri.ts +3 -1
- package/src/internal/Filter/index.ts +5 -0
- package/src/internal/Filter/match.ts +284 -0
- package/src/internal/Format/format.ts +0 -2
- package/src/internal/Format/index.ts +2 -2
- package/src/internal/Format/string.ts +0 -12
- package/src/internal/Format/types.ts +4 -2
- package/src/internal/JsonSchema/json-schema.test.ts +82 -54
- package/src/internal/Obj/atoms.ts +31 -18
- package/src/internal/Obj/clone.ts +61 -20
- package/src/internal/Obj/create-object.test.ts +3 -3
- package/src/internal/Obj/create-object.ts +3 -4
- package/src/internal/Obj/inspect.ts +1 -1
- package/src/internal/Obj/json-serializer.test.ts +7 -5
- package/src/internal/Obj/json-serializer.ts +1 -1
- package/src/internal/Obj/schema-validator.test.ts +53 -0
- package/src/internal/Obj/set-value.test.ts +29 -19
- package/src/internal/Query/index.ts +1 -0
- package/src/internal/Query/query-result-effect.ts +35 -0
- package/src/internal/Ref/atoms.ts +23 -3
- package/src/internal/Ref/ref.test.ts +8 -8
- package/src/internal/Ref/ref.ts +26 -20
- package/src/internal/common/atom-snapshot.ts +19 -0
- package/src/internal/common/proxy/make-object.ts +1 -1
- package/src/internal/common/proxy/proxy-types.ts +20 -0
- package/src/internal/common/proxy/proxy-utils.ts +37 -1
- package/src/internal/common/proxy/reactive-array.ts +3 -3
- package/src/internal/common/proxy/reactive.test.ts +1 -1
- package/src/internal/common/proxy/schema-validator.ts +181 -8
- package/src/internal/common/proxy/typed-handler.ts +254 -76
- package/src/internal/common/types/entity.ts +15 -7
- package/src/internal/index.ts +1 -0
- package/src/testing/test-schema.ts +75 -90
- package/dist/lib/neutral/chunk-35INCYOE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3JXQUMUI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4TPQZPLY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-5GAIWRQB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6CEISA3V.mjs.map +0 -7
- package/dist/lib/neutral/chunk-AFANXSGV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BBFJWWAV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-E7IOTOZD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-EVK6XBXO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HGWQR5KH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JZ6LFQU5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NOL7O3BL.mjs.map +0 -7
- package/dist/lib/neutral/chunk-O6BH7EPN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TF5BSLF6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TFEWTY5A.mjs.map +0 -7
- package/dist/lib/neutral/chunk-VQYL2OQF.mjs +0 -2353
- package/dist/lib/neutral/chunk-VQYL2OQF.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WU3GIANS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WZ32BF53.mjs.map +0 -7
- package/dist/lib/neutral/chunk-X3NUUON4.mjs +0 -184
- package/dist/lib/neutral/chunk-X3NUUON4.mjs.map +0 -7
- package/dist/types/src/exemplars.test.d.ts +0 -2
- package/dist/types/src/exemplars.test.d.ts.map +0 -1
- package/dist/types/src/hierarchy.test.d.ts +0 -2
- package/dist/types/src/hierarchy.test.d.ts.map +0 -1
- package/src/exemplars.test.ts +0 -21
- package/src/hierarchy.test.ts +0 -33
- /package/dist/lib/neutral/{chunk-K3S7D5PR.mjs.map → chunk-4A2NWKRJ.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-PLDMJJJC.mjs.map → chunk-UGFFV2FR.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-L3OBWBV2.mjs.map → chunk-WKIT2YY5.mjs.map} +0 -0
|
@@ -395,8 +395,11 @@ export const setDescriptionWithSchema = <S extends Schema.Schema.Any>(
|
|
|
395
395
|
object: Schema.Schema.Type<S>,
|
|
396
396
|
description: string,
|
|
397
397
|
) => {
|
|
398
|
-
const
|
|
399
|
-
|
|
398
|
+
const accessorOpt = DescriptionAnnotation.get(schema);
|
|
399
|
+
if (Option.isNone(accessorOpt)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
object[accessorOpt.value] = description;
|
|
400
403
|
};
|
|
401
404
|
|
|
402
405
|
/**
|
|
@@ -413,6 +416,16 @@ export const FormInputAnnotation = createAnnotationHelper<boolean>(FormInputAnno
|
|
|
413
416
|
export const FormInlineAnnotationId = Symbol.for('@dxos/schema/annotation/FormInline');
|
|
414
417
|
export const FormInlineAnnotation = createAnnotationHelper<boolean>(FormInlineAnnotationId);
|
|
415
418
|
|
|
419
|
+
/**
|
|
420
|
+
* When set on a `Ref` (or array-of-`Ref`) property, the form OWNS the referenced target(s): "add" creates a
|
|
421
|
+
* new object of the annotation's typename (instead of opening the existing-object picker) and each target is
|
|
422
|
+
* rendered inline — its own fields — like {@link FormInlineAnnotation}. The target typename is carried here
|
|
423
|
+
* (rather than inferred from the element type) so the field can stay `Ref.Ref(Obj.Unknown)` and avoid pulling
|
|
424
|
+
* the target's type (e.g. query-AST-laden `Trigger`) into the schema's emitted declaration.
|
|
425
|
+
*/
|
|
426
|
+
export const FormCreateAnnotationId = Symbol.for('@dxos/schema/annotation/FormCreate');
|
|
427
|
+
export const FormCreateAnnotation = createAnnotationHelper<string>(FormCreateAnnotationId);
|
|
428
|
+
|
|
416
429
|
/**
|
|
417
430
|
* When set on an array property, the form renders it as an ordered,
|
|
418
431
|
* drag-to-reorder list (drag handles per row). Element order is meaningful and
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Atom from '@effect-atom/atom/Atom';
|
|
6
|
+
import * as Data from 'effect/Data';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
|
|
9
|
+
import { assertArgument } from '@dxos/invariant';
|
|
10
|
+
|
|
11
|
+
import type * as Annotation from '../../Annotation';
|
|
12
|
+
import type * as Entity from '../../Entity';
|
|
13
|
+
import { snapshotForComparison } from '../common/atom-snapshot';
|
|
14
|
+
import { subscribe } from '../common/proxy/reactive';
|
|
15
|
+
import { isEntity } from '../Entity';
|
|
16
|
+
import { get as getAnnotation } from './entity-dictionary';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Atom family for an annotation value on an entity instance.
|
|
20
|
+
* Mirrors the object-property atom family: re-emits a fresh reference whenever the entity changes
|
|
21
|
+
* (so an in-place array mutation is observed) and dedupes primitive values via `!==`.
|
|
22
|
+
*/
|
|
23
|
+
const annotationFamily = Atom.family((target: Entity.Unknown) =>
|
|
24
|
+
Atom.family(<T>(annotation: Annotation.Annotation<T>): Atom.Atom<Option.Option<T>> => {
|
|
25
|
+
const read = (): Option.Option<T> => Option.map(getAnnotation(target, annotation), snapshotForComparison);
|
|
26
|
+
|
|
27
|
+
return Atom.make<Option.Option<T>>((get) => {
|
|
28
|
+
let previous = read();
|
|
29
|
+
|
|
30
|
+
const unsubscribe = subscribe(target, () => {
|
|
31
|
+
const next = read();
|
|
32
|
+
if (!sameOption(previous, next)) {
|
|
33
|
+
previous = next;
|
|
34
|
+
get.setSelf(next);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
get.addFinalizer(() => unsubscribe());
|
|
38
|
+
|
|
39
|
+
return previous;
|
|
40
|
+
}).pipe(Atom.keepAlive);
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Atom family for a single key of a record-valued annotation on an entity instance.
|
|
46
|
+
* Keyed by a value-equal `Data.tuple([target, annotation, key])` so nested families are avoided.
|
|
47
|
+
*/
|
|
48
|
+
const annotationPropertyFamily = Atom.family(
|
|
49
|
+
([target, annotation, key]: readonly [
|
|
50
|
+
Entity.Unknown,
|
|
51
|
+
Annotation.Annotation<Record<string, any>>,
|
|
52
|
+
string,
|
|
53
|
+
]): Atom.Atom<any> => {
|
|
54
|
+
const read = (): unknown =>
|
|
55
|
+
getAnnotation(target, annotation).pipe(
|
|
56
|
+
Option.map((value) => snapshotForComparison(value[key])),
|
|
57
|
+
Option.getOrUndefined,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return Atom.make<unknown>((get) => {
|
|
61
|
+
let previous = read();
|
|
62
|
+
|
|
63
|
+
const unsubscribe = subscribe(target, () => {
|
|
64
|
+
const next = read();
|
|
65
|
+
if (next !== previous) {
|
|
66
|
+
previous = next;
|
|
67
|
+
get.setSelf(next);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
get.addFinalizer(() => unsubscribe());
|
|
71
|
+
|
|
72
|
+
return previous;
|
|
73
|
+
}).pipe(Atom.keepAlive);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/** Equal when both empty, or both present with the same (snapshotted) value. */
|
|
78
|
+
const sameOption = <T>(a: Option.Option<T>, b: Option.Option<T>): boolean =>
|
|
79
|
+
Option.isNone(a) || Option.isNone(b) ? Option.isNone(a) && Option.isNone(b) : a.value === b.value;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reactive atom for an annotation value on an entity instance. Emits a shallow snapshot (a fresh
|
|
83
|
+
* reference for objects/arrays) so dependent atoms recompute on change. Mirrors {@link makeProperty}.
|
|
84
|
+
*/
|
|
85
|
+
export const makeAtom = <T>(
|
|
86
|
+
target: Entity.Unknown,
|
|
87
|
+
annotation: Annotation.Annotation<T>,
|
|
88
|
+
): Atom.Atom<Option.Option<T>> => {
|
|
89
|
+
assertArgument(isEntity(target), 'target', 'Must be a reactive ECHO entity');
|
|
90
|
+
return annotationFamily(target)(annotation);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reactive atom for a single key of a record-valued annotation on an entity instance.
|
|
95
|
+
*/
|
|
96
|
+
export const makeProperty = <V>(
|
|
97
|
+
target: Entity.Unknown,
|
|
98
|
+
annotation: Annotation.Annotation<Record<string, V>>,
|
|
99
|
+
key: string,
|
|
100
|
+
): Atom.Atom<V | undefined> => {
|
|
101
|
+
assertArgument(isEntity(target), 'target', 'Must be a reactive ECHO entity');
|
|
102
|
+
// The flattened family key is a single concrete tuple type, so the generic `V` is erased at the
|
|
103
|
+
// family boundary and recovered here; no typed alternative exists for a per-call-generic family.
|
|
104
|
+
return annotationPropertyFamily(
|
|
105
|
+
Data.tuple(target, annotation as Annotation.Annotation<Record<string, any>>, key),
|
|
106
|
+
) as Atom.Atom<V | undefined>;
|
|
107
|
+
};
|
|
@@ -3,72 +3,75 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import * as Option from 'effect/Option';
|
|
6
|
-
|
|
7
|
-
import { deepMapValues } from '@dxos/util';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
8
7
|
|
|
9
8
|
import type * as Annotation from '../../Annotation';
|
|
10
9
|
import type * as Entity from '../../Entity';
|
|
11
10
|
import { getMetaChecked } from '../common/api/meta';
|
|
12
|
-
import { type Mutable } from '../common/proxy/reactive';
|
|
13
|
-
import { getDatabase } from '../Entity/api';
|
|
11
|
+
import { type Mutable, change } from '../common/proxy/reactive';
|
|
14
12
|
import { isEntity, isSnapshot } from '../Entity/guard';
|
|
15
|
-
import {
|
|
16
|
-
import { getDictionary, setDictionary } from './dictionary';
|
|
13
|
+
import { getDictionary } from './dictionary';
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* Get the value of an annotation from an entity instance or snapshot.
|
|
17
|
+
*
|
|
18
|
+
* The value is read-only (schema types are readonly by default). To mutate it, use {@link update}
|
|
19
|
+
* (which reads it as mutable inside a change transaction); the reactive proxy rejects mutation of the
|
|
20
|
+
* live value outside an `Obj.update`. Snapshots return a decoded detached copy.
|
|
20
21
|
*/
|
|
21
22
|
export const get = <T>(
|
|
22
23
|
target: Entity.Unknown | Entity.Snapshot,
|
|
23
24
|
annotation: Annotation.Annotation<T>,
|
|
24
25
|
): Option.Option<T> => {
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if (isSnapshot(target)) {
|
|
27
|
+
return getDictionary(getMetaChecked(target).annotations, annotation);
|
|
28
|
+
}
|
|
29
|
+
if (isEntity(target)) {
|
|
30
|
+
const annotations = getMetaChecked(target).annotations;
|
|
31
|
+
// The dictionary slot is typed `unknown`; at runtime it holds the annotation's live value
|
|
32
|
+
// (the proxy codecs nested refs in both directions), so the read coerces to the declared type.
|
|
33
|
+
return annotation.key in annotations ? Option.some(annotations[annotation.key] as T) : Option.none();
|
|
30
34
|
}
|
|
35
|
+
throw new TypeError('Target is not an annotation target.');
|
|
31
36
|
};
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Set the value of an annotation on an entity instance.
|
|
35
40
|
* Must be called with a mutable entity — i.e. inside an `Obj.update` callback.
|
|
41
|
+
*
|
|
42
|
+
* The value is assigned directly to the reactive meta dictionary; the proxy encodes nested Refs and
|
|
43
|
+
* links their unsaved targets on write (matching ordinary property assignment), so no manual
|
|
44
|
+
* encode/persist step is needed.
|
|
36
45
|
*/
|
|
37
46
|
export const set = <T>(target: Mutable<Entity.Unknown>, annotation: Annotation.Annotation<T>, value: T): void => {
|
|
38
47
|
if (isEntity(target)) {
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// `database.add`. `setDictionary` pre-encodes the value (`Schema.encodeSync`),
|
|
44
|
-
// so the live Ref never reaches the proxy's link handling and would otherwise
|
|
45
|
-
// be stored as a dangling reference.
|
|
46
|
-
persistRefTargets(target, value);
|
|
47
|
-
setDictionary(meta.annotations, annotation, value);
|
|
48
|
+
// The dictionary slot is untyped, so the proxy can't validate against the annotation schema;
|
|
49
|
+
// validate here (without encoding — the proxy encodes nested refs and links targets on assignment).
|
|
50
|
+
Schema.validateSync(annotation.schema)(value);
|
|
51
|
+
getMetaChecked(target).annotations[annotation.key] = value;
|
|
48
52
|
} else {
|
|
49
53
|
throw new TypeError('Target is not an annotation target.');
|
|
50
54
|
}
|
|
51
55
|
};
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* already lives in a database (the host's or a foreign one) are left untouched.
|
|
58
|
+
* Mutate an existing annotation value in place via a callback, wrapping the mutation in a change
|
|
59
|
+
* transaction (like `Obj.update`). Use when only an annotation needs to change. No-op when absent.
|
|
57
60
|
*/
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
export const update = <T>(
|
|
62
|
+
target: Entity.Unknown,
|
|
63
|
+
annotation: Annotation.Annotation<T>,
|
|
64
|
+
mutator: (value: Mutable<T>) => void,
|
|
65
|
+
): void => {
|
|
66
|
+
change(target, (mutable) => {
|
|
67
|
+
const current = get(mutable, annotation);
|
|
68
|
+
if (Option.isSome(current)) {
|
|
69
|
+
// `get` returns the value read-only; inside this change transaction the live value is mutable.
|
|
70
|
+
const value = current.value as Mutable<T>;
|
|
71
|
+
mutator(value);
|
|
72
|
+
// Validate against the annotation's own schema — the dictionary slot is untyped, so the proxy
|
|
73
|
+
// can't. Schema validation checks ref structure only (not targets), so it is cycle-safe.
|
|
74
|
+
Schema.validateSync(annotation.schema)(value);
|
|
71
75
|
}
|
|
72
|
-
return recurse(current);
|
|
73
76
|
});
|
|
74
77
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { invariant } from '@dxos/invariant';
|
|
6
|
-
import { DXN, EID,
|
|
6
|
+
import { DXN, EID, type SpaceId, URI } from '@dxos/keys';
|
|
7
7
|
import { assumeType } from '@dxos/util';
|
|
8
8
|
|
|
9
9
|
import type { AnyEntity } from '../common/types';
|
|
@@ -9,7 +9,8 @@ import { KindId, SnapshotKindId } from '../common/types';
|
|
|
9
9
|
* Returns true if the value is an ECHO entity instance (object or relation).
|
|
10
10
|
*/
|
|
11
11
|
export const isEntity = (value: unknown): value is Entity.Unknown => {
|
|
12
|
-
|
|
12
|
+
// (some) type entities are functions since they are class declarations.
|
|
13
|
+
if (value == null || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
13
14
|
return false;
|
|
14
15
|
}
|
|
15
16
|
return (value as any)[KindId] !== undefined;
|
|
@@ -11,13 +11,13 @@ import { assumeType } from '@dxos/util';
|
|
|
11
11
|
|
|
12
12
|
import type * as Database from '../../Database';
|
|
13
13
|
import {
|
|
14
|
-
type ATTR_PARENT,
|
|
15
|
-
type ATTR_TYPE,
|
|
16
14
|
ATTR_DELETED,
|
|
15
|
+
type ATTR_PARENT,
|
|
17
16
|
ATTR_RELATION_SOURCE,
|
|
18
17
|
ATTR_RELATION_TARGET,
|
|
19
18
|
ATTR_SELF_URI,
|
|
20
19
|
ATTR_SELF_URI_LEGACY,
|
|
20
|
+
type ATTR_TYPE,
|
|
21
21
|
EntityKind,
|
|
22
22
|
KindId,
|
|
23
23
|
ObjectDatabaseId,
|
|
@@ -6,8 +6,9 @@ import * as Schema from 'effect/Schema';
|
|
|
6
6
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
7
|
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import { DXN } from '@dxos/keys';
|
|
9
|
+
import { DXN, type EntityId } from '@dxos/keys';
|
|
10
10
|
|
|
11
|
+
import type * as Type from '../../Type';
|
|
11
12
|
import { type TypeAnnotation, TypeAnnotationId } from '../Annotation/annotations';
|
|
12
13
|
import { makeTypeJsonSchemaAnnotation } from '../Annotation/util';
|
|
13
14
|
import { EntityKind } from '../common/types';
|
|
@@ -71,3 +72,15 @@ export const EchoObjectSchema: {
|
|
|
71
72
|
);
|
|
72
73
|
};
|
|
73
74
|
};
|
|
75
|
+
|
|
76
|
+
export const makeObjectType = <Self, _Schema extends Schema.Schema.Any>(
|
|
77
|
+
dxn: DXN.DXN,
|
|
78
|
+
schema: _Schema,
|
|
79
|
+
options?: { id?: EntityId },
|
|
80
|
+
): Type.ObjClass<Self, Schema.Schema.Type<_Schema>, {}> => {
|
|
81
|
+
const type = EchoObjectSchema(dxn, options)(schema);
|
|
82
|
+
const constructor = function ObjectType() {};
|
|
83
|
+
Object.setPrototypeOf(constructor, type);
|
|
84
|
+
// Boundary cast: constructor/prototype wiring cannot be expressed in TypeScript's type system.
|
|
85
|
+
return constructor as unknown as Type.ObjClass<Self, Schema.Schema.Type<_Schema>, {}>;
|
|
86
|
+
};
|
|
@@ -186,3 +186,22 @@ export const getDXNForRelationSchemaRef = (schema: Schema.Schema.Any): DXN.DXN =
|
|
|
186
186
|
|
|
187
187
|
return DXN.make(typename);
|
|
188
188
|
};
|
|
189
|
+
|
|
190
|
+
export const makeRelationType = (options: {
|
|
191
|
+
dxn: DXN.DXN;
|
|
192
|
+
source: RelationEndpoint;
|
|
193
|
+
target: RelationEndpoint;
|
|
194
|
+
schema: Schema.Schema.Any;
|
|
195
|
+
id?: EntityId;
|
|
196
|
+
}): Type.RelationClass<unknown, unknown, unknown, unknown, {}> => {
|
|
197
|
+
const type = EchoRelationSchema({
|
|
198
|
+
dxn: options.dxn,
|
|
199
|
+
source: options.source,
|
|
200
|
+
target: options.target,
|
|
201
|
+
id: options.id,
|
|
202
|
+
})(options.schema);
|
|
203
|
+
const constructor = function RelationType() {};
|
|
204
|
+
Object.setPrototypeOf(constructor, type);
|
|
205
|
+
// Boundary cast: constructor/prototype wiring cannot be expressed in TypeScript's type system.
|
|
206
|
+
return constructor as unknown as Type.RelationClass<unknown, unknown, unknown, unknown, {}>;
|
|
207
|
+
};
|
|
@@ -23,7 +23,9 @@ export const getTypeURIFromSpecifier = (input: Schema.Schema.All | AnyEntity | U
|
|
|
23
23
|
if (Schema.isSchema(input)) {
|
|
24
24
|
return getSchemaURI(input) ?? raise(new TypeError('Schema has no URI'));
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// Type entities are normally proxy objects, but class-declared types from
|
|
27
|
+
// `Type.makeObject` are functions whose prototype chain reaches the entity, so accept functions too.
|
|
28
|
+
if (input !== null && (typeof input === 'object' || typeof input === 'function') && KindId in input) {
|
|
27
29
|
// `Type.Type` entity. Both in-memory and persisted forms expose the schema
|
|
28
30
|
// they declare via `StaticTypeSchemaSlot`, whose URI is exactly what
|
|
29
31
|
// `Obj.make` stamps on `system.type` — a static declaration carries
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as semver from 'semver';
|
|
6
|
+
|
|
7
|
+
import { EncodedReference, type QueryAST, isEncodedReference } from '@dxos/echo-protocol';
|
|
8
|
+
import { DXN, EID } from '@dxos/keys';
|
|
9
|
+
|
|
10
|
+
import { getTypeURI } from '../Annotation/annotations';
|
|
11
|
+
import { getMetaChecked } from '../common/api/meta';
|
|
12
|
+
import { type AnyEntity } from '../common/types';
|
|
13
|
+
import { objectToJSON } from '../Obj/json-serializer';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Matches a tag filter against an object's stored tags. Tags may be stored as encoded references or
|
|
17
|
+
* (in legacy data) bare URI strings, and those URIs may be in legacy DXN form. Both sides are
|
|
18
|
+
* normalized to the canonical EID before comparing.
|
|
19
|
+
*/
|
|
20
|
+
export const matchesTag = (tags: readonly unknown[], filterTag: string): boolean => {
|
|
21
|
+
const canonical = (uri: string): string => {
|
|
22
|
+
const eid = EID.tryParse(uri);
|
|
23
|
+
return (eid && EID.getEntityId(eid)) || uri;
|
|
24
|
+
};
|
|
25
|
+
const normalize = (tag: unknown): string =>
|
|
26
|
+
canonical(isEncodedReference(tag) ? EncodedReference.toURI(tag) : (tag as string));
|
|
27
|
+
const target = canonical(filterTag);
|
|
28
|
+
return tags.some((tag) => normalize(tag) === target);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Matches a meta `key` / `version` constraint against an object's meta `key` and `version`.
|
|
33
|
+
*/
|
|
34
|
+
export const matchMetaKey = (
|
|
35
|
+
key: string,
|
|
36
|
+
versionRange: string | undefined,
|
|
37
|
+
objKey: string | undefined,
|
|
38
|
+
objVersion: string | undefined,
|
|
39
|
+
): boolean => {
|
|
40
|
+
if (objKey !== key) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (versionRange === undefined) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (objVersion === undefined || semver.valid(objVersion) === null) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (semver.validRange(versionRange) === null) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return semver.satisfies(objVersion, versionRange, { includePrerelease: true });
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compares a filter's type discriminator against the value stored on an object's `system.type`.
|
|
57
|
+
*/
|
|
58
|
+
export const compareTypenameStrings = (expectedStr: string, actualStr: string): boolean => {
|
|
59
|
+
const expectedEid = EID.tryParse(expectedStr);
|
|
60
|
+
if (expectedEid) {
|
|
61
|
+
const actualEid = EID.tryParse(actualStr);
|
|
62
|
+
if (!actualEid) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (EID.getEntityId(expectedEid) !== EID.getEntityId(actualEid)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const expectedSpaceId = EID.getSpaceId(expectedEid);
|
|
69
|
+
const actualSpaceId = EID.getSpaceId(actualEid);
|
|
70
|
+
return expectedSpaceId === undefined || actualSpaceId === undefined || expectedSpaceId === actualSpaceId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const expectedDxn = DXN.tryMake(expectedStr);
|
|
74
|
+
if (expectedDxn) {
|
|
75
|
+
const actualDxn = DXN.tryMake(actualStr);
|
|
76
|
+
if (!actualDxn) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (DXN.getName(expectedDxn) !== DXN.getName(actualDxn)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const expectedVersion = DXN.getVersion(expectedDxn);
|
|
83
|
+
const actualVersion = DXN.getVersion(actualDxn);
|
|
84
|
+
return expectedVersion === undefined || actualVersion === undefined || expectedVersion === actualVersion;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return expectedStr === actualStr;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const structuralMatch = (filterObj: any, targetObj: any, strict = true): boolean => {
|
|
91
|
+
if (typeof filterObj !== 'object' || filterObj === null) {
|
|
92
|
+
return filterObj === targetObj;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof targetObj !== 'object' || targetObj === null) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const filterKeys = Object.keys(filterObj);
|
|
100
|
+
const targetKeys = Object.keys(targetObj);
|
|
101
|
+
if (strict && filterKeys.length !== targetKeys.length) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return filterKeys.every((key) => {
|
|
106
|
+
if (!(key in targetObj)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const filterValue = filterObj[key];
|
|
110
|
+
const targetValue = targetObj[key];
|
|
111
|
+
|
|
112
|
+
if (typeof filterValue === 'object' && filterValue !== null) {
|
|
113
|
+
return structuralMatch(filterValue, targetValue);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return filterValue === targetValue;
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boolean => {
|
|
121
|
+
switch (filter.type) {
|
|
122
|
+
case 'compare': {
|
|
123
|
+
const compareValue = filter.value as any;
|
|
124
|
+
switch (filter.operator) {
|
|
125
|
+
case 'eq':
|
|
126
|
+
if (isEncodedReference(compareValue)) {
|
|
127
|
+
if (!isEncodedReference(value)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return EncodedReference.toURI(value) === EncodedReference.toURI(compareValue);
|
|
131
|
+
}
|
|
132
|
+
return value === compareValue;
|
|
133
|
+
case 'neq':
|
|
134
|
+
return value !== compareValue;
|
|
135
|
+
case 'gt':
|
|
136
|
+
return (value as any) > compareValue;
|
|
137
|
+
case 'gte':
|
|
138
|
+
return (value as any) >= compareValue;
|
|
139
|
+
case 'lt':
|
|
140
|
+
return (value as any) < compareValue;
|
|
141
|
+
case 'lte':
|
|
142
|
+
return (value as any) <= compareValue;
|
|
143
|
+
default:
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
case 'object': {
|
|
148
|
+
if (typeof value !== 'object' || value === null) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (filter.props) {
|
|
153
|
+
for (const [key, valueFilter] of Object.entries(filter.props)) {
|
|
154
|
+
const nestedValue = (value as any)[key];
|
|
155
|
+
if (!filterMatchValue(valueFilter, nestedValue)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
case 'in': {
|
|
164
|
+
return filter.values.includes(value);
|
|
165
|
+
}
|
|
166
|
+
case 'contains': {
|
|
167
|
+
if (!Array.isArray(value)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return value.some((element) => {
|
|
172
|
+
if (typeof filter.value === 'object' && filter.value !== null && !Array.isArray(filter.value)) {
|
|
173
|
+
return structuralMatch(filter.value, element);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return element === filter.value;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
case 'range': {
|
|
180
|
+
return (value as any) >= filter.from && (value as any) <= filter.to;
|
|
181
|
+
}
|
|
182
|
+
case 'not': {
|
|
183
|
+
return !filterMatchValue(filter.filter, value);
|
|
184
|
+
}
|
|
185
|
+
case 'and': {
|
|
186
|
+
return filter.filters.every((f) => filterMatchValue(f, value));
|
|
187
|
+
}
|
|
188
|
+
case 'or': {
|
|
189
|
+
return filter.filters.some((f) => filterMatchValue(f, value));
|
|
190
|
+
}
|
|
191
|
+
default:
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Matches a filter against an entity proxy without full JSON serialization when possible.
|
|
198
|
+
*/
|
|
199
|
+
export const filterMatchEntity = (filter: QueryAST.Filter, entity: AnyEntity): boolean => {
|
|
200
|
+
switch (filter.type) {
|
|
201
|
+
case 'object': {
|
|
202
|
+
if (filter.typename !== null) {
|
|
203
|
+
const typeURI = getTypeURI(entity);
|
|
204
|
+
if (!typeURI || !compareTypenameStrings(filter.typename, typeURI)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (filter.id && filter.id.length > 0 && !filter.id.includes(entity.id)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (filter.props) {
|
|
214
|
+
let json: ReturnType<typeof objectToJSON> | null = null;
|
|
215
|
+
try {
|
|
216
|
+
json = objectToJSON(entity);
|
|
217
|
+
} catch {
|
|
218
|
+
// Serialization of complex static-type entities may throw.
|
|
219
|
+
}
|
|
220
|
+
if (json === null) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (const [key, valueFilter] of Object.entries(filter.props)) {
|
|
224
|
+
if (key.startsWith('@')) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!filterMatchValue(valueFilter, (json as any)[key])) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const meta = getMetaChecked(entity);
|
|
234
|
+
|
|
235
|
+
if (filter.foreignKeys && filter.foreignKeys.length > 0) {
|
|
236
|
+
const hasKey = filter.foreignKeys.some((fk) => meta.keys.some((k) => k.source === fk.source && k.id === fk.id));
|
|
237
|
+
if (!hasKey) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (filter.metaKey !== undefined) {
|
|
243
|
+
if (!matchMetaKey(filter.metaKey, filter.metaVersion, meta.key, meta.version)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case 'tag': {
|
|
252
|
+
const rawTags = getMetaChecked(entity).tags;
|
|
253
|
+
const tags = rawTags.map((tag: any) => (typeof tag?.encode === 'function' ? tag.encode() : tag));
|
|
254
|
+
return matchesTag(tags, filter.tag);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
case 'text-search': {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'timestamp': {
|
|
262
|
+
throw new Error('Timestamp filters must be handled at the index level, not in-memory matching.');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case 'child-of': {
|
|
266
|
+
throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'not': {
|
|
270
|
+
return !filterMatchEntity(filter.filter, entity);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case 'and': {
|
|
274
|
+
return filter.filters.every((f) => filterMatchEntity(f, entity));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'or': {
|
|
278
|
+
return filter.filters.some((f) => filterMatchEntity(f, entity));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
default:
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
@@ -29,8 +29,6 @@ export namespace Format {
|
|
|
29
29
|
export const Email = StringUtil.Email;
|
|
30
30
|
export const Formula = StringUtil.Formula;
|
|
31
31
|
export const Hostname = StringUtil.Hostname;
|
|
32
|
-
export const JSON = StringUtil.JSON;
|
|
33
|
-
export const Markdown = StringUtil.Markdown;
|
|
34
32
|
export const Regex = StringUtil.Regex;
|
|
35
33
|
export const URL = StringUtil.URL;
|
|
36
34
|
export const UUID = Schema.UUID;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
export * from './date';
|
|
6
6
|
export * from './format';
|
|
7
|
-
export { CurrencyAnnotationId, DecimalPrecision
|
|
8
|
-
export {
|
|
7
|
+
export { Currency, CurrencyAnnotationId, DecimalPrecision } from './number';
|
|
8
|
+
export { GeoLocation, GeoPoint } from './object';
|
|
9
9
|
export * from './select';
|
|
10
10
|
export * from './string';
|
|
11
11
|
export * from './types';
|