@livestore/livestore 0.4.0-dev.9 → 0.5.0-dev.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 (132) hide show
  1. package/README.md +0 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.js +1 -1
  4. package/dist/QueryCache.js.map +1 -1
  5. package/dist/SqliteDbWrapper.d.ts +5 -5
  6. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  7. package/dist/SqliteDbWrapper.js +15 -13
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.js +24 -6
  10. package/dist/SqliteDbWrapper.test.js.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +134 -6
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +190 -11
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/effect/LiveStore.test.d.ts +2 -0
  16. package/dist/effect/LiveStore.test.d.ts.map +1 -0
  17. package/dist/effect/LiveStore.test.js +41 -0
  18. package/dist/effect/LiveStore.test.js.map +1 -0
  19. package/dist/effect/mod.d.ts +1 -1
  20. package/dist/effect/mod.d.ts.map +1 -1
  21. package/dist/effect/mod.js +3 -1
  22. package/dist/effect/mod.js.map +1 -1
  23. package/dist/live-queries/base-class.d.ts +129 -9
  24. package/dist/live-queries/base-class.d.ts.map +1 -1
  25. package/dist/live-queries/base-class.js +30 -4
  26. package/dist/live-queries/base-class.js.map +1 -1
  27. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  28. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  29. package/dist/live-queries/client-document-get-query.js +4 -3
  30. package/dist/live-queries/client-document-get-query.js.map +1 -1
  31. package/dist/live-queries/computed.d.ts +56 -0
  32. package/dist/live-queries/computed.d.ts.map +1 -1
  33. package/dist/live-queries/computed.js +62 -6
  34. package/dist/live-queries/computed.js.map +1 -1
  35. package/dist/live-queries/db-query.d.ts +2 -2
  36. package/dist/live-queries/db-query.d.ts.map +1 -1
  37. package/dist/live-queries/db-query.js +41 -30
  38. package/dist/live-queries/db-query.js.map +1 -1
  39. package/dist/live-queries/db-query.test.js +112 -29
  40. package/dist/live-queries/db-query.test.js.map +1 -1
  41. package/dist/live-queries/signal.d.ts +49 -0
  42. package/dist/live-queries/signal.d.ts.map +1 -1
  43. package/dist/live-queries/signal.js +51 -2
  44. package/dist/live-queries/signal.js.map +1 -1
  45. package/dist/live-queries/signal.test.js +4 -4
  46. package/dist/live-queries/signal.test.js.map +1 -1
  47. package/dist/mod.d.ts +3 -3
  48. package/dist/mod.d.ts.map +1 -1
  49. package/dist/mod.js +3 -2
  50. package/dist/mod.js.map +1 -1
  51. package/dist/reactive.d.ts +10 -10
  52. package/dist/reactive.d.ts.map +1 -1
  53. package/dist/reactive.js +29 -27
  54. package/dist/reactive.js.map +1 -1
  55. package/dist/reactive.test.js +2 -2
  56. package/dist/reactive.test.js.map +1 -1
  57. package/dist/store/StoreRegistry.d.ts +215 -0
  58. package/dist/store/StoreRegistry.d.ts.map +1 -0
  59. package/dist/store/StoreRegistry.js +267 -0
  60. package/dist/store/StoreRegistry.js.map +1 -0
  61. package/dist/store/StoreRegistry.test.d.ts +2 -0
  62. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  63. package/dist/store/StoreRegistry.test.js +384 -0
  64. package/dist/store/StoreRegistry.test.js.map +1 -0
  65. package/dist/store/create-store.d.ts +99 -22
  66. package/dist/store/create-store.d.ts.map +1 -1
  67. package/dist/store/create-store.js +85 -37
  68. package/dist/store/create-store.js.map +1 -1
  69. package/dist/store/devtools.d.ts +6 -18
  70. package/dist/store/devtools.d.ts.map +1 -1
  71. package/dist/store/devtools.js +61 -19
  72. package/dist/store/devtools.js.map +1 -1
  73. package/dist/store/store-eventstream.test.d.ts +2 -0
  74. package/dist/store/store-eventstream.test.d.ts.map +1 -0
  75. package/dist/store/store-eventstream.test.js +65 -0
  76. package/dist/store/store-eventstream.test.js.map +1 -0
  77. package/dist/store/store-types.d.ts +271 -31
  78. package/dist/store/store-types.d.ts.map +1 -1
  79. package/dist/store/store-types.js +41 -1
  80. package/dist/store/store-types.js.map +1 -1
  81. package/dist/store/store-types.test.d.ts +2 -0
  82. package/dist/store/store-types.test.d.ts.map +1 -0
  83. package/dist/store/store-types.test.js +39 -0
  84. package/dist/store/store-types.test.js.map +1 -0
  85. package/dist/store/store.d.ts +255 -68
  86. package/dist/store/store.d.ts.map +1 -1
  87. package/dist/store/store.js +533 -186
  88. package/dist/store/store.js.map +1 -1
  89. package/dist/utils/dev.d.ts.map +1 -1
  90. package/dist/utils/dev.js +1 -1
  91. package/dist/utils/dev.js.map +1 -1
  92. package/dist/utils/stack-info.d.ts.map +1 -1
  93. package/dist/utils/stack-info.js +3 -2
  94. package/dist/utils/stack-info.js.map +1 -1
  95. package/dist/utils/tests/fixture.d.ts +46 -39
  96. package/dist/utils/tests/fixture.d.ts.map +1 -1
  97. package/dist/utils/tests/fixture.js +7 -0
  98. package/dist/utils/tests/fixture.js.map +1 -1
  99. package/dist/utils/tests/otel.d.ts.map +1 -1
  100. package/dist/utils/tests/otel.js +5 -5
  101. package/dist/utils/tests/otel.js.map +1 -1
  102. package/package.json +50 -18
  103. package/src/QueryCache.ts +1 -1
  104. package/src/SqliteDbWrapper.test.ts +30 -6
  105. package/src/SqliteDbWrapper.ts +21 -17
  106. package/src/ambient.d.ts +0 -7
  107. package/src/effect/LiveStore.test.ts +61 -0
  108. package/src/effect/LiveStore.ts +426 -22
  109. package/src/effect/mod.ts +13 -1
  110. package/src/live-queries/__snapshots__/db-query.test.ts.snap +814 -178
  111. package/src/live-queries/base-class.ts +162 -32
  112. package/src/live-queries/client-document-get-query.ts +6 -4
  113. package/src/live-queries/computed.ts +65 -8
  114. package/src/live-queries/db-query.test.ts +168 -30
  115. package/src/live-queries/db-query.ts +58 -43
  116. package/src/live-queries/signal.test.ts +5 -4
  117. package/src/live-queries/signal.ts +52 -3
  118. package/src/mod.ts +19 -2
  119. package/src/reactive.test.ts +3 -2
  120. package/src/reactive.ts +58 -49
  121. package/src/store/StoreRegistry.test.ts +543 -0
  122. package/src/store/StoreRegistry.ts +429 -0
  123. package/src/store/create-store.ts +227 -74
  124. package/src/store/devtools.ts +305 -261
  125. package/src/store/store-eventstream.test.ts +123 -0
  126. package/src/store/store-types.test.ts +52 -0
  127. package/src/store/store-types.ts +318 -44
  128. package/src/store/store.ts +695 -283
  129. package/src/utils/dev.ts +3 -4
  130. package/src/utils/stack-info.ts +5 -2
  131. package/src/utils/tests/fixture.ts +9 -1
  132. package/src/utils/tests/otel.ts +8 -7
@@ -1,13 +1,29 @@
1
- import type { UnexpectedError } from '@livestore/common'
2
- import type { LiveStoreSchema } from '@livestore/common/schema'
1
+ import type { UnknownError } from '@livestore/common'
2
+ import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
3
3
  import { omitUndefineds } from '@livestore/utils'
4
- import type { Cause, OtelTracer, Scope } from '@livestore/utils/effect'
5
- import { Deferred, Duration, Effect, Layer, pipe } from '@livestore/utils/effect'
4
+ import type { Schema } from '@livestore/utils/effect'
5
+ import {
6
+ type Cause,
7
+ type OtelTracer,
8
+ type Scope,
9
+ Context,
10
+ Deferred,
11
+ Duration,
12
+ Effect,
13
+ Layer,
14
+ pipe,
15
+ } from '@livestore/utils/effect'
6
16
 
7
17
  import type { LiveStoreContextProps } from '../store/create-store.ts'
8
18
  import { createStore, DeferredStoreContext, LiveStoreContextRunning } from '../store/create-store.ts'
19
+ import type { LiveStoreContextRunning as LiveStoreContextRunningType, Queryable } from '../store/store-types.ts'
20
+ import type { Store as StoreClass } from '../store/store.ts'
9
21
 
10
- export const makeLiveStoreContext = <TSchema extends LiveStoreSchema, TContext = {}>({
22
+ export const makeLiveStoreContext = <
23
+ TSchema extends LiveStoreSchema,
24
+ TContext = {},
25
+ TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json,
26
+ >({
11
27
  schema,
12
28
  storeId = 'default',
13
29
  context,
@@ -16,9 +32,11 @@ export const makeLiveStoreContext = <TSchema extends LiveStoreSchema, TContext =
16
32
  disableDevtools,
17
33
  onBootStatus,
18
34
  batchUpdates,
19
- }: LiveStoreContextProps<TSchema, TContext>): Effect.Effect<
20
- LiveStoreContextRunning['Type'],
21
- UnexpectedError | Cause.TimeoutException,
35
+ syncPayload,
36
+ syncPayloadSchema,
37
+ }: LiveStoreContextProps<TSchema, TContext, TSyncPayloadSchema>): Effect.Effect<
38
+ LiveStoreContextRunning['Service'],
39
+ UnknownError | Cause.TimeoutError,
22
40
  DeferredStoreContext | Scope.Scope | OtelTracer.OtelTracer
23
41
  > =>
24
42
  pipe(
@@ -28,18 +46,12 @@ export const makeLiveStoreContext = <TSchema extends LiveStoreSchema, TContext =
28
46
  storeId,
29
47
  adapter,
30
48
  batchUpdates,
31
- ...omitUndefineds({ context, boot, disableDevtools, onBootStatus }),
49
+ ...omitUndefineds({ context, boot, disableDevtools, onBootStatus, syncPayload, syncPayloadSchema }),
32
50
  })
33
51
 
34
- globalThis.__debugLiveStore ??= {}
35
- if (Object.keys(globalThis.__debugLiveStore).length === 0) {
36
- globalThis.__debugLiveStore._ = store
37
- }
38
- globalThis.__debugLiveStore[storeId] = store
39
-
40
- return { stage: 'running', store } as any as LiveStoreContextRunning['Type']
52
+ return LiveStoreContextRunning.of({ stage: 'running', store } as any as LiveStoreContextRunning['Service'])
41
53
  }),
42
- Effect.tapErrorCause((cause) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.failCause(def, cause))),
54
+ Effect.tapCause((cause) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.failCause(def, cause))),
43
55
  Effect.tap((storeCtx) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.succeed(def, storeCtx))),
44
56
  // This can take quite a while.
45
57
  // TODO make this configurable
@@ -47,15 +59,407 @@ export const makeLiveStoreContext = <TSchema extends LiveStoreSchema, TContext =
47
59
  Effect.withSpan('@livestore/livestore/effect:makeLiveStoreContext'),
48
60
  )
49
61
 
50
- export const LiveStoreContextLayer = <TSchema extends LiveStoreSchema, TContext = {}>(
51
- props: LiveStoreContextProps<TSchema, TContext>,
52
- ): Layer.Layer<LiveStoreContextRunning, UnexpectedError | Cause.TimeoutException, OtelTracer.OtelTracer> =>
53
- Layer.scoped(LiveStoreContextRunning, makeLiveStoreContext(props)).pipe(
62
+ /**
63
+ * @deprecated Use `Store.Tag(schema, storeId)` instead for type-safe store contexts.
64
+ *
65
+ * @example Migration
66
+ * ```ts
67
+ * // Before
68
+ * const layer = LiveStoreContextLayer({ schema, adapter, ... })
69
+ *
70
+ * // After
71
+ * class MainStore extends Store.Tag(schema, 'main') {}
72
+ * const layer = MainStore.layer({ adapter, ... })
73
+ * ```
74
+ */
75
+ export const LiveStoreContextLayer = <
76
+ TSchema extends LiveStoreSchema,
77
+ TContext = {},
78
+ TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json,
79
+ >(
80
+ props: LiveStoreContextProps<TSchema, TContext, TSyncPayloadSchema>,
81
+ ): Layer.Layer<LiveStoreContextRunning, UnknownError | Cause.TimeoutError, OtelTracer.OtelTracer> =>
82
+ Layer.effect(LiveStoreContextRunning, makeLiveStoreContext(props)).pipe(
54
83
  Layer.withSpan('LiveStore'),
55
84
  Layer.provide(LiveStoreContextDeferred),
56
85
  )
57
86
 
87
+ /**
88
+ * @deprecated Use `Store.Tag(schema, storeId)` and `MainStore.DeferredLayer` instead.
89
+ */
58
90
  export const LiveStoreContextDeferred = Layer.effect(
59
91
  DeferredStoreContext,
60
- Deferred.make<LiveStoreContextRunning['Type'], UnexpectedError>(),
92
+ Deferred.make<LiveStoreContextRunning['Service'], UnknownError>(),
61
93
  )
94
+
95
+ // =============================================================================
96
+ // Store.Tag - Idiomatic Effect API
97
+ // =============================================================================
98
+
99
+ /** Branded type for unique store context identity */
100
+ declare const StoreContextTypeId: unique symbol
101
+
102
+ /** Phantom type carrying schema and storeId information */
103
+ export interface StoreContextId<TSchema extends LiveStoreSchema, TStoreId extends string> {
104
+ readonly [StoreContextTypeId]: {
105
+ readonly schema: TSchema
106
+ readonly storeId: TStoreId
107
+ }
108
+ }
109
+
110
+ /** Phantom type for deferred store context */
111
+ declare const DeferredContextTypeId: unique symbol
112
+
113
+ export interface DeferredContextId<TStoreId extends string> {
114
+ readonly [DeferredContextTypeId]: {
115
+ readonly storeId: TStoreId
116
+ }
117
+ }
118
+
119
+ /** Props for creating a store layer (schema and storeId are already provided) */
120
+ export type StoreLayerProps<
121
+ TSchema extends LiveStoreSchema,
122
+ TContext = {},
123
+ TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json,
124
+ > = Omit<LiveStoreContextProps<TSchema, TContext, TSyncPayloadSchema>, 'storeId' | 'schema'>
125
+
126
+ /**
127
+ * Type for a Store.Tag class. This is the return type of `Store.Tag(schema, storeId)`.
128
+ * Can be extended as a class and is yieldable in Effect.gen.
129
+ *
130
+ * Note: This uses a type alias with a new() signature to make it extendable.
131
+ */
132
+ /**
133
+ * Type for a Store.Tag class. Uses self-referential identifier so that `yield*`
134
+ * and method R channels resolve to the same type, enabling proper layer composition.
135
+ *
136
+ * Uses `interface` (not `type`) to allow the self-referential identifier without
137
+ * triggering TS2456 (circular type alias).
138
+ */
139
+ export interface StoreTagClass<TSchema extends LiveStoreSchema, TStoreId extends string> extends Context.Key<
140
+ StoreTagClass<TSchema, TStoreId>,
141
+ LiveStoreContextRunningType<TSchema>
142
+ > {
143
+ /** Constructor signature (makes the type extendable as a class) */
144
+ new (): Context.Key<StoreTagClass<TSchema, TStoreId>, LiveStoreContextRunningType<TSchema>>
145
+
146
+ /** Key identity type (from Context.Key) */
147
+ readonly Id: StoreTagClass<TSchema, TStoreId>
148
+
149
+ /** Service type (from Context.Key) */
150
+ readonly Service: LiveStoreContextRunningType<TSchema>
151
+
152
+ /** The LiveStore schema for this store */
153
+ readonly schema: TSchema
154
+
155
+ /** Unique identifier for this store */
156
+ readonly storeId: TStoreId
157
+
158
+ /** Creates a layer that initializes the store */
159
+ layer<TContext, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(
160
+ props: StoreLayerProps<TSchema, TContext, TSyncPayloadSchema>,
161
+ ): Layer.Layer<StoreTagClass<TSchema, TStoreId>, UnknownError | Cause.TimeoutError, OtelTracer.OtelTracer>
162
+
163
+ /** Deferred store tag for async initialization patterns */
164
+ readonly Deferred: Context.Key<
165
+ DeferredContextId<TStoreId>,
166
+ Deferred.Deferred<LiveStoreContextRunningType<TSchema>, UnknownError>
167
+ >
168
+
169
+ /** Layer that provides the Deferred tag */
170
+ readonly DeferredLayer: Layer.Layer<DeferredContextId<TStoreId>>
171
+
172
+ /** Layer that waits for Deferred and provides the running store */
173
+ readonly fromDeferred: Layer.Layer<StoreTagClass<TSchema, TStoreId>, UnknownError, DeferredContextId<TStoreId>>
174
+
175
+ /** Query the store. Returns an Effect that yields the query result. */
176
+ query<TResult>(query: Queryable<TResult>): Effect.Effect<TResult, never, StoreTagClass<TSchema, TStoreId>>
177
+
178
+ /** Commit events to the store. */
179
+ commit(
180
+ ...eventInputs: LiveStoreEvent.Input.ForSchema<TSchema>[]
181
+ ): Effect.Effect<void, never, StoreTagClass<TSchema, TStoreId>>
182
+
183
+ /** Use the store with a callback function. */
184
+ use<A, E, R>(
185
+ f: (ctx: LiveStoreContextRunningType<TSchema>) => Effect.Effect<A, E, R>,
186
+ ): Effect.Effect<A, E, R | StoreTagClass<TSchema, TStoreId>>
187
+ }
188
+
189
+ /**
190
+ * Create a typed store context class for use with Effect.
191
+ *
192
+ * Returns a class that extends `Context.Service`, making it directly yieldable in Effect code.
193
+ * The class includes static methods for creating layers and accessors for common operations.
194
+ *
195
+ * @param schema - The LiveStore schema (used for type inference and runtime)
196
+ * @param storeId - Unique identifier for this store
197
+ *
198
+ * @example Basic usage
199
+ * ```ts
200
+ * import { Store } from '@livestore/livestore/effect'
201
+ * import { schema } from './schema.ts'
202
+ *
203
+ * // Define your store (once per store)
204
+ * export class MainStore extends Store.Tag(schema, 'main') {}
205
+ *
206
+ * // Create the layer
207
+ * const storeLayer = MainStore.layer({
208
+ * adapter: myAdapter,
209
+ * batchUpdates: ReactDOM.unstable_batchedUpdates,
210
+ * })
211
+ *
212
+ * // Use in Effect code
213
+ * Effect.gen(function* () {
214
+ * const { store } = yield* MainStore
215
+ * // ^? Store<typeof schema> - fully typed!
216
+ *
217
+ * // Or use accessors
218
+ * const users = yield* MainStore.query(tables.users.all())
219
+ * yield* MainStore.commit(events.createUser({ id: '1', name: 'Alice' }))
220
+ * })
221
+ * ```
222
+ *
223
+ * @example Multiple stores
224
+ * ```ts
225
+ * class MainStore extends Store.Tag(mainSchema, 'main') {}
226
+ * class SettingsStore extends Store.Tag(settingsSchema, 'settings') {}
227
+ *
228
+ * // Both available in same Effect context
229
+ * Effect.gen(function* () {
230
+ * const main = yield* MainStore
231
+ * const settings = yield* SettingsStore
232
+ * })
233
+ *
234
+ * const layer = Layer.mergeAll(
235
+ * MainStore.layer({ adapter: mainAdapter }),
236
+ * SettingsStore.layer({ adapter: settingsAdapter }),
237
+ * )
238
+ * ```
239
+ */
240
+ const makeStoreTag = <TSchema extends LiveStoreSchema, TStoreId extends string>(
241
+ schema: TSchema,
242
+ storeId: TStoreId,
243
+ ): StoreTagClass<TSchema, TStoreId> => {
244
+ type RunningType = LiveStoreContextRunningType<TSchema>
245
+ type DeferredType = Deferred.Deferred<RunningType, UnknownError>
246
+
247
+ // Create the deferred tag and layers upfront
248
+ const _DeferredTag = Context.Service<DeferredContextId<TStoreId>, DeferredType>(
249
+ `@livestore/store-deferred/${storeId}`,
250
+ )
251
+
252
+ const _DeferredLayer = Layer.effect(_DeferredTag, Deferred.make<RunningType, UnknownError>())
253
+
254
+ class Tag extends Context.Service<Tag, RunningType>()(`@livestore/store/${storeId}`) {
255
+ static readonly schema: TSchema = schema
256
+ static readonly storeId: TStoreId = storeId
257
+
258
+ static layer<TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(
259
+ props: StoreLayerProps<TSchema, TContext, TSyncPayloadSchema>,
260
+ ) {
261
+ return pipe(
262
+ Effect.gen(function* () {
263
+ const store = yield* createStore({
264
+ schema,
265
+ storeId,
266
+ adapter: props.adapter,
267
+ batchUpdates: props.batchUpdates,
268
+ ...omitUndefineds({
269
+ context: props.context,
270
+ boot: props.boot,
271
+ disableDevtools: props.disableDevtools,
272
+ onBootStatus: props.onBootStatus,
273
+ syncPayload: props.syncPayload,
274
+ syncPayloadSchema: props.syncPayloadSchema,
275
+ }),
276
+ })
277
+
278
+ const ctx: RunningType = { stage: 'running', store: store as StoreClass<TSchema> }
279
+
280
+ // Also fulfill the deferred if it exists in context
281
+ yield* Effect.flatMap(Effect.serviceOption(_DeferredTag), (optDeferred) =>
282
+ optDeferred._tag === 'Some' ? Deferred.succeed(optDeferred.value, ctx) : Effect.void,
283
+ )
284
+
285
+ return Tag.of(ctx)
286
+ }),
287
+ Effect.timeout(Duration.minutes(5)),
288
+ Effect.withSpan(`@livestore/effect:Store.Tag:${storeId}`),
289
+ Layer.effect(Tag),
290
+ Layer.withSpan(`LiveStore:${storeId}`),
291
+ Layer.provide(_DeferredLayer),
292
+ )
293
+ }
294
+
295
+ static readonly Deferred = _DeferredTag
296
+ static readonly DeferredLayer = _DeferredLayer
297
+
298
+ static readonly fromDeferred = pipe(
299
+ Effect.gen(function* () {
300
+ const deferred = yield* _DeferredTag
301
+ const ctx = yield* Deferred.await(deferred)
302
+ return Layer.succeed(Tag, Tag.of(ctx))
303
+ }),
304
+ Layer.unwrap,
305
+ )
306
+
307
+ static query<TResult>(query: Queryable<TResult>) {
308
+ return Effect.map(Tag, ({ store }) => store.query(query))
309
+ }
310
+
311
+ static commit(...eventInputs: LiveStoreEvent.Input.ForSchema<TSchema>[]) {
312
+ return Effect.map(Tag, ({ store }) => {
313
+ store.commit(...eventInputs)
314
+ })
315
+ }
316
+
317
+ static override use<A, E, R>(f: (ctx: RunningType) => Effect.Effect<A, E, R>) {
318
+ return Effect.flatMap(Tag, f)
319
+ }
320
+ }
321
+
322
+ return Tag as unknown as StoreTagClass<TSchema, TStoreId>
323
+ }
324
+
325
+ /**
326
+ * Store utilities for Effect integration.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * import { Store } from '@livestore/livestore/effect'
331
+ *
332
+ * export class MainStore extends Store.Tag(schema, 'main') {}
333
+ * ```
334
+ */
335
+ export const Store = {
336
+ /**
337
+ * Create a typed store context class for use with Effect.
338
+ * @see {@link makeStoreTag} for full documentation
339
+ */
340
+ Tag: makeStoreTag,
341
+ }
342
+
343
+ // =============================================================================
344
+ // Legacy API (deprecated)
345
+ // =============================================================================
346
+
347
+ /**
348
+ * @deprecated Use `Store.Tag(schema, storeId)` instead.
349
+ *
350
+ * @example Migration
351
+ * ```ts
352
+ * // Before
353
+ * const MainStoreContext = makeStoreContext<typeof schema>()('main')
354
+ * export const MainStore = MainStoreContext.Tag
355
+ * export const MainStoreLayer = MainStoreContext.Layer
356
+ *
357
+ * // After
358
+ * export class MainStore extends Store.Tag(schema, 'main') {}
359
+ * // MainStore.layer({ ... }) for the layer
360
+ * ```
361
+ */
362
+ export interface StoreContext<TSchema extends LiveStoreSchema, TStoreId extends string> {
363
+ readonly storeId: TStoreId
364
+ readonly Tag: Context.Key<StoreContextId<TSchema, TStoreId>, LiveStoreContextRunningType<TSchema>>
365
+ readonly DeferredTag: Context.Key<
366
+ DeferredContextId<TStoreId>,
367
+ Deferred.Deferred<LiveStoreContextRunningType<TSchema>, UnknownError>
368
+ >
369
+ readonly Layer: <
370
+ TContext = {},
371
+ TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json,
372
+ >(
373
+ props: Omit<LiveStoreContextProps<TSchema, TContext, TSyncPayloadSchema>, 'storeId'>,
374
+ ) => Layer.Layer<StoreContextId<TSchema, TStoreId>, UnknownError | Cause.TimeoutError, OtelTracer.OtelTracer>
375
+ readonly DeferredLayer: Layer.Layer<DeferredContextId<TStoreId>>
376
+ readonly fromDeferred: Layer.Layer<StoreContextId<TSchema, TStoreId>, UnknownError, DeferredContextId<TStoreId>>
377
+ }
378
+
379
+ /**
380
+ * @deprecated Use `Store.Tag(schema, storeId)` instead.
381
+ *
382
+ * @example Migration
383
+ * ```ts
384
+ * // Before
385
+ * const MainStoreContext = makeStoreContext<typeof schema>()('main')
386
+ *
387
+ * // After
388
+ * class MainStore extends Store.Tag(schema, 'main') {}
389
+ * ```
390
+ */
391
+ export const makeStoreContext =
392
+ <TSchema extends LiveStoreSchema>() =>
393
+ <TStoreId extends string>(storeId: TStoreId): StoreContext<TSchema, TStoreId> => {
394
+ type RunningType = LiveStoreContextRunningType<TSchema>
395
+ type DeferredType = Deferred.Deferred<RunningType, UnknownError>
396
+
397
+ const Tag = Context.Service<StoreContextId<TSchema, TStoreId>, RunningType>(`@livestore/store/${storeId}`)
398
+
399
+ const DeferredTag = Context.Service<DeferredContextId<TStoreId>, DeferredType>(
400
+ `@livestore/store-deferred/${storeId}`,
401
+ )
402
+
403
+ const DeferredLayer = Layer.effect(DeferredTag, Deferred.make<RunningType, UnknownError>())
404
+
405
+ const makeLayer = <
406
+ TContext = {},
407
+ TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json,
408
+ >(
409
+ props: Omit<LiveStoreContextProps<TSchema, TContext, TSyncPayloadSchema>, 'storeId'>,
410
+ ): Layer.Layer<StoreContextId<TSchema, TStoreId>, UnknownError | Cause.TimeoutError, OtelTracer.OtelTracer> =>
411
+ pipe(
412
+ Effect.gen(function* () {
413
+ const store = yield* createStore({
414
+ schema: props.schema,
415
+ storeId,
416
+ adapter: props.adapter,
417
+ batchUpdates: props.batchUpdates,
418
+ ...omitUndefineds({
419
+ context: props.context,
420
+ boot: props.boot,
421
+ disableDevtools: props.disableDevtools,
422
+ onBootStatus: props.onBootStatus,
423
+ syncPayload: props.syncPayload,
424
+ syncPayloadSchema: props.syncPayloadSchema,
425
+ }),
426
+ })
427
+
428
+ const ctx: RunningType = { stage: 'running', store: store as StoreClass<TSchema> }
429
+
430
+ // Also fulfill the deferred if it exists in context
431
+ yield* Effect.flatMap(Effect.serviceOption(DeferredTag), (optDeferred) =>
432
+ optDeferred._tag === 'Some' ? Deferred.succeed(optDeferred.value, ctx) : Effect.void,
433
+ )
434
+
435
+ return ctx
436
+ }),
437
+ Effect.timeout(Duration.minutes(5)),
438
+ Effect.withSpan(`@livestore/effect:makeStoreContext:${storeId}`),
439
+ Layer.effect(Tag),
440
+ Layer.withSpan(`LiveStore:${storeId}`),
441
+ Layer.provide(DeferredLayer),
442
+ )
443
+
444
+ const fromDeferred: Layer.Layer<
445
+ StoreContextId<TSchema, TStoreId>,
446
+ UnknownError,
447
+ DeferredContextId<TStoreId>
448
+ > = pipe(
449
+ Effect.gen(function* () {
450
+ const deferred = yield* DeferredTag
451
+ const ctx = yield* Deferred.await(deferred)
452
+ return Layer.succeed(Tag, ctx)
453
+ }),
454
+ Layer.unwrap,
455
+ )
456
+
457
+ return {
458
+ storeId,
459
+ Tag,
460
+ DeferredTag,
461
+ Layer: makeLayer,
462
+ DeferredLayer,
463
+ fromDeferred,
464
+ }
465
+ }
package/src/effect/mod.ts CHANGED
@@ -4,4 +4,16 @@ export {
4
4
  LiveStoreContextRunning as LiveStoreContext,
5
5
  LiveStoreContextRunning,
6
6
  } from '../store/create-store.ts'
7
- export { LiveStoreContextDeferred, LiveStoreContextLayer } from './LiveStore.ts'
7
+ // Store.Tag - Idiomatic Effect API
8
+ // Legacy API (deprecated)
9
+ export {
10
+ type DeferredContextId,
11
+ LiveStoreContextDeferred,
12
+ LiveStoreContextLayer,
13
+ makeStoreContext,
14
+ Store,
15
+ type StoreContext,
16
+ type StoreContextId,
17
+ type StoreLayerProps,
18
+ type StoreTagClass,
19
+ } from './LiveStore.ts'