jazz-tools 0.7.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }