@livestore/livestore 0.4.0 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +7 -5
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +20 -3
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +15 -15
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +17 -17
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.js +5 -6
- package/dist/effect/LiveStore.test.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +19 -2
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +28 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +4 -4
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +2 -2
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +26 -17
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +9 -9
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.d.ts.map +1 -1
- package/dist/live-queries/signal.js +2 -2
- package/dist/live-queries/signal.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/reactive.d.ts +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +20 -1
- package/dist/reactive.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +12 -12
- package/dist/store/StoreRegistry.d.ts.map +1 -1
- package/dist/store/StoreRegistry.js +24 -24
- package/dist/store/StoreRegistry.js.map +1 -1
- package/dist/store/StoreRegistry.test.js +62 -59
- package/dist/store/StoreRegistry.test.js.map +1 -1
- package/dist/store/create-store.d.ts +12 -15
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +43 -24
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -2
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +3 -2
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +5 -5
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +10 -28
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +2 -38
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store.d.ts +2 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +114 -56
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.js +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +2 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +26 -34
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/package.json +14 -24
- package/src/SqliteDbWrapper.test.ts +25 -3
- package/src/SqliteDbWrapper.ts +12 -9
- package/src/effect/LiveStore.test.ts +16 -16
- package/src/effect/LiveStore.ts +79 -50
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +273 -49
- package/src/live-queries/base-class.ts +38 -6
- package/src/live-queries/computed.ts +5 -5
- package/src/live-queries/db-query.test.ts +9 -9
- package/src/live-queries/db-query.ts +42 -30
- package/src/live-queries/signal.test.ts +2 -2
- package/src/live-queries/signal.ts +3 -3
- package/src/reactive.ts +45 -35
- package/src/store/StoreRegistry.test.ts +62 -59
- package/src/store/StoreRegistry.ts +49 -38
- package/src/store/create-store.ts +85 -51
- package/src/store/devtools.ts +283 -283
- package/src/store/store-eventstream.test.ts +16 -7
- package/src/store/store-types.test.ts +1 -1
- package/src/store/store-types.ts +10 -56
- package/src/store/store.ts +148 -71
- package/src/utils/dev.ts +1 -1
- package/src/utils/stack-info.ts +4 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { UnknownError } from '@livestore/common';
|
|
2
2
|
import type { LiveStoreSchema } from '@livestore/common/schema';
|
|
3
|
-
import { Effect, type OtelTracer,
|
|
3
|
+
import { type Context, Effect, type OtelTracer, type Schema, Scope } from '@livestore/utils/effect';
|
|
4
4
|
import { type CreateStoreOptions } from './create-store.ts';
|
|
5
|
-
import type { Store } from './store.ts';
|
|
6
5
|
import type { OtelOptions } from './store-types.ts';
|
|
6
|
+
import type { Store } from './store.ts';
|
|
7
7
|
/**
|
|
8
8
|
* Default time to keep unused stores in cache.
|
|
9
9
|
*
|
|
@@ -26,7 +26,7 @@ export declare const DEFAULT_UNUSED_CACHE_TIME: number;
|
|
|
26
26
|
* @see {@link storeOptions} for defining reusable store configurations
|
|
27
27
|
* @see {@link StoreRegistry} for managing store lifecycles
|
|
28
28
|
*/
|
|
29
|
-
export interface RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
29
|
+
export interface RegistryStoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json> extends CreateStoreOptions<TSchema, TContext, TSyncPayloadSchema> {
|
|
30
30
|
/**
|
|
31
31
|
* OpenTelemetry configuration for tracing store operations.
|
|
32
32
|
*
|
|
@@ -71,10 +71,10 @@ type StoreRegistryConfig = {
|
|
|
71
71
|
*/
|
|
72
72
|
defaultOptions?: Partial<Pick<RegistryStoreOptions, 'batchUpdates' | 'disableDevtools' | 'confirmUnsavedChanges' | 'debug' | 'otelOptions' | 'unusedCacheTime'>>;
|
|
73
73
|
/**
|
|
74
|
-
* Custom Effect
|
|
75
|
-
* When the
|
|
74
|
+
* Custom Effect context for all registry operations (loading, caching, etc.).
|
|
75
|
+
* When the context's scope closes, all managed stores are automatically shut down.
|
|
76
76
|
*/
|
|
77
|
-
|
|
77
|
+
context?: Context.Context<Scope.Scope | OtelTracer.OtelTracer>;
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
80
80
|
* Store Registry coordinating store loading, caching, and retention
|
|
@@ -111,7 +111,7 @@ export declare class StoreRegistry {
|
|
|
111
111
|
* if no other scopes retain it
|
|
112
112
|
* - Concurrent calls with the same storeId share the same store instance
|
|
113
113
|
*/
|
|
114
|
-
getOrLoad: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
114
|
+
getOrLoad: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Effect.Effect<Store<TSchema, TContext>, UnknownError, Scope.Scope>;
|
|
115
115
|
/**
|
|
116
116
|
* Get or load a store, returning it directly if already loaded or a promise if loading.
|
|
117
117
|
*
|
|
@@ -128,7 +128,7 @@ export declare class StoreRegistry {
|
|
|
128
128
|
* - Applies default options from registry config, with call-site options taking precedence
|
|
129
129
|
* - Concurrent calls with the same storeId share the same store instance
|
|
130
130
|
*/
|
|
131
|
-
getOrLoadPromise: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
131
|
+
getOrLoadPromise: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> | Promise<Store<TSchema, TContext>>;
|
|
132
132
|
/**
|
|
133
133
|
* Retains the store in cache.
|
|
134
134
|
*
|
|
@@ -142,7 +142,7 @@ export declare class StoreRegistry {
|
|
|
142
142
|
* - If the store isn't cached yet, it will be loaded and then retained
|
|
143
143
|
* - The store will remain in cache until all retains are released and after `unusedCacheTime` expires
|
|
144
144
|
*/
|
|
145
|
-
retain: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
145
|
+
retain: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => (() => void);
|
|
146
146
|
/**
|
|
147
147
|
* Loads a store (without suspending) to warm up the cache.
|
|
148
148
|
*
|
|
@@ -156,7 +156,7 @@ export declare class StoreRegistry {
|
|
|
156
156
|
* - If the entry remains unused after preload resolves/rejects, it is scheduled for disposal.
|
|
157
157
|
* - Does not affect the retention of the store in cache.
|
|
158
158
|
*/
|
|
159
|
-
preload: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
159
|
+
preload: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Promise<void>;
|
|
160
160
|
/**
|
|
161
161
|
* Disposes the registry and all its managed stores, immediately releasing resources
|
|
162
162
|
* (database connections, WebSocket connections, web workers, etc.).
|
|
@@ -168,7 +168,7 @@ export declare class StoreRegistry {
|
|
|
168
168
|
* @returns A promise that resolves when disposal is complete
|
|
169
169
|
*
|
|
170
170
|
* @remarks
|
|
171
|
-
* - No-op if a custom `
|
|
171
|
+
* - No-op if a custom `context` was provided to the constructor (caller owns cleanup)
|
|
172
172
|
* - Idempotent: safe to call multiple times
|
|
173
173
|
* - After disposal, the registry should not be used
|
|
174
174
|
*/
|
|
@@ -210,6 +210,6 @@ export declare class StoreRegistry {
|
|
|
210
210
|
* });
|
|
211
211
|
* ```
|
|
212
212
|
*/
|
|
213
|
-
export declare const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema
|
|
213
|
+
export declare const storeOptions: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Codec<Schema.Json, Schema.Json> = typeof Schema.Json>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>;
|
|
214
214
|
export {};
|
|
215
215
|
//# sourceMappingURL=StoreRegistry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoreRegistry.d.ts","sourceRoot":"","sources":["../../src/store/StoreRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"StoreRegistry.d.ts","sourceRoot":"","sources":["../../src/store/StoreRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { omitUndefineds } from '@livestore/utils';
|
|
3
|
-
import { Cause, Effect, Equal, Exit, Fiber, Hash, Layer, ManagedRuntime, RcMap,
|
|
1
|
+
import { OtelLiveDummy, provideOtel, UnknownError } from '@livestore/common';
|
|
2
|
+
import { isDevEnv, omitUndefineds } from '@livestore/utils';
|
|
3
|
+
import { Cause, Effect, Equal, Exit, Fiber, Hash, Layer, ManagedRuntime, Result, RcMap, References, Scope, } from '@livestore/utils/effect';
|
|
4
4
|
import { createStore } from "./create-store.js";
|
|
5
5
|
/**
|
|
6
6
|
* Default time to keep unused stores in cache.
|
|
@@ -51,13 +51,13 @@ export class StoreRegistry {
|
|
|
51
51
|
*/
|
|
52
52
|
#rcMap;
|
|
53
53
|
/**
|
|
54
|
-
* Effect
|
|
55
|
-
* When the
|
|
54
|
+
* Effect context providing Scope and OtelTracer for all registry operations.
|
|
55
|
+
* When the context's scope closes, all managed stores are automatically shut down.
|
|
56
56
|
*/
|
|
57
|
-
#
|
|
57
|
+
#context;
|
|
58
58
|
/**
|
|
59
59
|
* Disposal callback for the runtime created by the registry.
|
|
60
|
-
* Undefined when caller provided their own
|
|
60
|
+
* Undefined when caller provided their own services (caller owns cleanup in that case).
|
|
61
61
|
*/
|
|
62
62
|
#disposeOwnedRuntime;
|
|
63
63
|
/**
|
|
@@ -79,25 +79,25 @@ export class StoreRegistry {
|
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
81
|
constructor(config = {}) {
|
|
82
|
-
if (config.
|
|
83
|
-
this.#
|
|
82
|
+
if (config.context !== undefined) {
|
|
83
|
+
this.#context = config.context;
|
|
84
84
|
}
|
|
85
85
|
else {
|
|
86
|
-
const ownedRuntime = ManagedRuntime.make(Layer.mergeAll(Layer.scope, OtelLiveDummy));
|
|
87
|
-
this.#
|
|
86
|
+
const ownedRuntime = ManagedRuntime.make(Layer.mergeAll(Layer.effect(Scope.Scope, Effect.scope), OtelLiveDummy));
|
|
87
|
+
this.#context = ownedRuntime.contextEffect.pipe(Effect.runSync);
|
|
88
88
|
this.#disposeOwnedRuntime = () => ownedRuntime.dispose();
|
|
89
89
|
}
|
|
90
90
|
this.#rcMap = RcMap.make({
|
|
91
91
|
lookup: ({ options }) => {
|
|
92
92
|
// Merge registry defaults with call-site options (call-site takes precedence)
|
|
93
93
|
const mergedOptions = { ...config.defaultOptions, ...options };
|
|
94
|
-
return createStore(mergedOptions).pipe(Effect.
|
|
94
|
+
return createStore(mergedOptions).pipe(Effect.catchDefect((cause) => UnknownError.make({ cause })), Effect.withSpan(`StoreRegistry.lookup:${mergedOptions.storeId}`), Effect.provide(Layer.mergeAll(mergedOptions.logger ?? Layer.empty, Layer.succeed(References.MinimumLogLevel, mergedOptions.logLevel ?? (isDevEnv() === true ? 'Debug' : 'Info')))), provideOtel(omitUndefineds({
|
|
95
95
|
parentSpanContext: mergedOptions.otelOptions?.rootSpanContext,
|
|
96
96
|
otelTracer: mergedOptions.otelOptions?.tracer,
|
|
97
97
|
})));
|
|
98
98
|
},
|
|
99
99
|
idleTimeToLive: ({ options }) => options.unusedCacheTime ?? config.defaultOptions?.unusedCacheTime ?? DEFAULT_UNUSED_CACHE_TIME,
|
|
100
|
-
}).pipe(
|
|
100
|
+
}).pipe(Effect.runSyncWith(this.#context));
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Gets a cached store or loads a new one, with the store lifetime scoped to the caller.
|
|
@@ -113,7 +113,7 @@ export class StoreRegistry {
|
|
|
113
113
|
* if no other scopes retain it
|
|
114
114
|
* - Concurrent calls with the same storeId share the same store instance
|
|
115
115
|
*/
|
|
116
|
-
getOrLoad = (options) => Effect.gen(this, function* () {
|
|
116
|
+
getOrLoad = (options) => Effect.gen({ self: this }, function* () {
|
|
117
117
|
// Cast options to satisfy StoreCacheKey's wider type (type safety enforced at API boundary)
|
|
118
118
|
const key = new StoreCacheKey(options);
|
|
119
119
|
const store = yield* RcMap.get(this.#rcMap, key);
|
|
@@ -136,16 +136,16 @@ export class StoreRegistry {
|
|
|
136
136
|
* - Concurrent calls with the same storeId share the same store instance
|
|
137
137
|
*/
|
|
138
138
|
getOrLoadPromise = (options) => {
|
|
139
|
-
const exit = this.getOrLoad(options).pipe(Effect.scoped,
|
|
139
|
+
const exit = this.getOrLoad(options).pipe(Effect.scoped, Effect.runSyncExitWith(this.#context));
|
|
140
140
|
if (Exit.isSuccess(exit) === true)
|
|
141
141
|
return exit.value;
|
|
142
142
|
// Check if the failure is due to async work
|
|
143
|
-
const defect = Cause.
|
|
144
|
-
if (defect
|
|
143
|
+
const defect = Cause.findDefect(exit.cause);
|
|
144
|
+
if (Result.isFailure(defect) === true) {
|
|
145
145
|
// Handle synchronous failure
|
|
146
146
|
throw Cause.squash(exit.cause);
|
|
147
147
|
}
|
|
148
|
-
if (
|
|
148
|
+
if (Cause.isAsyncFiberError(defect.success) === false) {
|
|
149
149
|
// Handle synchronous failure
|
|
150
150
|
throw Cause.squash(exit.cause);
|
|
151
151
|
}
|
|
@@ -155,9 +155,9 @@ export class StoreRegistry {
|
|
|
155
155
|
if (cached !== undefined)
|
|
156
156
|
return cached;
|
|
157
157
|
// Create and cache the promise
|
|
158
|
-
const fiber = defect.
|
|
158
|
+
const fiber = defect.success.fiber;
|
|
159
159
|
const promise = Fiber.join(fiber)
|
|
160
|
-
.pipe(
|
|
160
|
+
.pipe(Effect.runPromiseWith(this.#context))
|
|
161
161
|
.finally(() => this.#loadingPromises.delete(storeId));
|
|
162
162
|
this.#loadingPromises.set(storeId, promise);
|
|
163
163
|
return promise;
|
|
@@ -176,15 +176,15 @@ export class StoreRegistry {
|
|
|
176
176
|
* - The store will remain in cache until all retains are released and after `unusedCacheTime` expires
|
|
177
177
|
*/
|
|
178
178
|
retain = (options) => {
|
|
179
|
-
const release = Effect.gen(this, function* () {
|
|
179
|
+
const release = Effect.gen({ self: this }, function* () {
|
|
180
180
|
// Cast options to satisfy StoreCacheKey's wider type (type safety enforced at API boundary)
|
|
181
181
|
const key = new StoreCacheKey(options);
|
|
182
182
|
yield* RcMap.get(this.#rcMap, key);
|
|
183
183
|
// Effect.never suspends indefinitely, keeping the RcMap reference alive.
|
|
184
184
|
// When `release()` is called, the fiber is interrupted, closing the scope
|
|
185
185
|
// and releasing the RcMap entry (which may trigger disposal after idleTimeToLive).
|
|
186
|
-
yield* Effect.never;
|
|
187
|
-
}).pipe(Effect.scoped,
|
|
186
|
+
return yield* Effect.never;
|
|
187
|
+
}).pipe(Effect.scoped, Effect.runCallbackWith(this.#context));
|
|
188
188
|
return () => release();
|
|
189
189
|
};
|
|
190
190
|
/**
|
|
@@ -219,7 +219,7 @@ export class StoreRegistry {
|
|
|
219
219
|
* @returns A promise that resolves when disposal is complete
|
|
220
220
|
*
|
|
221
221
|
* @remarks
|
|
222
|
-
* - No-op if a custom `
|
|
222
|
+
* - No-op if a custom `context` was provided to the constructor (caller owns cleanup)
|
|
223
223
|
* - Idempotent: safe to call multiple times
|
|
224
224
|
* - After disposal, the registry should not be used
|
|
225
225
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoreRegistry.js","sourceRoot":"","sources":["../../src/store/StoreRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"StoreRegistry.js","sourceRoot":"","sources":["../../src/store/StoreRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -124,17 +124,17 @@ describe('StoreRegistry', () => {
|
|
|
124
124
|
await store.shutdownPromise();
|
|
125
125
|
});
|
|
126
126
|
it.layer(OtelLiveDummy)('time-dependent (using TestClock)', (it) => {
|
|
127
|
-
it.
|
|
127
|
+
it.effect('disposes store after unusedCacheTime expires', () => Effect.gen(function* () {
|
|
128
128
|
const unusedCacheTime = 25;
|
|
129
|
-
const
|
|
130
|
-
const registry = new StoreRegistry({
|
|
129
|
+
const services = yield* Effect.context();
|
|
130
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
131
131
|
const options = testStoreOptions();
|
|
132
132
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
133
133
|
// Store should still be in cache
|
|
134
134
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
135
135
|
expect(cached).toBe(store);
|
|
136
136
|
// Let the idle timer fiber register its sleep with TestClock
|
|
137
|
-
yield* Effect.yieldNow
|
|
137
|
+
yield* Effect.yieldNow;
|
|
138
138
|
// Advance time past unusedCacheTime → idle timer fires → entry evicted
|
|
139
139
|
yield* TestClock.adjust(unusedCacheTime);
|
|
140
140
|
// After eviction, a new load should produce a different store
|
|
@@ -142,9 +142,12 @@ describe('StoreRegistry', () => {
|
|
|
142
142
|
expect(nextStore).not.toBe(store);
|
|
143
143
|
expect(nextStore[StoreInternalsSymbol].clientSession.debugInstanceId).toBeDefined();
|
|
144
144
|
}));
|
|
145
|
-
it.
|
|
146
|
-
const
|
|
147
|
-
const registry = new StoreRegistry({
|
|
145
|
+
it.effect('does not dispose when unusedCacheTime is Infinity', () => Effect.gen(function* () {
|
|
146
|
+
const services = yield* Effect.context();
|
|
147
|
+
const registry = new StoreRegistry({
|
|
148
|
+
context: services,
|
|
149
|
+
defaultOptions: { unusedCacheTime: Number.POSITIVE_INFINITY },
|
|
150
|
+
});
|
|
148
151
|
const options = testStoreOptions();
|
|
149
152
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
150
153
|
// Advance a large amount of time — no idle timer was started for Infinity
|
|
@@ -153,39 +156,39 @@ describe('StoreRegistry', () => {
|
|
|
153
156
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
154
157
|
expect(cached).toBe(store);
|
|
155
158
|
}));
|
|
156
|
-
it.
|
|
159
|
+
it.effect('schedules disposal if store becomes unused during loading', () => Effect.gen(function* () {
|
|
157
160
|
const unusedCacheTime = 50;
|
|
158
|
-
const
|
|
159
|
-
const registry = new StoreRegistry({
|
|
161
|
+
const services = yield* Effect.context();
|
|
162
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
160
163
|
const options = testStoreOptions();
|
|
161
164
|
// Load without retaining — disposal is scheduled when scope closes
|
|
162
165
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
163
|
-
yield* Effect.yieldNow
|
|
166
|
+
yield* Effect.yieldNow;
|
|
164
167
|
yield* TestClock.adjust(unusedCacheTime);
|
|
165
168
|
// Store should be disposed
|
|
166
169
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
167
170
|
expect(nextStore).not.toBe(store);
|
|
168
171
|
}));
|
|
169
|
-
it.
|
|
170
|
-
const
|
|
171
|
-
const registry = new StoreRegistry({
|
|
172
|
+
it.effect('allows call-site options to override default options', () => Effect.gen(function* () {
|
|
173
|
+
const services = yield* Effect.context();
|
|
174
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime: 10_000 } }); // Long default
|
|
172
175
|
const unusedCacheTimeOverride = 25;
|
|
173
176
|
const options = testStoreOptions({ unusedCacheTime: unusedCacheTimeOverride });
|
|
174
177
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
175
|
-
yield* Effect.yieldNow
|
|
178
|
+
yield* Effect.yieldNow;
|
|
176
179
|
yield* TestClock.adjust(unusedCacheTimeOverride);
|
|
177
180
|
// Should be disposed according to the override time, not default
|
|
178
181
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
179
182
|
expect(nextStore).not.toBe(store);
|
|
180
183
|
}));
|
|
181
|
-
it.
|
|
182
|
-
const
|
|
183
|
-
const registry = new StoreRegistry({
|
|
184
|
+
it.effect('disposes different stores according to their own unusedCacheTime', () => Effect.gen(function* () {
|
|
185
|
+
const services = yield* Effect.context();
|
|
186
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime: 1000 } });
|
|
184
187
|
const shortOptions = testStoreOptions({ storeId: 'short-lived', unusedCacheTime: 25 });
|
|
185
188
|
const longOptions = testStoreOptions({ storeId: 'long-lived', unusedCacheTime: 10_000 });
|
|
186
189
|
const shortStore = yield* registry.getOrLoad(shortOptions).pipe(Effect.scoped);
|
|
187
190
|
const longStore = yield* registry.getOrLoad(longOptions).pipe(Effect.scoped);
|
|
188
|
-
yield* Effect.yieldNow
|
|
191
|
+
yield* Effect.yieldNow;
|
|
189
192
|
// Advance past short store's unusedCacheTime only
|
|
190
193
|
yield* TestClock.adjust(25);
|
|
191
194
|
// Short store should be disposed, long store should still be cached
|
|
@@ -196,16 +199,16 @@ describe('StoreRegistry', () => {
|
|
|
196
199
|
}));
|
|
197
200
|
// This test is skipped because we don't yet support dynamic `unusedCacheTime` updates for cached stores.
|
|
198
201
|
// See https://github.com/livestorejs/livestore/issues/918
|
|
199
|
-
it.
|
|
200
|
-
const
|
|
201
|
-
const registry = new StoreRegistry({
|
|
202
|
+
it.effect.skip('keeps the longest unusedCacheTime seen for a store when options vary across calls', () => Effect.gen(function* () {
|
|
203
|
+
const services = yield* Effect.context();
|
|
204
|
+
const registry = new StoreRegistry({ context: services });
|
|
202
205
|
const options = testStoreOptions({ unusedCacheTime: 10 });
|
|
203
206
|
const release = registry.retain(options);
|
|
204
207
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
205
208
|
// Call with longer unusedCacheTime
|
|
206
209
|
yield* registry.getOrLoad(testStoreOptions({ unusedCacheTime: 100 })).pipe(Effect.scoped);
|
|
207
210
|
release();
|
|
208
|
-
yield* Effect.yieldNow
|
|
211
|
+
yield* Effect.yieldNow;
|
|
209
212
|
// After 99ms, store should still be alive (100ms unusedCacheTime used)
|
|
210
213
|
yield* TestClock.adjust(99);
|
|
211
214
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
@@ -215,10 +218,10 @@ describe('StoreRegistry', () => {
|
|
|
215
218
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
216
219
|
expect(nextStore).not.toBe(store);
|
|
217
220
|
}));
|
|
218
|
-
it.
|
|
221
|
+
it.effect('handles rapid retain/release cycles without errors', () => Effect.gen(function* () {
|
|
219
222
|
const unusedCacheTime = 50;
|
|
220
|
-
const
|
|
221
|
-
const registry = new StoreRegistry({
|
|
223
|
+
const services = yield* Effect.context();
|
|
224
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
222
225
|
const options = testStoreOptions();
|
|
223
226
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
224
227
|
// Rapidly retain and release multiple times
|
|
@@ -226,19 +229,19 @@ describe('StoreRegistry', () => {
|
|
|
226
229
|
const release = registry.retain(options);
|
|
227
230
|
release();
|
|
228
231
|
}
|
|
229
|
-
yield* Effect.yieldNow
|
|
232
|
+
yield* Effect.yieldNow;
|
|
230
233
|
yield* TestClock.adjust(unusedCacheTime);
|
|
231
234
|
// Store should be disposed after the last release
|
|
232
235
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
233
236
|
expect(nextStore).not.toBe(store);
|
|
234
237
|
}));
|
|
235
|
-
it.
|
|
238
|
+
it.effect('cancels disposal when new retain', () => Effect.gen(function* () {
|
|
236
239
|
const unusedCacheTime = 50;
|
|
237
|
-
const
|
|
238
|
-
const registry = new StoreRegistry({
|
|
240
|
+
const services = yield* Effect.context();
|
|
241
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
239
242
|
const options = testStoreOptions();
|
|
240
243
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
241
|
-
yield* Effect.yieldNow
|
|
244
|
+
yield* Effect.yieldNow;
|
|
242
245
|
// Advance almost to disposal threshold
|
|
243
246
|
yield* TestClock.adjust(unusedCacheTime - 5);
|
|
244
247
|
// Add a new retain before disposal triggers
|
|
@@ -250,17 +253,17 @@ describe('StoreRegistry', () => {
|
|
|
250
253
|
expect(cached).toBe(store);
|
|
251
254
|
// Release retain — new idle timer starts
|
|
252
255
|
release();
|
|
253
|
-
yield* Effect.yieldNow
|
|
256
|
+
yield* Effect.yieldNow;
|
|
254
257
|
yield* TestClock.adjust(unusedCacheTime);
|
|
255
258
|
// Now it should be disposed
|
|
256
259
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
257
260
|
expect(nextStore).not.toBe(store);
|
|
258
261
|
}));
|
|
259
|
-
it.
|
|
262
|
+
it.effect('aborts loading when disposal fires while store is still loading', () => Effect.gen(function* () {
|
|
260
263
|
const unusedCacheTime = 10;
|
|
261
264
|
const loadDelay = 1000;
|
|
262
|
-
const
|
|
263
|
-
const registry = new StoreRegistry({
|
|
265
|
+
const services = yield* Effect.context();
|
|
266
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
264
267
|
// Adapter that takes time to load (controlled by TestClock)
|
|
265
268
|
const baseAdapter = makeInMemoryAdapter();
|
|
266
269
|
const options = testStoreOptions({
|
|
@@ -271,30 +274,30 @@ describe('StoreRegistry', () => {
|
|
|
271
274
|
});
|
|
272
275
|
// Retain triggers loading (won't complete until clock advances past loadDelay)
|
|
273
276
|
const release = registry.retain(options);
|
|
274
|
-
yield* Effect.yieldNow
|
|
277
|
+
yield* Effect.yieldNow;
|
|
275
278
|
// Release immediately — schedules disposal after unusedCacheTime
|
|
276
279
|
release();
|
|
277
|
-
yield* Effect.yieldNow
|
|
280
|
+
yield* Effect.yieldNow;
|
|
278
281
|
// Advance past unusedCacheTime but NOT past loadDelay → disposal fires, interrupts loading
|
|
279
282
|
yield* TestClock.adjust(unusedCacheTime);
|
|
280
283
|
// Start a fresh load — since the first was aborted, this should be a new entry
|
|
281
|
-
const freshLoadFiber = yield* Effect.
|
|
282
|
-
yield* Effect.yieldNow
|
|
284
|
+
const freshLoadFiber = yield* Effect.forkChild(registry.getOrLoad(options).pipe(Effect.scoped));
|
|
285
|
+
yield* Effect.yieldNow;
|
|
283
286
|
// Advance enough for the fresh load to complete
|
|
284
287
|
yield* TestClock.adjust(loadDelay);
|
|
285
288
|
const store = yield* Fiber.join(freshLoadFiber);
|
|
286
289
|
expect(store).toBeDefined();
|
|
287
290
|
}));
|
|
288
|
-
it.
|
|
291
|
+
it.effect('retain keeps store alive past unusedCacheTime', () => Effect.gen(function* () {
|
|
289
292
|
const unusedCacheTime = 50;
|
|
290
|
-
const
|
|
291
|
-
const registry = new StoreRegistry({
|
|
293
|
+
const services = yield* Effect.context();
|
|
294
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
292
295
|
const options = testStoreOptions();
|
|
293
296
|
// Load the store
|
|
294
297
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
295
298
|
// Retain the store before disposal could fire
|
|
296
299
|
const release = registry.retain(options);
|
|
297
|
-
yield* Effect.yieldNow
|
|
300
|
+
yield* Effect.yieldNow;
|
|
298
301
|
// Advance past unusedCacheTime — idle timer fires but refCount > 0, so no eviction
|
|
299
302
|
yield* TestClock.adjust(unusedCacheTime + 50);
|
|
300
303
|
// Store should still be cached because retain keeps it alive
|
|
@@ -302,10 +305,10 @@ describe('StoreRegistry', () => {
|
|
|
302
305
|
expect(cached).toBe(store);
|
|
303
306
|
release();
|
|
304
307
|
}));
|
|
305
|
-
it.
|
|
308
|
+
it.effect('manages multiple stores with different IDs independently', () => Effect.gen(function* () {
|
|
306
309
|
const unusedCacheTime = 50;
|
|
307
|
-
const
|
|
308
|
-
const registry = new StoreRegistry({
|
|
310
|
+
const services = yield* Effect.context();
|
|
311
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
309
312
|
const options1 = testStoreOptions({ storeId: 'store-1' });
|
|
310
313
|
const options2 = testStoreOptions({ storeId: 'store-2' });
|
|
311
314
|
const store1 = yield* registry.getOrLoad(options1).pipe(Effect.scoped);
|
|
@@ -317,7 +320,7 @@ describe('StoreRegistry', () => {
|
|
|
317
320
|
const cached2 = yield* registry.getOrLoad(options2).pipe(Effect.scoped);
|
|
318
321
|
expect(cached1).toBe(store1);
|
|
319
322
|
expect(cached2).toBe(store2);
|
|
320
|
-
yield* Effect.yieldNow
|
|
323
|
+
yield* Effect.yieldNow;
|
|
321
324
|
yield* TestClock.adjust(unusedCacheTime);
|
|
322
325
|
// Both stores should be disposed
|
|
323
326
|
const newStore1 = yield* registry.getOrLoad(options1).pipe(Effect.scoped);
|
|
@@ -325,46 +328,46 @@ describe('StoreRegistry', () => {
|
|
|
325
328
|
expect(newStore1).not.toBe(store1);
|
|
326
329
|
expect(newStore2).not.toBe(store2);
|
|
327
330
|
}));
|
|
328
|
-
it.
|
|
329
|
-
const
|
|
330
|
-
const registry = new StoreRegistry({
|
|
331
|
+
it.effect('applies default options from constructor', () => Effect.gen(function* () {
|
|
332
|
+
const services = yield* Effect.context();
|
|
333
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime: 100 } });
|
|
331
334
|
const options = testStoreOptions();
|
|
332
335
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
333
336
|
// Verify the store loads successfully
|
|
334
337
|
expect(store).toBeDefined();
|
|
335
338
|
expect(store[StoreInternalsSymbol].clientSession.debugInstanceId).toBeDefined();
|
|
336
|
-
yield* Effect.yieldNow
|
|
339
|
+
yield* Effect.yieldNow;
|
|
337
340
|
// After 50ms, store should still be cached (default is 100ms)
|
|
338
341
|
yield* TestClock.adjust(50);
|
|
339
342
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
340
343
|
expect(cached).toBe(store);
|
|
341
344
|
}));
|
|
342
|
-
it.
|
|
345
|
+
it.effect('does not serve a disposed store from cache', () => Effect.gen(function* () {
|
|
343
346
|
const unusedCacheTime = 25;
|
|
344
|
-
const
|
|
345
|
-
const registry = new StoreRegistry({
|
|
347
|
+
const services = yield* Effect.context();
|
|
348
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
346
349
|
const options = testStoreOptions();
|
|
347
350
|
const originalStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
348
351
|
// Verify store is cached
|
|
349
352
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
350
353
|
expect(cached).toBe(originalStore);
|
|
351
|
-
yield* Effect.yieldNow
|
|
354
|
+
yield* Effect.yieldNow;
|
|
352
355
|
yield* TestClock.adjust(unusedCacheTime);
|
|
353
356
|
// After disposal, calling getOrLoad should produce a fresh store
|
|
354
357
|
const freshStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
355
358
|
expect(freshStore).not.toBe(originalStore);
|
|
356
359
|
}));
|
|
357
|
-
it.
|
|
360
|
+
it.effect('schedules disposal after preload if no retainers are added', () => Effect.gen(function* () {
|
|
358
361
|
const unusedCacheTime = 50;
|
|
359
|
-
const
|
|
360
|
-
const registry = new StoreRegistry({
|
|
362
|
+
const services = yield* Effect.context();
|
|
363
|
+
const registry = new StoreRegistry({ context: services, defaultOptions: { unusedCacheTime } });
|
|
361
364
|
const options = testStoreOptions();
|
|
362
365
|
// Preload without retaining (load + immediate release)
|
|
363
366
|
const store = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
364
367
|
// Verify it's cached
|
|
365
368
|
const cached = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|
|
366
369
|
expect(cached).toBe(store);
|
|
367
|
-
yield* Effect.yieldNow
|
|
370
|
+
yield* Effect.yieldNow;
|
|
368
371
|
yield* TestClock.adjust(unusedCacheTime);
|
|
369
372
|
// Store should be disposed since no retainers were added
|
|
370
373
|
const nextStore = yield* registry.getOrLoad(options).pipe(Effect.scoped);
|