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.
Files changed (79) hide show
  1. package/dist/coValue.d.ts +7 -12
  2. package/dist/coValue.js.map +1 -1
  3. package/dist/coValueCore.d.ts +9 -4
  4. package/dist/coValueCore.js +69 -33
  5. package/dist/coValueCore.js.map +1 -1
  6. package/dist/coValues/account.d.ts +62 -0
  7. package/dist/{account.js → coValues/account.js} +19 -11
  8. package/dist/coValues/account.js.map +1 -0
  9. package/dist/coValues/coList.d.ts +19 -19
  10. package/dist/coValues/coList.js.map +1 -1
  11. package/dist/coValues/coMap.d.ts +31 -23
  12. package/dist/coValues/coMap.js +4 -6
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.d.ts +20 -20
  15. package/dist/coValues/coStream.js +2 -1
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/{group.d.ts → coValues/group.d.ts} +27 -38
  18. package/dist/{group.js → coValues/group.js} +69 -73
  19. package/dist/coValues/group.js.map +1 -0
  20. package/dist/ids.d.ts +1 -1
  21. package/dist/index.d.ts +15 -11
  22. package/dist/index.js +10 -5
  23. package/dist/index.js.map +1 -1
  24. package/dist/localNode.d.ts +20 -7
  25. package/dist/localNode.js +74 -39
  26. package/dist/localNode.js.map +1 -1
  27. package/dist/media.d.ts +1 -1
  28. package/dist/permissions.d.ts +1 -1
  29. package/dist/permissions.js +43 -22
  30. package/dist/permissions.js.map +1 -1
  31. package/dist/queriedCoValues/queriedAccount.d.ts +13 -0
  32. package/dist/queriedCoValues/queriedAccount.js +24 -0
  33. package/dist/queriedCoValues/queriedAccount.js.map +1 -0
  34. package/dist/queriedCoValues/queriedCoList.d.ts +10 -10
  35. package/dist/queriedCoValues/queriedCoList.js +11 -15
  36. package/dist/queriedCoValues/queriedCoList.js.map +1 -1
  37. package/dist/queriedCoValues/queriedCoMap.d.ts +14 -21
  38. package/dist/queriedCoValues/queriedCoMap.js +27 -28
  39. package/dist/queriedCoValues/queriedCoMap.js.map +1 -1
  40. package/dist/queriedCoValues/queriedCoStream.d.ts +13 -17
  41. package/dist/queriedCoValues/queriedCoStream.js +57 -32
  42. package/dist/queriedCoValues/queriedCoStream.js.map +1 -1
  43. package/dist/queriedCoValues/queriedGroup.d.ts +29 -0
  44. package/dist/queriedCoValues/queriedGroup.js +54 -0
  45. package/dist/queriedCoValues/queriedGroup.js.map +1 -0
  46. package/dist/queries.d.ts +40 -9
  47. package/dist/queries.js +104 -39
  48. package/dist/queries.js.map +1 -1
  49. package/dist/tests/testUtils.d.ts +15 -7
  50. package/dist/tests/testUtils.js +16 -17
  51. package/dist/tests/testUtils.js.map +1 -1
  52. package/package.json +2 -2
  53. package/src/coValue.ts +12 -31
  54. package/src/coValueCore.ts +100 -40
  55. package/src/{account.ts → coValues/account.ts} +46 -27
  56. package/src/coValues/coList.ts +24 -28
  57. package/src/coValues/coMap.ts +42 -68
  58. package/src/coValues/coStream.ts +22 -28
  59. package/src/{group.ts → coValues/group.ts} +121 -141
  60. package/src/ids.ts +1 -1
  61. package/src/index.ts +25 -10
  62. package/src/localNode.ts +180 -77
  63. package/src/media.ts +1 -1
  64. package/src/permissions.ts +67 -36
  65. package/src/queriedCoValues/queriedAccount.ts +40 -0
  66. package/src/queriedCoValues/queriedCoList.ts +22 -30
  67. package/src/queriedCoValues/queriedCoMap.ts +60 -72
  68. package/src/queriedCoValues/queriedCoStream.ts +105 -79
  69. package/src/queriedCoValues/queriedGroup.ts +90 -0
  70. package/src/queries.ts +181 -60
  71. package/src/tests/account.test.ts +14 -9
  72. package/src/tests/coValueCore.test.ts +2 -2
  73. package/src/tests/permissions.test.ts +351 -242
  74. package/src/tests/queries.test.ts +162 -82
  75. package/src/tests/sync.test.ts +11 -11
  76. package/src/tests/testUtils.ts +16 -18
  77. package/dist/account.d.ts +0 -58
  78. package/dist/account.js.map +0 -1
  79. 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 { AnyCoList, AnyCoMap, AnyCoStream, CoID, CoValue } from "./coValue.js";
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 AnyCoMap
17
- ? QueriedCoMap<T>
18
- : T extends AnyCoList
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 AnyCoStream
26
+ : T extends CoStream
21
27
  ? T["meta"] extends { type: "binary" }
22
28
  ? never
23
29
  : QueriedCoStream<T>
24
- : never;
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
- getChildLastQueriedOrSubscribe<T extends CoValue>(valueID: CoID<T>) {
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
- unsubscribe: query(valueID, this.node, (childQueried) => {
60
- value!.lastQueried = childQueried as Queried<CoValue>;
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
- resolveAccount(accountID: AccountID) {
70
- return this.getChildLastQueriedOrSubscribe(
71
- accountID
72
- ) as QueriedAccountAndProfile;
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
- resolveValue<T extends JsonValue>(
76
- value: T
77
- ): T extends CoID<infer C> ? Queried<C> | undefined : T {
78
- return (
79
- typeof value === "string" && value.startsWith("co_")
80
- ? this.getChildLastQueriedOrSubscribe(value as CoID<CoValue>)
81
- : value
82
- ) as T extends CoID<infer C> ? Queried<C> | undefined : T;
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 = parentContext || new QueryContext(node, onUpdate);
101
-
102
- const unsubscribe = node.subscribe(id, (update) => {
103
- lastRootValue = update;
104
- onUpdate();
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
- let lastRootValue: T | undefined;
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
- LocalNode.withNewlyCreatedAccount("Hermes Puggington");
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", {trace: true, peer1role: "server", peer2role: "client"});
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.underlyingMap.core.expectedNewHashAfter(sessionID, [
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.underlyingMap.core.tryAddTransactions(node.currentSessionID, [resignationThatWeJustLearnedAbout], expectedNewHash, signature);
178
+ const manuallyAdddedTxSuccess = group.core.tryAddTransactions(node.currentSessionID, [resignationThatWeJustLearnedAbout], expectedNewHash, signature);
179
179
 
180
180
  expect(manuallyAdddedTxSuccess).toBe(true);
181
181