jazz-tools 0.7.0-alpha.0

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 (85) hide show
  1. package/.eslintrc.cjs +24 -0
  2. package/.turbo/turbo-build.log +24 -0
  3. package/CHANGELOG.md +42 -0
  4. package/LICENSE.txt +19 -0
  5. package/README.md +3 -0
  6. package/dist/coValueInterfaces.js +8 -0
  7. package/dist/coValueInterfaces.js.map +1 -0
  8. package/dist/coValues/account/account.js +11 -0
  9. package/dist/coValues/account/account.js.map +1 -0
  10. package/dist/coValues/account/accountOf.js +150 -0
  11. package/dist/coValues/account/accountOf.js.map +1 -0
  12. package/dist/coValues/account/migration.js +4 -0
  13. package/dist/coValues/account/migration.js.map +1 -0
  14. package/dist/coValues/coList/coList.js +2 -0
  15. package/dist/coValues/coList/coList.js.map +1 -0
  16. package/dist/coValues/coList/coListOf.js +235 -0
  17. package/dist/coValues/coList/coListOf.js.map +1 -0
  18. package/dist/coValues/coList/internalDocs.js +2 -0
  19. package/dist/coValues/coList/internalDocs.js.map +1 -0
  20. package/dist/coValues/coMap/coMap.js +2 -0
  21. package/dist/coValues/coMap/coMap.js.map +1 -0
  22. package/dist/coValues/coMap/coMapOf.js +262 -0
  23. package/dist/coValues/coMap/coMapOf.js.map +1 -0
  24. package/dist/coValues/coMap/internalDocs.js +2 -0
  25. package/dist/coValues/coMap/internalDocs.js.map +1 -0
  26. package/dist/coValues/coStream/coStream.js +2 -0
  27. package/dist/coValues/coStream/coStream.js.map +1 -0
  28. package/dist/coValues/coStream/coStreamOf.js +244 -0
  29. package/dist/coValues/coStream/coStreamOf.js.map +1 -0
  30. package/dist/coValues/construction.js +34 -0
  31. package/dist/coValues/construction.js.map +1 -0
  32. package/dist/coValues/extensions/imageDef.js +36 -0
  33. package/dist/coValues/extensions/imageDef.js.map +1 -0
  34. package/dist/coValues/group/group.js +2 -0
  35. package/dist/coValues/group/group.js.map +1 -0
  36. package/dist/coValues/group/groupOf.js +109 -0
  37. package/dist/coValues/group/groupOf.js.map +1 -0
  38. package/dist/coValues/resolution.js +66 -0
  39. package/dist/coValues/resolution.js.map +1 -0
  40. package/dist/errors.js +2 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/index.js +31 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/refs.js +95 -0
  45. package/dist/refs.js.map +1 -0
  46. package/dist/schemaHelpers.js +14 -0
  47. package/dist/schemaHelpers.js.map +1 -0
  48. package/dist/subscriptionScope.js +81 -0
  49. package/dist/subscriptionScope.js.map +1 -0
  50. package/dist/tests/coList.test.js +207 -0
  51. package/dist/tests/coList.test.js.map +1 -0
  52. package/dist/tests/coMap.test.js +238 -0
  53. package/dist/tests/coMap.test.js.map +1 -0
  54. package/dist/tests/coStream.test.js +263 -0
  55. package/dist/tests/coStream.test.js.map +1 -0
  56. package/dist/tests/types.test.js +33 -0
  57. package/dist/tests/types.test.js.map +1 -0
  58. package/package.json +23 -0
  59. package/src/coValueInterfaces.ts +105 -0
  60. package/src/coValues/account/account.ts +106 -0
  61. package/src/coValues/account/accountOf.ts +284 -0
  62. package/src/coValues/account/migration.ts +12 -0
  63. package/src/coValues/coList/coList.ts +57 -0
  64. package/src/coValues/coList/coListOf.ts +377 -0
  65. package/src/coValues/coList/internalDocs.ts +1 -0
  66. package/src/coValues/coMap/coMap.ts +110 -0
  67. package/src/coValues/coMap/coMapOf.ts +451 -0
  68. package/src/coValues/coMap/internalDocs.ts +1 -0
  69. package/src/coValues/coStream/coStream.ts +63 -0
  70. package/src/coValues/coStream/coStreamOf.ts +404 -0
  71. package/src/coValues/construction.ts +110 -0
  72. package/src/coValues/extensions/imageDef.ts +51 -0
  73. package/src/coValues/group/group.ts +27 -0
  74. package/src/coValues/group/groupOf.ts +183 -0
  75. package/src/coValues/resolution.ts +111 -0
  76. package/src/errors.ts +1 -0
  77. package/src/index.ts +68 -0
  78. package/src/refs.ts +128 -0
  79. package/src/schemaHelpers.ts +72 -0
  80. package/src/subscriptionScope.ts +118 -0
  81. package/src/tests/coList.test.ts +283 -0
  82. package/src/tests/coMap.test.ts +357 -0
  83. package/src/tests/coStream.test.ts +415 -0
  84. package/src/tests/types.test.ts +37 -0
  85. package/tsconfig.json +15 -0
@@ -0,0 +1,183 @@
1
+ import {
2
+ CoValueSchema,
3
+ ID,
4
+ inspect,
5
+ isCoValueSchema,
6
+ } from "../../coValueInterfaces.js";
7
+ import * as S from "@effect/schema/Schema";
8
+ import { AnyGroup, GroupSchema } from "./group.js";
9
+ import {
10
+ AnyAccount,
11
+ ControlledAccount,
12
+ isControlledAccount,
13
+ } from "../account/account.js";
14
+ import { AST, Schema } from "@effect/schema";
15
+ import { constructorOfSchemaSym } from "../resolution.js";
16
+ import { pipeArguments } from "effect/Pipeable";
17
+ import { Everyone, RawGroup, Role } from "cojson";
18
+ import { ValueRef } from "../../refs.js";
19
+ import { controlledAccountFromNode } from "../account/accountOf.js";
20
+ import { SharedCoValueConstructor } from "../construction.js";
21
+
22
+ export function GroupOf<
23
+ P extends CoValueSchema | S.Schema<null>,
24
+ R extends CoValueSchema | S.Schema<null>,
25
+ >(fields: { profile: P; root: R }) {
26
+ class GroupOfProfileAndRoot
27
+ extends SharedCoValueConstructor
28
+ implements AnyGroup<P, R>
29
+ {
30
+ static get ast() {
31
+ return AST.setAnnotation(
32
+ Schema.instanceOf(this).ast,
33
+ constructorOfSchemaSym,
34
+ this
35
+ );
36
+ }
37
+ static [Schema.TypeId]: Schema.Schema.Variance<
38
+ AnyGroup<P, R> & GroupOfProfileAndRoot,
39
+ AnyGroup<P, R> & GroupOfProfileAndRoot,
40
+ never
41
+ >[Schema.TypeId];
42
+ static pipe() {
43
+ // eslint-disable-next-line prefer-rest-params
44
+ return pipeArguments(this, arguments);
45
+ }
46
+ static type = "Group" as const;
47
+
48
+ id!: ID<this>;
49
+ _type!: "Group";
50
+ _owner!: AnyAccount | AnyGroup;
51
+ _refs!: AnyGroup<P, R>["_refs"];
52
+ _raw!: RawGroup;
53
+ _loadedAs!: ControlledAccount;
54
+ _schema!: typeof GroupOfProfileAndRoot;
55
+
56
+ constructor(options: { owner: AnyAccount | AnyGroup });
57
+ constructor(init: any, options: { fromRaw: RawGroup });
58
+ constructor(init: undefined, options: { owner: AnyAccount | AnyGroup });
59
+ constructor(
60
+ init: undefined | { owner: AnyAccount | AnyGroup },
61
+ options?: { fromRaw: RawGroup } | { owner: AnyAccount | AnyGroup }
62
+ ) {
63
+ super();
64
+ let raw: RawGroup;
65
+
66
+ if (options && "fromRaw" in options) {
67
+ raw = options.fromRaw;
68
+ } else {
69
+ const initOwner = options?.owner || init?.owner;
70
+ if (!initOwner) throw new Error("No owner provided");
71
+ if (isControlledAccount(initOwner)) {
72
+ const rawOwner = initOwner._raw;
73
+ raw = rawOwner.createGroup();
74
+ } else {
75
+ throw new Error(
76
+ "Can only construct group as a controlled account"
77
+ );
78
+ }
79
+ }
80
+
81
+ const refs = {
82
+ get profile() {
83
+ if (isCoValueSchema(fields.profile)) {
84
+ const profileID = raw.get("profile");
85
+ return (
86
+ profileID &&
87
+ new ValueRef(
88
+ profileID as unknown as ID<
89
+ Schema.Schema.To<typeof fields.profile>
90
+ >,
91
+ controlledAccountFromNode(raw.core.node),
92
+ fields.profile
93
+ )
94
+ );
95
+ }
96
+ },
97
+ get root() {
98
+ if (isCoValueSchema(fields.root)) {
99
+ const rootID = raw.get("root");
100
+ return (
101
+ rootID &&
102
+ new ValueRef(
103
+ rootID as unknown as ID<
104
+ Schema.Schema.To<typeof fields.root>
105
+ >,
106
+ controlledAccountFromNode(raw.core.node),
107
+ fields.root
108
+ )
109
+ );
110
+ }
111
+ },
112
+ };
113
+
114
+ Object.defineProperties(this, {
115
+ id: { value: raw.id, enumerable: false },
116
+ _type: { value: "Group", enumerable: false },
117
+ _owner: { value: this, enumerable: false },
118
+ _refs: { value: refs, enumerable: false },
119
+ _raw: { value: raw, enumerable: false },
120
+ _loadedAs: {
121
+ get: () => controlledAccountFromNode(raw.core.node),
122
+ enumerable: false,
123
+ },
124
+ _schema: { value: GroupOfProfileAndRoot, enumerable: false },
125
+ });
126
+ }
127
+
128
+ static fromRaw(raw: RawGroup) {
129
+ return new GroupOfProfileAndRoot(undefined, {
130
+ fromRaw: raw,
131
+ });
132
+ }
133
+
134
+ get profile(): S.Schema.To<P> | undefined {
135
+ return this._refs.profile.accessFrom(this);
136
+ }
137
+
138
+ get root(): S.Schema.To<R> | undefined {
139
+ return this._refs.root.accessFrom(this);
140
+ }
141
+
142
+ addMember(member: AnyAccount | Everyone, role: Role) {
143
+ this._raw.addMember(
144
+ typeof member === "string" ? member : member._raw,
145
+ role
146
+ );
147
+
148
+ return this;
149
+ }
150
+
151
+ myRole(): Role | undefined {
152
+ return this._raw.myRole();
153
+ }
154
+
155
+ toJSON() {
156
+ return {
157
+ co: {
158
+ id: this.id,
159
+ type: this._type,
160
+ },
161
+ profile: this.profile?.toJSON(),
162
+ root: this.root?.toJSON(),
163
+ };
164
+ }
165
+
166
+ [inspect]() {
167
+ return this.toJSON();
168
+ }
169
+
170
+ static as<SubClass>() {
171
+ return this as unknown as GroupSchema<SubClass, P, R>;
172
+ }
173
+ }
174
+
175
+ return GroupOfProfileAndRoot as GroupSchema<GroupOfProfileAndRoot, P, R> & {
176
+ as<SubClass>(): GroupSchema<SubClass, P, R>;
177
+ };
178
+ }
179
+
180
+ export class Group extends GroupOf({
181
+ profile: S.null,
182
+ root: S.null,
183
+ }).as<Group>() {}
@@ -0,0 +1,111 @@
1
+ import { Schema, AST } from "@effect/schema";
2
+ import { RawCoValue } from "cojson";
3
+ import { CoValue, CoValueSchema } from "../coValueInterfaces.js";
4
+
5
+ // TODO: can we get rid of this because effect schema already has an annotation for the constructor?
6
+ export const constructorOfSchemaSym = Symbol.for("@jazz/constructorOfSymbol");
7
+ export type constructorOfSchemaSym = typeof constructorOfSchemaSym;
8
+
9
+ export function propertyIsCoValueSchema(
10
+ prop:
11
+ | Schema.Schema<any>
12
+ | Schema.PropertySignature<any, boolean, any, boolean, never>
13
+ ): prop is CoValueSchema {
14
+ if ("propertySignatureAST" in prop) {
15
+ return astIsCoValueSchema(
16
+ (prop.propertySignatureAST as { from: AST.AST }).from
17
+ );
18
+ } else {
19
+ return astIsCoValueSchema((prop as Schema.Schema<unknown>).ast);
20
+ }
21
+ }
22
+
23
+ function astIsCoValueSchema(ast: AST.AST): boolean {
24
+ if (
25
+ (ast._tag === "Declaration") &&
26
+ ast.annotations[constructorOfSchemaSym]
27
+ ) {
28
+ return true;
29
+ } else if (ast._tag === "Union") {
30
+ return ast.types.every(
31
+ (member) =>
32
+ member._tag === "UndefinedKeyword" || astIsCoValueSchema(member)
33
+ );
34
+ } else if (
35
+ ast._tag === "BooleanKeyword" ||
36
+ ast._tag === "NumberKeyword" ||
37
+ ast._tag === "StringKeyword" ||
38
+ ast._tag === "Literal" ||
39
+ ast._tag === "Tuple"
40
+ ) {
41
+ return false;
42
+ } else if (ast._tag === "Refinement") {
43
+ return astIsCoValueSchema(ast.from);
44
+ } else if (ast._tag === "Transform") {
45
+ return astIsCoValueSchema(ast.from);
46
+ } else {
47
+ throw new Error(
48
+ `astIsCoValueSchema can't yet handle ${ast._tag}: ${JSON.stringify(
49
+ ast
50
+ )}`
51
+ );
52
+ }
53
+ }
54
+
55
+ export function getCoValueConstructorInProperty(
56
+ prop:
57
+ | Schema.Schema<unknown>
58
+ | Schema.PropertySignature<unknown, boolean, unknown, boolean, never>,
59
+ rawValue: RawCoValue
60
+ ):
61
+ | (new (init: undefined, options: { fromRaw: RawCoValue }) => CoValue)
62
+ | undefined {
63
+ if ("propertySignatureAST" in prop) {
64
+ return getCoValueConstructorInAST(
65
+ (prop.propertySignatureAST as { from: AST.AST }).from,
66
+ rawValue
67
+ );
68
+ } else {
69
+ return getCoValueConstructorInAST(
70
+ (prop as Schema.Schema<unknown>).ast,
71
+ rawValue
72
+ );
73
+ }
74
+ }
75
+
76
+ // TODO (optimization): make this meta, so this creates a tailor-made function that will take a RawCoValue at call time
77
+ function getCoValueConstructorInAST(
78
+ ast: AST.AST,
79
+ rawValue: RawCoValue
80
+ ):
81
+ | (new (init: undefined, options: { fromRaw: RawCoValue }) => CoValue)
82
+ | undefined {
83
+ if (
84
+ (ast._tag === "Declaration") &&
85
+ ast.annotations[constructorOfSchemaSym]
86
+ ) {
87
+ return ast.annotations[constructorOfSchemaSym] as new (
88
+ init: undefined,
89
+ options: { fromRaw: RawCoValue }
90
+ ) => CoValue;
91
+ } else if (ast._tag === "Union" && ast.types.length === 2) {
92
+ const [a, b] = ast.types;
93
+ if (a._tag === "UndefinedKeyword") {
94
+ return getCoValueConstructorInAST(b, rawValue);
95
+ } else if (b._tag === "UndefinedKeyword") {
96
+ return getCoValueConstructorInAST(a, rawValue);
97
+ } else {
98
+ throw new Error(
99
+ `getCoValueConstructorInAST can't yet handle Union of: ${JSON.stringify(
100
+ ast.types
101
+ )}`
102
+ );
103
+ }
104
+ } else {
105
+ throw new Error(
106
+ `getCoValueConstructorInAST can't yet handle ${
107
+ ast._tag
108
+ }: ${JSON.stringify(ast)}`
109
+ );
110
+ }
111
+ }
package/src/errors.ts ADDED
@@ -0,0 +1 @@
1
+ export type UnavailableError = "unavailable";
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { AccountOf } from "./coValues/account/accountOf.js";
2
+ import { CoListOf } from "./coValues/coList/coListOf.js";
3
+ import { CoMapOf } from "./coValues/coMap/coMapOf.js";
4
+ import {
5
+ BinaryCoStreamImpl,
6
+ CoStreamOf,
7
+ } from "./coValues/coStream/coStreamOf.js";
8
+ import { ImageDefinition } from "./coValues/extensions/imageDef.js";
9
+ import { GroupOf } from "./coValues/group/groupOf.js";
10
+
11
+ /** @hidden */
12
+ export * as S from "@effect/schema/Schema";
13
+
14
+ /** @category Schemas & CoValues - Schema definers */
15
+ export const Co = {
16
+ map: CoMapOf,
17
+ list: CoListOf,
18
+ stream: CoStreamOf,
19
+ binaryStream: BinaryCoStreamImpl,
20
+ account: AccountOf,
21
+ group: GroupOf,
22
+ media: {
23
+ imageDef: ImageDefinition
24
+ }
25
+ };
26
+
27
+ /** @category Internal types */
28
+ export {
29
+ cojsonReady as jazzReady,
30
+ InviteSecret,
31
+ Peer,
32
+ SessionID,
33
+ AgentID,
34
+ SyncMessage,
35
+ cojsonInternals,
36
+ MAX_RECOMMENDED_TX_SIZE,
37
+ } from "cojson";
38
+
39
+ export { ID, CoValue, CoValueSchema } from "./coValueInterfaces.js";
40
+
41
+
42
+ export { CoMap, CoMapSchema } from "./coValues/coMap/coMap.js";
43
+ /** @category Schemas & CoValues - CoMap */
44
+ export * as CoMapInternals from "./coValues/coMap/internalDocs.js";
45
+
46
+ export { CoList, CoListSchema } from "./coValues/coList/coList.js";
47
+ /** @category Schemas & CoValues - CoList */
48
+ export * as CoListInternals from "./coValues/coList/internalDocs.js";
49
+
50
+ export {
51
+ CoStream,
52
+ CoStreamSchema,
53
+ BinaryCoStream,
54
+ } from "./coValues/coStream/coStream.js";
55
+
56
+ export {
57
+ AnyAccount,
58
+ ControlledAccount,
59
+ AccountSchema,
60
+ controlledAccountSym,
61
+ } from "./coValues/account/account.js";
62
+
63
+ export { AccountMigration } from "./coValues/account/migration.js";
64
+ export { Account, BaseProfile } from "./coValues/account/accountOf.js";
65
+ export { AnyGroup } from "./coValues/group/group.js";
66
+ export { Group } from "./coValues/group/groupOf.js";
67
+
68
+ export { ImageDefinition } from "./coValues/extensions/imageDef.js";
package/src/refs.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { Effect } from "effect";
2
+ import { CoValue, ID } from "./coValueInterfaces.js";
3
+ import { ControlledAccount } from "./coValues/account/account.js";
4
+ import { CoID, RawCoValue } from "cojson";
5
+ import { UnavailableError } from "./errors.js";
6
+ import { getCoValueConstructorInProperty } from "./coValues/resolution.js";
7
+ import { PropDef } from "./schemaHelpers.js";
8
+ import { subscriptionsScopes } from "./subscriptionScope.js";
9
+
10
+ export class ValueRef<V extends CoValue> {
11
+ private cachedValue: V | undefined;
12
+
13
+ constructor(
14
+ readonly id: ID<V>,
15
+ readonly controlledAccount: ControlledAccount,
16
+ readonly propDef: PropDef<any>
17
+ ) {}
18
+
19
+ get value() {
20
+ if (this.cachedValue) return this.cachedValue;
21
+ // TODO: cache it for object identity!!!
22
+ const raw = this.controlledAccount._raw.core.node.getLoaded(
23
+ this.id as unknown as CoID<RawCoValue>
24
+ );
25
+ if (raw) {
26
+ const Constructor = getCoValueConstructorInProperty(
27
+ this.propDef,
28
+ raw
29
+ );
30
+ if (!Constructor) {
31
+ throw new Error(
32
+ "Couldn't extract CoValue constructor from property definition"
33
+ );
34
+ }
35
+ const value = new Constructor(undefined, { fromRaw: raw }) as V;
36
+ this.cachedValue = value;
37
+ return value;
38
+ }
39
+ }
40
+
41
+ loadEf() {
42
+ return Effect.async<V, UnavailableError>((fulfill) => {
43
+ this.loadHelper()
44
+ .then((value) => {
45
+ if (value === "unavailable") {
46
+ fulfill(Effect.fail<UnavailableError>("unavailable"));
47
+ } else {
48
+ fulfill(Effect.succeed(value));
49
+ }
50
+ })
51
+ .catch((e) => {
52
+ fulfill(Effect.die(e));
53
+ });
54
+ });
55
+ }
56
+
57
+ private async loadHelper(options?: {onProgress: (p: number) => void}): Promise<V | "unavailable"> {
58
+ const raw = await this.controlledAccount._raw.core.node.load(
59
+ this.id as unknown as CoID<RawCoValue>,
60
+ options?.onProgress
61
+ );
62
+ if (raw === "unavailable") {
63
+ return "unavailable";
64
+ } else {
65
+ return new ValueRef(this.id, this.controlledAccount, this.propDef)
66
+ .value!;
67
+ }
68
+ }
69
+
70
+ async load(options?: {onProgress: (p: number) => void}): Promise<V | undefined> {
71
+ const result = await this.loadHelper(options);
72
+ if (result === "unavailable") {
73
+ return undefined;
74
+ } else {
75
+ return result;
76
+ }
77
+ }
78
+
79
+ accessFrom(fromScopeValue: CoValue): V | undefined {
80
+ const subScope = subscriptionsScopes.get(fromScopeValue);
81
+
82
+ subScope?.onRefAccessedOrSet(this.id);
83
+
84
+ if (this.value && subScope) {
85
+ subscriptionsScopes.set(this.value, subScope);
86
+ }
87
+
88
+ return this.value;
89
+ }
90
+ }
91
+
92
+ export function makeRefs<F extends { [key: string | number]: CoValue }>(
93
+ getIdForKey: <K extends keyof F>(key: K) => F[K]["id"] | undefined,
94
+ getKeysWithIds: () => (keyof F)[],
95
+ controlledAccount: ControlledAccount,
96
+ propDefForKey: <K extends keyof F>(key: K) => PropDef<F[K]>
97
+ ): { [K in keyof F]: ValueRef<F[K]> } {
98
+ const refs = {} as { [K in keyof F]: ValueRef<F[K]> };
99
+ return new Proxy(refs, {
100
+ get(target, key) {
101
+ if (key === Symbol.iterator) {
102
+ return function* () {
103
+ for (const key of getKeysWithIds()) {
104
+ yield new ValueRef(
105
+ getIdForKey(key)!,
106
+ controlledAccount,
107
+ propDefForKey(key)
108
+ );
109
+ }
110
+ };
111
+ }
112
+ if (typeof key === "symbol") return undefined;
113
+ if (key === "length") {
114
+ return getKeysWithIds().length;
115
+ }
116
+ const id = getIdForKey(key as keyof F);
117
+ if (!id) return undefined;
118
+ return new ValueRef(
119
+ id as ID<F[typeof key]>,
120
+ controlledAccount,
121
+ propDefForKey(key as keyof F)
122
+ );
123
+ },
124
+ ownKeys() {
125
+ return getKeysWithIds().map((key) => key.toString());
126
+ },
127
+ });
128
+ }
@@ -0,0 +1,72 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { AST } from "@effect/schema";
3
+ import * as S from "@effect/schema/Schema";
4
+
5
+ export type SchemaWithOutput<I> = S.Schema<any, any, never> & {
6
+ [S.TypeId]: {
7
+ _I: (_: any) => I;
8
+ };
9
+ };
10
+
11
+ export type SchemaWithInputAndOutput<A, I = A> = S.Schema<any, any, never> & {
12
+ [S.TypeId]: {
13
+ _A: (_: any) => A;
14
+ _I: (_: any) => I;
15
+ };
16
+ };
17
+
18
+ export type PropertySignatureWithOutput<I> = S.PropertySignature<
19
+ any,
20
+ boolean,
21
+ any,
22
+ boolean,
23
+ never
24
+ > & {
25
+ [S.TypeId]: {
26
+ _I: (_: any) => I | undefined;
27
+ };
28
+ };
29
+
30
+ export type PropertySignatureWithInputAndOutput<I, A> = S.PropertySignature<
31
+ any,
32
+ boolean,
33
+ any,
34
+ boolean,
35
+ never
36
+ > & {
37
+ [S.TypeId]: {
38
+ _A: (_: any) => A | undefined;
39
+ _I: (_: any) => I | undefined;
40
+ };
41
+ };
42
+
43
+ export type PropDef<I> =
44
+ | S.Schema<I>
45
+ | S.PropertySignature<I, boolean, I, boolean, never>;
46
+
47
+ export function CoSchema<Raw extends S.Schema<any>>(
48
+ raw: Raw
49
+ ): Raw & S.Schema<Raw, any, any> {
50
+ return raw as any;
51
+ }
52
+
53
+ export function propSigToSchema<
54
+ S extends S.Schema<any> | PropertySignatureWithInputAndOutput<any, any>,
55
+ >(
56
+ propSig: S
57
+ ): S extends S.Schema<any>
58
+ ? S
59
+ : S extends PropertySignatureWithInputAndOutput<infer A, infer I>
60
+ ? S.Schema<A, I>
61
+ : never {
62
+ if ("ast" in propSig) {
63
+ return propSig as any;
64
+ } else {
65
+ const ast = (
66
+ propSig as unknown as {
67
+ propertySignatureAST: { from: AST.AST };
68
+ }
69
+ ).propertySignatureAST;
70
+ return S.make<any, unknown, never>(ast.from) as any;
71
+ }
72
+ }
@@ -0,0 +1,118 @@
1
+ import { ControlledAccount } from "./coValues/account/account.js";
2
+ import { CoValueSchema, CoValue, ID } from "./coValueInterfaces.js";
3
+ import * as S from "@effect/schema/Schema";
4
+ import { RawCoValue } from "cojson";
5
+
6
+ export const subscriptionsScopes = new WeakMap<
7
+ CoValue,
8
+ SubscriptionScope<any>
9
+ >();
10
+
11
+ export class SubscriptionScope<
12
+ RootSchema extends CoValueSchema = CoValueSchema,
13
+ > {
14
+ scopeID: string = `scope-${Math.random().toString(36).slice(2)}`;
15
+ subscriber: ControlledAccount;
16
+ entries = new Map<
17
+ ID<CoValue>,
18
+ | { state: "loading"; immediatelyUnsub?: boolean }
19
+ | { state: "loaded"; rawUnsub: () => void }
20
+ >();
21
+ rootEntry: {
22
+ state: "loaded";
23
+ value: S.Schema.To<RootSchema>;
24
+ rawUnsub: () => void;
25
+ };
26
+ onUpdate: (newRoot: S.Schema.To<RootSchema>) => void;
27
+ scheduledUpdate: boolean = false;
28
+
29
+ constructor(
30
+ root: S.Schema.To<RootSchema>,
31
+ rootSchema: RootSchema,
32
+ onUpdate: (newRoot: S.Schema.To<RootSchema>) => void
33
+ ) {
34
+ this.rootEntry = {
35
+ state: "loaded" as const,
36
+ value: root,
37
+ rawUnsub: () => {}, // placeholder
38
+ };
39
+ this.entries.set(root.id, this.rootEntry);
40
+
41
+ subscriptionsScopes.set(root, this);
42
+
43
+ this.subscriber = root._loadedAs;
44
+ this.onUpdate = onUpdate;
45
+ this.rootEntry.rawUnsub = root._raw.core.subscribe(
46
+ (rawUpdate: RawCoValue | undefined) => {
47
+ if (!rawUpdate) return;
48
+ this.rootEntry.value = rootSchema.fromRaw(
49
+ rawUpdate
50
+ ) as S.Schema.To<RootSchema>;
51
+ // console.log("root update", this.rootEntry.value.toJSON());
52
+ subscriptionsScopes.set(this.rootEntry.value, this);
53
+ this.scheduleUpdate();
54
+ }
55
+ );
56
+ }
57
+
58
+ scheduleUpdate() {
59
+ if (!this.scheduledUpdate) {
60
+ this.scheduledUpdate = true;
61
+ queueMicrotask(() => {
62
+ this.scheduledUpdate = false;
63
+ this.onUpdate(this.rootEntry.value);
64
+ });
65
+ }
66
+ }
67
+
68
+ onRefAccessedOrSet(accessedOrSetId: ID<CoValue> | undefined) {
69
+ // console.log("onRefAccessedOrSet", this.scopeID, accessedOrSetId);
70
+ if (!accessedOrSetId) {
71
+ return;
72
+ }
73
+
74
+ if (!this.entries.has(accessedOrSetId)) {
75
+ const loadingEntry = {
76
+ state: "loading",
77
+ immediatelyUnsub: false,
78
+ } as const;
79
+ this.entries.set(accessedOrSetId, loadingEntry);
80
+ this.subscriber._raw.core.node
81
+ .loadCoValueCore(accessedOrSetId)
82
+ .then((core) => {
83
+ if (
84
+ loadingEntry.state === "loading" &&
85
+ loadingEntry.immediatelyUnsub
86
+ ) {
87
+ return;
88
+ }
89
+ if (core !== "unavailable") {
90
+ const entry = {
91
+ state: "loaded" as const,
92
+ rawUnsub: () => {}, // placeholder
93
+ };
94
+ this.entries.set(accessedOrSetId, entry);
95
+
96
+ const rawUnsub = core.subscribe((rawUpdate) => {
97
+ // console.log("ref update", this.scopeID, accessedOrSetId, JSON.stringify(rawUpdate))
98
+ if (!rawUpdate) return;
99
+ this.scheduleUpdate();
100
+ });
101
+
102
+ entry.rawUnsub = rawUnsub;
103
+ }
104
+ });
105
+ }
106
+ }
107
+
108
+ unsubscribeAll() {
109
+ for (const entry of this.entries.values()) {
110
+ if (entry.state === "loaded") {
111
+ entry.rawUnsub();
112
+ } else {
113
+ entry.immediatelyUnsub = true;
114
+ }
115
+ }
116
+ this.entries.clear();
117
+ }
118
+ }