@dxos/echo 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/neutral/Annotation.mjs +22 -8
- package/dist/lib/neutral/Database.mjs +3 -3
- package/dist/lib/neutral/Entity.mjs +11 -11
- package/dist/lib/neutral/Err.mjs +7 -3
- package/dist/lib/neutral/Feed.mjs +15 -19
- package/dist/lib/neutral/Filter.mjs +13 -11
- package/dist/lib/neutral/Format.mjs +3 -7
- package/dist/lib/neutral/JsonSchema.mjs +7 -7
- package/dist/lib/neutral/Migration.mjs +11 -11
- package/dist/lib/neutral/Obj.mjs +15 -13
- package/dist/lib/neutral/Order.mjs +1 -1
- package/dist/lib/neutral/Query.mjs +17 -17
- package/dist/lib/neutral/QueryResult.mjs +1 -1
- package/dist/lib/neutral/Ref.mjs +6 -6
- package/dist/lib/neutral/Registry.mjs +1 -1
- package/dist/lib/neutral/Relation.mjs +12 -12
- package/dist/lib/neutral/Tag.mjs +15 -15
- package/dist/lib/neutral/Text.mjs +25 -0
- package/dist/lib/neutral/Text.mjs.map +7 -0
- package/dist/lib/neutral/Type.mjs +10 -10
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs +1394 -0
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-O6BH7EPN.mjs → chunk-2ZHECXUR.mjs} +5 -4
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-AFANXSGV.mjs → chunk-3KTE2Y7Q.mjs} +20 -20
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WZ32BF53.mjs → chunk-3TN2VMI4.mjs} +49 -37
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-E7IOTOZD.mjs → chunk-42L4HJ3Z.mjs} +11 -15
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-K3S7D5PR.mjs → chunk-4A2NWKRJ.mjs} +5 -5
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +1531 -0
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-Z3WMRRAZ.mjs → chunk-BEFR7LEA.mjs} +11 -9
- package/dist/lib/neutral/{chunk-Z3WMRRAZ.mjs.map → chunk-BEFR7LEA.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs → chunk-BXIKXT7Q.mjs} +2 -6
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs.map → chunk-BXIKXT7Q.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-3JXQUMUI.mjs → chunk-C57XMMBY.mjs} +21 -20
- package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-JZ6LFQU5.mjs → chunk-C62JYFAD.mjs} +82 -8
- package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TF5BSLF6.mjs → chunk-FSGX2R62.mjs} +3 -3
- package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6CEISA3V.mjs → chunk-HBXT3GSB.mjs} +29 -15
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WU3GIANS.mjs → chunk-IQR45HAD.mjs} +1 -1
- package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TFEWTY5A.mjs → chunk-KCBW4BR6.mjs} +4 -8
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-4TPQZPLY.mjs → chunk-L3E5IXAE.mjs} +198 -30
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-EVK6XBXO.mjs → chunk-PRC4FEKJ.mjs} +2 -2
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-NOL7O3BL.mjs → chunk-SCT6FFCR.mjs} +9 -4
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BBFJWWAV.mjs → chunk-TOCVVFK2.mjs} +26 -2
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-A53NTBHE.mjs → chunk-U53LSQQA.mjs} +3 -3
- package/dist/lib/neutral/{chunk-A53NTBHE.mjs.map → chunk-U53LSQQA.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-PLDMJJJC.mjs → chunk-UGFFV2FR.mjs} +2 -2
- package/dist/lib/neutral/{chunk-35INCYOE.mjs → chunk-UWLP4RFM.mjs} +1 -1
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SZ5WB6HF.mjs → chunk-UZHCQ6NI.mjs} +13 -11
- package/dist/lib/neutral/{chunk-SZ5WB6HF.mjs.map → chunk-UZHCQ6NI.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-L3OBWBV2.mjs → chunk-WKIT2YY5.mjs} +2 -2
- package/dist/lib/neutral/{chunk-HGWQR5KH.mjs → chunk-XB6ISRAO.mjs} +27 -15
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs +84 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-5GAIWRQB.mjs → chunk-XXWK4J53.mjs} +27 -42
- package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +30 -26
- package/dist/lib/neutral/internal/index.mjs +91 -69
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +79 -60
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/types/src/Annotation.d.ts +35 -1
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Collection.d.ts +9 -8
- package/dist/types/src/Collection.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +16 -0
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +1 -16
- package/dist/types/src/Dataset.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +67 -0
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +56 -80
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +9 -1
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +2 -9
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Migration.d.ts +1 -1
- package/dist/types/src/Migration.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +31 -6
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +1 -1
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +1 -1
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/QueryResult.d.ts +2 -2
- package/dist/types/src/QueryResult.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +8 -4
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Registry.d.ts +1 -1
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Tag.d.ts +11 -13
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Text.d.ts +41 -0
- package/dist/types/src/Text.d.ts.map +1 -0
- package/dist/types/src/Text.test.d.ts +2 -0
- package/dist/types/src/Text.test.d.ts.map +1 -0
- package/dist/types/src/Type.d.ts +33 -20
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +8 -9
- package/dist/types/src/View.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +9 -0
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/atoms.d.ts +14 -0
- package/dist/types/src/internal/Annotation/atoms.d.ts.map +1 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts +13 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/guard.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +1 -1
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/object.d.ts +5 -1
- package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/relation.d.ts +7 -0
- package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/type-uri.d.ts.map +1 -1
- package/dist/types/src/internal/Filter/index.d.ts +2 -0
- package/dist/types/src/internal/Filter/index.d.ts.map +1 -0
- package/dist/types/src/internal/Filter/match.d.ts +22 -0
- package/dist/types/src/internal/Filter/match.d.ts.map +1 -0
- package/dist/types/src/internal/Format/format.d.ts +0 -2
- package/dist/types/src/internal/Format/format.d.ts.map +1 -1
- package/dist/types/src/internal/Format/index.d.ts +2 -2
- package/dist/types/src/internal/Format/index.d.ts.map +1 -1
- package/dist/types/src/internal/Format/string.d.ts +0 -8
- package/dist/types/src/internal/Format/string.d.ts.map +1 -1
- package/dist/types/src/internal/Format/types.d.ts +2 -1
- package/dist/types/src/internal/Format/types.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts +7 -3
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/clone.d.ts +2 -1
- package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/create-object.d.ts +3 -4
- package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Query/index.d.ts +1 -0
- package/dist/types/src/internal/Query/index.d.ts.map +1 -1
- package/dist/types/src/internal/Query/query-result-effect.d.ts +9 -0
- package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/atoms.d.ts +1 -2
- package/dist/types/src/internal/Ref/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +13 -6
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/common/atom-snapshot.d.ts +8 -0
- package/dist/types/src/internal/common/atom-snapshot.d.ts.map +1 -0
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts +12 -0
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts +25 -0
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts +5 -0
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts +17 -0
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/entity.d.ts +4 -4
- package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/meta.d.ts +2 -4
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/src/internal/index.d.ts +1 -0
- package/dist/types/src/internal/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +42 -171
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -13
- package/src/Annotation.test.ts +257 -10
- package/src/Annotation.ts +55 -6
- package/src/Collection.ts +10 -12
- package/src/Database.ts +22 -20
- package/src/Err.ts +22 -0
- package/src/Feed.ts +114 -141
- package/src/Filter.test.ts +38 -0
- package/src/Filter.ts +17 -3
- package/src/Hypergraph.ts +2 -11
- package/src/Migration.ts +1 -1
- package/src/Obj.test.ts +92 -12
- package/src/Obj.ts +36 -5
- package/src/Order.ts +3 -3
- package/src/Query.test.ts +2 -2
- package/src/Query.ts +16 -16
- package/src/QueryResult.ts +2 -2
- package/src/Ref.ts +12 -4
- package/src/Registry.ts +1 -1
- package/src/Relation.ts +1 -1
- package/src/Tag.ts +12 -16
- package/src/Text.test.ts +186 -0
- package/src/Text.ts +116 -0
- package/src/Type.test.ts +52 -24
- package/src/Type.ts +69 -31
- package/src/View.ts +22 -30
- package/src/index.ts +1 -1
- package/src/internal/Annotation/annotations.ts +15 -2
- package/src/internal/Annotation/atoms.ts +107 -0
- package/src/internal/Annotation/entity-dictionary.ts +40 -37
- package/src/internal/Entity/api.ts +1 -1
- package/src/internal/Entity/guard.ts +2 -1
- package/src/internal/Entity/model.ts +2 -2
- package/src/internal/Entity/object.ts +14 -1
- package/src/internal/Entity/relation.ts +19 -0
- package/src/internal/Entity/type-uri.ts +3 -1
- package/src/internal/Filter/index.ts +5 -0
- package/src/internal/Filter/match.ts +284 -0
- package/src/internal/Format/format.ts +0 -2
- package/src/internal/Format/index.ts +2 -2
- package/src/internal/Format/string.ts +0 -12
- package/src/internal/Format/types.ts +4 -2
- package/src/internal/JsonSchema/json-schema.test.ts +82 -54
- package/src/internal/Obj/atoms.ts +31 -18
- package/src/internal/Obj/clone.ts +61 -20
- package/src/internal/Obj/create-object.test.ts +3 -3
- package/src/internal/Obj/create-object.ts +3 -4
- package/src/internal/Obj/inspect.ts +1 -1
- package/src/internal/Obj/json-serializer.test.ts +7 -5
- package/src/internal/Obj/json-serializer.ts +1 -1
- package/src/internal/Obj/schema-validator.test.ts +53 -0
- package/src/internal/Obj/set-value.test.ts +29 -19
- package/src/internal/Query/index.ts +1 -0
- package/src/internal/Query/query-result-effect.ts +35 -0
- package/src/internal/Ref/atoms.ts +23 -3
- package/src/internal/Ref/ref.test.ts +8 -8
- package/src/internal/Ref/ref.ts +26 -20
- package/src/internal/common/atom-snapshot.ts +19 -0
- package/src/internal/common/proxy/make-object.ts +1 -1
- package/src/internal/common/proxy/proxy-types.ts +20 -0
- package/src/internal/common/proxy/proxy-utils.ts +37 -1
- package/src/internal/common/proxy/reactive-array.ts +3 -3
- package/src/internal/common/proxy/reactive.test.ts +1 -1
- package/src/internal/common/proxy/schema-validator.ts +181 -8
- package/src/internal/common/proxy/typed-handler.ts +254 -76
- package/src/internal/common/types/entity.ts +15 -7
- package/src/internal/index.ts +1 -0
- package/src/testing/test-schema.ts +75 -90
- package/dist/lib/neutral/chunk-35INCYOE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3JXQUMUI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4TPQZPLY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-5GAIWRQB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6CEISA3V.mjs.map +0 -7
- package/dist/lib/neutral/chunk-AFANXSGV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BBFJWWAV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-E7IOTOZD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-EVK6XBXO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HGWQR5KH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JZ6LFQU5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NOL7O3BL.mjs.map +0 -7
- package/dist/lib/neutral/chunk-O6BH7EPN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TF5BSLF6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TFEWTY5A.mjs.map +0 -7
- package/dist/lib/neutral/chunk-VQYL2OQF.mjs +0 -2353
- package/dist/lib/neutral/chunk-VQYL2OQF.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WU3GIANS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WZ32BF53.mjs.map +0 -7
- package/dist/lib/neutral/chunk-X3NUUON4.mjs +0 -184
- package/dist/lib/neutral/chunk-X3NUUON4.mjs.map +0 -7
- package/dist/types/src/exemplars.test.d.ts +0 -2
- package/dist/types/src/exemplars.test.d.ts.map +0 -1
- package/dist/types/src/hierarchy.test.d.ts +0 -2
- package/dist/types/src/hierarchy.test.d.ts.map +0 -1
- package/src/exemplars.test.ts +0 -21
- package/src/hierarchy.test.ts +0 -33
- /package/dist/lib/neutral/{chunk-K3S7D5PR.mjs.map → chunk-4A2NWKRJ.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-PLDMJJJC.mjs.map → chunk-UGFFV2FR.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-L3OBWBV2.mjs.map → chunk-WKIT2YY5.mjs.map} +0 -0
package/src/Text.test.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import * as Obj from './Obj';
|
|
8
|
+
import { TestSchema } from './testing';
|
|
9
|
+
import * as Text from './Text';
|
|
10
|
+
|
|
11
|
+
describe('Text', () => {
|
|
12
|
+
describe('update', () => {
|
|
13
|
+
test('replaces the value; readable via getValue', ({ expect }) => {
|
|
14
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
15
|
+
Obj.update(obj, () => {
|
|
16
|
+
Text.update(obj, 'name', 'goodbye');
|
|
17
|
+
});
|
|
18
|
+
expect(Obj.getValue(obj, ['name'])).toBe('goodbye');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('accepts a key-path array', ({ expect }) => {
|
|
22
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
23
|
+
Obj.update(obj, () => {
|
|
24
|
+
Text.update(obj, ['name'], 'world');
|
|
25
|
+
});
|
|
26
|
+
expect(Obj.getValue(obj, ['name'])).toBe('world');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('updates a nested string field', ({ expect }) => {
|
|
30
|
+
const obj = Obj.make(TestSchema.Person, { name: 'a', address: { city: 'Paris', coordinates: {} } });
|
|
31
|
+
Obj.update(obj, () => {
|
|
32
|
+
Text.update(obj, ['address', 'city'], 'London');
|
|
33
|
+
});
|
|
34
|
+
expect(Obj.getValue(obj, ['address', 'city'])).toBe('London');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('splice', () => {
|
|
39
|
+
test('inserts at an index', ({ expect }) => {
|
|
40
|
+
const obj = Obj.make(TestSchema.Person, { name: 'helloworld' });
|
|
41
|
+
Obj.update(obj, () => {
|
|
42
|
+
const removed = Text.splice(obj, 'name', 5, 0, ' ');
|
|
43
|
+
expect(removed).toBe('');
|
|
44
|
+
});
|
|
45
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hello world');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('deletes and returns the removed substring', ({ expect }) => {
|
|
49
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello world' });
|
|
50
|
+
Obj.update(obj, () => {
|
|
51
|
+
const removed = Text.splice(obj, 'name', 5, 6);
|
|
52
|
+
expect(removed).toBe(' world');
|
|
53
|
+
});
|
|
54
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hello');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('replaces in place', ({ expect }) => {
|
|
58
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello world' });
|
|
59
|
+
Obj.update(obj, () => {
|
|
60
|
+
const removed = Text.splice(obj, 'name', 6, 5, 'there');
|
|
61
|
+
expect(removed).toBe('world');
|
|
62
|
+
});
|
|
63
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hello there');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('start past end appends', ({ expect }) => {
|
|
67
|
+
const obj = Obj.make(TestSchema.Person, { name: 'abc' });
|
|
68
|
+
Obj.update(obj, () => {
|
|
69
|
+
const removed = Text.splice(obj, 'name', 100, 0, 'def');
|
|
70
|
+
expect(removed).toBe('');
|
|
71
|
+
});
|
|
72
|
+
expect(Obj.getValue(obj, ['name'])).toBe('abcdef');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('mirrors Array.splice index semantics', ({ expect }) => {
|
|
76
|
+
const reference = 'hello world';
|
|
77
|
+
const obj = Obj.make(TestSchema.Person, { name: reference });
|
|
78
|
+
let removed = '';
|
|
79
|
+
Obj.update(obj, () => {
|
|
80
|
+
removed = Text.splice(obj, 'name', 0, 5, 'HELLO');
|
|
81
|
+
});
|
|
82
|
+
// Mirror the same operation on a character array.
|
|
83
|
+
const chars = [...reference];
|
|
84
|
+
const removedChars = chars.splice(0, 5, ...'HELLO');
|
|
85
|
+
expect(removed).toBe(removedChars.join(''));
|
|
86
|
+
expect(Obj.getValue(obj, ['name'])).toBe(chars.join(''));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('apply', () => {
|
|
91
|
+
test('replaces the first occurrence', ({ expect }) => {
|
|
92
|
+
const obj = Obj.make(TestSchema.Person, { name: 'foo foo foo' });
|
|
93
|
+
Obj.update(obj, () => {
|
|
94
|
+
Text.apply(obj, 'name', [{ oldString: 'foo', newString: 'bar' }]);
|
|
95
|
+
});
|
|
96
|
+
expect(Obj.getValue(obj, ['name'])).toBe('bar foo foo');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('replaces all occurrences', ({ expect }) => {
|
|
100
|
+
const obj = Obj.make(TestSchema.Person, { name: 'foo foo foo' });
|
|
101
|
+
Obj.update(obj, () => {
|
|
102
|
+
Text.apply(obj, 'name', [{ oldString: 'foo', newString: 'bar', replaceAll: true }]);
|
|
103
|
+
});
|
|
104
|
+
expect(Obj.getValue(obj, ['name'])).toBe('bar bar bar');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('replaceAll does not re-match inserted text', ({ expect }) => {
|
|
108
|
+
const obj = Obj.make(TestSchema.Person, { name: 'a a a' });
|
|
109
|
+
Obj.update(obj, () => {
|
|
110
|
+
Text.apply(obj, 'name', [{ oldString: 'a', newString: 'aa', replaceAll: true }]);
|
|
111
|
+
});
|
|
112
|
+
expect(Obj.getValue(obj, ['name'])).toBe('aa aa aa');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('appends when oldString is missing', ({ expect }) => {
|
|
116
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
117
|
+
Obj.update(obj, () => {
|
|
118
|
+
Text.apply(obj, 'name', [{ newString: ' world' }]);
|
|
119
|
+
});
|
|
120
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hello world');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('appends when oldString is empty', ({ expect }) => {
|
|
124
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
125
|
+
Obj.update(obj, () => {
|
|
126
|
+
Text.apply(obj, 'name', [{ oldString: '', newString: '!' }]);
|
|
127
|
+
});
|
|
128
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hello!');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('applies edits sequentially and returns the final text', ({ expect }) => {
|
|
132
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
133
|
+
let result = '';
|
|
134
|
+
Obj.update(obj, () => {
|
|
135
|
+
result = Text.apply(obj, 'name', [{ oldString: 'hello', newString: 'hi' }, { newString: ' there' }]);
|
|
136
|
+
});
|
|
137
|
+
expect(result).toBe('hi there');
|
|
138
|
+
expect(Obj.getValue(obj, ['name'])).toBe('hi there');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('throws when a non-replaceAll oldString is not found', ({ expect }) => {
|
|
142
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
143
|
+
expect(() =>
|
|
144
|
+
Obj.update(obj, () => {
|
|
145
|
+
Text.apply(obj, 'name', [{ oldString: 'missing', newString: 'x' }]);
|
|
146
|
+
}),
|
|
147
|
+
).toThrow();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('strict mode', () => {
|
|
152
|
+
test('update throws outside Obj.update', ({ expect }) => {
|
|
153
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
154
|
+
expect(() => Text.update(obj, 'name', 'x')).toThrow(/Obj\.update/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('splice throws outside Obj.update', ({ expect }) => {
|
|
158
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
159
|
+
expect(() => Text.splice(obj, 'name', 0, 1, 'x')).toThrow(/Obj\.update/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('apply throws outside Obj.update', ({ expect }) => {
|
|
163
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
164
|
+
expect(() => Text.apply(obj, 'name', [{ oldString: 'h', newString: 'H' }])).toThrow(/Obj\.update/);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('reactivity', () => {
|
|
169
|
+
test('a subscriber fires once per Obj.update batch', ({ expect }) => {
|
|
170
|
+
const obj = Obj.make(TestSchema.Person, { name: 'hello' });
|
|
171
|
+
let count = 0;
|
|
172
|
+
const unsubscribe = Obj.subscribe(obj, () => {
|
|
173
|
+
count++;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
Obj.update(obj, () => {
|
|
177
|
+
Text.splice(obj, 'name', 0, 5, 'goodbye');
|
|
178
|
+
Text.splice(obj, 'name', 0, 0, '!');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(count).toBe(1);
|
|
182
|
+
expect(Obj.getValue(obj, ['name'])).toBe('!goodbye');
|
|
183
|
+
unsubscribe();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
package/src/Text.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import * as Err from './Err';
|
|
8
|
+
import * as internal from './internal';
|
|
9
|
+
import * as Obj from './Obj';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A key path addressing a string value within an object, mirroring `Obj.getValue` / `Obj.setValue`.
|
|
13
|
+
*/
|
|
14
|
+
export type KeyPath = readonly (string | number)[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A single find/replace edit applied to a string field. When `oldString` is missing or empty the
|
|
18
|
+
* `newString` is appended to the end of the current value.
|
|
19
|
+
*/
|
|
20
|
+
export type Edit = {
|
|
21
|
+
oldString?: string;
|
|
22
|
+
newString: string;
|
|
23
|
+
replaceAll?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Replace the string CRDT value at `path` with `newText`.
|
|
28
|
+
*
|
|
29
|
+
* For database-backed objects the change is applied as a minimal Automerge diff (preserving cursors,
|
|
30
|
+
* anchors, and merging cleanly with concurrent edits); for in-memory objects it is a plain assignment.
|
|
31
|
+
*
|
|
32
|
+
* Must be called inside `Obj.update(obj, () => { ... })`.
|
|
33
|
+
*/
|
|
34
|
+
export const update = (obj: Obj.Unknown, path: KeyPath | string | number, newText: string): void => {
|
|
35
|
+
const { handler, target } = resolve(obj, 'update');
|
|
36
|
+
if (typeof handler.textUpdate !== 'function') {
|
|
37
|
+
throw new Err.TextNotSupportedError('update');
|
|
38
|
+
}
|
|
39
|
+
handler.textUpdate(target, normalizePath(path), newText);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Splice the string CRDT value at `path` by character index, mirroring `Array.prototype.splice`:
|
|
44
|
+
* remove `deleteCount` characters at `start` and insert `insert`. Returns the removed substring.
|
|
45
|
+
*
|
|
46
|
+
* Must be called inside `Obj.update(obj, () => { ... })`.
|
|
47
|
+
*/
|
|
48
|
+
export const splice = (
|
|
49
|
+
obj: Obj.Unknown,
|
|
50
|
+
path: KeyPath | string | number,
|
|
51
|
+
start: number,
|
|
52
|
+
deleteCount: number,
|
|
53
|
+
insert = '',
|
|
54
|
+
): string => {
|
|
55
|
+
const { handler, target } = resolve(obj, 'splice');
|
|
56
|
+
if (typeof handler.textSplice !== 'function') {
|
|
57
|
+
throw new Err.TextNotSupportedError('splice');
|
|
58
|
+
}
|
|
59
|
+
return handler.textSplice(target, normalizePath(path), start, deleteCount, insert);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Apply a sequence of find/replace {@link Edit}s to the string value at `path`, returning the resulting
|
|
64
|
+
* full string. Implemented on top of {@link splice} so it works for both backends.
|
|
65
|
+
*
|
|
66
|
+
* An edit with a missing or empty `oldString` appends its `newString`. A non-`replaceAll` edit whose
|
|
67
|
+
* `oldString` is not found throws {@link Err.TextEditNotFoundError}.
|
|
68
|
+
*
|
|
69
|
+
* Must be called inside `Obj.update(obj, () => { ... })`.
|
|
70
|
+
*/
|
|
71
|
+
export const apply = (obj: Obj.Unknown, path: KeyPath | string | number, edits: readonly Edit[]): string => {
|
|
72
|
+
const keyPath = normalizePath(path);
|
|
73
|
+
for (const edit of edits) {
|
|
74
|
+
const text: string = Obj.getValue(obj, keyPath) ?? '';
|
|
75
|
+
if (edit.oldString == null || edit.oldString.length === 0) {
|
|
76
|
+
// Append: an empty match would loop forever in `replaceAll`, so treat it as an append instead.
|
|
77
|
+
splice(obj, keyPath, text.length, 0, edit.newString);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (edit.replaceAll) {
|
|
82
|
+
let idx = text.indexOf(edit.oldString);
|
|
83
|
+
while (idx !== -1) {
|
|
84
|
+
splice(obj, keyPath, idx, edit.oldString.length, edit.newString);
|
|
85
|
+
const updated: string = Obj.getValue(obj, keyPath) ?? '';
|
|
86
|
+
// Resume past the inserted text so a `newString` that contains `oldString` is not re-matched.
|
|
87
|
+
idx = updated.indexOf(edit.oldString, idx + edit.newString.length);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
const idx = text.indexOf(edit.oldString);
|
|
91
|
+
if (idx === -1) {
|
|
92
|
+
throw new Err.TextEditNotFoundError(edit.oldString);
|
|
93
|
+
}
|
|
94
|
+
splice(obj, keyPath, idx, edit.oldString.length, edit.newString);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Obj.getValue(obj, keyPath) ?? '';
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const normalizePath = (path: KeyPath | string | number): KeyPath =>
|
|
102
|
+
typeof path === 'string' || typeof path === 'number' ? [path] : path;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the reactive handler and raw target for `obj`. The handler is shared between the in-memory
|
|
106
|
+
* and database-backed proxy systems, so this is the single dispatch seam for both backends. Snapshots
|
|
107
|
+
* and non-reactive values are not proxies and cannot be edited.
|
|
108
|
+
*/
|
|
109
|
+
const resolve = (obj: Obj.Unknown, operation: string) => {
|
|
110
|
+
if (!internal.isProxy(obj)) {
|
|
111
|
+
throw new Err.TextNotSupportedError(operation);
|
|
112
|
+
}
|
|
113
|
+
const handler = internal.getProxyHandler(obj);
|
|
114
|
+
const target = internal.getProxyTarget(obj);
|
|
115
|
+
return { handler, target };
|
|
116
|
+
};
|
package/src/Type.test.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { describe, test } from 'vitest';
|
|
|
8
8
|
import { DXN, EntityId } from '@dxos/keys';
|
|
9
9
|
|
|
10
10
|
import * as Entity from './Entity';
|
|
11
|
+
import * as Filter from './Filter';
|
|
11
12
|
import * as JsonSchema from './JsonSchema';
|
|
12
13
|
import * as Obj from './Obj';
|
|
14
|
+
import * as Ref from './Ref';
|
|
13
15
|
import * as Relation from './Relation';
|
|
14
16
|
import { TestSchema } from './testing';
|
|
15
17
|
import * as Type from './Type';
|
|
@@ -205,53 +207,79 @@ describe('Type', () => {
|
|
|
205
207
|
const worksForDxn = DXN.make('com.example.type.deterministic.worksFor', '0.1.0');
|
|
206
208
|
|
|
207
209
|
test('Type.makeObject with the same DXN twice yields entities with identical ids', ({ expect }) => {
|
|
208
|
-
const a = Schema.Struct({ name: Schema.String })
|
|
209
|
-
const b = Schema.Struct({ name: Schema.String })
|
|
210
|
+
const a = Type.makeObject(personDxn)(Schema.Struct({ name: Schema.String }));
|
|
211
|
+
const b = Type.makeObject(personDxn)(Schema.Struct({ name: Schema.String }));
|
|
210
212
|
expect(a.id).toBe(b.id);
|
|
211
213
|
expect(a.id).toBe(EntityId.deterministic('com.example.type.deterministic.person', '0.1.0'));
|
|
212
214
|
});
|
|
213
215
|
|
|
214
216
|
test('different versions produce different ids', ({ expect }) => {
|
|
215
|
-
const v1 =
|
|
216
|
-
|
|
217
|
+
const v1 = Type.makeObject(DXN.make('com.example.type.deterministic.person', '0.1.0'))(
|
|
218
|
+
Schema.Struct({ name: Schema.String }),
|
|
217
219
|
);
|
|
218
|
-
const v2 =
|
|
219
|
-
|
|
220
|
+
const v2 = Type.makeObject(DXN.make('com.example.type.deterministic.person', '0.2.0'))(
|
|
221
|
+
Schema.Struct({ name: Schema.String }),
|
|
220
222
|
);
|
|
221
223
|
expect(v1.id).not.toBe(v2.id);
|
|
222
224
|
});
|
|
223
225
|
|
|
224
226
|
test('Type.makeObject({ id }) override is honoured', ({ expect }) => {
|
|
225
227
|
const explicit = EntityId.random();
|
|
226
|
-
const entity =
|
|
228
|
+
const entity = Type.makeObject(personDxn, { id: explicit })(Schema.Struct({ name: Schema.String }));
|
|
227
229
|
expect(entity.id).toBe(explicit);
|
|
228
230
|
// And the default still kicks in when no override is supplied.
|
|
229
|
-
const defaultEntity = Schema.Struct({ name: Schema.String })
|
|
231
|
+
const defaultEntity = Type.makeObject(personDxn)(Schema.Struct({ name: Schema.String }));
|
|
230
232
|
expect(defaultEntity.id).not.toBe(explicit);
|
|
231
233
|
});
|
|
232
234
|
|
|
233
235
|
test('Type.makeRelation defaults id to deterministic(typename, version)', ({ expect }) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
|
|
236
|
+
class RelationA extends Type.makeRelation<RelationA>(worksForDxn)({
|
|
237
|
+
source: TestSchema.Person,
|
|
238
|
+
target: TestSchema.Organization,
|
|
239
|
+
})(Schema.Struct({ since: Schema.Number })) {}
|
|
240
|
+
|
|
241
|
+
class RelationB extends Type.makeRelation<RelationB>(worksForDxn)({
|
|
242
|
+
source: TestSchema.Person,
|
|
243
|
+
target: TestSchema.Organization,
|
|
244
|
+
})(Schema.Struct({ since: Schema.Number })) {}
|
|
245
|
+
|
|
246
|
+
expect(RelationA.id).toBe(RelationB.id);
|
|
247
|
+
expect(RelationA.id).toBe(EntityId.deterministic('com.example.type.deterministic.worksFor', '0.1.0'));
|
|
242
248
|
});
|
|
243
249
|
|
|
244
250
|
test('Type.makeRelation({ id }) override is honoured', ({ expect }) => {
|
|
245
251
|
const explicit = EntityId.random();
|
|
246
|
-
const entity =
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
id: explicit,
|
|
252
|
-
}),
|
|
253
|
-
);
|
|
252
|
+
const entity = Type.makeRelation(worksForDxn)({
|
|
253
|
+
source: TestSchema.Person,
|
|
254
|
+
target: TestSchema.Organization,
|
|
255
|
+
id: explicit,
|
|
256
|
+
})(Schema.Struct({ since: Schema.Number }));
|
|
254
257
|
expect(entity.id).toBe(explicit);
|
|
255
258
|
});
|
|
259
|
+
|
|
260
|
+
test('Type.makeObject class syntax', ({ expect }) => {
|
|
261
|
+
class Person extends Type.makeObject<Person>(DXN.make('com.example.type.person', '0.1.0'))(
|
|
262
|
+
Schema.Struct({ name: Schema.String }),
|
|
263
|
+
) {}
|
|
264
|
+
expect(Person.jsonSchema).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('Ref of Type.makeObject class', ({ expect }) => {
|
|
268
|
+
class Person extends Type.makeObject<Person>(DXN.make('com.example.type.person', '0.1.0'))(
|
|
269
|
+
Schema.Struct({ name: Schema.String }),
|
|
270
|
+
) {}
|
|
271
|
+
|
|
272
|
+
const refSchema = Ref.Ref(Person);
|
|
273
|
+
expect(refSchema).toBeDefined();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('Filter of Type.makeObject class', ({ expect }) => {
|
|
277
|
+
class Person extends Type.makeObject<Person>(DXN.make('com.example.type.person', '0.1.0'))(
|
|
278
|
+
Schema.Struct({ name: Schema.String }),
|
|
279
|
+
) {}
|
|
280
|
+
|
|
281
|
+
const filter = Filter.type(Person);
|
|
282
|
+
expect(filter).toBeDefined();
|
|
283
|
+
});
|
|
256
284
|
});
|
|
257
285
|
});
|
package/src/Type.ts
CHANGED
|
@@ -73,11 +73,9 @@ interface BaseTypeEntity<A> {
|
|
|
73
73
|
*
|
|
74
74
|
* @example
|
|
75
75
|
* ```ts
|
|
76
|
-
*
|
|
77
|
-
* name: Schema.String,
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* type Person = Type.InstanceType<typeof Person>;
|
|
76
|
+
* class Person extends Type.makeObject<Person>(DXN.make('com.example.type.person', '0.1.0'))(
|
|
77
|
+
* Schema.Struct({ name: Schema.String }),
|
|
78
|
+
* ) {}
|
|
81
79
|
* ```
|
|
82
80
|
*/
|
|
83
81
|
export interface Obj<T, Fields extends Schema.Struct.Fields = Schema.Struct.Fields> extends BaseTypeEntity<
|
|
@@ -96,6 +94,15 @@ export interface Obj<T, Fields extends Schema.Struct.Fields = Schema.Struct.Fiel
|
|
|
96
94
|
readonly fields: Fields;
|
|
97
95
|
}
|
|
98
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Return type for {@link makeObject}.
|
|
99
|
+
*
|
|
100
|
+
* Not to be used directly, but must be exported for typescript to infer the type.
|
|
101
|
+
*/
|
|
102
|
+
export interface ObjClass<Self, T, Fields extends Schema.Struct.Fields> extends Obj<Self, Fields> {
|
|
103
|
+
new (_: never): T & EntityModule.OfKind<typeof EntityModule.Kind.Object>;
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
/**
|
|
100
107
|
* Type that represents any ECHO object type — a `Type.Type` entity branded
|
|
101
108
|
* with the object entity kind, i.e. what `Type.makeObject(dxn)` produces.
|
|
@@ -115,19 +122,26 @@ export type AnyObj = Obj<unknown>;
|
|
|
115
122
|
* scope. Pass `{ id }` to override (e.g. with `EntityId.random()` from a
|
|
116
123
|
* request handler).
|
|
117
124
|
*
|
|
125
|
+
* Giving the type a nominal class name keeps its inferred type portable for
|
|
126
|
+
* declaration emit: structural expansion (which can pull in unnameable union
|
|
127
|
+
* members from referenced schemas) is replaced by a reference to the class.
|
|
128
|
+
*
|
|
118
129
|
* @example
|
|
119
130
|
* ```ts
|
|
120
|
-
*
|
|
131
|
+
* export class Person extends Type.makeObject<Person>(DXN.make('com.example.type.myType', '0.1.0'))(Schema.Struct({
|
|
121
132
|
* name: Schema.String,
|
|
122
|
-
* })
|
|
133
|
+
* })) {}
|
|
123
134
|
* ```
|
|
124
135
|
*/
|
|
136
|
+
// TODO(burdon): Require { typename, version } in options.
|
|
125
137
|
export const makeObject: {
|
|
126
|
-
(
|
|
138
|
+
<Self>(
|
|
127
139
|
dxn: DXN.DXN,
|
|
128
140
|
options?: { id?: EntityId },
|
|
129
|
-
): <
|
|
130
|
-
|
|
141
|
+
): <_Schema extends Schema.Schema.Any>(schema: _Schema) => ObjClass<Self, Schema.Schema.Type<_Schema>, {}>;
|
|
142
|
+
// Boundary cast: overload implementation bodies cannot access outer generic params (`Self`),
|
|
143
|
+
// so TypeScript cannot verify that makeObjectType's return matches the declared ObjClass<Self,…>.
|
|
144
|
+
} = (dxn, options) => (schema) => internal.makeObjectType(dxn, schema, options) as any;
|
|
131
145
|
|
|
132
146
|
//
|
|
133
147
|
// Type — the ECHO entity that holds a schema and metadata.
|
|
@@ -255,6 +269,20 @@ export interface Relation<
|
|
|
255
269
|
readonly fields: Fields;
|
|
256
270
|
}
|
|
257
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Return type for {@link makeRelation}.
|
|
274
|
+
*
|
|
275
|
+
* Not to be used directly, but must be exported for typescript to infer the type.
|
|
276
|
+
*/
|
|
277
|
+
export interface RelationClass<Self, T, Source, Target, Fields extends Schema.Struct.Fields> extends Relation<
|
|
278
|
+
Self,
|
|
279
|
+
Source,
|
|
280
|
+
Target,
|
|
281
|
+
Fields
|
|
282
|
+
> {
|
|
283
|
+
new (_: never): RelationModule.Endpoints<Source, Target> & T & EntityModule.OfKind<typeof EntityModule.Kind.Relation>;
|
|
284
|
+
}
|
|
285
|
+
|
|
258
286
|
/**
|
|
259
287
|
* Type that represents any ECHO relation type — a `Type.Type` entity branded
|
|
260
288
|
* with the relation entity kind, i.e. what `Type.makeRelation(...)` produces.
|
|
@@ -267,18 +295,13 @@ export type AnyRelation = Relation<unknown, unknown, unknown>;
|
|
|
267
295
|
*
|
|
268
296
|
* @example
|
|
269
297
|
* ```ts
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* dxn: DXN.make('com.example.type.worksFor', '0.1.0'),
|
|
274
|
-
* source: Person,
|
|
275
|
-
* target: Company,
|
|
276
|
-
* }));
|
|
298
|
+
* class WorksFor extends Type.makeRelation<WorksFor>(DXN.make('com.example.type.worksFor', '0.1.0'))(
|
|
299
|
+
* { source: Person, target: Company },
|
|
300
|
+
* )(Schema.Struct({ role: Schema.String })) {}
|
|
277
301
|
* ```
|
|
278
302
|
*/
|
|
279
303
|
export const makeRelation: {
|
|
280
|
-
<SourceInstance, TargetInstance>(opts: {
|
|
281
|
-
dxn: DXN.DXN;
|
|
304
|
+
<Self>(dxn: DXN.DXN): <SourceInstance, TargetInstance>(opts: {
|
|
282
305
|
source: Obj<SourceInstance, any> | internal.UnknownTypeSchema<SourceInstance, typeof EntityModule.Kind.Object>;
|
|
283
306
|
target: Obj<TargetInstance, any> | internal.UnknownTypeSchema<TargetInstance, typeof EntityModule.Kind.Object>;
|
|
284
307
|
/**
|
|
@@ -286,14 +309,25 @@ export const makeRelation: {
|
|
|
286
309
|
* see `Type.makeObject` for the workerd motivation.
|
|
287
310
|
*/
|
|
288
311
|
id?: EntityId;
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
) =>
|
|
292
|
-
|
|
312
|
+
}) => <_Schema extends Schema.Schema.Any>(
|
|
313
|
+
schema: _Schema,
|
|
314
|
+
) => RelationClass<
|
|
315
|
+
Self,
|
|
316
|
+
Schema.Schema.Type<_Schema>,
|
|
293
317
|
SourceInstance & EntityModule.OfKind<typeof EntityModule.Kind.Object>,
|
|
294
|
-
TargetInstance & EntityModule.OfKind<typeof EntityModule.Kind.Object
|
|
318
|
+
TargetInstance & EntityModule.OfKind<typeof EntityModule.Kind.Object>,
|
|
319
|
+
{}
|
|
295
320
|
>;
|
|
296
|
-
} =
|
|
321
|
+
} = (dxn) => (opts) => (schema) =>
|
|
322
|
+
// Boundary cast: makeRelationType erases generic params since the constructor/prototype
|
|
323
|
+
// wiring cannot thread them through the type system.
|
|
324
|
+
internal.makeRelationType({
|
|
325
|
+
dxn,
|
|
326
|
+
source: opts.source,
|
|
327
|
+
target: opts.target,
|
|
328
|
+
schema,
|
|
329
|
+
id: opts.id,
|
|
330
|
+
}) as any;
|
|
297
331
|
|
|
298
332
|
/**
|
|
299
333
|
* Type that represents any ECHO type-kind entity — a `Type.Type` meta-schema
|
|
@@ -611,13 +645,17 @@ export interface Type<A = unknown> extends BaseTypeEntity<A & EntityModule.OfKin
|
|
|
611
645
|
* - `Type<A>` → `A & OfKind<Type>`
|
|
612
646
|
*/
|
|
613
647
|
export type InstanceType<T extends AnyEntity> =
|
|
614
|
-
T extends
|
|
648
|
+
T extends RelationClass<any, infer Props, infer Source, infer Target, any>
|
|
615
649
|
? RelationModule.Endpoints<Source, Target> & Props & EntityModule.OfKind<typeof EntityModule.Kind.Relation>
|
|
616
|
-
: T extends
|
|
617
|
-
?
|
|
618
|
-
: T extends
|
|
619
|
-
? A & EntityModule.OfKind<typeof EntityModule.Kind.
|
|
620
|
-
:
|
|
650
|
+
: T extends Relation<infer Props, infer Source, infer Target, any>
|
|
651
|
+
? RelationModule.Endpoints<Source, Target> & Props & EntityModule.OfKind<typeof EntityModule.Kind.Relation>
|
|
652
|
+
: T extends ObjClass<any, infer A, any>
|
|
653
|
+
? A & EntityModule.OfKind<typeof EntityModule.Kind.Object>
|
|
654
|
+
: T extends Obj<infer A, any>
|
|
655
|
+
? A & EntityModule.OfKind<typeof EntityModule.Kind.Object>
|
|
656
|
+
: T extends Type<infer A>
|
|
657
|
+
? A & EntityModule.OfKind<typeof EntityModule.Kind.Type>
|
|
658
|
+
: never;
|
|
621
659
|
|
|
622
660
|
/**
|
|
623
661
|
* Returns the Effect Schema for a type entity.
|
package/src/View.ts
CHANGED
|
@@ -60,36 +60,28 @@ export type Projection = Schema.Schema.Type<typeof Projection>;
|
|
|
60
60
|
* Views are generated or user-defined projections of a schema's properties.
|
|
61
61
|
* They are used to configure the visual representation of the data.
|
|
62
62
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Without the named interface, embedding `Type.getSchema(View.View)` in a downstream
|
|
86
|
-
// `Schema.Struct` triggers TS2742 portability errors (e.g. plugin-kanban, plugin-table).
|
|
87
|
-
// TODO(wittjosiah): Find a better solution that doesn't require manually keeping the interface in sync.
|
|
88
|
-
export interface View extends Type.InstanceType<typeof ViewSchema> {}
|
|
89
|
-
|
|
90
|
-
export const View: Type.Obj<View> = ViewSchema as any;
|
|
91
|
-
|
|
92
|
-
export const make = (props: Partial<Obj.MakeProps<typeof View>>): View => {
|
|
63
|
+
export class View extends Type.makeObject<View>(DXN.make('org.dxos.type.view', '0.1.0'))(
|
|
64
|
+
Schema.Struct({
|
|
65
|
+
/**
|
|
66
|
+
* Query used to retrieve data.
|
|
67
|
+
* Can be a user-provided query grammar string or a query AST.
|
|
68
|
+
*/
|
|
69
|
+
query: Schema.Struct({
|
|
70
|
+
raw: Schema.optional(Schema.String),
|
|
71
|
+
ast: QueryAST.Query,
|
|
72
|
+
}),
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Projection of the data returned from the query.
|
|
76
|
+
*/
|
|
77
|
+
projection: Projection,
|
|
78
|
+
}).pipe(
|
|
79
|
+
internal.HiddenAnnotation.set(true),
|
|
80
|
+
Annotation.IconAnnotation.set({ icon: 'ph--funnel--regular', hue: 'green' }),
|
|
81
|
+
),
|
|
82
|
+
) {}
|
|
83
|
+
|
|
84
|
+
export const make = (props: Partial<Obj.MakeProps<typeof View>>): Type.InstanceType<typeof View> => {
|
|
93
85
|
return Obj.make(View, {
|
|
94
86
|
query: { ast: Query.select(Filter.nothing()).ast },
|
|
95
87
|
projection: { fields: [] },
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ export { DXN, EID, URI } from '@dxos/keys';
|
|
|
8
8
|
export * as Annotation from './Annotation';
|
|
9
9
|
export * as Database from './Database';
|
|
10
10
|
export * as Entity from './Entity';
|
|
11
|
-
// TODO(burdon): Rename to Error (less problematic than Obj/Object).
|
|
12
11
|
export * as Err from './Err';
|
|
13
12
|
export * as Feed from './Feed';
|
|
14
13
|
export * as Filter from './Filter';
|
|
@@ -27,6 +26,7 @@ export * as Registry from './Registry';
|
|
|
27
26
|
export * as Relation from './Relation';
|
|
28
27
|
export * as Scope from './Scope';
|
|
29
28
|
export * as Tag from './Tag';
|
|
29
|
+
export * as Text from './Text';
|
|
30
30
|
export * as Type from './Type';
|
|
31
31
|
export * as Collection from './Collection';
|
|
32
32
|
export * as View from './View';
|