@dxos/echo 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced
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/LICENSE +102 -5
- package/README.md +3 -3
- package/dist/lib/neutral/Annotation.mjs +5 -3
- package/dist/lib/neutral/Database.mjs +8 -4
- package/dist/lib/neutral/Entity.mjs +16 -14
- package/dist/lib/neutral/Err.mjs +1 -1
- package/dist/lib/neutral/Extension.mjs +18 -0
- package/dist/lib/neutral/Extension.mjs.map +7 -0
- package/dist/lib/neutral/Feed.mjs +23 -18
- package/dist/lib/neutral/Filter.mjs +23 -13
- package/dist/lib/neutral/Format.mjs +3 -3
- package/dist/lib/neutral/JsonSchema.mjs +8 -8
- package/dist/lib/neutral/Key.mjs +1 -1
- package/dist/lib/neutral/Migration.mjs +17 -0
- package/dist/lib/neutral/Migration.mjs.map +7 -0
- package/dist/lib/neutral/Obj.mjs +16 -13
- package/dist/lib/neutral/Order.mjs +1 -1
- package/dist/lib/neutral/Query.mjs +19 -17
- package/dist/lib/neutral/QueryResult.mjs +1 -1
- package/dist/lib/neutral/Ref.mjs +9 -7
- package/dist/lib/neutral/Relation.mjs +15 -14
- package/dist/lib/neutral/SchemaRegistry.mjs +1 -1
- package/dist/lib/neutral/Tag.mjs +14 -13
- package/dist/lib/neutral/Type.mjs +10 -10
- package/dist/lib/neutral/{chunk-FZO7LQO7.mjs → chunk-44HT3MEC.mjs} +2 -2
- package/dist/lib/neutral/{chunk-ROAGDPV7.mjs → chunk-4A2GS5LQ.mjs} +12 -8
- package/dist/lib/neutral/chunk-4A2GS5LQ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-OENWMTE6.mjs → chunk-5SL5LDLD.mjs} +4 -2
- package/dist/lib/neutral/chunk-5SL5LDLD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-ANHVGJI4.mjs → chunk-7RVZT53K.mjs} +1 -1
- package/dist/lib/neutral/{chunk-YWXWXIE5.mjs → chunk-APHSOTIX.mjs} +2 -2
- package/dist/lib/neutral/{chunk-YWXWXIE5.mjs.map → chunk-APHSOTIX.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-6DNYDXCV.mjs → chunk-B2P7IVG3.mjs} +61 -14
- package/dist/lib/neutral/chunk-B2P7IVG3.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BNCCGLJN.mjs → chunk-BICZKPQG.mjs} +1 -1
- package/dist/lib/neutral/{chunk-43Y5DOS6.mjs → chunk-BMB7IHGB.mjs} +16 -66
- package/dist/lib/neutral/chunk-BMB7IHGB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6K2MVI2O.mjs → chunk-BUBEC474.mjs} +4 -4
- package/dist/lib/neutral/{chunk-YMNSMKKW.mjs → chunk-C4PSESGN.mjs} +6 -22
- package/dist/lib/neutral/{chunk-YMNSMKKW.mjs.map → chunk-C4PSESGN.mjs.map} +3 -3
- package/dist/lib/neutral/chunk-FIWO2FZK.mjs +36 -0
- package/dist/lib/neutral/chunk-FIWO2FZK.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GWFFC34K.mjs +50 -0
- package/dist/lib/neutral/chunk-GWFFC34K.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-NXMFBIT5.mjs → chunk-HKETO4L4.mjs} +72 -6
- package/dist/lib/neutral/chunk-HKETO4L4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-NEGC54NE.mjs → chunk-I2DARWPX.mjs} +17 -19
- package/dist/lib/neutral/chunk-I2DARWPX.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7VNVH63N.mjs → chunk-IVSI7QO6.mjs} +50 -20
- package/dist/lib/neutral/chunk-IVSI7QO6.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BOZZPUXE.mjs → chunk-MLS7U7AT.mjs} +12 -65
- package/dist/lib/neutral/chunk-MLS7U7AT.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WYOKA6AE.mjs → chunk-N4B7FHQT.mjs} +2 -2
- package/dist/lib/neutral/{chunk-WYOKA6AE.mjs.map → chunk-N4B7FHQT.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-FXEG7EOK.mjs → chunk-N7VOEPSV.mjs} +6 -3
- package/dist/lib/neutral/{chunk-FXEG7EOK.mjs.map → chunk-N7VOEPSV.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-UTBRYVQC.mjs → chunk-QRZ2I3ZM.mjs} +2 -2
- package/dist/lib/neutral/{chunk-SEMVAGBM.mjs → chunk-TNBK56IN.mjs} +19 -24
- package/dist/lib/neutral/chunk-TNBK56IN.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-B5OXLWZL.mjs → chunk-TRPZU2HV.mjs} +2 -2
- package/dist/lib/neutral/{chunk-UI6MWK5W.mjs → chunk-TTCSATUD.mjs} +1 -1
- package/dist/lib/neutral/{chunk-OMUPQMLR.mjs → chunk-V72DY6LU.mjs} +1 -1
- package/dist/lib/neutral/{chunk-6GPU7XC3.mjs → chunk-VW42HESL.mjs} +54 -17
- package/dist/lib/neutral/chunk-VW42HESL.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-4JRI2ZJI.mjs → chunk-X3356HPV.mjs} +120 -12
- package/dist/lib/neutral/chunk-X3356HPV.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-W47JKR3X.mjs → chunk-XEXM5HWQ.mjs} +18 -46
- package/dist/lib/neutral/chunk-XEXM5HWQ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-C4JZK4J7.mjs → chunk-Z5GKP74O.mjs} +231 -479
- package/dist/lib/neutral/chunk-Z5GKP74O.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-UBEZSGXY.mjs → chunk-ZISMEVKD.mjs} +1 -1
- package/dist/lib/neutral/{chunk-UBEZSGXY.mjs.map → chunk-ZISMEVKD.mjs.map} +2 -2
- package/dist/lib/neutral/index.mjs +38 -28
- package/dist/lib/neutral/internal/index.mjs +15 -9
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +185 -129
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/types/src/Annotation.d.ts +1 -1
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Collection.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +14 -2
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +2 -1
- package/dist/types/src/Dataset.d.ts.map +1 -1
- package/dist/types/src/Entity.d.ts +17 -11
- package/dist/types/src/Entity.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +18 -18
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Extension.d.ts +80 -0
- package/dist/types/src/Extension.d.ts.map +1 -0
- package/dist/types/src/Extension.test.d.ts +2 -0
- package/dist/types/src/Extension.test.d.ts.map +1 -0
- package/dist/types/src/Feed.d.ts +62 -21
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +54 -4
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Filter.test.d.ts +2 -0
- package/dist/types/src/Filter.test.d.ts.map +1 -0
- package/dist/types/src/Hypergraph.d.ts +3 -3
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Json.d.ts +33 -0
- package/dist/types/src/Json.d.ts.map +1 -0
- package/dist/types/src/Json.test.d.ts +2 -0
- package/dist/types/src/Json.test.d.ts.map +1 -0
- package/dist/types/src/JsonSchema.d.ts +1 -1
- package/dist/types/src/Migration.d.ts +69 -0
- package/dist/types/src/Migration.d.ts.map +1 -0
- package/dist/types/src/Obj.d.ts +42 -28
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +13 -2
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +1 -0
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Relation.d.ts +17 -18
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Tag.d.ts +2 -2
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts +3 -3
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +1 -1
- package/dist/types/src/View.d.ts.map +1 -1
- package/dist/types/src/exemplars.test.d.ts +2 -0
- package/dist/types/src/exemplars.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +12 -2
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/sorting.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/util.d.ts +1 -1
- package/dist/types/src/internal/Annotation/util.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +2 -0
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/version.d.ts.map +1 -1
- package/dist/types/src/internal/Format/date.d.ts.map +1 -1
- package/dist/types/src/internal/Format/format.d.ts.map +1 -1
- package/dist/types/src/internal/Format/number.d.ts.map +1 -1
- package/dist/types/src/internal/Format/object.d.ts.map +1 -1
- package/dist/types/src/internal/Format/types.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-normalize.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +28 -28
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/common.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/deleted.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/ids.d.ts +1 -1
- package/dist/types/src/internal/Obj/ids.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts +4 -3
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/set-value.d.ts +1 -1
- package/dist/types/src/internal/Obj/set-value.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/snapshot.d.ts.map +1 -1
- package/dist/types/src/internal/Query.d.ts +10 -0
- package/dist/types/src/internal/Query.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/ref-array.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +14 -1
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/Type/compose.d.ts.map +1 -1
- package/dist/types/src/internal/Type/echo-schema.d.ts +2 -2
- package/dist/types/src/internal/Type/echo-schema.d.ts.map +1 -1
- package/dist/types/src/internal/Type/manipulation.d.ts.map +1 -1
- package/dist/types/src/internal/common/api/meta.d.ts +3 -3
- package/dist/types/src/internal/common/api/meta.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/change-context.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/change-context.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/define-hidden-property.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/errors.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/errors.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/event-batch.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/ownership.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive-array.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive.test.d.ts +2 -0
- package/dist/types/src/internal/common/proxy/reactive.test.d.ts.map +1 -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.map +1 -1
- package/dist/types/src/internal/common/types/base.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 +10 -0
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/version.d.ts +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-data.d.ts +8 -8
- package/dist/types/src/testing/test-data.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +53 -53
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -15
- package/src/Annotation.ts +1 -0
- package/src/Collection.ts +2 -2
- package/src/Database.ts +50 -15
- package/src/Entity.ts +18 -12
- package/src/Extension.test.ts +235 -0
- package/src/Extension.ts +122 -0
- package/src/Feed.ts +107 -34
- package/src/Filter.test.ts +90 -0
- package/src/Filter.ts +97 -3
- package/src/Hypergraph.ts +3 -3
- package/src/Json.test.ts +175 -0
- package/src/Json.ts +102 -0
- package/src/Migration.ts +106 -0
- package/src/Obj.test.ts +105 -13
- package/src/Obj.ts +154 -33
- package/src/Query.test.ts +199 -9
- package/src/Query.ts +58 -8
- package/src/Ref.ts +2 -0
- package/src/Relation.ts +24 -20
- package/src/Type.ts +1 -1
- package/src/View.ts +1 -1
- package/src/exemplars.test.ts +21 -0
- package/src/index.ts +4 -0
- package/src/internal/Annotation/annotations.test.ts +51 -2
- package/src/internal/Annotation/annotations.ts +33 -14
- package/src/internal/Annotation/sorting.ts +0 -1
- package/src/internal/Entity/api.ts +0 -1
- package/src/internal/Entity/model.ts +2 -0
- package/src/internal/Entity/object.ts +0 -1
- package/src/internal/Entity/version.ts +0 -1
- package/src/internal/Format/date.test.ts +0 -1
- package/src/internal/Format/format.test.ts +0 -1
- package/src/internal/JsonSchema/json-schema-type.ts +1 -1
- package/src/internal/JsonSchema/json-schema.test.ts +1 -2
- package/src/internal/JsonSchema/json-schema.ts +1 -2
- package/src/internal/Obj/clone.ts +1 -1
- package/src/internal/Obj/create-object.test.ts +2 -4
- package/src/internal/Obj/create-object.ts +2 -3
- package/src/internal/Obj/deleted.ts +1 -1
- package/src/internal/Obj/ids.ts +1 -1
- package/src/internal/Obj/json-serializer.test.ts +49 -5
- package/src/internal/Obj/json-serializer.ts +47 -25
- package/src/internal/Obj/set-value.test.ts +24 -24
- package/src/internal/Obj/set-value.ts +1 -1
- package/src/internal/Query.ts +156 -0
- package/src/internal/Ref/ref-array.ts +0 -1
- package/src/internal/Ref/ref.test.ts +0 -1
- package/src/internal/Ref/ref.ts +18 -1
- package/src/internal/Type/compose.test.ts +0 -1
- package/src/internal/Type/echo-schema.ts +3 -4
- package/src/internal/common/README.md +1 -1
- package/src/internal/common/api/meta.ts +3 -3
- package/src/internal/common/proxy/change-context.ts +1 -1
- package/src/internal/common/proxy/change.test.ts +94 -94
- package/src/internal/common/proxy/errors.ts +2 -2
- package/src/internal/common/proxy/handler.test.ts +0 -2
- package/src/internal/common/proxy/json-serializer.ts +4 -1
- package/src/internal/common/proxy/make-object.ts +0 -1
- package/src/internal/common/proxy/ownership.ts +0 -1
- package/src/internal/common/proxy/reactive-array.ts +1 -1
- package/src/internal/common/proxy/reactive.test.ts +54 -0
- package/src/internal/common/proxy/reactive.ts +11 -3
- package/src/internal/common/proxy/typed-handler.test.ts +0 -1
- package/src/internal/common/proxy/typed-handler.ts +8 -10
- package/src/internal/common/proxy/typed-object.test.ts +2 -3
- package/src/internal/common/types/entity.ts +1 -1
- package/src/internal/common/types/meta.ts +12 -1
- package/src/internal/index.ts +1 -0
- package/src/testing/api.test.ts +0 -1
- package/src/testing/test-data.ts +157 -98
- package/dist/lib/neutral/chunk-43Y5DOS6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4JRI2ZJI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6DNYDXCV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6GPU7XC3.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7VNVH63N.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BOZZPUXE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-C4JZK4J7.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NEGC54NE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NXMFBIT5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-OENWMTE6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ROAGDPV7.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SEMVAGBM.mjs.map +0 -7
- package/dist/lib/neutral/chunk-W47JKR3X.mjs.map +0 -7
- /package/dist/lib/neutral/{chunk-FZO7LQO7.mjs.map → chunk-44HT3MEC.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-ANHVGJI4.mjs.map → chunk-7RVZT53K.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-BNCCGLJN.mjs.map → chunk-BICZKPQG.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-6K2MVI2O.mjs.map → chunk-BUBEC474.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-UTBRYVQC.mjs.map → chunk-QRZ2I3ZM.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-B5OXLWZL.mjs.map → chunk-TRPZU2HV.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-UI6MWK5W.mjs.map → chunk-TTCSATUD.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-OMUPQMLR.mjs.map → chunk-V72DY6LU.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.8baae0fced",
|
|
4
4
|
"description": "ECHO API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "info@dxos.org",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"type": "module",
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"types": "./dist/types/src/Key.d.ts",
|
|
74
74
|
"default": "./dist/lib/neutral/Key.mjs"
|
|
75
75
|
},
|
|
76
|
+
"./Migration": {
|
|
77
|
+
"source": "./src/Migration.ts",
|
|
78
|
+
"types": "./dist/types/src/Migration.d.ts",
|
|
79
|
+
"default": "./dist/lib/neutral/Migration.mjs"
|
|
80
|
+
},
|
|
76
81
|
"./Obj": {
|
|
77
82
|
"source": "./src/Obj.ts",
|
|
78
83
|
"types": "./dist/types/src/Obj.d.ts",
|
|
@@ -117,6 +122,11 @@
|
|
|
117
122
|
"source": "./src/Type.ts",
|
|
118
123
|
"types": "./dist/types/src/Type.d.ts",
|
|
119
124
|
"default": "./dist/lib/neutral/Type.mjs"
|
|
125
|
+
},
|
|
126
|
+
"./Extension": {
|
|
127
|
+
"source": "./src/Extension.ts",
|
|
128
|
+
"types": "./dist/types/src/Extension.d.ts",
|
|
129
|
+
"default": "./dist/lib/neutral/Extension.mjs"
|
|
120
130
|
}
|
|
121
131
|
},
|
|
122
132
|
"files": [
|
|
@@ -124,19 +134,19 @@
|
|
|
124
134
|
"src"
|
|
125
135
|
],
|
|
126
136
|
"dependencies": {
|
|
127
|
-
"effect": "3.
|
|
128
|
-
"@dxos/
|
|
129
|
-
"@dxos/
|
|
130
|
-
"@dxos/
|
|
131
|
-
"@dxos/
|
|
132
|
-
"@dxos/
|
|
133
|
-
"@dxos/
|
|
134
|
-
"@dxos/
|
|
135
|
-
"@dxos/
|
|
136
|
-
"@dxos/
|
|
137
|
-
"@dxos/
|
|
138
|
-
"@dxos/
|
|
139
|
-
"@dxos/
|
|
137
|
+
"effect": "3.20.0",
|
|
138
|
+
"@dxos/async": "0.8.4-main.8baae0fced",
|
|
139
|
+
"@dxos/context": "0.8.4-main.8baae0fced",
|
|
140
|
+
"@dxos/debug": "0.8.4-main.8baae0fced",
|
|
141
|
+
"@dxos/echo-protocol": "0.8.4-main.8baae0fced",
|
|
142
|
+
"@dxos/effect": "0.8.4-main.8baae0fced",
|
|
143
|
+
"@dxos/errors": "0.8.4-main.8baae0fced",
|
|
144
|
+
"@dxos/invariant": "0.8.4-main.8baae0fced",
|
|
145
|
+
"@dxos/keys": "0.8.4-main.8baae0fced",
|
|
146
|
+
"@dxos/log": "0.8.4-main.8baae0fced",
|
|
147
|
+
"@dxos/node-std": "0.8.4-main.8baae0fced",
|
|
148
|
+
"@dxos/util": "0.8.4-main.8baae0fced",
|
|
149
|
+
"@dxos/protocols": "0.8.4-main.8baae0fced"
|
|
140
150
|
},
|
|
141
151
|
"publishConfig": {
|
|
142
152
|
"access": "public"
|
package/src/Annotation.ts
CHANGED
package/src/Collection.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Schema from 'effect/Schema';
|
|
8
8
|
|
|
9
|
-
import * as internal from './internal';
|
|
10
9
|
import * as Annotation from './Annotation';
|
|
10
|
+
import * as internal from './internal';
|
|
11
11
|
import * as Obj from './Obj';
|
|
12
12
|
import * as Ref from './Ref';
|
|
13
13
|
import * as Type from './Type';
|
|
@@ -25,7 +25,7 @@ export const Collection = Schema.Struct({
|
|
|
25
25
|
}),
|
|
26
26
|
Annotation.IconAnnotation.set({
|
|
27
27
|
icon: 'ph--folder--regular',
|
|
28
|
-
hue: '
|
|
28
|
+
hue: 'amber',
|
|
29
29
|
}),
|
|
30
30
|
);
|
|
31
31
|
|
package/src/Database.ts
CHANGED
|
@@ -20,8 +20,8 @@ import * as Err from './Err';
|
|
|
20
20
|
import type * as Filter from './Filter';
|
|
21
21
|
import type * as Hypergraph from './Hypergraph';
|
|
22
22
|
import { isInstanceOf } from './internal/Annotation';
|
|
23
|
-
import type { Ref } from './internal/Ref/ref';
|
|
24
23
|
import { type AnyProperties } from './internal/common/types';
|
|
24
|
+
import type { Ref } from './internal/Ref/ref';
|
|
25
25
|
import type * as Obj from './Obj';
|
|
26
26
|
import type * as Query from './Query';
|
|
27
27
|
import type * as QueryResult from './QueryResult';
|
|
@@ -38,7 +38,7 @@ export interface QueryFn {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Common interface for Database and
|
|
41
|
+
* Common interface for Database, Feed, and Hypergraph.
|
|
42
42
|
*/
|
|
43
43
|
export interface Queryable {
|
|
44
44
|
query: QueryFn;
|
|
@@ -229,24 +229,28 @@ export const resolve: {
|
|
|
229
229
|
}
|
|
230
230
|
invariant(!schema || isInstanceOf(schema, object), 'Object type mismatch.');
|
|
231
231
|
return object as any;
|
|
232
|
-
})) as any;
|
|
232
|
+
}).pipe(Effect.withSpan('Database.resolve'))) as any;
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
235
|
* Loads an object reference.
|
|
236
236
|
*/
|
|
237
|
-
export const load: <T>(ref: Ref<T>) => Effect.Effect<T, Err.ObjectNotFoundError, never> = Effect.fn(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
237
|
+
export const load: <T>(ref: Ref<T>) => Effect.Effect<T, Err.ObjectNotFoundError, never> = Effect.fn('Database.load')(
|
|
238
|
+
function* (ref) {
|
|
239
|
+
const object = yield* promiseWithCauseCapture(() => ref.tryLoad());
|
|
240
|
+
if (!object) {
|
|
241
|
+
return yield* Effect.fail(new Err.ObjectNotFoundError(ref.dxn));
|
|
242
|
+
}
|
|
243
|
+
return object;
|
|
244
|
+
},
|
|
245
|
+
);
|
|
244
246
|
|
|
245
247
|
/**
|
|
246
248
|
* Loads an object reference option.
|
|
247
249
|
*/
|
|
248
250
|
// TODO(dmaretskyi): Do we need this -- you can just use `Effect.catchTag` in calling code instead.
|
|
249
|
-
export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, never, never> = Effect.fn(
|
|
251
|
+
export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, never, never> = Effect.fn(
|
|
252
|
+
'Database.loadOption',
|
|
253
|
+
)(function* (ref) {
|
|
250
254
|
const object = yield* load(ref).pipe(Effect.catchTag('ObjectNotFoundError', () => Effect.succeed(undefined)));
|
|
251
255
|
|
|
252
256
|
return Option.fromNullable(object);
|
|
@@ -257,21 +261,23 @@ export const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, nev
|
|
|
257
261
|
* @see {@link Database.add}
|
|
258
262
|
*/
|
|
259
263
|
export const add = <T extends Entity.Unknown>(obj: T): Effect.Effect<T, never, Service> =>
|
|
260
|
-
Service.pipe(Effect.map(({ db }) => db.add(obj)));
|
|
264
|
+
Service.pipe(Effect.map(({ db }) => db.add(obj))).pipe(Effect.withSpan('Database.add'));
|
|
261
265
|
|
|
262
266
|
/**
|
|
263
267
|
* Removes an object from the database.
|
|
264
268
|
* @see {@link Database.remove}
|
|
265
269
|
*/
|
|
266
270
|
export const remove = <T extends Entity.Unknown>(obj: T): Effect.Effect<void, never, Service> =>
|
|
267
|
-
Service.pipe(Effect.map(({ db }) => db.remove(obj)));
|
|
271
|
+
Service.pipe(Effect.map(({ db }) => db.remove(obj))).pipe(Effect.withSpan('Database.remove'));
|
|
268
272
|
|
|
269
273
|
/**
|
|
270
274
|
* Flushes pending changes to disk.
|
|
271
275
|
* @see {@link Database.flush}
|
|
272
276
|
*/
|
|
273
277
|
export const flush = (opts?: FlushOptions) =>
|
|
274
|
-
Service.pipe(Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.flush(opts))))
|
|
278
|
+
Service.pipe(Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.flush(opts)))).pipe(
|
|
279
|
+
Effect.withSpan('Database.flush'),
|
|
280
|
+
);
|
|
275
281
|
|
|
276
282
|
/**
|
|
277
283
|
* Creates a `QueryResult` object that can be subscribed to.
|
|
@@ -292,7 +298,36 @@ export const runQuery: {
|
|
|
292
298
|
<Q extends Query.Any>(query: Q): Effect.Effect<Query.Type<Q>[], never, Service>;
|
|
293
299
|
<F extends Filter.Any>(filter: F): Effect.Effect<Filter.Type<F>[], never, Service>;
|
|
294
300
|
} = (queryOrFilter: Query.Any | Filter.Any) =>
|
|
295
|
-
query(queryOrFilter as any).pipe(
|
|
301
|
+
query(queryOrFilter as any).pipe(
|
|
302
|
+
Effect.flatMap((queryResult) => promiseWithCauseCapture(() => queryResult.run())),
|
|
303
|
+
Effect.withSpan('Database.runQuery'),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Executes the query once and returns the first result as or None.
|
|
308
|
+
*/
|
|
309
|
+
export const runQueryFirst: {
|
|
310
|
+
<Q extends Query.Any>(query: Q): Effect.Effect<Option.Option<Query.Type<Q>>, never, Service>;
|
|
311
|
+
<F extends Filter.Any>(filter: F): Effect.Effect<Option.Option<Filter.Type<F>>, never, Service>;
|
|
312
|
+
} = (queryOrFilter: Query.Any | Filter.Any) =>
|
|
313
|
+
query(queryOrFilter as any).pipe(
|
|
314
|
+
Effect.flatMap((queryResult) =>
|
|
315
|
+
promiseWithCauseCapture(async () => Option.fromNullable(await queryResult.firstOrUndefined())),
|
|
316
|
+
),
|
|
317
|
+
Effect.withSpan('Database.runQueryFirst'),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Persists schemas in the database so they replicate to other clients.
|
|
322
|
+
* @see {@link SchemaRegistry.SchemaRegistry.register}
|
|
323
|
+
*/
|
|
324
|
+
export const registerSchema = (
|
|
325
|
+
input: SchemaRegistry.RegisterSchemaInput[],
|
|
326
|
+
): Effect.Effect<Type.RuntimeType[], never, Service> =>
|
|
327
|
+
Service.pipe(
|
|
328
|
+
Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.schemaRegistry.register(input))),
|
|
329
|
+
Effect.withSpan('Database.registerSchema'),
|
|
330
|
+
);
|
|
296
331
|
|
|
297
332
|
/**
|
|
298
333
|
* Creates a schema query result that can be subscribed to.
|
package/src/Entity.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import type { ForeignKey } from '@dxos/echo-protocol';
|
|
8
8
|
import type { DXN, ObjectId } from '@dxos/keys';
|
|
9
9
|
|
|
10
|
-
import * as entityInternal from './internal/Entity';
|
|
11
10
|
import * as internal from './internal';
|
|
12
11
|
import type * as Relation from './Relation';
|
|
12
|
+
import type * as Type from './Type';
|
|
13
13
|
|
|
14
14
|
// Re-export KindId and SnapshotKindId from internal.
|
|
15
15
|
export const KindId = internal.KindId;
|
|
@@ -132,6 +132,12 @@ export const getDXN = (entity: Unknown | Snapshot): DXN => internal.getDXN(entit
|
|
|
132
132
|
*/
|
|
133
133
|
export const getTypeDXN = internal.getTypeDXN;
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Get the schema of an entity.
|
|
137
|
+
* Returns the branded ECHO schema used to create the entity.
|
|
138
|
+
*/
|
|
139
|
+
export const getSchema: (entity: Unknown | Snapshot) => Type.AnyEntity | undefined = internal.getSchema as any;
|
|
140
|
+
|
|
135
141
|
/**
|
|
136
142
|
* Get the typename of an entity's type.
|
|
137
143
|
*/
|
|
@@ -192,7 +198,7 @@ export const subscribe = (entity: Unknown, callback: () => void): (() => void) =
|
|
|
192
198
|
//
|
|
193
199
|
|
|
194
200
|
/**
|
|
195
|
-
* Used to provide a mutable view of an entity within `Entity.
|
|
201
|
+
* Used to provide a mutable view of an entity within `Entity.update`.
|
|
196
202
|
*/
|
|
197
203
|
export type Mutable<T> = internal.Mutable<T>;
|
|
198
204
|
|
|
@@ -200,7 +206,7 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
200
206
|
* Perform mutations on an entity (object or relation) within a change context.
|
|
201
207
|
*
|
|
202
208
|
* Entities are read-only by default. Mutations are batched and notifications fire
|
|
203
|
-
* when the callback completes. Direct mutations outside of `Entity.
|
|
209
|
+
* when the callback completes. Direct mutations outside of `Entity.update` will throw
|
|
204
210
|
* at runtime.
|
|
205
211
|
*
|
|
206
212
|
* @param entity - The echo entity (object or relation) to mutate.
|
|
@@ -208,30 +214,30 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
208
214
|
*
|
|
209
215
|
* @example
|
|
210
216
|
* ```typescript
|
|
211
|
-
* // Mutate within Entity.
|
|
212
|
-
* Entity.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
217
|
+
* // Mutate within Entity.update
|
|
218
|
+
* Entity.update(entity, (obj) => {
|
|
219
|
+
* obj.name = 'Updated';
|
|
220
|
+
* obj.count = 42;
|
|
215
221
|
* });
|
|
216
222
|
*
|
|
217
223
|
* // Direct mutation throws
|
|
218
|
-
* entity.name = 'Bob'; // Error: Cannot modify outside Entity.
|
|
224
|
+
* entity.name = 'Bob'; // Error: Cannot modify outside Entity.update()
|
|
219
225
|
* ```
|
|
220
226
|
*
|
|
221
|
-
* Note: For type-specific operations, prefer `Obj.
|
|
227
|
+
* Note: For type-specific operations, prefer `Obj.update` or `Relation.update`.
|
|
222
228
|
*/
|
|
223
|
-
export const
|
|
229
|
+
export const update = <T extends Unknown>(entity: T, callback: internal.ChangeCallback<T>): void => {
|
|
224
230
|
internal.change(entity, callback);
|
|
225
231
|
};
|
|
226
232
|
|
|
227
233
|
/**
|
|
228
234
|
* Add a tag to an entity.
|
|
229
|
-
* Must be called within an `Entity.
|
|
235
|
+
* Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
|
|
230
236
|
*/
|
|
231
237
|
export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.addTag(entity, tag);
|
|
232
238
|
|
|
233
239
|
/**
|
|
234
240
|
* Remove a tag from an entity.
|
|
235
|
-
* Must be called within an `Entity.
|
|
241
|
+
* Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
|
|
236
242
|
*/
|
|
237
243
|
export const removeTag = (entity: Mutable<Unknown>, tag: string): void => internal.removeTag(entity, tag);
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import { describe, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import * as Extension from './Extension';
|
|
10
|
+
|
|
11
|
+
describe('Extension', () => {
|
|
12
|
+
describe('make', () => {
|
|
13
|
+
test('creates an extension with a string schema', ({ expect }) => {
|
|
14
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
15
|
+
|
|
16
|
+
expect(ColorExtension.key).toBe('color');
|
|
17
|
+
expect(ColorExtension.valueSchema).toBe(Schema.String);
|
|
18
|
+
expect(ColorExtension[Extension.TypeId]).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('creates an extension with a number schema', ({ expect }) => {
|
|
22
|
+
const PriorityExtension = Extension.make('priority', Schema.Number);
|
|
23
|
+
|
|
24
|
+
expect(PriorityExtension.key).toBe('priority');
|
|
25
|
+
expect(PriorityExtension.valueSchema).toBe(Schema.Number);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('creates an extension with a complex schema', ({ expect }) => {
|
|
29
|
+
const MetadataSchema = Schema.Struct({
|
|
30
|
+
author: Schema.String,
|
|
31
|
+
version: Schema.Number,
|
|
32
|
+
tags: Schema.Array(Schema.String),
|
|
33
|
+
});
|
|
34
|
+
const MetadataExtension = Extension.make('metadata', MetadataSchema);
|
|
35
|
+
|
|
36
|
+
expect(MetadataExtension.key).toBe('metadata');
|
|
37
|
+
expect(MetadataExtension.valueSchema).toBe(MetadataSchema);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('get and set', () => {
|
|
42
|
+
test('set and get a string value', ({ expect }) => {
|
|
43
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
44
|
+
const values: Extension.Values = {};
|
|
45
|
+
|
|
46
|
+
Extension.set(values, ColorExtension, 'red');
|
|
47
|
+
const result = Extension.get(values, ColorExtension);
|
|
48
|
+
|
|
49
|
+
expect(Option.isSome(result)).toBe(true);
|
|
50
|
+
expect(Option.getOrThrow(result)).toBe('red');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('set and get a number value', ({ expect }) => {
|
|
54
|
+
const PriorityExtension = Extension.make('priority', Schema.Number);
|
|
55
|
+
const values: Extension.Values = {};
|
|
56
|
+
|
|
57
|
+
Extension.set(values, PriorityExtension, 42);
|
|
58
|
+
const result = Extension.get(values, PriorityExtension);
|
|
59
|
+
|
|
60
|
+
expect(Option.isSome(result)).toBe(true);
|
|
61
|
+
expect(Option.getOrThrow(result)).toBe(42);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('set and get a boolean value', ({ expect }) => {
|
|
65
|
+
const EnabledExtension = Extension.make('enabled', Schema.Boolean);
|
|
66
|
+
const values: Extension.Values = {};
|
|
67
|
+
|
|
68
|
+
Extension.set(values, EnabledExtension, true);
|
|
69
|
+
const result = Extension.get(values, EnabledExtension);
|
|
70
|
+
|
|
71
|
+
expect(Option.isSome(result)).toBe(true);
|
|
72
|
+
expect(Option.getOrThrow(result)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('set and get a complex object value', ({ expect }) => {
|
|
76
|
+
const ConfigSchema = Schema.Struct({
|
|
77
|
+
theme: Schema.String,
|
|
78
|
+
fontSize: Schema.Number,
|
|
79
|
+
darkMode: Schema.Boolean,
|
|
80
|
+
});
|
|
81
|
+
const ConfigExtension = Extension.make('config', ConfigSchema);
|
|
82
|
+
const values: Extension.Values = {};
|
|
83
|
+
|
|
84
|
+
const config = { theme: 'modern', fontSize: 14, darkMode: true };
|
|
85
|
+
Extension.set(values, ConfigExtension, config);
|
|
86
|
+
const result = Extension.get(values, ConfigExtension);
|
|
87
|
+
|
|
88
|
+
expect(Option.isSome(result)).toBe(true);
|
|
89
|
+
expect(Option.getOrThrow(result)).toEqual(config);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('set and get an array value', ({ expect }) => {
|
|
93
|
+
const TagsExtension = Extension.make('tags', Schema.Array(Schema.String));
|
|
94
|
+
const values: Extension.Values = {};
|
|
95
|
+
|
|
96
|
+
const tags = ['important', 'urgent', 'review'];
|
|
97
|
+
Extension.set(values, TagsExtension, tags);
|
|
98
|
+
const result = Extension.get(values, TagsExtension);
|
|
99
|
+
|
|
100
|
+
expect(Option.isSome(result)).toBe(true);
|
|
101
|
+
expect(Option.getOrThrow(result)).toEqual(tags);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('get returns None for missing extension', ({ expect }) => {
|
|
105
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
106
|
+
const values: Extension.Values = {};
|
|
107
|
+
|
|
108
|
+
const result = Extension.get(values, ColorExtension);
|
|
109
|
+
|
|
110
|
+
expect(Option.isNone(result)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('overwriting an extension value', ({ expect }) => {
|
|
114
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
115
|
+
const values: Extension.Values = {};
|
|
116
|
+
|
|
117
|
+
Extension.set(values, ColorExtension, 'red');
|
|
118
|
+
Extension.set(values, ColorExtension, 'blue');
|
|
119
|
+
const result = Extension.get(values, ColorExtension);
|
|
120
|
+
|
|
121
|
+
expect(Option.isSome(result)).toBe(true);
|
|
122
|
+
expect(Option.getOrThrow(result)).toBe('blue');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('multiple extensions on the same values object', ({ expect }) => {
|
|
126
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
127
|
+
const PriorityExtension = Extension.make('priority', Schema.Number);
|
|
128
|
+
const EnabledExtension = Extension.make('enabled', Schema.Boolean);
|
|
129
|
+
const values: Extension.Values = {};
|
|
130
|
+
|
|
131
|
+
Extension.set(values, ColorExtension, 'green');
|
|
132
|
+
Extension.set(values, PriorityExtension, 5);
|
|
133
|
+
Extension.set(values, EnabledExtension, false);
|
|
134
|
+
|
|
135
|
+
expect(Option.getOrThrow(Extension.get(values, ColorExtension))).toBe('green');
|
|
136
|
+
expect(Option.getOrThrow(Extension.get(values, PriorityExtension))).toBe(5);
|
|
137
|
+
expect(Option.getOrThrow(Extension.get(values, EnabledExtension))).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('curried forms', () => {
|
|
142
|
+
test('get curried form', ({ expect }) => {
|
|
143
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
144
|
+
const values: Extension.Values = {};
|
|
145
|
+
Extension.set(values, ColorExtension, 'purple');
|
|
146
|
+
|
|
147
|
+
const getColor = Extension.get(ColorExtension);
|
|
148
|
+
const result = getColor(values);
|
|
149
|
+
|
|
150
|
+
expect(Option.isSome(result)).toBe(true);
|
|
151
|
+
expect(Option.getOrThrow(result)).toBe('purple');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('set curried form', ({ expect }) => {
|
|
155
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
156
|
+
const values: Extension.Values = {};
|
|
157
|
+
|
|
158
|
+
const setOrange = Extension.set(ColorExtension, 'orange');
|
|
159
|
+
setOrange(values);
|
|
160
|
+
const result = Extension.get(values, ColorExtension);
|
|
161
|
+
|
|
162
|
+
expect(Option.isSome(result)).toBe(true);
|
|
163
|
+
expect(Option.getOrThrow(result)).toBe('orange');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('curried forms work in pipelines', ({ expect }) => {
|
|
167
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
168
|
+
const values: Extension.Values = {};
|
|
169
|
+
|
|
170
|
+
Extension.set(ColorExtension, 'cyan')(values);
|
|
171
|
+
const result = Extension.get(ColorExtension)(values);
|
|
172
|
+
|
|
173
|
+
expect(Option.isSome(result)).toBe(true);
|
|
174
|
+
expect(Option.getOrThrow(result)).toBe('cyan');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Key', () => {
|
|
179
|
+
test('Key.make creates a branded key', ({ expect }) => {
|
|
180
|
+
const key = Extension.Key.make('my-extension');
|
|
181
|
+
|
|
182
|
+
expect(key).toBe('my-extension');
|
|
183
|
+
expect(typeof key).toBe('string');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('Values schema', () => {
|
|
188
|
+
test('Values can be used as a schema', ({ expect }) => {
|
|
189
|
+
const PersonWithExtensions = Schema.Struct({
|
|
190
|
+
name: Schema.String,
|
|
191
|
+
extensions: Extension.Values,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const person = Schema.decodeUnknownSync(PersonWithExtensions)({
|
|
195
|
+
name: 'Alice',
|
|
196
|
+
extensions: {},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(person.name).toBe('Alice');
|
|
200
|
+
expect(person.extensions).toEqual({});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('Values schema accepts extension data', ({ expect }) => {
|
|
204
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
205
|
+
const values: Extension.Values = {};
|
|
206
|
+
Extension.set(values, ColorExtension, 'red');
|
|
207
|
+
|
|
208
|
+
const PersonWithExtensions = Schema.Struct({
|
|
209
|
+
name: Schema.String,
|
|
210
|
+
extensions: Extension.Values,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const person = Schema.decodeUnknownSync(PersonWithExtensions)({
|
|
214
|
+
name: 'Bob',
|
|
215
|
+
extensions: values,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(person.name).toBe('Bob');
|
|
219
|
+
const color = Extension.get(person.extensions, ColorExtension);
|
|
220
|
+
expect(Option.getOrThrow(color)).toBe('red');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('TypeId', () => {
|
|
225
|
+
test('TypeId is a unique symbol-like string', ({ expect }) => {
|
|
226
|
+
expect(Extension.TypeId).toBe('~@dxos/echo/Extension');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('extensions have TypeId property', ({ expect }) => {
|
|
230
|
+
const ColorExtension = Extension.make('color', Schema.String);
|
|
231
|
+
|
|
232
|
+
expect(Extension.TypeId in ColorExtension).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/Extension.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// @import-as-namespace
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2026 DXOS.org
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import * as Function from 'effect/Function';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
import type * as Types from 'effect/Types';
|
|
10
|
+
|
|
11
|
+
// @import-as-namespace
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extensions allow objects to contain typed properties that are not part of the schema.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const TypeId = '~@dxos/echo/Extension' as const;
|
|
18
|
+
export type TypeId = typeof TypeId;
|
|
19
|
+
|
|
20
|
+
export interface Extension<T> extends Record<
|
|
21
|
+
TypeId,
|
|
22
|
+
{
|
|
23
|
+
_Type: T;
|
|
24
|
+
}
|
|
25
|
+
> {
|
|
26
|
+
readonly [TypeId]: {
|
|
27
|
+
_Type: T;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
readonly key: Key;
|
|
31
|
+
readonly valueSchema: Schema.Schema<T>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a new typed extension.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const ColorExtension = Extension.make('color', Schema.String);
|
|
39
|
+
*
|
|
40
|
+
* const obj = Obj.make(Person, {
|
|
41
|
+
* [Obj.Meta]: { keys: [{ source: 'external', id: '123' }] },
|
|
42
|
+
* name: 'John',
|
|
43
|
+
* email: 'john@example.com',
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* Obj.update(obj, (obj) => {
|
|
47
|
+
* Extension.set(obj.extensions, ColorExtension, 'red');
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* console.log(Extension.get(obj.extensions, ColorExtension)); // 'red'
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const make = <S extends Schema.Schema.AnyNoContext>(
|
|
54
|
+
key: string,
|
|
55
|
+
valueSchema: S,
|
|
56
|
+
): Extension<Schema.Schema.Type<S>> => {
|
|
57
|
+
return {
|
|
58
|
+
[TypeId]: {} as any,
|
|
59
|
+
key: Key.make(key),
|
|
60
|
+
valueSchema,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unique identifier for an extension.
|
|
66
|
+
*/
|
|
67
|
+
// TODO(dmaretskyi): filter to be fully qualified: (e.g., org.dxos.extension.color)
|
|
68
|
+
export const Key = Schema.String.pipe(Schema.brand('~@dxos/echo/ExtensionKey'));
|
|
69
|
+
export type Key = Schema.Schema.Type<typeof Key>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set of extension values.
|
|
73
|
+
*
|
|
74
|
+
* Can be used inside an object/relation schema:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* const Person = Schema.Struct({
|
|
78
|
+
* name: Schema.String,
|
|
79
|
+
* extensions: Extension.Values,
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const Values = Schema.Record({ key: Key, value: Schema.Unknown });
|
|
84
|
+
export interface Values extends Schema.Schema.Type<typeof Values> {}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the value of an extension from a set of values.
|
|
88
|
+
*/
|
|
89
|
+
export const get: {
|
|
90
|
+
<T>(extension: Extension<T>): (values: Values) => Option.Option<T>;
|
|
91
|
+
<T>(values: Values, extension: Extension<T>): Option.Option<T>;
|
|
92
|
+
} = Function.dual<
|
|
93
|
+
<T>(extension: Extension<T>) => (values: Values) => Option.Option<T>,
|
|
94
|
+
<T>(values: Values, extension: Extension<T>) => Option.Option<T>
|
|
95
|
+
>(2, (values, extension) => {
|
|
96
|
+
if (!(extension.key in values)) {
|
|
97
|
+
return Option.none();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Function.pipe(values[extension.key], Schema.decodeUnknownSync(extension.valueSchema), Option.some);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set the value of an extension in a set of values.
|
|
105
|
+
*
|
|
106
|
+
* Can also be used within Obj.update callback:
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* Obj.update(obj, (obj) => {
|
|
110
|
+
* Extension.set(obj.extensions, ColorExtension, 'red');
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export const set: {
|
|
115
|
+
<T>(extension: Extension<T>, value: T): (values: Values) => void;
|
|
116
|
+
<T>(values: Types.Mutable<Values>, extension: Extension<T>, value: T): void;
|
|
117
|
+
} = Function.dual<
|
|
118
|
+
<T>(extension: Extension<T>, value: T) => (values: Values) => void,
|
|
119
|
+
<T>(values: Types.Mutable<Values>, extension: Extension<T>, value: T) => void
|
|
120
|
+
>(3, (values, extension, value) => {
|
|
121
|
+
values[extension.key] = Schema.encodeSync(extension.valueSchema)(value);
|
|
122
|
+
});
|