cojson 0.3.7 → 0.4.1
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/coValue.d.ts +7 -12
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore.d.ts +9 -4
- package/dist/coValueCore.js +69 -33
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/account.d.ts +62 -0
- package/dist/{account.js → coValues/account.js} +19 -11
- package/dist/coValues/account.js.map +1 -0
- package/dist/coValues/coList.d.ts +19 -19
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +31 -23
- package/dist/coValues/coMap.js +4 -6
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.d.ts +20 -20
- package/dist/coValues/coStream.js +2 -1
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/{group.d.ts → coValues/group.d.ts} +27 -38
- package/dist/{group.js → coValues/group.js} +69 -73
- package/dist/coValues/group.js.map +1 -0
- package/dist/ids.d.ts +1 -1
- package/dist/index.d.ts +15 -11
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/localNode.d.ts +20 -7
- package/dist/localNode.js +74 -39
- package/dist/localNode.js.map +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/permissions.d.ts +1 -1
- package/dist/permissions.js +43 -22
- package/dist/permissions.js.map +1 -1
- package/dist/queriedCoValues/queriedAccount.d.ts +13 -0
- package/dist/queriedCoValues/queriedAccount.js +24 -0
- package/dist/queriedCoValues/queriedAccount.js.map +1 -0
- package/dist/queriedCoValues/queriedCoList.d.ts +10 -10
- package/dist/queriedCoValues/queriedCoList.js +11 -15
- package/dist/queriedCoValues/queriedCoList.js.map +1 -1
- package/dist/queriedCoValues/queriedCoMap.d.ts +14 -21
- package/dist/queriedCoValues/queriedCoMap.js +27 -28
- package/dist/queriedCoValues/queriedCoMap.js.map +1 -1
- package/dist/queriedCoValues/queriedCoStream.d.ts +13 -17
- package/dist/queriedCoValues/queriedCoStream.js +57 -32
- package/dist/queriedCoValues/queriedCoStream.js.map +1 -1
- package/dist/queriedCoValues/queriedGroup.d.ts +29 -0
- package/dist/queriedCoValues/queriedGroup.js +54 -0
- package/dist/queriedCoValues/queriedGroup.js.map +1 -0
- package/dist/queries.d.ts +40 -9
- package/dist/queries.js +104 -39
- package/dist/queries.js.map +1 -1
- package/dist/tests/testUtils.d.ts +15 -7
- package/dist/tests/testUtils.js +16 -17
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/coValue.ts +12 -31
- package/src/coValueCore.ts +100 -40
- package/src/{account.ts → coValues/account.ts} +46 -27
- package/src/coValues/coList.ts +24 -28
- package/src/coValues/coMap.ts +42 -68
- package/src/coValues/coStream.ts +22 -28
- package/src/{group.ts → coValues/group.ts} +121 -141
- package/src/ids.ts +1 -1
- package/src/index.ts +25 -10
- package/src/localNode.ts +180 -77
- package/src/media.ts +1 -1
- package/src/permissions.ts +67 -36
- package/src/queriedCoValues/queriedAccount.ts +40 -0
- package/src/queriedCoValues/queriedCoList.ts +22 -30
- package/src/queriedCoValues/queriedCoMap.ts +60 -72
- package/src/queriedCoValues/queriedCoStream.ts +105 -79
- package/src/queriedCoValues/queriedGroup.ts +90 -0
- package/src/queries.ts +181 -60
- package/src/tests/account.test.ts +14 -9
- package/src/tests/coValueCore.test.ts +2 -2
- package/src/tests/permissions.test.ts +351 -242
- package/src/tests/queries.test.ts +162 -82
- package/src/tests/sync.test.ts +11 -11
- package/src/tests/testUtils.ts +16 -18
- package/dist/account.d.ts +0 -58
- package/dist/account.js.map +0 -1
- package/dist/group.js.map +0 -1
package/src/queries.ts
CHANGED
|
@@ -2,26 +2,37 @@ import { JsonValue } from "./jsonValue.js";
|
|
|
2
2
|
import { CoMap } from "./coValues/coMap.js";
|
|
3
3
|
import { CoStream } from "./coValues/coStream.js";
|
|
4
4
|
import { CoList } from "./coValues/coList.js";
|
|
5
|
-
import { AccountID } from "./account.js";
|
|
6
|
-
import {
|
|
5
|
+
import { Account, AccountID } from "./coValues/account.js";
|
|
6
|
+
import { CoID, CoValue } from "./coValue.js";
|
|
7
7
|
import { LocalNode } from "./localNode.js";
|
|
8
8
|
import {
|
|
9
|
-
QueriedAccountAndProfile,
|
|
10
9
|
QueriedCoMap,
|
|
11
10
|
QueriedCoMapBase,
|
|
12
11
|
} from "./queriedCoValues/queriedCoMap.js";
|
|
13
12
|
import { QueriedCoList } from "./queriedCoValues/queriedCoList.js";
|
|
14
13
|
import { QueriedCoStream } from "./queriedCoValues/queriedCoStream.js";
|
|
14
|
+
import { Group } from "./coValues/group.js";
|
|
15
|
+
import { QueriedAccount } from "./queriedCoValues/queriedAccount.js";
|
|
16
|
+
import { QueriedGroup } from "./queriedCoValues/queriedGroup.js";
|
|
15
17
|
|
|
16
|
-
export type Queried<T extends CoValue> = T extends
|
|
17
|
-
?
|
|
18
|
-
|
|
18
|
+
export type Queried<T extends CoValue> = T extends CoMap
|
|
19
|
+
? T extends Account
|
|
20
|
+
? QueriedAccount<T>
|
|
21
|
+
: T extends Group
|
|
22
|
+
? QueriedGroup<T>
|
|
23
|
+
: QueriedCoMap<T>
|
|
24
|
+
: T extends CoList
|
|
19
25
|
? QueriedCoList<T>
|
|
20
|
-
: T extends
|
|
26
|
+
: T extends CoStream
|
|
21
27
|
? T["meta"] extends { type: "binary" }
|
|
22
28
|
? never
|
|
23
29
|
: QueriedCoStream<T>
|
|
24
|
-
:
|
|
30
|
+
:
|
|
31
|
+
| QueriedAccount
|
|
32
|
+
| QueriedGroup
|
|
33
|
+
| QueriedCoMap<CoMap>
|
|
34
|
+
| QueriedCoList<CoList>
|
|
35
|
+
| QueriedCoStream<CoStream>;
|
|
25
36
|
|
|
26
37
|
export type ValueOrSubQueried<
|
|
27
38
|
V extends JsonValue | CoValue | CoID<CoValue> | undefined
|
|
@@ -36,10 +47,27 @@ export interface CleanupCallbackAndUsable {
|
|
|
36
47
|
[Symbol.dispose]: () => void;
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
export interface QueryExtension<T extends CoValue, O> {
|
|
51
|
+
id: string;
|
|
52
|
+
query(
|
|
53
|
+
base: T,
|
|
54
|
+
queryContext: QueryContext,
|
|
55
|
+
onUpdate: (value: O) => void
|
|
56
|
+
): () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
export class QueryContext {
|
|
40
60
|
values: {
|
|
41
61
|
[id: CoID<CoValue>]: {
|
|
62
|
+
lastUpdate: CoValue | undefined;
|
|
42
63
|
lastQueried: Queried<CoValue> | undefined;
|
|
64
|
+
render: () => void;
|
|
65
|
+
unsubscribe: () => void;
|
|
66
|
+
};
|
|
67
|
+
} = {};
|
|
68
|
+
extensions: {
|
|
69
|
+
[id: `${CoID<CoValue>}_${string}`]: {
|
|
70
|
+
lastOutput: unknown;
|
|
43
71
|
unsubscribe: () => void;
|
|
44
72
|
};
|
|
45
73
|
} = {};
|
|
@@ -51,13 +79,68 @@ export class QueryContext {
|
|
|
51
79
|
this.onUpdate = onUpdate;
|
|
52
80
|
}
|
|
53
81
|
|
|
54
|
-
|
|
82
|
+
query<T extends CoValue>(valueID: CoID<T>, alsoRender: CoID<CoValue>[]) {
|
|
55
83
|
let value = this.values[valueID];
|
|
56
84
|
if (!value) {
|
|
85
|
+
const render = () => {
|
|
86
|
+
let newQueried;
|
|
87
|
+
const lastUpdate = value!.lastUpdate;
|
|
88
|
+
|
|
89
|
+
if (lastUpdate instanceof CoMap) {
|
|
90
|
+
if (lastUpdate instanceof Account) {
|
|
91
|
+
newQueried = new QueriedAccount(
|
|
92
|
+
lastUpdate,
|
|
93
|
+
this
|
|
94
|
+
) as Queried<T>;
|
|
95
|
+
} else if (lastUpdate instanceof Group) {
|
|
96
|
+
newQueried = new QueriedGroup(
|
|
97
|
+
lastUpdate,
|
|
98
|
+
this
|
|
99
|
+
) as Queried<T>;
|
|
100
|
+
} else {
|
|
101
|
+
newQueried = QueriedCoMapBase.newWithKVPairs(
|
|
102
|
+
lastUpdate,
|
|
103
|
+
this
|
|
104
|
+
) as Queried<T>;
|
|
105
|
+
}
|
|
106
|
+
} else if (lastUpdate instanceof CoList) {
|
|
107
|
+
newQueried = new QueriedCoList(
|
|
108
|
+
lastUpdate,
|
|
109
|
+
this
|
|
110
|
+
) as Queried<T>;
|
|
111
|
+
} else if (lastUpdate instanceof CoStream) {
|
|
112
|
+
if (lastUpdate.meta?.type === "binary") {
|
|
113
|
+
// Querying binary string not yet implemented
|
|
114
|
+
} else {
|
|
115
|
+
newQueried = new QueriedCoStream(
|
|
116
|
+
lastUpdate,
|
|
117
|
+
this
|
|
118
|
+
) as Queried<T>;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// console.log(
|
|
123
|
+
// "Rendered ",
|
|
124
|
+
// valueID,
|
|
125
|
+
// lastUpdate?.constructor.name,
|
|
126
|
+
// newQueried
|
|
127
|
+
// );
|
|
128
|
+
|
|
129
|
+
value!.lastQueried = newQueried;
|
|
130
|
+
|
|
131
|
+
for (const alsoRenderID of alsoRender) {
|
|
132
|
+
// console.log("Also rendering", alsoRenderID);
|
|
133
|
+
this.values[alsoRenderID]?.render();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
57
137
|
value = {
|
|
58
138
|
lastQueried: undefined,
|
|
59
|
-
|
|
60
|
-
|
|
139
|
+
lastUpdate: undefined,
|
|
140
|
+
render,
|
|
141
|
+
unsubscribe: this.node.subscribe(valueID, (valueUpdate) => {
|
|
142
|
+
value!.lastUpdate = valueUpdate;
|
|
143
|
+
value!.render();
|
|
61
144
|
this.onUpdate();
|
|
62
145
|
}),
|
|
63
146
|
};
|
|
@@ -66,25 +149,91 @@ export class QueryContext {
|
|
|
66
149
|
return value.lastQueried as Queried<T> | undefined;
|
|
67
150
|
}
|
|
68
151
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
152
|
+
queryIfCoID<T extends JsonValue | undefined>(value: T, alsoRender: CoID<CoValue>[]): T extends CoID<infer C> ? Queried<C> | undefined : T {
|
|
153
|
+
if (typeof value === "string" && value.startsWith("co_")) {
|
|
154
|
+
return this.query(value as CoID<CoValue>, alsoRender) as T extends CoID<infer C> ? Queried<C> | undefined : never;
|
|
155
|
+
} else {
|
|
156
|
+
return value as T extends CoID<infer C> ? Queried<C> | undefined : T;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
valueOrSubQueryPropertyDescriptor<T extends JsonValue | undefined>(
|
|
161
|
+
value: T,
|
|
162
|
+
alsoRender: CoID<CoValue>[]
|
|
163
|
+
): T extends CoID<infer C>
|
|
164
|
+
? { get(): Queried<C> | undefined }
|
|
165
|
+
: { value: T } {
|
|
166
|
+
if (typeof value === "string" && value.startsWith("co_")) {
|
|
167
|
+
// TODO: when we track render dirty status, we can actually return the queried value without a getter if it's up to date
|
|
168
|
+
return {
|
|
169
|
+
get: () => this.query(value as CoID<CoValue>, alsoRender),
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
} as any;
|
|
172
|
+
} else {
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
return { value: value } as any;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
defineSubqueryPropertiesIn<
|
|
179
|
+
O extends object,
|
|
180
|
+
P extends {
|
|
181
|
+
[key: string]: { value: JsonValue | undefined; enumerable: boolean };
|
|
182
|
+
}
|
|
183
|
+
>(
|
|
184
|
+
obj: O,
|
|
185
|
+
subqueryProps: P,
|
|
186
|
+
alsoRender: CoID<CoValue>[]
|
|
187
|
+
): O & {
|
|
188
|
+
[Key in keyof P]: ValueOrSubQueried<P[Key]["value"]>;
|
|
189
|
+
} {
|
|
190
|
+
for (const [key, descriptor] of Object.entries(subqueryProps)) {
|
|
191
|
+
Object.defineProperty(
|
|
192
|
+
obj,
|
|
193
|
+
key,
|
|
194
|
+
{
|
|
195
|
+
...this.valueOrSubQueryPropertyDescriptor(descriptor.value, alsoRender),
|
|
196
|
+
enumerable: descriptor.enumerable,
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return obj as O & {
|
|
201
|
+
[Key in keyof P]: ValueOrSubQueried<P[Key]["value"]>
|
|
202
|
+
};
|
|
73
203
|
}
|
|
74
204
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
205
|
+
getOrCreateExtension<T extends CoValue, O>(
|
|
206
|
+
valueID: CoID<T>,
|
|
207
|
+
extension: QueryExtension<T, O>
|
|
208
|
+
): O | undefined {
|
|
209
|
+
const id = `${valueID}_${extension.id}`;
|
|
210
|
+
let ext = this.extensions[id as keyof typeof this.extensions];
|
|
211
|
+
if (!ext) {
|
|
212
|
+
ext = {
|
|
213
|
+
lastOutput: undefined,
|
|
214
|
+
unsubscribe: extension.query(
|
|
215
|
+
this.node
|
|
216
|
+
.expectCoValueLoaded(valueID)
|
|
217
|
+
.getCurrentContent() as T,
|
|
218
|
+
this,
|
|
219
|
+
(output) => {
|
|
220
|
+
ext!.lastOutput = output;
|
|
221
|
+
this.values[valueID]?.render();
|
|
222
|
+
this.onUpdate();
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
};
|
|
226
|
+
this.extensions[id as keyof typeof this.extensions] = ext;
|
|
227
|
+
}
|
|
228
|
+
return ext.lastOutput as O | undefined;
|
|
83
229
|
}
|
|
84
230
|
|
|
85
231
|
cleanup() {
|
|
86
232
|
for (const child of Object.values(this.values)) {
|
|
87
|
-
child.unsubscribe();
|
|
233
|
+
child.unsubscribe?.();
|
|
234
|
+
}
|
|
235
|
+
for (const extension of Object.values(this.extensions)) {
|
|
236
|
+
extension.unsubscribe();
|
|
88
237
|
}
|
|
89
238
|
}
|
|
90
239
|
}
|
|
@@ -92,49 +241,21 @@ export class QueryContext {
|
|
|
92
241
|
export function query<T extends CoValue>(
|
|
93
242
|
id: CoID<T>,
|
|
94
243
|
node: LocalNode,
|
|
95
|
-
callback: (queried: Queried<T> | undefined) => void
|
|
96
|
-
parentContext?: QueryContext
|
|
244
|
+
callback: (queried: Queried<T> | undefined) => void
|
|
97
245
|
): CleanupCallbackAndUsable {
|
|
98
|
-
console.log("querying", id);
|
|
246
|
+
// console.log("querying", id);
|
|
99
247
|
|
|
100
|
-
const context =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
248
|
+
const context = new QueryContext(node, () => {
|
|
249
|
+
const rootQueried = context.values[id]?.lastQueried as
|
|
250
|
+
| Queried<T>
|
|
251
|
+
| undefined;
|
|
252
|
+
callback(rootQueried);
|
|
105
253
|
});
|
|
106
254
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
function onUpdate() {
|
|
110
|
-
const rootValue = lastRootValue;
|
|
111
|
-
|
|
112
|
-
if (rootValue === undefined) {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (rootValue instanceof CoMap) {
|
|
117
|
-
callback(
|
|
118
|
-
QueriedCoMapBase.newWithKVPairs(
|
|
119
|
-
rootValue,
|
|
120
|
-
context
|
|
121
|
-
) as Queried<T>
|
|
122
|
-
);
|
|
123
|
-
} else if (rootValue instanceof CoList) {
|
|
124
|
-
callback(new QueriedCoList(rootValue, context) as Queried<T>);
|
|
125
|
-
} else if (rootValue instanceof CoStream) {
|
|
126
|
-
if (rootValue.meta?.type === "binary") {
|
|
127
|
-
// Querying binary string not yet implemented
|
|
128
|
-
return {};
|
|
129
|
-
} else {
|
|
130
|
-
callback(new QueriedCoStream(rootValue, context) as Queried<T>);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
255
|
+
context.query(id, []);
|
|
134
256
|
|
|
135
257
|
const cleanup = function cleanup() {
|
|
136
258
|
context.cleanup();
|
|
137
|
-
unsubscribe();
|
|
138
259
|
} as CleanupCallbackAndUsable;
|
|
139
260
|
cleanup[Symbol.dispose] = cleanup;
|
|
140
261
|
|
|
@@ -9,7 +9,7 @@ beforeEach(async () => {
|
|
|
9
9
|
|
|
10
10
|
test("Can create a node while creating a new account with profile", async () => {
|
|
11
11
|
const { node, accountID, accountSecret, sessionID } =
|
|
12
|
-
LocalNode.withNewlyCreatedAccount("Hermes Puggington");
|
|
12
|
+
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
|
13
13
|
|
|
14
14
|
expect(node).not.toBeNull();
|
|
15
15
|
expect(accountID).not.toBeNull();
|
|
@@ -22,8 +22,9 @@ test("Can create a node while creating a new account with profile", async () =>
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test("A node with an account can create groups and and objects within them", async () => {
|
|
25
|
-
const { node, accountID } =
|
|
26
|
-
|
|
25
|
+
const { node, accountID } = LocalNode.withNewlyCreatedAccount({
|
|
26
|
+
name: "Hermes Puggington",
|
|
27
|
+
});
|
|
27
28
|
|
|
28
29
|
const group = await node.createGroup();
|
|
29
30
|
expect(group).not.toBeNull();
|
|
@@ -41,7 +42,7 @@ test("A node with an account can create groups and and objects within them", asy
|
|
|
41
42
|
|
|
42
43
|
test("Can create account with one node, and then load it on another", async () => {
|
|
43
44
|
const { node, accountID, accountSecret } =
|
|
44
|
-
LocalNode.withNewlyCreatedAccount("Hermes Puggington");
|
|
45
|
+
LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
|
|
45
46
|
|
|
46
47
|
const group = await node.createGroup();
|
|
47
48
|
expect(group).not.toBeNull();
|
|
@@ -52,16 +53,20 @@ test("Can create account with one node, and then load it on another", async () =
|
|
|
52
53
|
expect(edit.get("foo")).toEqual("bar");
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
const [node1asPeer, node2asPeer] = connectedPeers("node1", "node2", {
|
|
56
|
+
const [node1asPeer, node2asPeer] = connectedPeers("node1", "node2", {
|
|
57
|
+
trace: true,
|
|
58
|
+
peer1role: "server",
|
|
59
|
+
peer2role: "client",
|
|
60
|
+
});
|
|
56
61
|
|
|
57
62
|
node.syncManager.addPeer(node2asPeer);
|
|
58
63
|
|
|
59
|
-
const node2 = await LocalNode.withLoadedAccount(
|
|
64
|
+
const node2 = await LocalNode.withLoadedAccount({
|
|
60
65
|
accountID,
|
|
61
66
|
accountSecret,
|
|
62
|
-
newRandomSessionID(accountID),
|
|
63
|
-
[node1asPeer]
|
|
64
|
-
);
|
|
67
|
+
sessionID: newRandomSessionID(accountID),
|
|
68
|
+
peersToLoadFrom: [node1asPeer],
|
|
69
|
+
});
|
|
65
70
|
|
|
66
71
|
const map2 = await node2.load(map.id);
|
|
67
72
|
|
|
@@ -164,7 +164,7 @@ test("New transactions in a group correctly update owned values, including subsc
|
|
|
164
164
|
])
|
|
165
165
|
} satisfies Transaction;
|
|
166
166
|
|
|
167
|
-
const { expectedNewHash } = group.
|
|
167
|
+
const { expectedNewHash } = group.core.expectedNewHashAfter(sessionID, [
|
|
168
168
|
resignationThatWeJustLearnedAbout,
|
|
169
169
|
]);
|
|
170
170
|
|
|
@@ -175,7 +175,7 @@ test("New transactions in a group correctly update owned values, including subsc
|
|
|
175
175
|
|
|
176
176
|
expect(map.core.getValidSortedTransactions().length).toBe(1);
|
|
177
177
|
|
|
178
|
-
const manuallyAdddedTxSuccess = group.
|
|
178
|
+
const manuallyAdddedTxSuccess = group.core.tryAddTransactions(node.currentSessionID, [resignationThatWeJustLearnedAbout], expectedNewHash, signature);
|
|
179
179
|
|
|
180
180
|
expect(manuallyAdddedTxSuccess).toBe(true);
|
|
181
181
|
|