@dxos/echo 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934
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 +6 -4
- package/dist/lib/neutral/Entity.mjs +16 -14
- package/dist/lib/neutral/Err.mjs +1 -1
- package/dist/lib/neutral/Extension.mjs +1 -1
- package/dist/lib/neutral/Feed.mjs +21 -17
- package/dist/lib/neutral/Filter.mjs +15 -11
- 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 +14 -14
- 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 +9 -7
- package/dist/lib/neutral/Relation.mjs +15 -15
- package/dist/lib/neutral/SchemaRegistry.mjs +1 -1
- package/dist/lib/neutral/Tag.mjs +14 -14
- package/dist/lib/neutral/Type.mjs +10 -10
- package/dist/lib/neutral/{chunk-GZQTCRJB.mjs → chunk-44HT3MEC.mjs} +2 -2
- package/dist/lib/neutral/{chunk-ZIXGDU6F.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-MYMU4TWK.mjs → chunk-7VWWUTRQ.mjs} +38 -17
- package/dist/lib/neutral/chunk-7VWWUTRQ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-66GBFH43.mjs → chunk-A6IYRT5A.mjs} +17 -45
- package/dist/lib/neutral/chunk-A6IYRT5A.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-R72KFH2X.mjs → chunk-APHSOTIX.mjs} +2 -2
- package/dist/lib/neutral/{chunk-XZJUNTML.mjs → chunk-APJKDGFL.mjs} +36 -6
- package/dist/lib/neutral/chunk-APJKDGFL.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BNCCGLJN.mjs → chunk-BICZKPQG.mjs} +1 -1
- package/dist/lib/neutral/{chunk-QWAOTFCY.mjs → chunk-BMB7IHGB.mjs} +16 -66
- package/dist/lib/neutral/chunk-BMB7IHGB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-KPY6FC5I.mjs → chunk-BVOWYVTD.mjs} +17 -12
- package/dist/lib/neutral/chunk-BVOWYVTD.mjs.map +7 -0
- package/dist/lib/neutral/chunk-FIWO2FZK.mjs +36 -0
- package/dist/lib/neutral/chunk-FIWO2FZK.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WMFVS4KR.mjs → chunk-G54OX4IX.mjs} +102 -480
- package/dist/lib/neutral/chunk-G54OX4IX.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7QQMUDE6.mjs → chunk-GMS5PKGQ.mjs} +59 -12
- package/dist/lib/neutral/chunk-GMS5PKGQ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-YS6Q3XAD.mjs → chunk-GWFFC34K.mjs} +1 -1
- package/dist/lib/neutral/{chunk-YS6Q3XAD.mjs.map → chunk-GWFFC34K.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-EBVB5NOH.mjs → chunk-I2DARWPX.mjs} +15 -20
- package/dist/lib/neutral/chunk-I2DARWPX.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-FNEFSO2C.mjs → chunk-MLS7U7AT.mjs} +12 -65
- package/dist/lib/neutral/{chunk-FNEFSO2C.mjs.map → chunk-MLS7U7AT.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-TBKX6JQO.mjs → chunk-N4B7FHQT.mjs} +1 -1
- package/dist/lib/neutral/{chunk-46QNGNTY.mjs → chunk-N7VOEPSV.mjs} +6 -3
- package/dist/lib/neutral/{chunk-46QNGNTY.mjs.map → chunk-N7VOEPSV.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-MZXWVR3T.mjs → chunk-PSZBLH53.mjs} +6 -22
- package/dist/lib/neutral/{chunk-MZXWVR3T.mjs.map → chunk-PSZBLH53.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-XZM7FERR.mjs → chunk-QBIKZ3TY.mjs} +4 -4
- package/dist/lib/neutral/{chunk-E5PBQJWV.mjs → chunk-QRZ2I3ZM.mjs} +2 -2
- package/dist/lib/neutral/{chunk-2U2BZDLU.mjs → chunk-SCPFDS2E.mjs} +12 -8
- package/dist/lib/neutral/chunk-SCPFDS2E.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-VCAP4OOT.mjs → chunk-SKDTKHDC.mjs} +21 -5
- package/dist/lib/neutral/chunk-SKDTKHDC.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6URFBQJH.mjs → chunk-TNBK56IN.mjs} +11 -22
- package/dist/lib/neutral/chunk-TNBK56IN.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-DQYLD2RB.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-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 +33 -27
- package/dist/lib/neutral/internal/index.mjs +11 -9
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +183 -128
- 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 +6 -1
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +1 -1
- package/dist/types/src/Entity.d.ts +15 -9
- 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 +4 -4
- package/dist/types/src/Extension.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +37 -2
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +34 -0
- package/dist/types/src/Filter.d.ts.map +1 -1
- 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 +23 -21
- 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 +5 -1
- 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 +15 -15
- 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.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 +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +11 -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/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 +2 -1
- 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.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +13 -0
- 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 +1 -1
- 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/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 +3 -3
- 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/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 +19 -14
- package/src/Annotation.ts +1 -0
- package/src/Collection.ts +1 -1
- package/src/Database.ts +36 -14
- package/src/Entity.ts +16 -9
- package/src/Extension.ts +3 -3
- package/src/Feed.ts +54 -2
- package/src/Filter.ts +61 -0
- 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 +23 -23
- package/src/Obj.ts +28 -25
- package/src/Query.test.ts +129 -5
- package/src/Query.ts +32 -7
- package/src/Ref.ts +2 -0
- package/src/Relation.ts +21 -17
- package/src/exemplars.test.ts +21 -0
- package/src/index.ts +3 -0
- package/src/internal/Annotation/annotations.ts +19 -7
- package/src/internal/Entity/model.ts +2 -0
- package/src/internal/Obj/ids.ts +1 -1
- package/src/internal/Obj/json-serializer.test.ts +2 -2
- package/src/internal/Obj/json-serializer.ts +17 -10
- package/src/internal/Obj/set-value.test.ts +24 -24
- package/src/internal/Obj/set-value.ts +1 -1
- package/src/internal/Query.ts +28 -4
- package/src/internal/Ref/ref.ts +17 -0
- package/src/internal/Type/echo-schema.ts +1 -1
- 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 +66 -66
- package/src/internal/common/proxy/errors.ts +2 -2
- 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 -2
- package/src/internal/common/proxy/typed-handler.ts +7 -7
- package/src/internal/common/proxy/typed-object.test.ts +1 -1
- package/src/internal/common/types/meta.ts +12 -0
- package/src/testing/test-data.ts +157 -97
- package/dist/lib/neutral/chunk-2U2BZDLU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-66GBFH43.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6URFBQJH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7QQMUDE6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-EBVB5NOH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-KPY6FC5I.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MYMU4TWK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-QWAOTFCY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-VCAP4OOT.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WMFVS4KR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XZJUNTML.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ZIXGDU6F.mjs.map +0 -7
- /package/dist/lib/neutral/{chunk-GZQTCRJB.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-R72KFH2X.mjs.map → chunk-APHSOTIX.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-BNCCGLJN.mjs.map → chunk-BICZKPQG.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-TBKX6JQO.mjs.map → chunk-N4B7FHQT.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-XZM7FERR.mjs.map → chunk-QBIKZ3TY.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-E5PBQJWV.mjs.map → chunk-QRZ2I3ZM.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-DQYLD2RB.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.765dc60934",
|
|
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",
|
|
@@ -130,18 +135,18 @@
|
|
|
130
135
|
],
|
|
131
136
|
"dependencies": {
|
|
132
137
|
"effect": "3.20.0",
|
|
133
|
-
"@dxos/async": "0.8.4-main.
|
|
134
|
-
"@dxos/
|
|
135
|
-
"@dxos/
|
|
136
|
-
"@dxos/effect": "0.8.4-main.
|
|
137
|
-
"@dxos/
|
|
138
|
-
"@dxos/
|
|
139
|
-
"@dxos/keys": "0.8.4-main.
|
|
140
|
-
"@dxos/
|
|
141
|
-
"@dxos/log": "0.8.4-main.
|
|
142
|
-
"@dxos/protocols": "0.8.4-main.
|
|
143
|
-
"@dxos/
|
|
144
|
-
"@dxos/
|
|
138
|
+
"@dxos/async": "0.8.4-main.765dc60934",
|
|
139
|
+
"@dxos/context": "0.8.4-main.765dc60934",
|
|
140
|
+
"@dxos/debug": "0.8.4-main.765dc60934",
|
|
141
|
+
"@dxos/effect": "0.8.4-main.765dc60934",
|
|
142
|
+
"@dxos/invariant": "0.8.4-main.765dc60934",
|
|
143
|
+
"@dxos/echo-protocol": "0.8.4-main.765dc60934",
|
|
144
|
+
"@dxos/keys": "0.8.4-main.765dc60934",
|
|
145
|
+
"@dxos/errors": "0.8.4-main.765dc60934",
|
|
146
|
+
"@dxos/log": "0.8.4-main.765dc60934",
|
|
147
|
+
"@dxos/protocols": "0.8.4-main.765dc60934",
|
|
148
|
+
"@dxos/node-std": "0.8.4-main.765dc60934",
|
|
149
|
+
"@dxos/util": "0.8.4-main.765dc60934"
|
|
145
150
|
},
|
|
146
151
|
"publishConfig": {
|
|
147
152
|
"access": "public"
|
package/src/Annotation.ts
CHANGED
package/src/Collection.ts
CHANGED
package/src/Database.ts
CHANGED
|
@@ -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,10 @@ 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
|
+
);
|
|
296
305
|
|
|
297
306
|
/**
|
|
298
307
|
* Executes the query once and returns the first result as or None.
|
|
@@ -305,6 +314,19 @@ export const runQueryFirst: {
|
|
|
305
314
|
Effect.flatMap((queryResult) =>
|
|
306
315
|
promiseWithCauseCapture(async () => Option.fromNullable(await queryResult.firstOrUndefined())),
|
|
307
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'),
|
|
308
330
|
);
|
|
309
331
|
|
|
310
332
|
/**
|
package/src/Entity.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { DXN, ObjectId } from '@dxos/keys';
|
|
|
9
9
|
|
|
10
10
|
import * as internal from './internal';
|
|
11
11
|
import type * as Relation from './Relation';
|
|
12
|
+
import type * as Type from './Type';
|
|
12
13
|
|
|
13
14
|
// Re-export KindId and SnapshotKindId from internal.
|
|
14
15
|
export const KindId = internal.KindId;
|
|
@@ -131,6 +132,12 @@ export const getDXN = (entity: Unknown | Snapshot): DXN => internal.getDXN(entit
|
|
|
131
132
|
*/
|
|
132
133
|
export const getTypeDXN = internal.getTypeDXN;
|
|
133
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
|
+
|
|
134
141
|
/**
|
|
135
142
|
* Get the typename of an entity's type.
|
|
136
143
|
*/
|
|
@@ -191,7 +198,7 @@ export const subscribe = (entity: Unknown, callback: () => void): (() => void) =
|
|
|
191
198
|
//
|
|
192
199
|
|
|
193
200
|
/**
|
|
194
|
-
* Used to provide a mutable view of an entity within `Entity.
|
|
201
|
+
* Used to provide a mutable view of an entity within `Entity.update`.
|
|
195
202
|
*/
|
|
196
203
|
export type Mutable<T> = internal.Mutable<T>;
|
|
197
204
|
|
|
@@ -199,7 +206,7 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
199
206
|
* Perform mutations on an entity (object or relation) within a change context.
|
|
200
207
|
*
|
|
201
208
|
* Entities are read-only by default. Mutations are batched and notifications fire
|
|
202
|
-
* when the callback completes. Direct mutations outside of `Entity.
|
|
209
|
+
* when the callback completes. Direct mutations outside of `Entity.update` will throw
|
|
203
210
|
* at runtime.
|
|
204
211
|
*
|
|
205
212
|
* @param entity - The echo entity (object or relation) to mutate.
|
|
@@ -207,30 +214,30 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
207
214
|
*
|
|
208
215
|
* @example
|
|
209
216
|
* ```typescript
|
|
210
|
-
* // Mutate within Entity.
|
|
211
|
-
* Entity.
|
|
217
|
+
* // Mutate within Entity.update
|
|
218
|
+
* Entity.update(entity, (obj) => {
|
|
212
219
|
* obj.name = 'Updated';
|
|
213
220
|
* obj.count = 42;
|
|
214
221
|
* });
|
|
215
222
|
*
|
|
216
223
|
* // Direct mutation throws
|
|
217
|
-
* entity.name = 'Bob'; // Error: Cannot modify outside Entity.
|
|
224
|
+
* entity.name = 'Bob'; // Error: Cannot modify outside Entity.update()
|
|
218
225
|
* ```
|
|
219
226
|
*
|
|
220
|
-
* Note: For type-specific operations, prefer `Obj.
|
|
227
|
+
* Note: For type-specific operations, prefer `Obj.update` or `Relation.update`.
|
|
221
228
|
*/
|
|
222
|
-
export const
|
|
229
|
+
export const update = <T extends Unknown>(entity: T, callback: internal.ChangeCallback<T>): void => {
|
|
223
230
|
internal.change(entity, callback);
|
|
224
231
|
};
|
|
225
232
|
|
|
226
233
|
/**
|
|
227
234
|
* Add a tag to an entity.
|
|
228
|
-
* Must be called within an `Entity.
|
|
235
|
+
* Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
|
|
229
236
|
*/
|
|
230
237
|
export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.addTag(entity, tag);
|
|
231
238
|
|
|
232
239
|
/**
|
|
233
240
|
* Remove a tag from an entity.
|
|
234
|
-
* Must be called within an `Entity.
|
|
241
|
+
* Must be called within an `Entity.update`, `Obj.update`, or `Relation.update` callback.
|
|
235
242
|
*/
|
|
236
243
|
export const removeTag = (entity: Mutable<Unknown>, tag: string): void => internal.removeTag(entity, tag);
|
package/src/Extension.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface Extension<T> extends Record<
|
|
|
43
43
|
* email: 'john@example.com',
|
|
44
44
|
* });
|
|
45
45
|
*
|
|
46
|
-
* Obj.
|
|
46
|
+
* Obj.update(obj, (obj) => {
|
|
47
47
|
* Extension.set(obj.extensions, ColorExtension, 'red');
|
|
48
48
|
* });
|
|
49
49
|
*
|
|
@@ -103,10 +103,10 @@ export const get: {
|
|
|
103
103
|
/**
|
|
104
104
|
* Set the value of an extension in a set of values.
|
|
105
105
|
*
|
|
106
|
-
* Can also be used within Obj.
|
|
106
|
+
* Can also be used within Obj.update callback:
|
|
107
107
|
*
|
|
108
108
|
* ```ts
|
|
109
|
-
* Obj.
|
|
109
|
+
* Obj.update(obj, (obj) => {
|
|
110
110
|
* Extension.set(obj.extensions, ColorExtension, 'red');
|
|
111
111
|
* });
|
|
112
112
|
* ```
|
package/src/Feed.ts
CHANGED
|
@@ -80,6 +80,16 @@ export interface RetentionOptions {
|
|
|
80
80
|
cursor?: string;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Sync options for a feed.
|
|
85
|
+
*/
|
|
86
|
+
export interface SyncOptions {
|
|
87
|
+
/** Push local changes to the server. Defaults to true. */
|
|
88
|
+
shouldPush?: boolean;
|
|
89
|
+
/** Pull remote changes from the server. Defaults to true. */
|
|
90
|
+
shouldPull?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
//
|
|
84
94
|
// Factory
|
|
85
95
|
//
|
|
@@ -139,6 +149,11 @@ export class FeedService extends Context.Tag('@dxos/echo/Feed/FeedService')<
|
|
|
139
149
|
<Q extends Query.Any>(feed: Feed, query: Q): QueryResult.QueryResult<Query.Type<Q>>;
|
|
140
150
|
<F extends Filter.Any>(feed: Feed, filter: F): QueryResult.QueryResult<Filter.Type<F>>;
|
|
141
151
|
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Syncs the feed with the server.
|
|
155
|
+
*/
|
|
156
|
+
sync(feed: Feed, options?: SyncOptions): Promise<void>;
|
|
142
157
|
}
|
|
143
158
|
>() {}
|
|
144
159
|
|
|
@@ -166,8 +181,30 @@ export const notAvailable: Layer.Layer<FeedService> = Layer.succeed(FeedService,
|
|
|
166
181
|
query: () => {
|
|
167
182
|
throw new Error('Feed.FeedService not available');
|
|
168
183
|
},
|
|
184
|
+
sync: () => {
|
|
185
|
+
throw new Error('Feed.FeedService not available');
|
|
186
|
+
},
|
|
169
187
|
} as Context.Tag.Service<FeedService>);
|
|
170
188
|
|
|
189
|
+
//
|
|
190
|
+
// Context (per-call) service
|
|
191
|
+
//
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Effect service exposing a single `Feed.Feed` as the "current" feed for a call site.
|
|
195
|
+
*
|
|
196
|
+
* @deprecated Prefer threading a `Feed.Feed` explicitly through function signatures
|
|
197
|
+
* over hiding it behind a context service.
|
|
198
|
+
*/
|
|
199
|
+
export class ContextFeedService extends Context.Tag('@dxos/echo/Feed/ContextFeedService')<
|
|
200
|
+
ContextFeedService,
|
|
201
|
+
{
|
|
202
|
+
readonly feed: Feed;
|
|
203
|
+
}
|
|
204
|
+
>() {
|
|
205
|
+
static layer = (feed: Feed): Layer.Layer<ContextFeedService> => Layer.succeed(ContextFeedService, { feed });
|
|
206
|
+
}
|
|
207
|
+
|
|
171
208
|
//
|
|
172
209
|
// Operations
|
|
173
210
|
//
|
|
@@ -241,6 +278,21 @@ export const runQuery: {
|
|
|
241
278
|
} = (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>
|
|
242
279
|
query(feed, queryOrFilter as any).pipe(Effect.flatMap((queryResult) => Effect.promise(() => queryResult.run())));
|
|
243
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Syncs the feed with the server.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* yield* Feed.sync(feed);
|
|
287
|
+
* yield* Feed.sync(feed, { shouldPush: false });
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export const sync = (feed: Feed, options?: SyncOptions): Effect.Effect<void, never, FeedService> =>
|
|
291
|
+
Effect.gen(function* () {
|
|
292
|
+
const service = yield* FeedService;
|
|
293
|
+
yield* Effect.promise(() => service.sync(feed, options));
|
|
294
|
+
});
|
|
295
|
+
|
|
244
296
|
/**
|
|
245
297
|
* Creates a cursor for iterating over feed items.
|
|
246
298
|
* Currently stubbed — cursor operations are not yet implemented.
|
|
@@ -271,13 +323,13 @@ export const nextOption = <T = Obj.Snapshot>(_cursor: Cursor<T>): Effect.Effect<
|
|
|
271
323
|
|
|
272
324
|
/**
|
|
273
325
|
* Sets the local retention policy for a feed.
|
|
274
|
-
* Currently stubbed —
|
|
326
|
+
* Currently stubbed — feeds do not yet support retention.
|
|
275
327
|
*
|
|
276
328
|
* @example
|
|
277
329
|
* ```ts
|
|
278
330
|
* yield* Feed.setRetention(feed, { count: 1000 });
|
|
279
331
|
* ```
|
|
280
332
|
*/
|
|
281
|
-
// TODO(
|
|
333
|
+
// TODO(dmaretskyi): Implement when feed retention is supported.
|
|
282
334
|
export const setRetention = (_feed: Feed, _options: RetentionOptions): Effect.Effect<void, never, FeedService> =>
|
|
283
335
|
Effect.void;
|
package/src/Filter.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { assertArgument } from '@dxos/invariant';
|
|
|
14
14
|
import { DXN, ObjectId } from '@dxos/keys';
|
|
15
15
|
|
|
16
16
|
import * as internal from './internal';
|
|
17
|
+
import type * as Obj from './Obj';
|
|
17
18
|
import * as Ref from './Ref';
|
|
18
19
|
|
|
19
20
|
export interface Filter<T> {
|
|
@@ -157,6 +158,37 @@ export const tag = (tag: string): Any => {
|
|
|
157
158
|
});
|
|
158
159
|
};
|
|
159
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Options for {@link key} filter.
|
|
163
|
+
*/
|
|
164
|
+
export type KeyFilterOptions = {
|
|
165
|
+
/**
|
|
166
|
+
* Optional semver range expression (e.g. `^1.2.3`, `~2.0.0`, `>=1.0.0 <2.0.0`).
|
|
167
|
+
* Matches the object's meta `version` field against the range.
|
|
168
|
+
* If omitted, matches any version (including objects with no version).
|
|
169
|
+
*/
|
|
170
|
+
version?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Filter by registry key stored in object meta.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* Filter.key('org.example.type.foo');
|
|
179
|
+
* Filter.key('org.example.type.foo', { version: '^1.2.3' });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export const key = (key: string, options?: KeyFilterOptions): Any => {
|
|
183
|
+
return new FilterClass({
|
|
184
|
+
type: 'object',
|
|
185
|
+
typename: null,
|
|
186
|
+
props: {},
|
|
187
|
+
metaKey: key,
|
|
188
|
+
metaVersion: options?.version,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
160
192
|
/**
|
|
161
193
|
* Filter by properties.
|
|
162
194
|
*/
|
|
@@ -338,6 +370,35 @@ export const updated = (range: TimeRange): Any => _timeRangeFilter('updatedAt',
|
|
|
338
370
|
*/
|
|
339
371
|
export const created = (range: TimeRange): Any => _timeRangeFilter('createdAt', range);
|
|
340
372
|
|
|
373
|
+
export type ChildOfOptions = {
|
|
374
|
+
/** Whether to match transitively (grandchildren, etc.). Defaults to true. */
|
|
375
|
+
transitive?: boolean;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Filter objects that are children of the specified parent(s).
|
|
380
|
+
* Accepts ECHO objects, Refs, or arrays of either.
|
|
381
|
+
* Refs are resolved to DXNs without loading; objects use {@link Obj.getDXN}.
|
|
382
|
+
* With transitive=true (default), also matches grandchildren and beyond.
|
|
383
|
+
*/
|
|
384
|
+
export const childOf = (
|
|
385
|
+
parents: Obj.Unknown | Ref.Unknown | readonly (Obj.Unknown | Ref.Unknown)[],
|
|
386
|
+
options?: ChildOfOptions,
|
|
387
|
+
): Any => {
|
|
388
|
+
const items = Array.isArray(parents) ? parents : [parents];
|
|
389
|
+
const dxns = items.map((item) => {
|
|
390
|
+
if (Ref.isRef(item)) {
|
|
391
|
+
return item.dxn.toString();
|
|
392
|
+
}
|
|
393
|
+
return internal.getDXN(item).toString();
|
|
394
|
+
});
|
|
395
|
+
return new FilterClass({
|
|
396
|
+
type: 'child-of',
|
|
397
|
+
parents: dxns,
|
|
398
|
+
transitive: options?.transitive ?? true,
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
|
|
341
402
|
/**
|
|
342
403
|
* Negate the filter.
|
|
343
404
|
*/
|
package/src/Hypergraph.ts
CHANGED
|
@@ -22,10 +22,10 @@ export interface RefResolutionContext {
|
|
|
22
22
|
space?: Key.SpaceId;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* This
|
|
25
|
+
* Feed that the resolution is happening from (as the underlying queue DXN).
|
|
26
|
+
* This feed will be searched first, and then the space it belongs to.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
feed?: DXN;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface RefResolverOptions {
|
package/src/Json.test.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { DXN, ObjectId } from '@dxos/keys';
|
|
8
|
+
import { safeStringify } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import * as Database from './Database';
|
|
11
|
+
import * as Json from './Json';
|
|
12
|
+
|
|
13
|
+
/** Mint a random ECHO object id usable as both a stub-db key and a DXN payload. */
|
|
14
|
+
const newId = (): string => ObjectId.random();
|
|
15
|
+
|
|
16
|
+
/** Build a fake encoded ref for a local-space object id. */
|
|
17
|
+
const encodeRef = (id: string): { '/': string } => ({ '/': DXN.fromLocalObjectId(id).toString() });
|
|
18
|
+
|
|
19
|
+
/** Minimal stub: `createRefReplacer` only touches `db.getObjectById`. */
|
|
20
|
+
const makeStubDb = (objects: Record<string, unknown>): Database.Database => {
|
|
21
|
+
return {
|
|
22
|
+
getObjectById: (id: string) => objects[id],
|
|
23
|
+
} as unknown as Database.Database;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run a value through the replacer's public contract — `JSON.stringify(value, replacer)` —
|
|
28
|
+
* then re-parse the result so tests can assert on plain JS shapes. This mirrors the way
|
|
29
|
+
* the JSON highlighter invokes the replacer in production.
|
|
30
|
+
*/
|
|
31
|
+
const stringifyWith = (replacer: Json.JsonReplacer, value: unknown): unknown =>
|
|
32
|
+
JSON.parse(JSON.stringify(value, replacer));
|
|
33
|
+
|
|
34
|
+
describe('createRefReplacer', () => {
|
|
35
|
+
test('passes plain values through unchanged', () => {
|
|
36
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
37
|
+
const subject = { a: 1, b: 'two', c: [3, { d: 4 }] };
|
|
38
|
+
expect(stringifyWith(replacer, subject)).toEqual(subject);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('inlines refs at default depth (1)', () => {
|
|
42
|
+
const id = newId();
|
|
43
|
+
const target = { name: 'inlined' };
|
|
44
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }) });
|
|
45
|
+
const subject = { ref: encodeRef(id) };
|
|
46
|
+
expect(stringifyWith(replacer, subject)).toEqual({ ref: target });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('does not follow refs when depth is 0', () => {
|
|
50
|
+
const id = newId();
|
|
51
|
+
const target = { name: 'inlined' };
|
|
52
|
+
const ref = encodeRef(id);
|
|
53
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }), depth: 0 });
|
|
54
|
+
expect(stringifyWith(replacer, { ref })).toEqual({ ref });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('inlines refs across multiple levels up to depth', () => {
|
|
58
|
+
const innerId = newId();
|
|
59
|
+
const middleId = newId();
|
|
60
|
+
const inner = { name: 'inner' };
|
|
61
|
+
const middle = { ref: encodeRef(innerId) };
|
|
62
|
+
const outer = { ref: encodeRef(middleId) };
|
|
63
|
+
const db = makeStubDb({ [innerId]: inner, [middleId]: middle });
|
|
64
|
+
|
|
65
|
+
expect(stringifyWith(Json.createRefReplacer({ db, depth: 1 }), outer)).toEqual({
|
|
66
|
+
ref: { ref: encodeRef(innerId) },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(stringifyWith(Json.createRefReplacer({ db, depth: 2 }), outer)).toEqual({
|
|
70
|
+
ref: { ref: inner },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('leaves refs encoded when the target is missing in the db', () => {
|
|
75
|
+
const ref = encodeRef(newId());
|
|
76
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
77
|
+
expect(stringifyWith(replacer, { ref })).toEqual({ ref });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('leaves non-DXN single-key { "/": string } objects untouched', () => {
|
|
81
|
+
// Same `{ '/': string }` shape is used by other IPLD-style refs (e.g. CIDs); those should
|
|
82
|
+
// not crash the replacer and should pass through verbatim.
|
|
83
|
+
const cidLike = { '/': 'bafybeibwzifw7izxykxz' };
|
|
84
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
85
|
+
expect(stringifyWith(replacer, { ref: cidLike })).toEqual({ ref: cidLike });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('leaves malformed dxn strings untouched', () => {
|
|
89
|
+
const malformed = { '/': 'dxn:not-a-real-dxn' };
|
|
90
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
91
|
+
expect(stringifyWith(replacer, { ref: malformed })).toEqual({ ref: malformed });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('leaves non-echo dxns untouched (e.g. type DXN)', () => {
|
|
95
|
+
// Type DXNs share the `dxn:` prefix but `asEchoDXN()` returns undefined.
|
|
96
|
+
const typeRef = { '/': DXN.fromTypename('com.example.Thing').toString() };
|
|
97
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
98
|
+
expect(stringifyWith(replacer, { ref: typeRef })).toEqual({ ref: typeRef });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('inlines refs inside arrays', () => {
|
|
102
|
+
const idA = newId();
|
|
103
|
+
const idB = newId();
|
|
104
|
+
const a = { name: 'a' };
|
|
105
|
+
const b = { name: 'b' };
|
|
106
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [idA]: a, [idB]: b }) });
|
|
107
|
+
expect(stringifyWith(replacer, { items: [encodeRef(idA), encodeRef(idB), { plain: true }] })).toEqual({
|
|
108
|
+
items: [a, b, { plain: true }],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('walks nested objects recursively', () => {
|
|
113
|
+
const innerId = newId();
|
|
114
|
+
const inner = { name: 'inner' };
|
|
115
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }) });
|
|
116
|
+
const subject = { outer: { mid: { ref: encodeRef(innerId) } } };
|
|
117
|
+
expect(stringifyWith(replacer, subject)).toEqual({ outer: { mid: { ref: inner } } });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a single replacer invocation does not recurse on its own', () => {
|
|
121
|
+
// The replacer is per-call; JSON.stringify drives the tree walk. Calling the replacer
|
|
122
|
+
// directly on a cyclic input must therefore return without touching the cycle.
|
|
123
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
124
|
+
const node: any = { name: 'self' };
|
|
125
|
+
node.self = node;
|
|
126
|
+
|
|
127
|
+
expect(() => replacer('', node)).not.toThrow();
|
|
128
|
+
expect(replacer('', node)).toBe(node);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('invokes `toJSON` on resolved targets so refs in the target are re-walked', () => {
|
|
132
|
+
// Simulates the ECHO-object branch: `db.getObjectById` returns a live proxy, the replacer
|
|
133
|
+
// calls `.toJSON()` to get the encoded form, then continues walking that form. A ref nested
|
|
134
|
+
// inside the target should be inlined when there's depth budget remaining.
|
|
135
|
+
const outerId = newId();
|
|
136
|
+
const innerId = newId();
|
|
137
|
+
const inner = { name: 'inner' };
|
|
138
|
+
const target = {
|
|
139
|
+
toJSON: () => ({ nestedRef: encodeRef(innerId) }),
|
|
140
|
+
};
|
|
141
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [outerId]: target, [innerId]: inner }), depth: 2 });
|
|
142
|
+
expect(stringifyWith(replacer, { ref: encodeRef(outerId) })).toEqual({
|
|
143
|
+
ref: { nestedRef: inner },
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('depth budget counts ref hops, not tree depth — a ref deep in a plain tree still resolves', () => {
|
|
148
|
+
// A ref nested under arbitrarily many plain objects is one ref hop from the root, so
|
|
149
|
+
// `depth: 1` resolves it. `depth: 0` leaves it encoded.
|
|
150
|
+
const innerId = newId();
|
|
151
|
+
const inner = { name: 'inner' };
|
|
152
|
+
const subject = { a: { b: { c: { d: { ref: encodeRef(innerId) } } } } };
|
|
153
|
+
|
|
154
|
+
const inlining = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 1 });
|
|
155
|
+
expect(stringifyWith(inlining, subject)).toEqual({ a: { b: { c: { d: { ref: inner } } } } });
|
|
156
|
+
|
|
157
|
+
const passthrough = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 0 });
|
|
158
|
+
expect(stringifyWith(passthrough, subject)).toEqual(subject);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('inlines refs when invoked through safeStringify (production path)', () => {
|
|
162
|
+
// `JsonHighlighter` runs the replacer through `@dxos/util/safeStringify`, whose inner
|
|
163
|
+
// wrapper short-circuits the root call without forwarding it to the user's filter. The
|
|
164
|
+
// replacer must therefore work on a per-call basis — not as a one-shot root tree walk.
|
|
165
|
+
// This regression-tests that integration: the `content` ref must inline.
|
|
166
|
+
const targetId = newId();
|
|
167
|
+
const target = { toJSON: () => ({ name: 'README content' }) };
|
|
168
|
+
const document = { id: '01ABC', name: 'README', content: encodeRef(targetId) };
|
|
169
|
+
|
|
170
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [targetId]: target }) });
|
|
171
|
+
const out = JSON.parse(safeStringify(document, replacer, 0)!);
|
|
172
|
+
|
|
173
|
+
expect(out).toEqual({ id: '01ABC', name: 'README', content: { name: 'README content' } });
|
|
174
|
+
});
|
|
175
|
+
});
|