@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/src/Json.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { DXN } from '@dxos/keys';
|
|
6
|
+
|
|
7
|
+
import * as Database from './Database';
|
|
8
|
+
import * as Obj from './Obj';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `JSON.stringify` replacer signature.
|
|
12
|
+
*
|
|
13
|
+
* Defined here (rather than re-imported from a UI package) so other ECHO-aware utilities can
|
|
14
|
+
* share a stable signature without creating a dependency edge into the UI tree.
|
|
15
|
+
*/
|
|
16
|
+
export type JsonReplacer = (key: string, value: any) => any;
|
|
17
|
+
|
|
18
|
+
export type CreateRefReplacerOptions = {
|
|
19
|
+
db: Database.Database;
|
|
20
|
+
/** How many ref hops to follow. `0` leaves all refs as-is. Default: `1`. */
|
|
21
|
+
depth?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isEncodedRef = (value: unknown): value is { '/': string } =>
|
|
25
|
+
typeof value === 'object' &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
Object.keys(value as object).length === 1 &&
|
|
28
|
+
typeof (value as { '/': unknown })['/'] === 'string';
|
|
29
|
+
|
|
30
|
+
const toJson = (obj: Obj.Any): unknown => (typeof (obj as any).toJSON === 'function' ? (obj as any).toJSON() : obj);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns a {@link JsonReplacer} that inlines ECHO ref objects (`{ "/": "dxn:echo:..." }`) up to
|
|
34
|
+
* `depth` ref hops. Beyond that depth refs are left in their encoded form.
|
|
35
|
+
*
|
|
36
|
+
* Implemented as a per-call `JSON.stringify` replacer (not a one-shot tree walk at root) so it
|
|
37
|
+
* composes with wrappers like `safeStringify` that intercept the root call. JSON.stringify
|
|
38
|
+
* already drives the recursion; we only need to (a) detect a ref at the current callback,
|
|
39
|
+
* (b) resolve and return the target if hop budget remains, and (c) tag the returned object
|
|
40
|
+
* with its hop count so children know how far in they are.
|
|
41
|
+
*
|
|
42
|
+
* The hop count is tracked per-object via a `WeakMap`: a ref-resolved target's children inherit
|
|
43
|
+
* `parentHops + 1`; a regular intermediate object's children inherit `parentHops`. This makes the
|
|
44
|
+
* budget count *ref hops*, not tree depth — a ref deep in a tree still resolves once when
|
|
45
|
+
* `depth >= 1`.
|
|
46
|
+
*
|
|
47
|
+
* Note: ECHO objects' `toJSON` runs before the replacer is invoked, so by the time we see a
|
|
48
|
+
* value refs are already encoded as `{ "/": "dxn:..." }`.
|
|
49
|
+
*/
|
|
50
|
+
export const createRefReplacer = ({ db, depth = 1 }: CreateRefReplacerOptions): JsonReplacer => {
|
|
51
|
+
// Per-object hop count. Set when we return an object (via ref resolution or pass-through) so
|
|
52
|
+
// the child callbacks (which carry that object as `this`) can read it.
|
|
53
|
+
const hops = new WeakMap<object, number>();
|
|
54
|
+
|
|
55
|
+
return function (this: any, key: string, value: any) {
|
|
56
|
+
// Hop count for this call: hops at the parent, or 0 for the root.
|
|
57
|
+
const parentHops = this && typeof this === 'object' ? (hops.get(this) ?? 0) : 0;
|
|
58
|
+
if (isEncodedRef(value)) {
|
|
59
|
+
if (parentHops >= depth) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The `{ '/': string }` shape is shared with non-DXN IPLD-style refs (e.g. CIDs);
|
|
64
|
+
// an unparseable string would otherwise crash the whole `JSON.stringify`.
|
|
65
|
+
// Treat any parse miss as "leave as-is" rather than propagating.
|
|
66
|
+
const dxnString = value['/'];
|
|
67
|
+
if (!dxnString.startsWith('dxn:')) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let echoId: string | undefined;
|
|
72
|
+
try {
|
|
73
|
+
echoId = DXN.parse(dxnString).asEchoDXN()?.echoId;
|
|
74
|
+
} catch {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!echoId) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
const target = db.getObjectById(echoId);
|
|
82
|
+
if (!target) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const encoded = toJson(target);
|
|
87
|
+
if (encoded != null && typeof encoded === 'object') {
|
|
88
|
+
// Children of the resolved target are one hop deeper.
|
|
89
|
+
hops.set(encoded as object, parentHops + 1);
|
|
90
|
+
}
|
|
91
|
+
return encoded;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Pass-through object: children inherit the parent's hop count (this branch doesn't burn
|
|
95
|
+
// budget).
|
|
96
|
+
if (value != null && typeof value === 'object') {
|
|
97
|
+
hops.set(value, parentHops);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return value;
|
|
101
|
+
};
|
|
102
|
+
};
|
package/src/Migration.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
|
+
|
|
9
|
+
import { type DXN } from '@dxos/keys';
|
|
10
|
+
|
|
11
|
+
import type * as Database from './Database';
|
|
12
|
+
import type * as Entity from './Entity';
|
|
13
|
+
import { MetaId, type ObjectMeta, getSchemaDXN } from './internal';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result returned by a migration's `transform` callback.
|
|
17
|
+
* The data shape matches the target schema; the optional `[Obj.Meta]` symbol key lets the
|
|
18
|
+
* transform update the object's meta (e.g. `key` / `version`) atomically with the data swap.
|
|
19
|
+
*/
|
|
20
|
+
export type TransformResult<To extends Schema.Schema.AnyNoContext> = Omit<
|
|
21
|
+
Schema.Schema.Type<To>,
|
|
22
|
+
'id' | Entity.KindId
|
|
23
|
+
> & {
|
|
24
|
+
[MetaId]?: Partial<ObjectMeta>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type DefineObjectMigrationOptions<From extends Schema.Schema.AnyNoContext, To extends Schema.Schema.AnyNoContext> = {
|
|
28
|
+
from: From;
|
|
29
|
+
to: To;
|
|
30
|
+
/**
|
|
31
|
+
* Pure function that converts the old object data to the new object data.
|
|
32
|
+
*
|
|
33
|
+
* The returned object may include an optional `[Obj.Meta]` entry to update the object's meta
|
|
34
|
+
* (e.g. registry `key` / `version`) atomically with the data swap.
|
|
35
|
+
*/
|
|
36
|
+
// TODO(dmaretskyi): `id` should not be a part of the schema.
|
|
37
|
+
transform: (from: Schema.Schema.Type<From>, context: ObjectMigrationContext) => Promise<TransformResult<To>>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Callback that is called after the object is migrated. Called for every object that is migrated.
|
|
41
|
+
*
|
|
42
|
+
* NOTE: Database mutations performed in this callback are not guaranteed to be idempotent.
|
|
43
|
+
* If multiple peers run the migration separately, the effects may be applied multiple times.
|
|
44
|
+
*/
|
|
45
|
+
onMigration?: (params: OnMigrateProps<From, To>) => Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Context passed to object migration callbacks.
|
|
50
|
+
*/
|
|
51
|
+
export type ObjectMigrationContext = {
|
|
52
|
+
db: Database.Database;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type OnMigrateProps<From extends Schema.Schema.AnyNoContext, To extends Schema.Schema.AnyNoContext> = {
|
|
56
|
+
before: Schema.Schema.Type<From>;
|
|
57
|
+
object: Schema.Schema.Type<To>;
|
|
58
|
+
db: Database.Database;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Definition of a migration from one object schema version to another.
|
|
63
|
+
*/
|
|
64
|
+
export type ObjectMigration = {
|
|
65
|
+
fromType: DXN;
|
|
66
|
+
toType: DXN;
|
|
67
|
+
fromSchema: Schema.Schema.AnyNoContext;
|
|
68
|
+
toSchema: Schema.Schema.AnyNoContext;
|
|
69
|
+
transform: (from: unknown, context: ObjectMigrationContext) => Promise<unknown>;
|
|
70
|
+
onMigration?: (params: OnMigrateProps<any, any>) => Promise<void>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Define a migration between two object schemas.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const migration = Migration.define({
|
|
79
|
+
* from: ContactV1,
|
|
80
|
+
* to: ContactV2,
|
|
81
|
+
* transform: async (from) => ({ name: `${from.firstName} ${from.lastName}` }),
|
|
82
|
+
* onMigration: async () => {},
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const define = <From extends Schema.Schema.AnyNoContext, To extends Schema.Schema.AnyNoContext>(
|
|
87
|
+
options: DefineObjectMigrationOptions<From, To>,
|
|
88
|
+
): ObjectMigration => {
|
|
89
|
+
const fromType = getSchemaDXN(options.from);
|
|
90
|
+
if (!fromType) {
|
|
91
|
+
throw new Error('Invalid from schema');
|
|
92
|
+
}
|
|
93
|
+
const toType = getSchemaDXN(options.to);
|
|
94
|
+
if (!toType) {
|
|
95
|
+
throw new Error('Invalid to schema');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
fromType,
|
|
100
|
+
toType,
|
|
101
|
+
fromSchema: options.from,
|
|
102
|
+
toSchema: options.to,
|
|
103
|
+
transform: options.transform as any,
|
|
104
|
+
onMigration: options.onMigration as any,
|
|
105
|
+
};
|
|
106
|
+
};
|
package/src/Obj.test.ts
CHANGED
|
@@ -117,7 +117,7 @@ describe('Obj', () => {
|
|
|
117
117
|
|
|
118
118
|
test('returns keys from reactive object and snapshot', ({ expect }) => {
|
|
119
119
|
const obj = Obj.make(TestSchema.Person, { name: 'Test' });
|
|
120
|
-
Obj.
|
|
120
|
+
Obj.update(obj, (obj) => {
|
|
121
121
|
const meta = Obj.getMeta(obj);
|
|
122
122
|
meta.keys.push({ source: SOURCE, id: 'key-1' });
|
|
123
123
|
meta.keys.push({ source: SOURCE, id: 'key-2' });
|
|
@@ -166,8 +166,8 @@ describe('Obj', () => {
|
|
|
166
166
|
|
|
167
167
|
const cloned = Obj.clone(original);
|
|
168
168
|
|
|
169
|
-
Obj.
|
|
170
|
-
|
|
169
|
+
Obj.update(original, (original) => {
|
|
170
|
+
original.name = 'Bob';
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
expect(original.name).toBe('Bob');
|
|
@@ -206,8 +206,8 @@ describe('Obj', () => {
|
|
|
206
206
|
expect(cloned.employer?.target).toBe(person.employer?.target);
|
|
207
207
|
|
|
208
208
|
// Modifying the referenced object affects both
|
|
209
|
-
Obj.
|
|
210
|
-
|
|
209
|
+
Obj.update(employer, (employer) => {
|
|
210
|
+
employer.name = 'Updated DXOS';
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
expect(cloned.employer?.target?.name).toBe('Updated DXOS');
|
|
@@ -234,8 +234,8 @@ describe('Obj', () => {
|
|
|
234
234
|
expect(cloned.employer?.target?.name).toBe(employer.name);
|
|
235
235
|
|
|
236
236
|
// Modifying the original referenced object does not affect the clone
|
|
237
|
-
Obj.
|
|
238
|
-
|
|
237
|
+
Obj.update(employer, (employer) => {
|
|
238
|
+
employer.name = 'Updated DXOS';
|
|
239
239
|
});
|
|
240
240
|
|
|
241
241
|
expect(cloned.employer?.target?.name).toBe('DXOS');
|
|
@@ -333,8 +333,8 @@ describe('Obj', () => {
|
|
|
333
333
|
expect(cloned.address?.coordinates.lng).toBe(-122.4194);
|
|
334
334
|
|
|
335
335
|
// Modifying nested properties should be independent
|
|
336
|
-
Obj.
|
|
337
|
-
|
|
336
|
+
Obj.update(person, (person) => {
|
|
337
|
+
person.address!.city = 'New York';
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
expect(cloned.address?.city).toBe('San Francisco');
|
|
@@ -366,8 +366,8 @@ describe('Obj', () => {
|
|
|
366
366
|
expect(cloned.tasks?.[2]?.target).not.toBe(task3);
|
|
367
367
|
|
|
368
368
|
// Modifying original tasks should not affect cloned ones
|
|
369
|
-
Obj.
|
|
370
|
-
|
|
369
|
+
Obj.update(task1, (task1) => {
|
|
370
|
+
task1.title = 'Updated Task 1';
|
|
371
371
|
});
|
|
372
372
|
|
|
373
373
|
expect(cloned.tasks?.[0]?.target?.title).toBe('Task 1');
|
|
@@ -449,8 +449,8 @@ describe('Obj', () => {
|
|
|
449
449
|
test('returns false when values already match', () => {
|
|
450
450
|
const target = Obj.make(TestSchema.Organization, { name: 'Acme', properties: { region: 'EU' } });
|
|
451
451
|
const source = Obj.make(TestSchema.Organization, { name: 'Acme', properties: { region: 'EU' } });
|
|
452
|
-
Obj.
|
|
453
|
-
expect(Obj.updateFrom(
|
|
452
|
+
Obj.update(target, (target) => {
|
|
453
|
+
expect(Obj.updateFrom(target, source)).toBe(false);
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
456
|
|
|
@@ -463,8 +463,8 @@ describe('Obj', () => {
|
|
|
463
463
|
name: 'New',
|
|
464
464
|
properties: { a: '2', b: '3' },
|
|
465
465
|
});
|
|
466
|
-
Obj.
|
|
467
|
-
expect(Obj.updateFrom(
|
|
466
|
+
Obj.update(target, (target) => {
|
|
467
|
+
expect(Obj.updateFrom(target, source)).toBe(true);
|
|
468
468
|
});
|
|
469
469
|
expect(target.name).toBe('New');
|
|
470
470
|
expect(target.properties).toEqual({ a: '2', b: '3' });
|
|
@@ -487,8 +487,8 @@ describe('Obj', () => {
|
|
|
487
487
|
employer: Ref.make(orgB),
|
|
488
488
|
address: { city: 'Portland', state: 'OR', zip: '97201', coordinates: { lat: 45.5, lng: -122.6 } },
|
|
489
489
|
});
|
|
490
|
-
Obj.
|
|
491
|
-
expect(Obj.updateFrom(
|
|
490
|
+
Obj.update(target, (target) => {
|
|
491
|
+
expect(Obj.updateFrom(target, source)).toBe(true);
|
|
492
492
|
});
|
|
493
493
|
expect(target.employer?.dxn.toString()).toBe(Ref.make(orgB).dxn.toString());
|
|
494
494
|
expect(target.address?.city).toBe('Portland');
|
|
@@ -510,8 +510,8 @@ describe('Obj', () => {
|
|
|
510
510
|
email: 'bob@x.test',
|
|
511
511
|
tasks: [Ref.make(t1), Ref.make(t3)],
|
|
512
512
|
});
|
|
513
|
-
Obj.
|
|
514
|
-
expect(Obj.updateFrom(
|
|
513
|
+
Obj.update(target, (target) => {
|
|
514
|
+
expect(Obj.updateFrom(target, source)).toBe(true);
|
|
515
515
|
});
|
|
516
516
|
expect(target.tasks?.map((r) => r.dxn.toString())).toEqual(source.tasks?.map((r) => r.dxn.toString()));
|
|
517
517
|
});
|
|
@@ -519,8 +519,8 @@ describe('Obj', () => {
|
|
|
519
519
|
test('respects include option', () => {
|
|
520
520
|
const target = Obj.make(TestSchema.Organization, { name: 'Keep', properties: { x: '1' } });
|
|
521
521
|
const source = Obj.make(TestSchema.Organization, { name: 'Drop', properties: { x: '2' } });
|
|
522
|
-
Obj.
|
|
523
|
-
expect(Obj.updateFrom(
|
|
522
|
+
Obj.update(target, (target) => {
|
|
523
|
+
expect(Obj.updateFrom(target, source, { include: ['properties'] })).toBe(true);
|
|
524
524
|
});
|
|
525
525
|
expect(target.name).toBe('Keep');
|
|
526
526
|
expect(target.properties).toEqual({ x: '2' });
|
|
@@ -529,8 +529,8 @@ describe('Obj', () => {
|
|
|
529
529
|
test('respects exclude option', () => {
|
|
530
530
|
const target = Obj.make(TestSchema.Organization, { name: 'Old', properties: { x: '1' } });
|
|
531
531
|
const source = Obj.make(TestSchema.Organization, { name: 'New', properties: { x: '2' } });
|
|
532
|
-
Obj.
|
|
533
|
-
expect(Obj.updateFrom(
|
|
532
|
+
Obj.update(target, (target) => {
|
|
533
|
+
expect(Obj.updateFrom(target, source, { exclude: ['name'] })).toBe(true);
|
|
534
534
|
});
|
|
535
535
|
expect(target.name).toBe('Old');
|
|
536
536
|
expect(target.properties).toEqual({ x: '2' });
|
package/src/Obj.ts
CHANGED
|
@@ -13,7 +13,7 @@ import * as Utils from 'effect/Utils';
|
|
|
13
13
|
|
|
14
14
|
import type { ForeignKey } from '@dxos/echo-protocol';
|
|
15
15
|
import { createJsonPath } from '@dxos/effect';
|
|
16
|
-
import { assertArgument } from '@dxos/invariant';
|
|
16
|
+
import { assertArgument, invariant } from '@dxos/invariant';
|
|
17
17
|
import { type DXN, ObjectId } from '@dxos/keys';
|
|
18
18
|
import { assumeType, deepMapValues } from '@dxos/util';
|
|
19
19
|
|
|
@@ -304,7 +304,7 @@ export const clone: <T extends Unknown>(obj: T, opts?: CloneOptions) => T = objI
|
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
306
|
* Makes all properties mutable recursively.
|
|
307
|
-
* Used to provide a mutable view of an object within `Obj.
|
|
307
|
+
* Used to provide a mutable view of an object within `Obj.update`.
|
|
308
308
|
*/
|
|
309
309
|
export type Mutable<T> = internal.Mutable<T>;
|
|
310
310
|
|
|
@@ -312,33 +312,33 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
312
312
|
* Perform mutations on an echo object within a controlled context.
|
|
313
313
|
*
|
|
314
314
|
* All mutations within the callback are batched and trigger a single notification
|
|
315
|
-
* when the callback completes. Direct mutations outside of `Obj.
|
|
315
|
+
* when the callback completes. Direct mutations outside of `Obj.update` will throw
|
|
316
316
|
* an error for echo objects.
|
|
317
317
|
*
|
|
318
318
|
* This function also works with nested objects within echo objects (e.g., Template structs)
|
|
319
319
|
* that are reactive at runtime.
|
|
320
320
|
*
|
|
321
|
-
* @param obj - The echo object to mutate. Use `Relation.
|
|
321
|
+
* @param obj - The echo object to mutate. Use `Relation.update` for relations.
|
|
322
322
|
* @param callback - The callback that performs mutations on the object.
|
|
323
323
|
*
|
|
324
324
|
* @example
|
|
325
325
|
* ```ts
|
|
326
326
|
* const person = Obj.make(Person, { name: 'John', age: 25 });
|
|
327
327
|
*
|
|
328
|
-
* // Mutate within Obj.
|
|
329
|
-
* Obj.
|
|
328
|
+
* // Mutate within Obj.update
|
|
329
|
+
* Obj.update(person, (obj) => {
|
|
330
330
|
* obj.name = 'Jane';
|
|
331
331
|
* obj.age = 30;
|
|
332
332
|
* });
|
|
333
333
|
* // ONE notification fires here
|
|
334
334
|
*
|
|
335
335
|
* // Direct mutation throws
|
|
336
|
-
* person.name = 'Bob'; // Error: Cannot modify outside Obj.
|
|
336
|
+
* person.name = 'Bob'; // Error: Cannot modify outside Obj.update()
|
|
337
337
|
* ```
|
|
338
338
|
*
|
|
339
|
-
* Note: Only accepts objects. Use `Relation.
|
|
339
|
+
* Note: Only accepts objects. Use `Relation.update` for relations.
|
|
340
340
|
*/
|
|
341
|
-
export const
|
|
341
|
+
export const update = <T extends Unknown>(obj: T, callback: internal.ChangeCallback<T>): void => {
|
|
342
342
|
internal.change(obj, callback);
|
|
343
343
|
};
|
|
344
344
|
|
|
@@ -373,7 +373,7 @@ export const getValue = (obj: Unknown | Snapshot, path: readonly (string | numbe
|
|
|
373
373
|
* whether to initialize nested data as an empty object or array.
|
|
374
374
|
*
|
|
375
375
|
* Similar to lodash.set and setDeep from @dxos/util, but schema-aware.
|
|
376
|
-
* Must be called within an `Obj.
|
|
376
|
+
* Must be called within an `Obj.update` callback.
|
|
377
377
|
*
|
|
378
378
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
379
379
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -387,7 +387,7 @@ export const getValue = (obj: Unknown | Snapshot, path: readonly (string | numbe
|
|
|
387
387
|
* ```ts
|
|
388
388
|
* const person = Obj.make(Person, { name: 'John' });
|
|
389
389
|
* // Person schema has: addresses: Schema.Array(Address)
|
|
390
|
-
* Obj.
|
|
390
|
+
* Obj.update(person, (obj) => {
|
|
391
391
|
* Obj.setValue(obj, ['addresses', 0, 'street'], '123 Main St');
|
|
392
392
|
* });
|
|
393
393
|
* // Creates: person.addresses = [{ street: '123 Main St' }]
|
|
@@ -470,16 +470,19 @@ export const getDXN = (entity: Unknown | Snapshot): DXN => {
|
|
|
470
470
|
/**
|
|
471
471
|
* @returns The DXN of the object's type.
|
|
472
472
|
* @example dxn:com.example.type.person:1.0.0
|
|
473
|
+
* @throws If the object is missing its type (corrupted object).
|
|
473
474
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
475
|
+
export const getTypeDXN = (obj: Unknown | Snapshot): DXN => {
|
|
476
|
+
const type = internal.getTypeDXN(obj);
|
|
477
|
+
invariant(type != null, 'Corrupted object: missing type.');
|
|
478
|
+
return type;
|
|
479
|
+
};
|
|
476
480
|
|
|
477
481
|
/**
|
|
478
482
|
* Get the schema of the object.
|
|
479
483
|
* Returns the branded ECHO schema used to create the object.
|
|
480
484
|
*/
|
|
481
|
-
|
|
482
|
-
export const getSchema: (obj: unknown | undefined) => Type.AnyEntity | undefined = internal.getSchema as any;
|
|
485
|
+
export const getSchema: (obj: Unknown | Snapshot) => Type.AnyEntity | undefined = internal.getSchema as any;
|
|
483
486
|
|
|
484
487
|
/**
|
|
485
488
|
* @returns The typename of the object's type.
|
|
@@ -517,7 +520,7 @@ export const Meta = internal.MetaId;
|
|
|
517
520
|
export type ReadonlyMeta = internal.ReadonlyMeta;
|
|
518
521
|
|
|
519
522
|
/**
|
|
520
|
-
* Mutable meta type returned by `Obj.getMeta` inside an `Obj.
|
|
523
|
+
* Mutable meta type returned by `Obj.getMeta` inside an `Obj.update` callback.
|
|
521
524
|
*/
|
|
522
525
|
export type Meta = internal.Meta;
|
|
523
526
|
|
|
@@ -525,7 +528,7 @@ export type Meta = internal.Meta;
|
|
|
525
528
|
// TODO(dmaretskyi): Allow returning undefined.
|
|
526
529
|
/**
|
|
527
530
|
* Get the metadata for an object.
|
|
528
|
-
* Returns mutable meta when passed a mutable object (inside `Obj.
|
|
531
|
+
* Returns mutable meta when passed a mutable object (inside `Obj.update` callback).
|
|
529
532
|
* Returns read-only meta when passed a regular object or snapshot.
|
|
530
533
|
*
|
|
531
534
|
* @example
|
|
@@ -534,7 +537,7 @@ export type Meta = internal.Meta;
|
|
|
534
537
|
* const meta = Obj.getMeta(person); // ReadonlyMeta
|
|
535
538
|
*
|
|
536
539
|
* // Mutable access inside change callback
|
|
537
|
-
* Obj.
|
|
540
|
+
* Obj.update(person, (obj) => {
|
|
538
541
|
* const meta = Obj.getMeta(obj); // ObjectMeta (mutable)
|
|
539
542
|
* meta.tags ??= [];
|
|
540
543
|
* meta.tags.push('important');
|
|
@@ -559,7 +562,7 @@ export const getKeys: {
|
|
|
559
562
|
|
|
560
563
|
/**
|
|
561
564
|
* Delete all keys from the object for the specified source.
|
|
562
|
-
* Must be called within an `Obj.
|
|
565
|
+
* Must be called within an `Obj.update` callback.
|
|
563
566
|
*
|
|
564
567
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
565
568
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -568,7 +571,7 @@ export const deleteKeys = (entity: Mutable<Unknown>, source: string): void => in
|
|
|
568
571
|
|
|
569
572
|
/**
|
|
570
573
|
* Add a tag to the object.
|
|
571
|
-
* Must be called within an `Obj.
|
|
574
|
+
* Must be called within an `Obj.update` callback.
|
|
572
575
|
*
|
|
573
576
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
574
577
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -577,7 +580,7 @@ export const addTag = (entity: Mutable<Unknown>, tag: string): void => internal.
|
|
|
577
580
|
|
|
578
581
|
/**
|
|
579
582
|
* Remove a tag from the object.
|
|
580
|
-
* Must be called within an `Obj.
|
|
583
|
+
* Must be called within an `Obj.update` callback.
|
|
581
584
|
*
|
|
582
585
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
583
586
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -603,7 +606,7 @@ export const getLabel = (entity: Unknown | Snapshot): string | undefined => inte
|
|
|
603
606
|
|
|
604
607
|
/**
|
|
605
608
|
* Set the label of the object.
|
|
606
|
-
* Must be called within an `Obj.
|
|
609
|
+
* Must be called within an `Obj.update` callback.
|
|
607
610
|
*
|
|
608
611
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
609
612
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -618,7 +621,7 @@ export const getDescription = (entity: Unknown | Snapshot): string | undefined =
|
|
|
618
621
|
|
|
619
622
|
/**
|
|
620
623
|
* Set the description of the object.
|
|
621
|
-
* Must be called within an `Obj.
|
|
624
|
+
* Must be called within an `Obj.update` callback.
|
|
622
625
|
*
|
|
623
626
|
* NOTE: TypeScript's structural typing allows readonly objects to be passed to `Mutable<T>`
|
|
624
627
|
* parameters, so there is no compile-time error. Enforcement is runtime-only.
|
|
@@ -741,7 +744,7 @@ const prepareAssignValue = (value: unknown): unknown =>
|
|
|
741
744
|
* References are compared by target DXN; other values use Effect `Equal.equals` inside a structural region,
|
|
742
745
|
* with recursive comparison for arrays and plain object-shaped property bags (excluding `id`).
|
|
743
746
|
*
|
|
744
|
-
* Must be called within an `Obj.
|
|
747
|
+
* Must be called within an `Obj.update` callback.
|
|
745
748
|
*
|
|
746
749
|
* @returns Whether any property was updated.
|
|
747
750
|
*/
|
|
@@ -808,7 +811,7 @@ export const toJSON = (entity: Unknown | Snapshot): JSON => objInternal.objectTo
|
|
|
808
811
|
*/
|
|
809
812
|
export const fromJSON: (
|
|
810
813
|
json: unknown,
|
|
811
|
-
options?: { refResolver?: Ref.Resolver; dxn?: DXN; database?: Database.Database },
|
|
814
|
+
options?: { refResolver?: Ref.Resolver; dxn?: DXN; database?: Database.Database; parent?: Unknown },
|
|
812
815
|
) => Promise<Unknown> = objInternal.objectFromJSON as any;
|
|
813
816
|
|
|
814
817
|
/**
|