atom.io 0.29.3 → 0.29.5

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.
@@ -0,0 +1,375 @@
1
+ import type { Refinement } from "atom.io/introspection"
2
+ import type { Json } from "atom.io/json"
3
+
4
+ export interface JunctionEntries<Content extends Json.Object | null>
5
+ extends Json.Object {
6
+ readonly relations: [string, string[]][]
7
+ readonly contents: [string, Content][]
8
+ }
9
+ export interface JunctionSchema<ASide extends string, BSide extends string>
10
+ extends Json.Object {
11
+ readonly between: [a: ASide, b: BSide]
12
+ readonly cardinality: `1:1` | `1:n` | `n:n`
13
+ }
14
+
15
+ export type BaseExternalStoreConfiguration = {
16
+ addRelation: (a: string, b: string) => void
17
+ deleteRelation: (a: string, b: string) => void
18
+ replaceRelationsSafely: (a: string, bs: string[]) => void
19
+ replaceRelationsUnsafely: (a: string, bs: string[]) => void
20
+ getRelatedKeys: (key: string) => Set<string> | undefined
21
+ has: (a: string, b?: string) => boolean
22
+ }
23
+
24
+ export type ExternalStoreWithContentConfiguration<Content extends Json.Object> =
25
+ {
26
+ getContent: (contentKey: string) => Content | undefined
27
+ setContent: (contentKey: string, content: Content) => void
28
+ deleteContent: (contentKey: string) => void
29
+ }
30
+
31
+ export type Empty<Obj extends object> = {
32
+ [Key in keyof Obj]?: undefined
33
+ }
34
+
35
+ export type ExternalStoreConfiguration<Content extends Json.Object | null> =
36
+ Content extends Json.Object
37
+ ? BaseExternalStoreConfiguration &
38
+ ExternalStoreWithContentConfiguration<Content>
39
+ : BaseExternalStoreConfiguration &
40
+ Empty<ExternalStoreWithContentConfiguration<Json.Object>>
41
+
42
+ export type JunctionAdvancedConfiguration<Content extends Json.Object | null> = {
43
+ warn?: (...args: any[]) => void
44
+ externalStore?: ExternalStoreConfiguration<Content>
45
+ isContent?: Refinement<unknown, Content>
46
+ makeContentKey?: (a: string, b: string) => string
47
+ }
48
+
49
+ export type JunctionJSON<
50
+ ASide extends string,
51
+ BSide extends string,
52
+ Content extends Json.Object | null,
53
+ > = JunctionEntries<Content> & JunctionSchema<ASide, BSide>
54
+
55
+ export class Junction<
56
+ const ASide extends string,
57
+ const BSide extends string,
58
+ const Content extends Json.Object | null = null,
59
+ > {
60
+ public readonly a: ASide
61
+ public readonly b: BSide
62
+ public readonly cardinality: `1:1` | `1:n` | `n:n`
63
+ public readonly relations = new Map<string, Set<string>>()
64
+ public readonly contents = new Map<string, Content>()
65
+
66
+ public isContent: Refinement<unknown, Content> | null
67
+ public makeContentKey = (a: string, b: string): string => `${a}:${b}`
68
+
69
+ public warn?: (...args: any[]) => void
70
+
71
+ public getRelatedKeys(key: string): Set<string> | undefined {
72
+ return this.relations.get(key)
73
+ }
74
+ protected addRelation(a: string, b: string): void {
75
+ let aRelations = this.relations.get(a)
76
+ let bRelations = this.relations.get(b)
77
+ if (aRelations) {
78
+ aRelations.add(b)
79
+ } else {
80
+ aRelations = new Set([b])
81
+ this.relations.set(a, aRelations)
82
+ }
83
+ if (bRelations) {
84
+ bRelations.add(a)
85
+ } else {
86
+ bRelations = new Set([a])
87
+ this.relations.set(b, bRelations)
88
+ }
89
+ }
90
+ protected deleteRelation(a: string, b: string): void {
91
+ const aRelations = this.relations.get(a)
92
+ if (aRelations) {
93
+ aRelations.delete(b)
94
+ if (aRelations.size === 0) {
95
+ this.relations.delete(a)
96
+ }
97
+ const bRelations = this.relations.get(b)
98
+ if (bRelations) {
99
+ bRelations.delete(a)
100
+ if (bRelations.size === 0) {
101
+ this.relations.delete(b)
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ protected replaceRelationsUnsafely(a: string, bs: string[]): void {
108
+ this.relations.set(a, new Set(bs))
109
+ for (const b of bs) {
110
+ const bRelations = new Set([a])
111
+ this.relations.set(b, bRelations)
112
+ }
113
+ }
114
+ protected replaceRelationsSafely(a: string, bs: string[]): void {
115
+ const aRelationsPrev = this.relations.get(a)
116
+ if (aRelationsPrev) {
117
+ for (const b of aRelationsPrev) {
118
+ const bRelations = this.relations.get(b)
119
+ if (bRelations) {
120
+ if (bRelations.size === 1) {
121
+ this.relations.delete(b)
122
+ } else {
123
+ bRelations.delete(a)
124
+ }
125
+ this.contents.delete(this.makeContentKey(a, b))
126
+ }
127
+ }
128
+ }
129
+ this.relations.set(a, new Set(bs))
130
+ for (const b of bs) {
131
+ let bRelations = this.relations.get(b)
132
+ if (bRelations) {
133
+ bRelations.add(a)
134
+ } else {
135
+ bRelations = new Set([a])
136
+ this.relations.set(b, bRelations)
137
+ }
138
+ }
139
+ }
140
+
141
+ protected getContentInternal(contentKey: string): Content | undefined {
142
+ return this.contents.get(contentKey)
143
+ }
144
+ protected setContent(contentKey: string, content: Content): void {
145
+ this.contents.set(contentKey, content)
146
+ }
147
+ protected deleteContent(contentKey: string): void {
148
+ this.contents.delete(contentKey)
149
+ }
150
+
151
+ public constructor(
152
+ data: JunctionSchema<ASide, BSide> & Partial<JunctionEntries<Content>>,
153
+ config?: JunctionAdvancedConfiguration<Content>,
154
+ ) {
155
+ this.a = data.between[0]
156
+ this.b = data.between[1]
157
+
158
+ this.cardinality = data.cardinality
159
+ if (!config?.externalStore) {
160
+ this.relations = new Map(data.relations?.map(([a, b]) => [a, new Set(b)]))
161
+ this.contents = new Map(data.contents)
162
+ }
163
+ this.isContent = config?.isContent ?? null
164
+ if (config?.makeContentKey) {
165
+ this.makeContentKey = config.makeContentKey
166
+ }
167
+ if (config?.externalStore) {
168
+ const externalStore = config.externalStore
169
+ this.has = (a, b) => externalStore.has(a, b)
170
+ this.addRelation = (a, b) => {
171
+ externalStore.addRelation(a, b)
172
+ }
173
+ this.deleteRelation = (a, b) => {
174
+ externalStore.deleteRelation(a, b)
175
+ }
176
+ this.replaceRelationsSafely = (a, bs) => {
177
+ externalStore.replaceRelationsSafely(a, bs)
178
+ }
179
+ this.replaceRelationsUnsafely = (a, bs) => {
180
+ externalStore.replaceRelationsUnsafely(a, bs)
181
+ }
182
+ this.getRelatedKeys = (key) => externalStore.getRelatedKeys(key)
183
+ if (externalStore.getContent) {
184
+ this.getContentInternal = (contentKey) => {
185
+ return externalStore.getContent(contentKey) as any
186
+ }
187
+ this.setContent = (contentKey, content) => {
188
+ externalStore.setContent(contentKey, content as any)
189
+ }
190
+ this.deleteContent = (contentKey) => {
191
+ externalStore.deleteContent(contentKey)
192
+ }
193
+ }
194
+ for (const [x, ys] of data.relations ?? []) {
195
+ for (const y of ys) this.addRelation(x, y)
196
+ }
197
+ for (const [contentKey, content] of data.contents ?? []) {
198
+ this.setContent(contentKey, content)
199
+ }
200
+ }
201
+ if (config?.warn) {
202
+ this.warn = config.warn
203
+ }
204
+ }
205
+ public toJSON(): JunctionJSON<ASide, BSide, Content> {
206
+ return {
207
+ between: [this.a, this.b],
208
+ cardinality: this.cardinality,
209
+ relations: [...this.relations.entries()].map(([a, b]) => [a, [...b]]),
210
+ contents: [...this.contents.entries()],
211
+ }
212
+ }
213
+
214
+ public set(
215
+ a: string,
216
+ ...rest: Content extends null ? [b: string] : [b: string, content: Content]
217
+ ): this
218
+ public set(
219
+ relation: { [Key in ASide | BSide]: string },
220
+ ...rest: Content extends null ? [] | [b?: undefined] : [content: Content]
221
+ ): this
222
+ public set(
223
+ a: string | { [Key in ASide | BSide]: string },
224
+ ...rest: Content extends null
225
+ ? [] | [b?: string | undefined]
226
+ : [b: string, content: Content] | [content: Content]
227
+ ): this {
228
+ const b: string =
229
+ typeof rest[0] === `string`
230
+ ? rest[0]
231
+ : (a[this.b as keyof typeof a] as string)
232
+ const content: Content | undefined =
233
+ (rest[1] ?? typeof rest[0] === `string`) ? undefined : (rest[0] as Content)
234
+ a = typeof a === `string` ? a : a[this.a]
235
+ switch (this.cardinality) {
236
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: perfect here
237
+ case `1:1`: {
238
+ const bPrev = this.getRelatedKey(a)
239
+ if (bPrev && bPrev !== b) this.delete(bPrev, a)
240
+ }
241
+ case `1:n`: {
242
+ const aPrev = this.getRelatedKey(b)
243
+ if (aPrev && aPrev !== a) this.delete(aPrev, b)
244
+ }
245
+ }
246
+ if (content) {
247
+ const contentKey = this.makeContentKey(a, b)
248
+ this.setContent(contentKey, content)
249
+ }
250
+ this.addRelation(a, b)
251
+ return this
252
+ }
253
+
254
+ public delete(a: string, b?: string): this
255
+ public delete(
256
+ relation:
257
+ | Record<ASide | BSide, string>
258
+ | Record<ASide, string>
259
+ | Record<BSide, string>,
260
+ b?: undefined,
261
+ ): this
262
+ public delete(
263
+ x:
264
+ | Record<ASide | BSide, string>
265
+ | Record<ASide, string>
266
+ | Record<BSide, string>
267
+ | string,
268
+ b?: string,
269
+ ): this {
270
+ // @ts-expect-error we deduce that this.b may index x
271
+ b = typeof b === `string` ? b : (x[this.b] as string | undefined)
272
+ // @ts-expect-error we deduce that this.a may index x
273
+ const a = typeof x === `string` ? x : (x[this.a] as string | undefined)
274
+
275
+ if (a === undefined && typeof b === `string`) {
276
+ const bRelations = this.getRelatedKeys(b)
277
+ if (bRelations) {
278
+ for (const bRelation of bRelations) {
279
+ this.delete(bRelation, b)
280
+ }
281
+ }
282
+ }
283
+ if (typeof a === `string` && b === undefined) {
284
+ const aRelations = this.getRelatedKeys(a)
285
+ if (aRelations) {
286
+ for (const aRelation of aRelations) {
287
+ this.delete(a, aRelation)
288
+ }
289
+ }
290
+ }
291
+ if (typeof a === `string` && typeof b === `string`) {
292
+ this.deleteRelation(a, b)
293
+ const contentKey = this.makeContentKey(a, b)
294
+ this.deleteContent(contentKey)
295
+ }
296
+ return this
297
+ }
298
+
299
+ public getRelatedKey(key: string): string | undefined {
300
+ const relations = this.getRelatedKeys(key)
301
+ if (relations) {
302
+ if (relations.size > 1) {
303
+ this.warn?.(
304
+ `${relations.size} related keys were found for key "${key}": (${[
305
+ ...relations,
306
+ ]
307
+ .map((k) => `"${k}"`)
308
+ .join(`, `)}). Only one related key was expected.`,
309
+ )
310
+ }
311
+ for (const relation of relations) {
312
+ return relation
313
+ }
314
+ }
315
+ }
316
+
317
+ public replaceRelations(
318
+ a: string,
319
+ relations: Content extends null ? string[] : Record<string, Content>,
320
+ config?: { reckless: boolean },
321
+ ): this {
322
+ const hasContent = !Array.isArray(relations)
323
+ const bs = hasContent ? Object.keys(relations) : relations
324
+ if (config?.reckless) {
325
+ this.replaceRelationsUnsafely(a, bs)
326
+ } else {
327
+ this.replaceRelationsSafely(a, bs)
328
+ }
329
+ if (hasContent) {
330
+ for (const b of bs) {
331
+ const contentKey = this.makeContentKey(a, b)
332
+ const content = relations[b] as Content
333
+ this.setContent(contentKey, content)
334
+ }
335
+ }
336
+ return this
337
+ }
338
+
339
+ public getContent(a: string, b: string): Content | undefined {
340
+ const contentKey = this.makeContentKey(a, b)
341
+ return this.getContentInternal(contentKey)
342
+ }
343
+
344
+ public getRelationEntries(
345
+ input: Record<ASide, string> | Record<BSide, string>,
346
+ ): [string, Content][] {
347
+ const a: string | undefined = (input as any)[this.a]
348
+ const b: string | undefined = (input as any)[this.b]
349
+ if (a !== undefined && b === undefined) {
350
+ const aRelations = this.getRelatedKeys(a)
351
+ if (aRelations) {
352
+ return [...aRelations].map((aRelation) => {
353
+ return [aRelation, this.getContent(a, aRelation) ?? (null as Content)]
354
+ })
355
+ }
356
+ }
357
+ if (a === undefined && b !== undefined) {
358
+ const bRelations = this.getRelatedKeys(b)
359
+ if (bRelations) {
360
+ return [...bRelations].map((bRelation) => {
361
+ return [bRelation, this.getContent(bRelation, b) ?? (null as Content)]
362
+ })
363
+ }
364
+ }
365
+ return []
366
+ }
367
+
368
+ public has(a: string, b?: string): boolean {
369
+ if (b) {
370
+ const setA = this.getRelatedKeys(a)
371
+ return setA?.has(b) ?? false
372
+ }
373
+ return this.relations.has(a)
374
+ }
375
+ }
@@ -10,22 +10,19 @@ import type {
10
10
  } from "atom.io"
11
11
  import { AtomIOLogger } from "atom.io"
12
12
 
13
- import { Junction } from "~/packages/rel8/junction/src"
14
-
15
13
  import type {
16
14
  Atom,
17
- Func,
18
- Molecule,
19
15
  MutableAtomFamily,
20
16
  ReadonlySelector,
21
17
  ReadonlySelectorFamily,
22
18
  RegularAtomFamily,
23
- Tracker,
24
- Transceiver,
25
19
  WritableSelector,
26
20
  WritableSelectorFamily,
27
21
  } from ".."
22
+ import { Junction } from "../junction"
28
23
  import type { Lineage } from "../lineage"
24
+ import type { Molecule } from "../molecule"
25
+ import type { Tracker, Transceiver } from "../mutable"
29
26
  import { getJsonToken, getUpdateToken } from "../mutable"
30
27
  import type { OperationProgress } from "../operation"
31
28
  import { StatefulSubject, Subject } from "../subject"
@@ -36,6 +33,7 @@ import type {
36
33
  TransactionProgress,
37
34
  } from "../transaction"
38
35
  import { isRootStore } from "../transaction"
36
+ import type { Func } from "../utility-types"
39
37
  import { CircularBuffer } from "./circular-buffer"
40
38
 
41
39
  export class Store implements Lineage {
@@ -2,12 +2,11 @@ import type { disposeState, getState, setState } from "atom.io"
2
2
  import type { findState } from "atom.io/ephemeral"
3
3
  import type { seekState } from "atom.io/immortal"
4
4
 
5
- import { Junction } from "~/packages/rel8/junction/src"
6
-
7
5
  import { arbitrary } from "../arbitrary"
8
6
  import { disposeFromStore, findInStore, seekInStore } from "../families"
9
7
  import { getEnvironmentData } from "../get-environment-data"
10
8
  import { getFromStore } from "../get-state"
9
+ import { Junction } from "../junction"
11
10
  import { LazyMap } from "../lazy-map"
12
11
  import { newest } from "../lineage"
13
12
  import { makeMoleculeInStore } from "../molecule"
@@ -1,5 +1,4 @@
1
- import { createWritableSelectorFamily } from '../../dist/chunk-HH7WBSVS.js';
2
- import '../../dist/chunk-IZXKJOQQ.js';
1
+ import { createWritableSelectorFamily } from '../../dist/chunk-TCINPEYE.js';
3
2
  import '../../dist/chunk-XWL6SNVU.js';
4
3
  import { createStandaloneSelector, IMPLICIT, growMoleculeInStore, initFamilyMemberInStore, withdraw, seekInStore } from 'atom.io/internal';
5
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atom.io",
3
- "version": "0.29.3",
3
+ "version": "0.29.5",
4
4
  "description": "Composable and testable reactive data library.",
5
5
  "homepage": "https://atom.io.fyi",
6
6
  "sideEffects": false,
@@ -54,10 +54,10 @@
54
54
  "devDependencies": {
55
55
  "@testing-library/react": "16.0.1",
56
56
  "@types/eslint": "9.6.1",
57
- "@types/estree": "1.0.5",
57
+ "@types/estree": "1.0.6",
58
58
  "@types/http-proxy": "1.17.15",
59
59
  "@types/npmlog": "7.0.0",
60
- "@types/react": "18.3.7",
60
+ "@types/react": "18.3.8",
61
61
  "@types/tmp": "0.2.6",
62
62
  "@typescript-eslint/parser": "8.6.0",
63
63
  "@typescript-eslint/rule-tester": "8.6.0",
@@ -66,8 +66,8 @@
66
66
  "concurrently": "9.0.1",
67
67
  "drizzle-kit": "0.24.2",
68
68
  "drizzle-orm": "0.33.0",
69
- "eslint": "9.10.0",
70
- "framer-motion": "11.5.4",
69
+ "eslint": "9.11.0",
70
+ "framer-motion": "11.5.6",
71
71
  "happy-dom": "15.7.4",
72
72
  "http-proxy": "1.18.1",
73
73
  "npmlog": "7.0.1",
@@ -76,15 +76,16 @@
76
76
  "react": "18.3.1",
77
77
  "react-dom": "18.3.1",
78
78
  "react-router-dom": "6.26.2",
79
- "socket.io": "4.7.5",
80
- "socket.io-client": "4.7.5",
79
+ "socket.io": "4.8.0",
80
+ "socket.io-client": "4.8.0",
81
81
  "tmp": "0.2.3",
82
82
  "tsup": "8.3.0",
83
83
  "tsx": "4.19.1",
84
84
  "typescript": "5.6.2",
85
- "vite": "5.4.6",
85
+ "vite": "5.4.7",
86
86
  "vite-tsconfig-paths": "5.0.1",
87
- "vitest": "2.1.1"
87
+ "vitest": "2.1.1",
88
+ "zod": "3.23.8"
88
89
  },
89
90
  "main": "dist/index.js",
90
91
  "types": "dist/index.d.ts",
@@ -260,6 +261,7 @@
260
261
  "test:once": "bun run test:manifest && cross-env IMPORT=dist vitest run",
261
262
  "test:once:public": "cross-env IMPORT=dist vitest run public",
262
263
  "test:manifest": "tsx __scripts__/manifest-test.node.ts",
263
- "test:semver": "bun ../break-check/src/break-check.x.ts --verbose"
264
+ "test:semver": "bun ../break-check/src/break-check.x.ts --verbose",
265
+ "postversion": "biome format --write package.json"
264
266
  }
265
267
  }
@@ -35,12 +35,12 @@ type RealtimeTestClientBuilder = {
35
35
  init: () => RealtimeTestClient;
36
36
  };
37
37
  type RealtimeTestServer = RealtimeTestTools & {
38
- dispose: () => void;
38
+ dispose: () => Promise<void>;
39
39
  port: number;
40
40
  };
41
41
  type RealtimeTestAPI = {
42
42
  server: RealtimeTestServer;
43
- teardown: () => void;
43
+ teardown: () => Promise<void>;
44
44
  };
45
45
  type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {
46
46
  client: RealtimeTestClientBuilder;
@@ -81,8 +81,8 @@ var setupRealtimeTestServer = (options) => {
81
81
  console.log(`${userKey} disconnected`);
82
82
  });
83
83
  });
84
- const dispose = () => {
85
- server.close();
84
+ const dispose = async () => {
85
+ await server.close();
86
86
  const roomKeys = getFromStore(silo.store, RT.roomIndex);
87
87
  for (const roomKey of roomKeys) {
88
88
  const roomState = findInStore(silo.store, RTS.roomSelectors, roomKey);
@@ -157,8 +157,8 @@ var singleClient = (options) => {
157
157
  return {
158
158
  client,
159
159
  server,
160
- teardown: () => {
161
- server.dispose();
160
+ teardown: async () => {
161
+ await server.dispose();
162
162
  client.dispose();
163
163
  }
164
164
  };
@@ -179,8 +179,8 @@ var multiClient = (options) => {
179
179
  return {
180
180
  clients,
181
181
  server,
182
- teardown: () => {
183
- server.dispose();
182
+ teardown: async () => {
183
+ await server.dispose();
184
184
  for (const [, client] of toEntries(clients)) {
185
185
  client.dispose();
186
186
  }
@@ -76,14 +76,13 @@ export type RealtimeTestClientBuilder = {
76
76
  }
77
77
 
78
78
  export type RealtimeTestServer = RealtimeTestTools & {
79
- dispose: () => void
79
+ dispose: () => Promise<void>
80
80
  port: number
81
- // enableLogging: () => void
82
81
  }
83
82
 
84
83
  export type RealtimeTestAPI = {
85
84
  server: RealtimeTestServer
86
- teardown: () => void
85
+ teardown: () => Promise<void>
87
86
  }
88
87
  export type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {
89
88
  client: RealtimeTestClientBuilder
@@ -152,8 +151,8 @@ export const setupRealtimeTestServer = (
152
151
  })
153
152
  })
154
153
 
155
- const dispose = () => {
156
- server.close()
154
+ const dispose = async () => {
155
+ await server.close()
157
156
  const roomKeys = getFromStore(silo.store, RT.roomIndex)
158
157
  for (const roomKey of roomKeys) {
159
158
  const roomState = findInStore(silo.store, RTS.roomSelectors, roomKey)
@@ -245,8 +244,8 @@ export const singleClient = (
245
244
  return {
246
245
  client,
247
246
  server,
248
- teardown: () => {
249
- server.dispose()
247
+ teardown: async () => {
248
+ await server.dispose()
250
249
  client.dispose()
251
250
  },
252
251
  }
@@ -271,8 +270,8 @@ export const multiClient = <ClientNames extends string>(
271
270
  return {
272
271
  clients,
273
272
  server,
274
- teardown: () => {
275
- server.dispose()
273
+ teardown: async () => {
274
+ await server.dispose()
276
275
  for (const [, client] of toEntries(clients)) {
277
276
  client.dispose()
278
277
  }