@milaboratories/pl-tree 1.3.6
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/index.cjs +1023 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +979 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/accessors.ts +422 -0
- package/src/index.ts +8 -0
- package/src/snapshot.test.ts +101 -0
- package/src/snapshot.ts +219 -0
- package/src/state.test.ts +316 -0
- package/src/state.ts +681 -0
- package/src/sync.test.ts +101 -0
- package/src/sync.ts +129 -0
- package/src/synchronized_tree.test.ts +210 -0
- package/src/synchronized_tree.ts +189 -0
- package/src/test_utils.ts +156 -0
- package/src/traversal_ops.ts +56 -0
- package/src/value_and_error.ts +19 -0
- package/src/value_or_error.ts +9 -0
package/src/snapshot.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { ResourceId, ResourceType } from '@milaboratories/pl-client';
|
|
2
|
+
import { Optional, Writable } from 'utility-types';
|
|
3
|
+
import { ZodType, z } from 'zod';
|
|
4
|
+
import { PlTreeEntry, PlTreeEntryAccessor, PlTreeNodeAccessor } from './accessors';
|
|
5
|
+
import { ComputableCtx } from '@milaboratories/computable';
|
|
6
|
+
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A DTO that can be generated from a tree node to make a snapshot of specific parts of it's state.
|
|
10
|
+
* Such snapshots can then be used in core that requires this information without the need of
|
|
11
|
+
* retrieving state from the tree.
|
|
12
|
+
*/
|
|
13
|
+
export type ResourceSnapshot<
|
|
14
|
+
Data = undefined,
|
|
15
|
+
Fields extends Record<string, ResourceId | undefined> | undefined = undefined,
|
|
16
|
+
KV extends Record<string, unknown> | undefined = undefined
|
|
17
|
+
> = {
|
|
18
|
+
readonly id: ResourceId;
|
|
19
|
+
readonly type: ResourceType;
|
|
20
|
+
readonly data: Data;
|
|
21
|
+
readonly fields: Fields;
|
|
22
|
+
readonly kv: KV;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** The most generic type of ResourceSnapshot. */
|
|
26
|
+
type ResourceSnapshotGeneric = ResourceSnapshot<
|
|
27
|
+
unknown,
|
|
28
|
+
Record<string, ResourceId | undefined> | undefined,
|
|
29
|
+
Record<string, unknown> | undefined
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
/** Request that we'll pass to getResourceSnapshot function. We infer the type of ResourceSnapshot from this. */
|
|
33
|
+
export type ResourceSnapshotSchema<
|
|
34
|
+
Data extends ZodType | 'raw' | undefined = undefined,
|
|
35
|
+
Fields extends Record<string, boolean> | undefined = undefined,
|
|
36
|
+
KV extends Record<string, ZodType | 'raw'> | undefined = undefined
|
|
37
|
+
> = {
|
|
38
|
+
readonly data: Data;
|
|
39
|
+
readonly fields: Fields;
|
|
40
|
+
readonly kv: KV;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Creates ResourceSnapshotSchema. It converts an optional schema type to schema type. */
|
|
44
|
+
export function rsSchema<
|
|
45
|
+
const Data extends ZodType | 'raw' | undefined = undefined,
|
|
46
|
+
const Fields extends Record<string, boolean> | undefined = undefined,
|
|
47
|
+
const KV extends Record<string, ZodType | 'raw'> | undefined = undefined
|
|
48
|
+
>(
|
|
49
|
+
schema: Optional<ResourceSnapshotSchema<Data, Fields, KV>>
|
|
50
|
+
): ResourceSnapshotSchema<Data, Fields, KV> {
|
|
51
|
+
return schema as any;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The most generic type of ResourceSnapshotSchema. */
|
|
55
|
+
type ResourceSnapshotSchemaGeneric = ResourceSnapshotSchema<
|
|
56
|
+
ZodType | 'raw' | undefined,
|
|
57
|
+
Record<string, boolean> | undefined,
|
|
58
|
+
Record<string, ZodType | 'raw'> | undefined
|
|
59
|
+
>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* If Data is 'raw' in schema, we'll get bytes,
|
|
63
|
+
* if it's Zod, we'll parse it via zod.
|
|
64
|
+
* Or else we just got undefined in the field.
|
|
65
|
+
*/
|
|
66
|
+
type InferDataType<Data extends ZodType | 'raw' | undefined> = Data extends 'raw'
|
|
67
|
+
? Uint8Array
|
|
68
|
+
: Data extends ZodType
|
|
69
|
+
? z.infer<Data>
|
|
70
|
+
: undefined;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* If Fields is a record of field names to booleans,
|
|
74
|
+
* then if the value of the field is true, we'll require this field and throw a Error if it wasn't found.
|
|
75
|
+
* If it's false and doesn't exist, we'll return undefined.
|
|
76
|
+
* If Fields type is undefined, we won't set fields at all.
|
|
77
|
+
*/
|
|
78
|
+
type InferFieldsType<Fields extends Record<string, boolean> | undefined> = Fields extends undefined
|
|
79
|
+
? undefined
|
|
80
|
+
: {
|
|
81
|
+
[FieldName in keyof Fields]: Fields[FieldName] extends true
|
|
82
|
+
? ResourceId
|
|
83
|
+
: ResourceId | undefined;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* If KV is undefined, won't set it.
|
|
88
|
+
* If one of values is Zod, we'll get KV and converts it to Zod schema.
|
|
89
|
+
* If the value is 'raw', just returns bytes.
|
|
90
|
+
*/
|
|
91
|
+
type InferKVType<KV extends Record<string, ZodType | 'raw'> | undefined> = KV extends undefined
|
|
92
|
+
? undefined
|
|
93
|
+
: {
|
|
94
|
+
[FieldName in keyof KV]: KV[FieldName] extends ZodType ? z.infer<KV[FieldName]> : Uint8Array;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Infer ResourceSnapshot from ResourceShapshotSchema, S can be any ResourceSnapshotSchema. */
|
|
98
|
+
export type InferSnapshot<S extends ResourceSnapshotSchemaGeneric> = ResourceSnapshot<
|
|
99
|
+
InferDataType<S['data']>,
|
|
100
|
+
InferFieldsType<S['fields']>,
|
|
101
|
+
InferKVType<S['kv']>
|
|
102
|
+
>;
|
|
103
|
+
|
|
104
|
+
/** Gets a ResourceSnapshot from PlTreeEntry. */
|
|
105
|
+
export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(
|
|
106
|
+
res: PlTreeEntry,
|
|
107
|
+
schema: Schema,
|
|
108
|
+
ctx: ComputableCtx
|
|
109
|
+
): InferSnapshot<Schema>;
|
|
110
|
+
export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(
|
|
111
|
+
res: PlTreeEntryAccessor | PlTreeNodeAccessor,
|
|
112
|
+
schema: Schema
|
|
113
|
+
): InferSnapshot<Schema>;
|
|
114
|
+
export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(
|
|
115
|
+
res: PlTreeEntry | PlTreeEntryAccessor | PlTreeNodeAccessor,
|
|
116
|
+
schema: Schema,
|
|
117
|
+
ctx?: ComputableCtx
|
|
118
|
+
): InferSnapshot<Schema> {
|
|
119
|
+
const node =
|
|
120
|
+
res instanceof PlTreeEntry
|
|
121
|
+
? notEmpty(ctx).accessor(res).node()
|
|
122
|
+
: res instanceof PlTreeEntryAccessor
|
|
123
|
+
? res.node()
|
|
124
|
+
: res;
|
|
125
|
+
const info = node.resourceInfo;
|
|
126
|
+
const result: Optional<Writable<ResourceSnapshotGeneric>, 'data' | 'fields' | 'kv'> = { ...info };
|
|
127
|
+
|
|
128
|
+
if (schema.data !== undefined) {
|
|
129
|
+
if (schema.data === 'raw') result.data = node.getData();
|
|
130
|
+
else result.data = schema.data.parse(node.getDataAsJson());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (schema.fields !== undefined) {
|
|
134
|
+
const fields: Record<string, ResourceId | undefined> = {};
|
|
135
|
+
for (const [fieldName, required] of Object.entries(schema.fields))
|
|
136
|
+
fields[fieldName] = node.traverse({
|
|
137
|
+
field: fieldName,
|
|
138
|
+
errorIfFieldNotSet: required,
|
|
139
|
+
stableIfNotFound: !required
|
|
140
|
+
})?.id;
|
|
141
|
+
result.fields = fields;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (schema.kv !== undefined) {
|
|
145
|
+
const kv: Record<string, unknown> = {};
|
|
146
|
+
for (const [fieldName, type] of Object.entries(schema.kv)) {
|
|
147
|
+
const value = node.getKeyValue(fieldName);
|
|
148
|
+
|
|
149
|
+
if (value === undefined) {
|
|
150
|
+
throw new Error(`Key not found ${fieldName}`);
|
|
151
|
+
} else if (type === 'raw') {
|
|
152
|
+
kv[fieldName] = value;
|
|
153
|
+
} else {
|
|
154
|
+
kv[fieldName] = type.parse(JSON.parse(Buffer.from(value).toString('utf-8')));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
result.kv = kv;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result as any;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** @deprecated */
|
|
164
|
+
export type ResourceWithData = {
|
|
165
|
+
readonly id: ResourceId;
|
|
166
|
+
readonly type: ResourceType;
|
|
167
|
+
readonly fields: Map<string, ResourceId | undefined>;
|
|
168
|
+
readonly data?: Uint8Array;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** @deprecated */
|
|
172
|
+
export function treeEntryToResourceWithData(
|
|
173
|
+
res: PlTreeEntry | ResourceWithData,
|
|
174
|
+
fields: string[],
|
|
175
|
+
ctx: ComputableCtx
|
|
176
|
+
): ResourceWithData {
|
|
177
|
+
if (res instanceof PlTreeEntry) {
|
|
178
|
+
const node = ctx.accessor(res as PlTreeEntry).node();
|
|
179
|
+
const info = node.resourceInfo;
|
|
180
|
+
|
|
181
|
+
const fValues: [string, ResourceId | undefined][] = fields.map((name) => [
|
|
182
|
+
name,
|
|
183
|
+
node.getField(name)?.value?.id
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...info,
|
|
188
|
+
fields: new Map(fValues),
|
|
189
|
+
data: node.getData() ?? new Uint8Array()
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return res;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @deprecated */
|
|
197
|
+
export type ResourceWithMetadata = {
|
|
198
|
+
readonly id: ResourceId;
|
|
199
|
+
readonly type: ResourceType;
|
|
200
|
+
readonly metadata: Record<string, any>;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** @deprecated */
|
|
204
|
+
export function treeEntryToResourceWithMetadata(
|
|
205
|
+
res: PlTreeEntry | ResourceWithMetadata,
|
|
206
|
+
mdKeys: string[],
|
|
207
|
+
ctx: ComputableCtx
|
|
208
|
+
): ResourceWithMetadata {
|
|
209
|
+
if (!(res instanceof PlTreeEntry)) return res;
|
|
210
|
+
|
|
211
|
+
const node = ctx.accessor(res as PlTreeEntry).node();
|
|
212
|
+
const info = node.resourceInfo;
|
|
213
|
+
const mdEntries: [string, any][] = mdKeys.map((k) => [k, node.getKeyValue(k)]);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
...info,
|
|
217
|
+
metadata: Object.fromEntries(mdEntries)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { isPlTreeEntry, isPlTreeEntryAccessor, isPlTreeNodeAccessor } from './accessors';
|
|
2
|
+
import { PlTreeState } from './state';
|
|
3
|
+
import {
|
|
4
|
+
dField,
|
|
5
|
+
iField,
|
|
6
|
+
ResourceReady,
|
|
7
|
+
TestDynamicRootId1,
|
|
8
|
+
TestDynamicRootState1,
|
|
9
|
+
TestStructuralResourceState1,
|
|
10
|
+
TestValueResourceState1,
|
|
11
|
+
TestErrorResourceState2
|
|
12
|
+
} from './test_utils';
|
|
13
|
+
import { Computable } from '@milaboratories/computable';
|
|
14
|
+
import { NullResourceId, ResourceId } from '@milaboratories/pl-client';
|
|
15
|
+
|
|
16
|
+
function rid(id: bigint): ResourceId {
|
|
17
|
+
return id as ResourceId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test('simple tree test 1', async () => {
|
|
21
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
22
|
+
const entry = tree.entry();
|
|
23
|
+
expect(isPlTreeEntry(entry)).toStrictEqual(true);
|
|
24
|
+
const c1 = Computable.make((c) => {
|
|
25
|
+
const eAcc = c.accessor(entry);
|
|
26
|
+
expect(isPlTreeEntryAccessor(eAcc)).toStrictEqual(true);
|
|
27
|
+
const nAcc = eAcc.node();
|
|
28
|
+
expect(isPlTreeNodeAccessor(nAcc)).toStrictEqual(true);
|
|
29
|
+
return nAcc.traverse('a', 'b')?.getDataAsString();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
33
|
+
await expect(async () => await c1.getValue()).rejects.toThrow(/not found/);
|
|
34
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
35
|
+
|
|
36
|
+
tree.updateFromResourceData([{ ...TestDynamicRootState1, fields: [] }]);
|
|
37
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
38
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
39
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
40
|
+
|
|
41
|
+
tree.updateFromResourceData([{ ...TestDynamicRootState1, fields: [dField('b')] }]);
|
|
42
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
43
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
44
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
45
|
+
|
|
46
|
+
tree.updateFromResourceData([{ ...TestDynamicRootState1, fields: [dField('b'), dField('a')] }]);
|
|
47
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
48
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
49
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
50
|
+
|
|
51
|
+
tree.updateFromResourceData([
|
|
52
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(rid(1n)))] },
|
|
53
|
+
{ ...TestStructuralResourceState1, id: rid(rid(1n)), fields: [iField('b', rid(rid(2n)))] },
|
|
54
|
+
{
|
|
55
|
+
...TestValueResourceState1,
|
|
56
|
+
id: rid(rid(2n)),
|
|
57
|
+
data: new TextEncoder().encode('Test1')
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
61
|
+
expect(await c1.getValue()).toStrictEqual('Test1');
|
|
62
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
63
|
+
|
|
64
|
+
tree.updateFromResourceData([{ ...TestDynamicRootState1, fields: [dField('a')] }]);
|
|
65
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
66
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
67
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('simple tree kv test', async () => {
|
|
71
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
72
|
+
const c1 = Computable.make((c) =>
|
|
73
|
+
c.accessor(tree.entry()).node().traverse('a', 'b')?.getKeyValueAsString('thekey')
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(JSON.stringify(tree.entry())).toMatch(/^"\[ENTRY:/);
|
|
77
|
+
|
|
78
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
79
|
+
await expect(async () => await c1.getValue()).rejects.toThrow(/not found/);
|
|
80
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
81
|
+
|
|
82
|
+
tree.updateFromResourceData([
|
|
83
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(rid(1n)))] },
|
|
84
|
+
{ ...TestStructuralResourceState1, id: rid(rid(1n)), fields: [iField('b', rid(rid(2n)))] },
|
|
85
|
+
{
|
|
86
|
+
...TestValueResourceState1,
|
|
87
|
+
id: rid(rid(2n)),
|
|
88
|
+
data: new TextEncoder().encode('Test1')
|
|
89
|
+
}
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
93
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
94
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
95
|
+
|
|
96
|
+
tree.updateFromResourceData([
|
|
97
|
+
{
|
|
98
|
+
...TestValueResourceState1,
|
|
99
|
+
id: rid(rid(2n)),
|
|
100
|
+
data: new TextEncoder().encode('Test1'),
|
|
101
|
+
kv: [{ key: 'thekey', value: Buffer.from('thevalue') }]
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
106
|
+
expect(await c1.getValue()).toEqual('thevalue');
|
|
107
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
108
|
+
|
|
109
|
+
tree.updateFromResourceData([
|
|
110
|
+
{
|
|
111
|
+
...TestValueResourceState1,
|
|
112
|
+
id: rid(rid(2n)),
|
|
113
|
+
data: new TextEncoder().encode('Test1'),
|
|
114
|
+
kv: []
|
|
115
|
+
}
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
119
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
120
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('partial tree update', async () => {
|
|
124
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
125
|
+
const c1 = Computable.make((c) =>
|
|
126
|
+
c
|
|
127
|
+
.accessor(tree.entry())
|
|
128
|
+
.node()
|
|
129
|
+
.traverse(
|
|
130
|
+
{ field: 'a', assertFieldType: 'Dynamic' },
|
|
131
|
+
{ field: 'b', assertFieldType: 'Dynamic' }
|
|
132
|
+
)
|
|
133
|
+
?.getDataAsString()
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
137
|
+
await expect(async () => await c1.getValue()).rejects.toThrow(/not found/);
|
|
138
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
139
|
+
|
|
140
|
+
tree.updateFromResourceData([
|
|
141
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
|
|
142
|
+
{ ...TestStructuralResourceState1, id: rid(1n), fields: [dField('b', rid(2n))] },
|
|
143
|
+
{
|
|
144
|
+
...TestValueResourceState1,
|
|
145
|
+
id: rid(2n),
|
|
146
|
+
data: new TextEncoder().encode('Test1')
|
|
147
|
+
}
|
|
148
|
+
]);
|
|
149
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
150
|
+
expect(await c1.getValue()).toStrictEqual('Test1');
|
|
151
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
152
|
+
|
|
153
|
+
tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: rid(1n), fields: [] }]);
|
|
154
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
155
|
+
expect(await c1.getValue()).toBeUndefined();
|
|
156
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('resource error', async () => {
|
|
160
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
161
|
+
const c1 = Computable.make((c) =>
|
|
162
|
+
c.accessor(tree.entry()).node().traverse('a', 'b')?.getKeyValueAsString('thekey')
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
166
|
+
await expect(async () => await c1.getValue()).rejects.toThrow(/not found/);
|
|
167
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
168
|
+
|
|
169
|
+
tree.updateFromResourceData([
|
|
170
|
+
{ ...TestDynamicRootState1, error: rid(7n), fields: [] },
|
|
171
|
+
{
|
|
172
|
+
...TestErrorResourceState2,
|
|
173
|
+
id: rid(7n),
|
|
174
|
+
data: Buffer.from('"error"'),
|
|
175
|
+
fields: []
|
|
176
|
+
}
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
expect((await c1.getValueOrError()).type).toEqual('error');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('field error', async () => {
|
|
183
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
184
|
+
const c1 = Computable.make((c) =>
|
|
185
|
+
c.accessor(tree.entry()).node().traverse('b', 'a')?.getKeyValueAsString('thekey')
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(c1.isChanged()).toBeTruthy();
|
|
189
|
+
await expect(async () => await c1.getValue()).rejects.toThrow(/not found/);
|
|
190
|
+
expect(c1.isChanged()).toBeFalsy();
|
|
191
|
+
|
|
192
|
+
tree.updateFromResourceData([
|
|
193
|
+
{
|
|
194
|
+
...TestDynamicRootState1,
|
|
195
|
+
fields: [dField('b', NullResourceId, rid(7n))]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
...TestErrorResourceState2,
|
|
199
|
+
id: rid(7n),
|
|
200
|
+
data: Buffer.from('"error"'),
|
|
201
|
+
fields: []
|
|
202
|
+
}
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
expect((await c1.getValueOrError()).type).toEqual('error');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('exception - deletion of input field', () => {
|
|
209
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
210
|
+
|
|
211
|
+
tree.updateFromResourceData([
|
|
212
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
|
|
213
|
+
{ ...TestStructuralResourceState1, id: rid(1n), fields: [iField('b', rid(2n))] },
|
|
214
|
+
{
|
|
215
|
+
...TestValueResourceState1,
|
|
216
|
+
id: rid(2n),
|
|
217
|
+
data: new TextEncoder().encode('Test1')
|
|
218
|
+
}
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
expect(() =>
|
|
222
|
+
tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: rid(1n), fields: [] }])
|
|
223
|
+
).toThrow(/removal of Input field/);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('exception - addition of input field', () => {
|
|
227
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
228
|
+
|
|
229
|
+
tree.updateFromResourceData([
|
|
230
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
|
|
231
|
+
{
|
|
232
|
+
...TestStructuralResourceState1,
|
|
233
|
+
id: rid(1n),
|
|
234
|
+
fields: [iField('b', rid(2n))],
|
|
235
|
+
...ResourceReady
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
...TestValueResourceState1,
|
|
239
|
+
id: rid(2n),
|
|
240
|
+
data: new TextEncoder().encode('Test1')
|
|
241
|
+
}
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
expect(() =>
|
|
245
|
+
tree.updateFromResourceData([
|
|
246
|
+
{
|
|
247
|
+
...TestStructuralResourceState1,
|
|
248
|
+
id: rid(1n),
|
|
249
|
+
fields: [iField('b', rid(2n)), iField('df')],
|
|
250
|
+
...ResourceReady
|
|
251
|
+
}
|
|
252
|
+
])
|
|
253
|
+
).toThrow(/adding Input/);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('exception - ready without locks 1', () => {
|
|
257
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
258
|
+
|
|
259
|
+
expect(() =>
|
|
260
|
+
tree.updateFromResourceData([
|
|
261
|
+
{
|
|
262
|
+
...TestDynamicRootState1,
|
|
263
|
+
fields: [dField('b'), dField('a', rid(1n))]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
...TestStructuralResourceState1,
|
|
267
|
+
id: rid(1n),
|
|
268
|
+
fields: [iField('b', rid(2n))],
|
|
269
|
+
resourceReady: true
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
...TestValueResourceState1,
|
|
273
|
+
id: rid(2n),
|
|
274
|
+
data: new TextEncoder().encode('Test1')
|
|
275
|
+
}
|
|
276
|
+
])
|
|
277
|
+
).toThrow(/ready without input or output lock/);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('exception - ready without locks 2', () => {
|
|
281
|
+
const tree = new PlTreeState(TestDynamicRootId1);
|
|
282
|
+
|
|
283
|
+
tree.updateFromResourceData([
|
|
284
|
+
{ ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
|
|
285
|
+
{
|
|
286
|
+
...TestStructuralResourceState1,
|
|
287
|
+
id: rid(1n),
|
|
288
|
+
fields: [iField('b', rid(2n))]
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
...TestValueResourceState1,
|
|
292
|
+
id: rid(2n),
|
|
293
|
+
data: new TextEncoder().encode('Test1')
|
|
294
|
+
}
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
expect(() =>
|
|
298
|
+
tree.updateFromResourceData([
|
|
299
|
+
{
|
|
300
|
+
...TestDynamicRootState1,
|
|
301
|
+
fields: [dField('b'), dField('a', rid(1n))]
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
...TestStructuralResourceState1,
|
|
305
|
+
id: rid(1n),
|
|
306
|
+
fields: [iField('b', rid(2n))],
|
|
307
|
+
resourceReady: true
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
...TestValueResourceState1,
|
|
311
|
+
id: rid(2n),
|
|
312
|
+
data: new TextEncoder().encode('Test1')
|
|
313
|
+
}
|
|
314
|
+
])
|
|
315
|
+
).toThrow(/ready without input or output lock/);
|
|
316
|
+
});
|