@effect-app/infra 4.0.0-beta.111 → 4.0.0-beta.113

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 (56) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/Model/Repository/Registry.d.ts +20 -0
  3. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  4. package/dist/Model/Repository/Registry.js +17 -0
  5. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  6. package/dist/Model/Repository/internal/internal.js +2 -1
  7. package/dist/Model/Repository/makeRepo.d.ts +3 -2
  8. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  9. package/dist/Model/Repository/makeRepo.js +4 -1
  10. package/dist/Model/Repository/service.d.ts +5 -0
  11. package/dist/Model/Repository/service.d.ts.map +1 -1
  12. package/dist/Model/Repository.d.ts +1 -0
  13. package/dist/Model/Repository.d.ts.map +1 -1
  14. package/dist/Model/Repository.js +2 -1
  15. package/dist/Model.d.ts +1 -0
  16. package/dist/Model.d.ts.map +1 -1
  17. package/dist/Model.js +2 -1
  18. package/dist/OperationsRepo.d.ts +1 -1
  19. package/dist/Store/ContextMapContainer.d.ts +1 -1
  20. package/dist/Store/Cosmos.d.ts.map +1 -1
  21. package/dist/Store/Cosmos.js +10 -10
  22. package/dist/Store/Disk.d.ts.map +1 -1
  23. package/dist/Store/Disk.js +22 -18
  24. package/dist/Store/Memory.d.ts.map +1 -1
  25. package/dist/Store/Memory.js +23 -18
  26. package/dist/Store/SQL/Pg.d.ts.map +1 -1
  27. package/dist/Store/SQL/Pg.js +27 -21
  28. package/dist/Store/SQL.d.ts.map +1 -1
  29. package/dist/Store/SQL.js +53 -41
  30. package/dist/Store/index.d.ts +1 -1
  31. package/dist/Store/index.d.ts.map +1 -1
  32. package/dist/Store/index.js +4 -2
  33. package/dist/Store/service.d.ts +5 -0
  34. package/dist/Store/service.d.ts.map +1 -1
  35. package/dist/Store/service.js +1 -1
  36. package/dist/api/internal/RequestContextMiddleware.d.ts +1 -1
  37. package/package.json +6 -2
  38. package/src/Model/Repository/Registry.ts +33 -0
  39. package/src/Model/Repository/internal/internal.ts +1 -0
  40. package/src/Model/Repository/makeRepo.ts +5 -2
  41. package/src/Model/Repository/service.ts +6 -0
  42. package/src/Model/Repository.ts +1 -0
  43. package/src/Model.ts +1 -0
  44. package/src/Store/Cosmos.ts +10 -12
  45. package/src/Store/Disk.ts +35 -30
  46. package/src/Store/Memory.ts +25 -18
  47. package/src/Store/SQL/Pg.ts +29 -42
  48. package/src/Store/SQL.ts +59 -84
  49. package/src/Store/index.ts +2 -1
  50. package/src/Store/service.ts +5 -0
  51. package/test/contextProvider.test.ts +6 -6
  52. package/test/dist/query.test.d.ts.map +1 -1
  53. package/test/dist/rawQuery.test.d.ts.map +1 -1
  54. package/test/query.test.ts +22 -19
  55. package/test/rawQuery.test.ts +4 -2
  56. package/test/validateSample.test.ts +11 -8
@@ -127,10 +127,6 @@ function makeCosmosStore({ prefix }: StorageConfig) {
127
127
  }
128
128
  yield* cached
129
129
  })
130
- const resolveAndSeed = resolveNamespace.pipe(
131
- Effect.tap((ns) => seedNamespace(ns))
132
- )
133
-
134
130
  const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
135
131
  Effect
136
132
  .gen(function*() {
@@ -239,10 +235,10 @@ function makeCosmosStore({ prefix }: StorageConfig) {
239
235
  )
240
236
 
241
237
  const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
242
- resolveAndSeed.pipe(Effect.flatMap((ns) => bulkSetInternal(items, ns)))
238
+ resolveNamespace.pipe(Effect.flatMap((ns) => bulkSetInternal(items, ns)))
243
239
 
244
240
  const batchSet = (items: NonEmptyReadonlyArray<PM>) => {
245
- return resolveAndSeed
241
+ return resolveNamespace
246
242
  .pipe(Effect.flatMap((ns) =>
247
243
  Effect
248
244
  .suspend(() => {
@@ -315,9 +311,11 @@ function makeCosmosStore({ prefix }: StorageConfig) {
315
311
  }
316
312
 
317
313
  const s: Store<IdKey, Encoded> = {
314
+ seedNamespace: (ns) => seedNamespace(ns),
315
+
318
316
  queryRaw: <Out>(query: RawQuery<Encoded, Out>) =>
319
317
  Effect
320
- .all({ q: Effect.sync(() => query.cosmos({ name })), ns: resolveAndSeed })
318
+ .all({ q: Effect.sync(() => query.cosmos({ name })), ns: resolveNamespace })
321
319
  .pipe(
322
320
  Effect.tap(({ q }) => logQuery(q)),
323
321
  Effect.flatMap(({ ns, q }) =>
@@ -345,7 +343,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
345
343
  )
346
344
  ),
347
345
  batchRemove: (ids, partitionKey?: string) =>
348
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
346
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
349
347
  Effect
350
348
  .promise(() =>
351
349
  execBatch(
@@ -376,7 +374,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
376
374
  query: `SELECT * FROM ${name}`,
377
375
  parameters: []
378
376
  })),
379
- ns: resolveAndSeed
377
+ ns: resolveNamespace
380
378
  })
381
379
  .pipe(
382
380
  Effect.tap(({ q }) => logQuery(q)),
@@ -430,7 +428,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
430
428
  limit
431
429
  )
432
430
  ),
433
- ns: resolveAndSeed
431
+ ns: resolveNamespace
434
432
  })
435
433
  .pipe(
436
434
  Effect.tap(({ q }) => logQuery(q)),
@@ -473,7 +471,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
473
471
  )
474
472
  },
475
473
  find: (id) =>
476
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
474
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
477
475
  Effect
478
476
  .promise(() =>
479
477
  container
@@ -497,7 +495,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
497
495
  }, { captureStackTrace: false }))
498
496
  )),
499
497
  set: (e) =>
500
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
498
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
501
499
  Option
502
500
  .match(
503
501
  Option
package/src/Store/Disk.ts CHANGED
@@ -139,43 +139,48 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
139
139
  config?: StoreConfig<Encoded>
140
140
  ) =>
141
141
  Effect.gen(function*() {
142
- const storesSem = Semaphore.makeUnsafe(1)
143
142
  const primary = yield* makeDiskStoreInt(prefix, idKey, "primary", dir, name, seed, config?.defaultValues)
144
143
  const stores = new Map<string, Store<IdKey, Encoded>>([["primary", primary]])
145
144
  const ctx = yield* Effect.context<R>()
145
+ const semaphores = new Map<string, Semaphore.Semaphore>()
146
+ const getSem = (ns: string) => {
147
+ let sem = semaphores.get(ns)
148
+ if (!sem) {
149
+ sem = Semaphore.makeUnsafe(1)
150
+ semaphores.set(ns, sem)
151
+ }
152
+ return sem
153
+ }
154
+ const ensureStore = (namespace: string) =>
155
+ getSem(namespace).withPermits(1)(
156
+ Effect.suspend(() => {
157
+ const existing = stores.get(namespace)
158
+ if (existing) return Effect.succeed(existing)
159
+ if (config?.allowNamespace && !config.allowNamespace(namespace)) {
160
+ throw new Error(`Namespace ${namespace} not allowed!`)
161
+ }
162
+ return makeDiskStoreInt<IdKey, Encoded, R, E>(
163
+ prefix,
164
+ idKey,
165
+ namespace,
166
+ dir,
167
+ name,
168
+ seed,
169
+ config?.defaultValues
170
+ )
171
+ .pipe(
172
+ Effect.orDie,
173
+ Effect.provide(ctx),
174
+ Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
175
+ )
176
+ })
177
+ )
146
178
  const getStore = !config?.allowNamespace
147
179
  ? Effect.succeed(primary)
148
- : storeId.asEffect().pipe(Effect.flatMap((namespace) => {
149
- const store = stores.get(namespace)
150
- if (store) {
151
- return Effect.succeed(store)
152
- }
153
- if (!config.allowNamespace!(namespace)) {
154
- throw new Error(`Namespace ${namespace} not allowed!`)
155
- }
156
- return storesSem.withPermits(1)(
157
- Effect.suspend(() => {
158
- const existing = stores.get(namespace)
159
- if (existing) return Effect.sync(() => existing)
160
- return makeDiskStoreInt<IdKey, Encoded, R, E>(
161
- prefix,
162
- idKey,
163
- namespace,
164
- dir,
165
- name,
166
- seed,
167
- config?.defaultValues
168
- )
169
- .pipe(
170
- Effect.orDie,
171
- Effect.provide(ctx),
172
- Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
173
- )
174
- })
175
- )
176
- }))
180
+ : storeId.asEffect().pipe(Effect.flatMap((namespace) => ensureStore(namespace)))
177
181
 
178
182
  const s: Store<IdKey, Encoded> = {
183
+ seedNamespace: (namespace) => ensureStore(namespace).pipe(Effect.asVoid),
179
184
  all: Effect.flatMap(getStore, (_) => _.all),
180
185
  find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
181
186
  filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
@@ -151,6 +151,8 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
151
151
  withPermit
152
152
  )
153
153
  const s: Store<IdKey, Encoded> = {
154
+ seedNamespace: () => Effect.void,
155
+
154
156
  queryRaw: (query) =>
155
157
  all
156
158
  .pipe(
@@ -257,7 +259,6 @@ export const makeMemoryStore = () => ({
257
259
  config?: StoreConfig<Encoded>
258
260
  ) =>
259
261
  Effect.gen(function*() {
260
- const storesSem = Semaphore.makeUnsafe(1)
261
262
  const primary = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
262
263
  modelName,
263
264
  idKey,
@@ -267,28 +268,34 @@ export const makeMemoryStore = () => ({
267
268
  )
268
269
  const ctx = yield* Effect.context<R>()
269
270
  const stores = new Map([["primary", primary]])
270
- const getStore = !config?.allowNamespace
271
- ? Effect.succeed(primary)
272
- : storeId.asEffect().pipe(Effect.flatMap((namespace) => {
271
+ const semaphores = new Map<string, Semaphore.Semaphore>()
272
+ const getSem = (ns: string) => {
273
+ let sem = semaphores.get(ns)
274
+ if (!sem) {
275
+ sem = Semaphore.makeUnsafe(1)
276
+ semaphores.set(ns, sem)
277
+ }
278
+ return sem
279
+ }
280
+ const ensureStore = (namespace: string) =>
281
+ getSem(namespace).withPermits(1)(Effect.suspend(() => {
273
282
  const store = stores.get(namespace)
274
- if (store) {
275
- return Effect.succeed(store)
276
- }
277
- if (!config.allowNamespace!(namespace)) {
283
+ if (store) return Effect.succeed(store)
284
+ if (config?.allowNamespace && !config.allowNamespace(namespace)) {
278
285
  throw new Error(`Namespace ${namespace} not allowed!`)
279
286
  }
280
- return storesSem.withPermits(1)(Effect.suspend(() => {
281
- const store = stores.get(namespace)
282
- if (store) return Effect.sync(() => store)
283
- return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
284
- .pipe(
285
- Effect.orDie,
286
- Effect.provide(ctx),
287
- Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
288
- )
289
- }))
287
+ return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
288
+ .pipe(
289
+ Effect.orDie,
290
+ Effect.provide(ctx),
291
+ Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
292
+ )
290
293
  }))
294
+ const getStore = !config?.allowNamespace
295
+ ? Effect.succeed(primary)
296
+ : storeId.asEffect().pipe(Effect.flatMap((namespace) => ensureStore(namespace)))
291
297
  const s: Store<IdKey, Encoded> = {
298
+ seedNamespace: (namespace) => ensureStore(namespace).pipe(Effect.asVoid),
292
299
  all: Effect.flatMap(getStore, (_) => _.all),
293
300
  queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args)),
294
301
  find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
@@ -61,11 +61,11 @@ function makePgStore({ prefix }: StorageConfig) {
61
61
  return namespace
62
62
  }))
63
63
 
64
- yield* sql
64
+ const ensureTable = sql
65
65
  .unsafe(
66
66
  `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL, _namespace TEXT NOT NULL DEFAULT 'primary', _etag TEXT, data JSONB NOT NULL, PRIMARY KEY (id, _namespace))`
67
67
  )
68
- .pipe(Effect.orDie)
68
+ .pipe(Effect.orDie, Effect.asVoid)
69
69
 
70
70
  const toRow = (e: PM) => {
71
71
  const newE = makeETag(e)
@@ -130,48 +130,35 @@ function makePgStore({ prefix }: StorageConfig) {
130
130
 
131
131
  const ctx = yield* Effect.context<R>()
132
132
  const seedCache = new Map<string, Effect.Effect<void>>()
133
- const makeSeedEffect = (ns: string) =>
134
- exec(
133
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
134
+ yield* ensureTable
135
+ if (!seed) return
136
+ const existing = yield* exec(
135
137
  `SELECT id FROM "${tableName}" WHERE id = $1 AND _namespace = $2`,
136
138
  [seedMarkerId, `__seed__::${ns}`]
137
139
  )
138
- .pipe(
139
- Effect.flatMap((existing) => {
140
- if ((existing as any[]).length > 0) return Effect.void
141
- return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
142
- Effect.andThen(seed!),
143
- Effect.flatMap((items) =>
144
- Effect.flatMapOption(
145
- Effect.succeed(toNonEmptyArray([...items])),
146
- (a) => bulkSetInternal(a, ns)
147
- )
148
- ),
149
- Effect.andThen(
150
- exec(
151
- `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES ($1, $2, $3, $4)`,
152
- [seedMarkerId, `__seed__::${ns}`, null, JSON.stringify({ _marker: true })]
153
- )
154
- ),
155
- Effect.provide(ctx),
156
- Effect.orDie
157
- )
158
- })
159
- )
160
- const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
161
- if (!seed) return
140
+ if ((existing as any[]).length > 0) return
141
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
142
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
143
+ const ne = toNonEmptyArray([...items])
144
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
145
+ yield* exec(
146
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES ($1, $2, $3, $4)`,
147
+ [seedMarkerId, `__seed__::${ns}`, null, JSON.stringify({ _marker: true })]
148
+ )
149
+ })
150
+ const seedNamespace = (ns: string) => {
162
151
  let cached = seedCache.get(ns)
163
152
  if (!cached) {
164
- cached = yield* Effect.cached(makeSeedEffect(ns))
153
+ cached = Effect.cached(makeSeedEffect(ns)).pipe(Effect.runSync)
165
154
  seedCache.set(ns, cached)
166
155
  }
167
- yield* cached
168
- })
169
- const resolveAndSeed = resolveNamespace.pipe(
170
- Effect.tap((ns) => seedNamespace(ns))
171
- )
172
-
156
+ return cached
157
+ }
173
158
  const s: Store<IdKey, Encoded> = {
174
- all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
159
+ seedNamespace: (ns) => seedNamespace(ns),
160
+
161
+ all: resolveNamespace.pipe(Effect.flatMap((ns) =>
175
162
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = $1`, [ns])
176
163
  .pipe(
177
164
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -186,7 +173,7 @@ function makePgStore({ prefix }: StorageConfig) {
186
173
  )),
187
174
 
188
175
  find: (id) =>
189
- resolveAndSeed.pipe(Effect
176
+ resolveNamespace.pipe(Effect
190
177
  .flatMap((ns) =>
191
178
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = $1 AND _namespace = $2`, [id, ns])
192
179
  .pipe(
@@ -206,7 +193,7 @@ function makePgStore({ prefix }: StorageConfig) {
206
193
  const filter = f
207
194
  .filter
208
195
  type M = U extends undefined ? Encoded : Pick<Encoded, U>
209
- return resolveAndSeed.pipe(Effect.flatMap((ns) =>
196
+ return resolveNamespace.pipe(Effect.flatMap((ns) =>
210
197
  Effect
211
198
  .sync(() => {
212
199
  const q = buildWhereSQLQuery(
@@ -261,7 +248,7 @@ function makePgStore({ prefix }: StorageConfig) {
261
248
  },
262
249
 
263
250
  set: (e) =>
264
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
251
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
265
252
  setInternal(e, ns).pipe(
266
253
  Effect.withSpan("PgSQL.set [effect-app/infra/Store]", {
267
254
  attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
@@ -270,7 +257,7 @@ function makePgStore({ prefix }: StorageConfig) {
270
257
  )),
271
258
 
272
259
  batchSet: (items) =>
273
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
260
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
274
261
  bulkSetInternal(items, ns).pipe(
275
262
  Effect.withSpan("PgSQL.batchSet [effect-app/infra/Store]", {
276
263
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -279,7 +266,7 @@ function makePgStore({ prefix }: StorageConfig) {
279
266
  )),
280
267
 
281
268
  bulkSet: (items) =>
282
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
269
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
283
270
  bulkSetInternal(items, ns).pipe(
284
271
  Effect.withSpan("PgSQL.bulkSet [effect-app/infra/Store]", {
285
272
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -290,7 +277,7 @@ function makePgStore({ prefix }: StorageConfig) {
290
277
  batchRemove: (ids) => {
291
278
  const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ")
292
279
  const nsPlaceholder = `$${ids.length + 1}`
293
- return resolveAndSeed.pipe(Effect.flatMap((ns) =>
280
+ return resolveNamespace.pipe(Effect.flatMap((ns) =>
294
281
  exec(
295
282
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ${nsPlaceholder}`,
296
283
  [...ids, ns]
package/src/Store/SQL.ts CHANGED
@@ -75,11 +75,11 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
75
75
  return namespace
76
76
  }))
77
77
 
78
- yield* sql
78
+ const ensureTable = sql
79
79
  .unsafe(
80
80
  `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL, _namespace TEXT NOT NULL DEFAULT 'primary', _etag TEXT, data ${jsonColumnType} NOT NULL, PRIMARY KEY (id, _namespace))`
81
81
  )
82
- .pipe(Effect.orDie)
82
+ .pipe(Effect.orDie, Effect.asVoid)
83
83
 
84
84
  const toRow = (e: PM) => {
85
85
  const newE = makeETag(e)
@@ -144,48 +144,35 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
144
144
 
145
145
  const ctx = yield* Effect.context<R>()
146
146
  const seedCache = new Map<string, Effect.Effect<void>>()
147
- const makeSeedEffect = (ns: string) =>
148
- exec(
147
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
148
+ yield* ensureTable
149
+ if (!seed) return
150
+ const existing = yield* exec(
149
151
  `SELECT id FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
150
152
  [seedMarkerId, `__seed__::${ns}`]
151
153
  )
152
- .pipe(
153
- Effect.flatMap((existing) => {
154
- if ((existing as any[]).length > 0) return Effect.void
155
- return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
156
- Effect.andThen(seed!),
157
- Effect.flatMap((items) =>
158
- Effect.flatMapOption(
159
- Effect.succeed(toNonEmptyArray([...items])),
160
- (a) => bulkSetInternal(a, ns)
161
- )
162
- ),
163
- Effect.andThen(
164
- exec(
165
- `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
166
- [seedMarkerId, `__seed__::${ns}`, null, JSON.stringify({ _marker: true })]
167
- )
168
- ),
169
- Effect.provide(ctx),
170
- Effect.orDie
171
- )
172
- })
173
- )
174
- const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
175
- if (!seed) return
154
+ if ((existing as any[]).length > 0) return
155
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
156
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
157
+ const ne = toNonEmptyArray([...items])
158
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
159
+ yield* exec(
160
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
161
+ [seedMarkerId, `__seed__::${ns}`, null, JSON.stringify({ _marker: true })]
162
+ )
163
+ })
164
+ const seedNamespace = (ns: string) => {
176
165
  let cached = seedCache.get(ns)
177
166
  if (!cached) {
178
- cached = yield* Effect.cached(makeSeedEffect(ns))
167
+ cached = Effect.cached(makeSeedEffect(ns)).pipe(Effect.runSync)
179
168
  seedCache.set(ns, cached)
180
169
  }
181
- yield* cached
182
- })
183
- const resolveAndSeed = resolveNamespace.pipe(
184
- Effect.tap((ns) => seedNamespace(ns))
185
- )
186
-
170
+ return cached
171
+ }
187
172
  const s: Store<IdKey, Encoded> = {
188
- all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
173
+ seedNamespace: (ns) => seedNamespace(ns),
174
+
175
+ all: resolveNamespace.pipe(Effect.flatMap((ns) =>
189
176
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
190
177
  .pipe(
191
178
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -200,7 +187,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
200
187
  )),
201
188
 
202
189
  find: (id) =>
203
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
190
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
204
191
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`, [id, ns])
205
192
  .pipe(
206
193
  Effect.map((rows) => {
@@ -220,7 +207,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
220
207
  .filter
221
208
  type M = U extends undefined ? Encoded
222
209
  : Pick<Encoded, U>
223
- return resolveAndSeed
210
+ return resolveNamespace
224
211
  .pipe(Effect
225
212
  .flatMap((ns) =>
226
213
  Effect
@@ -298,7 +285,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
298
285
  },
299
286
 
300
287
  set: (e) =>
301
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
288
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
302
289
  setInternal(e, ns).pipe(
303
290
  Effect.withSpan("SQL.set [effect-app/infra/Store]", {
304
291
  attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
@@ -307,7 +294,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
307
294
  )),
308
295
 
309
296
  batchSet: (items) =>
310
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
297
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
311
298
  bulkSetInternal(items, ns).pipe(
312
299
  Effect.withSpan("SQL.batchSet [effect-app/infra/Store]", {
313
300
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -316,7 +303,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
316
303
  )),
317
304
 
318
305
  bulkSet: (items) =>
319
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
306
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
320
307
  bulkSetInternal(items, ns).pipe(
321
308
  Effect.withSpan("SQL.bulkSet [effect-app/infra/Store]", {
322
309
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -326,7 +313,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
326
313
 
327
314
  batchRemove: (ids) => {
328
315
  const placeholders = ids.map(() => "?").join(", ")
329
- return resolveAndSeed.pipe(Effect.flatMap((ns) =>
316
+ return resolveNamespace.pipe(Effect.flatMap((ns) =>
330
317
  exec(
331
318
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
332
319
  [...ids, ns]
@@ -404,6 +391,7 @@ function makeSQLiteStorePerNs(
404
391
  ns,
405
392
  `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL PRIMARY KEY, _etag TEXT, data JSON NOT NULL)`
406
393
  )
394
+ .pipe(Effect.asVoid)
407
395
 
408
396
  const seedMarkerId = `__seed_marker__`
409
397
 
@@ -461,51 +449,38 @@ function makeSQLiteStorePerNs(
461
449
 
462
450
  const ctx = yield* Effect.context<R>()
463
451
  const seedCache = new Map<string, Effect.Effect<void>>()
464
- const makeSeedEffect = (ns: string) =>
465
- exec(
452
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
453
+ yield* ensureTable(ns)
454
+ if (!seed) return
455
+ const existing = yield* exec(
466
456
  ns,
467
457
  `SELECT id FROM "${tableName}" WHERE id = ?`,
468
458
  [seedMarkerId]
469
459
  )
470
- .pipe(
471
- Effect.flatMap((existing) => {
472
- if ((existing as any[]).length > 0) return Effect.void
473
- return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
474
- Effect.andThen(seed!),
475
- Effect.flatMap((items) =>
476
- Effect.flatMapOption(
477
- Effect.succeed(toNonEmptyArray([...items])),
478
- (a) => bulkSetInternal(a, ns)
479
- )
480
- ),
481
- Effect.andThen(
482
- exec(
483
- ns,
484
- `INSERT INTO "${tableName}" (id, _etag, data) VALUES (?, ?, ?)`,
485
- [seedMarkerId, null, JSON.stringify({ _marker: true })]
486
- )
487
- ),
488
- Effect.provide(ctx),
489
- Effect.orDie
490
- )
491
- })
492
- )
493
- const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
494
- yield* ensureTable(ns)
495
- if (!seed) return
460
+ if ((existing as any[]).length > 0) return
461
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
462
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
463
+ const ne = toNonEmptyArray([...items])
464
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
465
+ yield* exec(
466
+ ns,
467
+ `INSERT INTO "${tableName}" (id, _etag, data) VALUES (?, ?, ?)`,
468
+ [seedMarkerId, null, JSON.stringify({ _marker: true })]
469
+ )
470
+ })
471
+ const seedNamespace = (ns: string) => {
496
472
  let cached = seedCache.get(ns)
497
473
  if (!cached) {
498
- cached = yield* Effect.cached(makeSeedEffect(ns))
474
+ cached = Effect.cached(makeSeedEffect(ns)).pipe(Effect.runSync)
499
475
  seedCache.set(ns, cached)
500
476
  }
501
- yield* cached
502
- })
503
- const resolveAndSeed = resolveNamespace.pipe(
504
- Effect.tap((ns) => seedNamespace(ns))
505
- )
477
+ return cached
478
+ }
506
479
 
507
480
  const s: Store<IdKey, Encoded> = {
508
- all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
481
+ seedNamespace: (ns) => seedNamespace(ns),
482
+
483
+ all: resolveNamespace.pipe(Effect.flatMap((ns) =>
509
484
  exec(ns, `SELECT id, _etag, data FROM "${tableName}"`)
510
485
  .pipe(
511
486
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -520,7 +495,7 @@ function makeSQLiteStorePerNs(
520
495
  )),
521
496
 
522
497
  find: (id) =>
523
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
498
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
524
499
  exec(ns, `SELECT id, _etag, data FROM "${tableName}" WHERE id = ?`, [id])
525
500
  .pipe(
526
501
  Effect.map((rows) => {
@@ -540,7 +515,7 @@ function makeSQLiteStorePerNs(
540
515
  .filter
541
516
  type M = U extends undefined ? Encoded
542
517
  : Pick<Encoded, U>
543
- return resolveAndSeed
518
+ return resolveNamespace
544
519
  .pipe(Effect
545
520
  .flatMap((ns) =>
546
521
  Effect
@@ -595,7 +570,7 @@ function makeSQLiteStorePerNs(
595
570
  },
596
571
 
597
572
  set: (e) =>
598
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
573
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
599
574
  setInternal(e, ns).pipe(
600
575
  Effect.withSpan("SQLite.set [effect-app/infra/Store]", {
601
576
  attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
@@ -604,7 +579,7 @@ function makeSQLiteStorePerNs(
604
579
  )),
605
580
 
606
581
  batchSet: (items) =>
607
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
582
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
608
583
  bulkSetInternal(items, ns).pipe(
609
584
  Effect.withSpan("SQLite.batchSet [effect-app/infra/Store]", {
610
585
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -613,7 +588,7 @@ function makeSQLiteStorePerNs(
613
588
  )),
614
589
 
615
590
  bulkSet: (items) =>
616
- resolveAndSeed.pipe(Effect.flatMap((ns) =>
591
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
617
592
  bulkSetInternal(items, ns).pipe(
618
593
  Effect.withSpan("SQLite.bulkSet [effect-app/infra/Store]", {
619
594
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
@@ -623,7 +598,7 @@ function makeSQLiteStorePerNs(
623
598
 
624
599
  batchRemove: (ids) => {
625
600
  const placeholders = ids.map(() => "?").join(", ")
626
- return resolveAndSeed.pipe(Effect.flatMap((ns) =>
601
+ return resolveNamespace.pipe(Effect.flatMap((ns) =>
627
602
  exec(
628
603
  ns,
629
604
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders})`,
@@ -675,7 +650,7 @@ export function SQLiteStoreLayer(
675
650
  Effect.flatMap((ns) => withNsSql(ns, (sql) => sql.withTransaction(effect).pipe(Effect.orDie)))
676
651
  ) as any
677
652
 
678
- return StoreMaker.contextMap(storeMaker).pipe(
653
+ return StoreMaker.context(storeMaker).pipe(
679
654
  Context.add(WithNsTransaction, withTransaction)
680
655
  )
681
656
  })
@@ -5,6 +5,7 @@ import { CosmosStoreLayer } from "./Cosmos.js"
5
5
  import { DiskStoreLayer } from "./Disk.js"
6
6
  import { MemoryStoreLive } from "./Memory.js"
7
7
  // import { RedisStoreLayer } from "./Redis.js"
8
+ import { RepositoryRegistryLive } from "../Model.js"
8
9
  import type { StorageConfig } from "./service.js"
9
10
  import { SQLiteStoreLayer } from "./SQL.js"
10
11
  import { PgStoreLayer } from "./SQL/Pg.js"
@@ -41,7 +42,7 @@ export function StoreMakerLayer(
41
42
  console.log("Using Cosmos DB store")
42
43
  return CosmosStoreLayer(cfg)
43
44
  })
44
- .pipe(Layer.unwrap)
45
+ .pipe(Layer.unwrap, Layer.merge(RepositoryRegistryLive))
45
46
  }
46
47
 
47
48
  export * from "./service.js"