@effect-app/infra 4.0.0-beta.83 → 4.0.0-beta.85

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/infra",
3
- "version": "4.0.0-beta.83",
3
+ "version": "4.0.0-beta.85",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -13,7 +13,7 @@
13
13
  "proper-lockfile": "^4.1.2",
14
14
  "pure-rand": "7.0.1",
15
15
  "query-string": "^9.3.1",
16
- "effect-app": "4.0.0-beta.83"
16
+ "effect-app": "4.0.0-beta.85"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@azure/cosmos": "^4.9.2",
@@ -75,10 +75,65 @@ function makeCosmosStore({ prefix }: StorageConfig) {
75
75
  // then need to clean up the actual data.. perhaps first do with a config toggle to prescribe to it.
76
76
  const importedMarkerId = containerId
77
77
 
78
+ const ctx = yield* Effect.context<R>()
79
+ const seedCache = new Map<string, Effect.Effect<void>>()
80
+ const makeSeedEffect = (ns: string) => {
81
+ const markerId = ns === "primary" ? importedMarkerId : `${importedMarkerId}::${ns}`
82
+ return Effect
83
+ .promise(() =>
84
+ container
85
+ .item(markerId, markerId)
86
+ .read<{ id: string }>()
87
+ .then(({ resource }) => Option.fromNullishOr(resource))
88
+ )
89
+ .pipe(
90
+ Effect.flatMap((marker) => {
91
+ if (Option.isSome(marker)) return Effect.void
92
+ return InfraLogger.logInfo(`Creating mock data for ${name} (namespace: ${ns})`).pipe(
93
+ Effect.andThen(seed!),
94
+ Effect.flatMap((m) =>
95
+ Effect.flatMapOption(
96
+ Effect.succeed(toNonEmptyArray([...m])),
97
+ (a) =>
98
+ s.bulkSet(a).pipe(
99
+ Effect.orDie,
100
+ Effect.delay(Duration.millis(1100))
101
+ )
102
+ )
103
+ ),
104
+ Effect.andThen(
105
+ Effect.promise(() =>
106
+ container.items.create({
107
+ _partitionKey: markerId,
108
+ id: markerId,
109
+ ttl: -1
110
+ })
111
+ )
112
+ ),
113
+ Effect.provide(ctx),
114
+ Effect.orDie
115
+ )
116
+ })
117
+ )
118
+ }
119
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
120
+ if (!seed) return
121
+ let cached = seedCache.get(ns)
122
+ if (!cached) {
123
+ cached = yield* Effect.cached(makeSeedEffect(ns))
124
+ seedCache.set(ns, cached)
125
+ }
126
+ yield* cached
127
+ })
128
+ const resolveAndSeed = resolveNamespace.pipe(
129
+ Effect.tap((ns) => seedNamespace(ns))
130
+ )
131
+ const resolvePartitionKeyAndSeed = Effect.map(resolveAndSeed, (ns) => `${nsPrefix(ns)}${basePartitionKey}`)
132
+
78
133
  const bulkSet = (items: NonEmptyReadonlyArray<PM>) =>
79
134
  Effect
80
135
  .gen(function*() {
81
- const ns = yield* resolveNamespace
136
+ const ns = yield* resolveAndSeed
82
137
  // TODO: disable batching if need atomicity
83
138
  // we delay and batch to keep low amount of RUs
84
139
  const b = [...items]
@@ -182,7 +237,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
182
237
  }, { captureStackTrace: false }))
183
238
 
184
239
  const batchSet = (items: NonEmptyReadonlyArray<PM>) => {
185
- return resolveNamespace
240
+ return resolveAndSeed
186
241
  .pipe(Effect.flatMap((ns) =>
187
242
  Effect
188
243
  .suspend(() => {
@@ -253,7 +308,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
253
308
  const s: Store<IdKey, Encoded> = {
254
309
  queryRaw: <Out>(query: RawQuery<Encoded, Out>) =>
255
310
  Effect
256
- .all({ q: Effect.sync(() => query.cosmos({ name })), pk: resolvePartitionKey })
311
+ .all({ q: Effect.sync(() => query.cosmos({ name })), pk: resolvePartitionKeyAndSeed })
257
312
  .pipe(
258
313
  Effect.tap(({ q }) => logQuery(q)),
259
314
  Effect.flatMap(({ pk, q }) =>
@@ -275,7 +330,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
275
330
  }, { captureStackTrace: false })
276
331
  ),
277
332
  batchRemove: (ids, partitionKey?: string) =>
278
- resolvePartitionKey.pipe(Effect.flatMap((pk) =>
333
+ resolvePartitionKeyAndSeed.pipe(Effect.flatMap((pk) =>
279
334
  Effect.promise(() =>
280
335
  execBatch(
281
336
  mutable(ids.map((id) =>
@@ -296,7 +351,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
296
351
  query: `SELECT * FROM ${name}`,
297
352
  parameters: []
298
353
  })),
299
- pk: resolvePartitionKey
354
+ pk: resolvePartitionKeyAndSeed
300
355
  })
301
356
  .pipe(
302
357
  Effect.tap(({ q }) => logQuery(q)),
@@ -382,7 +437,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
382
437
  )
383
438
  },
384
439
  find: (id) =>
385
- resolveNamespace.pipe(Effect.flatMap((ns) =>
440
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
386
441
  Effect
387
442
  .promise(() =>
388
443
  container
@@ -405,7 +460,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
405
460
  }, { captureStackTrace: false }))
406
461
  )),
407
462
  set: (e) =>
408
- resolveNamespace.pipe(Effect.flatMap((ns) =>
463
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
409
464
  Option
410
465
  .match(
411
466
  Option
@@ -466,38 +521,9 @@ function makeCosmosStore({ prefix }: StorageConfig) {
466
521
  bulkSet
467
522
  }
468
523
 
469
- // handle mock data
470
- const marker = yield* Effect.promise(() =>
471
- container
472
- .item(importedMarkerId, importedMarkerId)
473
- .read<{ id: string }>()
474
- .then(({ resource }) => Option.fromNullishOr(resource))
475
- )
524
+ // Eagerly seed primary namespace on initialization
525
+ yield* seedNamespace("primary")
476
526
 
477
- if (!Option.isSome(marker)) {
478
- yield* InfraLogger.logInfo("Creating mock data for " + name)
479
- if (seed) {
480
- const m = yield* seed
481
- yield* Effect.flatMapOption(
482
- Effect.succeed(toNonEmptyArray([...m])),
483
- (a) =>
484
- s.bulkSet(a).pipe(
485
- Effect.orDie,
486
- Effect
487
- // we delay extra here, so that initial creation between Companies/POs also have an interval between them.
488
- .delay(Duration.millis(1100))
489
- )
490
- )
491
- }
492
- // Mark as imported
493
- yield* Effect.promise(() =>
494
- container.items.create({
495
- _partitionKey: importedMarkerId,
496
- id: importedMarkerId,
497
- ttl: -1
498
- })
499
- )
500
- }
501
527
  return s
502
528
  })
503
529
  }
@@ -78,8 +78,47 @@ function makePgStore({ prefix }: StorageConfig) {
78
78
  const exec = (query: string, params?: readonly unknown[]) =>
79
79
  sql.unsafe(query, params as any).pipe(Effect.orDie)
80
80
 
81
+ const ctx = yield* Effect.context<R>()
82
+ const seedCache = new Map<string, Effect.Effect<void>>()
83
+ const makeSeedEffect = (ns: string) =>
84
+ exec(
85
+ `SELECT COUNT(*) as cnt FROM "${tableName}" WHERE _namespace = $1`,
86
+ [ns]
87
+ )
88
+ .pipe(
89
+ Effect.flatMap((existing) => {
90
+ const count = Number((existing as any[])[0]?.cnt ?? 0)
91
+ if (count === 0) {
92
+ return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
93
+ Effect.andThen(seed!),
94
+ Effect.flatMap((items) =>
95
+ Effect.flatMapOption(
96
+ Effect.succeed(toNonEmptyArray([...items])),
97
+ (a) => s.bulkSet(a).pipe(Effect.orDie)
98
+ )
99
+ ),
100
+ Effect.provide(ctx),
101
+ Effect.orDie
102
+ )
103
+ }
104
+ return Effect.void
105
+ })
106
+ )
107
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
108
+ if (!seed) return
109
+ let cached = seedCache.get(ns)
110
+ if (!cached) {
111
+ cached = yield* Effect.cached(makeSeedEffect(ns))
112
+ seedCache.set(ns, cached)
113
+ }
114
+ yield* cached
115
+ })
116
+ const resolveAndSeed = resolveNamespace.pipe(
117
+ Effect.tap((ns) => seedNamespace(ns))
118
+ )
119
+
81
120
  const s: Store<IdKey, Encoded> = {
82
- all: resolveNamespace.pipe(Effect.flatMap((ns) =>
121
+ all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
83
122
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = $1`, [ns])
84
123
  .pipe(
85
124
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -94,7 +133,7 @@ function makePgStore({ prefix }: StorageConfig) {
94
133
  )),
95
134
 
96
135
  find: (id) =>
97
- resolveNamespace.pipe(Effect
136
+ resolveAndSeed.pipe(Effect
98
137
  .flatMap((ns) =>
99
138
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = $1 AND _namespace = $2`, [id, ns])
100
139
  .pipe(
@@ -114,7 +153,7 @@ function makePgStore({ prefix }: StorageConfig) {
114
153
  const filter = f
115
154
  .filter
116
155
  type M = U extends undefined ? Encoded : Pick<Encoded, U>
117
- return resolveNamespace.pipe(Effect.flatMap((ns) =>
156
+ return resolveAndSeed.pipe(Effect.flatMap((ns) =>
118
157
  Effect
119
158
  .sync(() => {
120
159
  const q = buildWhereSQLQuery(
@@ -169,7 +208,7 @@ function makePgStore({ prefix }: StorageConfig) {
169
208
  },
170
209
 
171
210
  set: (e) =>
172
- resolveNamespace.pipe(Effect.flatMap((ns) =>
211
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
173
212
  Effect
174
213
  .gen(function*() {
175
214
  const row = toRow(e)
@@ -245,7 +284,7 @@ function makePgStore({ prefix }: StorageConfig) {
245
284
  batchRemove: (ids) => {
246
285
  const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ")
247
286
  const nsPlaceholder = `$${ids.length + 1}`
248
- return resolveNamespace.pipe(Effect.flatMap((ns) =>
287
+ return resolveAndSeed.pipe(Effect.flatMap((ns) =>
249
288
  exec(
250
289
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ${nsPlaceholder}`,
251
290
  [...ids, ns]
@@ -268,21 +307,8 @@ function makePgStore({ prefix }: StorageConfig) {
268
307
  )
269
308
  }
270
309
 
271
- if (seed) {
272
- const existing = yield* exec(
273
- `SELECT COUNT(*) as cnt FROM "${tableName}" WHERE _namespace = $1`,
274
- ["primary"]
275
- )
276
- const count = Number((existing as any[])[0]?.cnt ?? 0)
277
- if (count === 0) {
278
- yield* InfraLogger.logInfo("Seeding data for " + name)
279
- const items = yield* seed
280
- yield* Effect.flatMapOption(
281
- Effect.succeed(toNonEmptyArray([...items])),
282
- (a) => s.bulkSet(a).pipe(Effect.orDie)
283
- )
284
- }
285
- }
310
+ // Eagerly seed primary namespace on initialization
311
+ yield* seedNamespace("primary")
286
312
 
287
313
  return s
288
314
  })
package/src/Store/SQL.ts CHANGED
@@ -86,8 +86,47 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
86
86
  const exec = (query: string, params?: readonly unknown[]) =>
87
87
  sql.unsafe(query, params as any).pipe(Effect.orDie)
88
88
 
89
+ const ctx = yield* Effect.context<R>()
90
+ const seedCache = new Map<string, Effect.Effect<void>>()
91
+ const makeSeedEffect = (ns: string) =>
92
+ exec(
93
+ `SELECT COUNT(*) as cnt FROM "${tableName}" WHERE _namespace = ?`,
94
+ [ns]
95
+ )
96
+ .pipe(
97
+ Effect.flatMap((existing) => {
98
+ const count = (existing as any[])[0]?.cnt ?? 0
99
+ if (count === 0) {
100
+ return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
101
+ Effect.andThen(seed!),
102
+ Effect.flatMap((items) =>
103
+ Effect.flatMapOption(
104
+ Effect.succeed(toNonEmptyArray([...items])),
105
+ (a) => s.bulkSet(a).pipe(Effect.orDie)
106
+ )
107
+ ),
108
+ Effect.provide(ctx),
109
+ Effect.orDie
110
+ )
111
+ }
112
+ return Effect.void
113
+ })
114
+ )
115
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
116
+ if (!seed) return
117
+ let cached = seedCache.get(ns)
118
+ if (!cached) {
119
+ cached = yield* Effect.cached(makeSeedEffect(ns))
120
+ seedCache.set(ns, cached)
121
+ }
122
+ yield* cached
123
+ })
124
+ const resolveAndSeed = resolveNamespace.pipe(
125
+ Effect.tap((ns) => seedNamespace(ns))
126
+ )
127
+
89
128
  const s: Store<IdKey, Encoded> = {
90
- all: resolveNamespace.pipe(Effect.flatMap((ns) =>
129
+ all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
91
130
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
92
131
  .pipe(
93
132
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -102,7 +141,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
102
141
  )),
103
142
 
104
143
  find: (id) =>
105
- resolveNamespace.pipe(Effect.flatMap((ns) =>
144
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
106
145
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`, [id, ns])
107
146
  .pipe(
108
147
  Effect.map((rows) => {
@@ -122,7 +161,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
122
161
  .filter
123
162
  type M = U extends undefined ? Encoded
124
163
  : Pick<Encoded, U>
125
- return resolveNamespace
164
+ return resolveAndSeed
126
165
  .pipe(Effect
127
166
  .flatMap((ns) =>
128
167
  Effect
@@ -144,31 +183,25 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
144
183
  f
145
184
  .limit
146
185
  )
147
- const nsPlaceholder = dialect
148
- .placeholder(
149
- q
150
- .params
151
- .length + 1
152
- )
153
186
  const hasWhere = q
154
187
  .sql
155
188
  .includes("WHERE")
156
189
  const nsSql = hasWhere
157
190
  ? q
158
191
  .sql
159
- .replace("WHERE", `WHERE _namespace = ${nsPlaceholder} AND`)
192
+ .replace("WHERE", `WHERE _namespace = ? AND`)
160
193
  : q
161
194
  .sql
162
195
  .replace(
163
196
  `FROM "${tableName}"`,
164
- `FROM "${tableName}" WHERE _namespace = ${nsPlaceholder}`
197
+ `FROM "${tableName}" WHERE _namespace = ?`
165
198
  )
166
199
  return {
167
200
  sql: nsSql,
168
201
  params: [
202
+ ns,
169
203
  ...q
170
- .params,
171
- ns
204
+ .params
172
205
  ]
173
206
  }
174
207
  })
@@ -206,7 +239,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
206
239
  },
207
240
 
208
241
  set: (e) =>
209
- resolveNamespace.pipe(Effect.flatMap((ns) =>
242
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
210
243
  Effect
211
244
  .gen(function*() {
212
245
  const row = toRow(e)
@@ -281,7 +314,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
281
314
 
282
315
  batchRemove: (ids) => {
283
316
  const placeholders = ids.map(() => "?").join(", ")
284
- return resolveNamespace.pipe(Effect.flatMap((ns) =>
317
+ return resolveAndSeed.pipe(Effect.flatMap((ns) =>
285
318
  exec(
286
319
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
287
320
  [...ids, ns]
@@ -304,21 +337,8 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
304
337
  )
305
338
  }
306
339
 
307
- if (seed) {
308
- const existing = yield* exec(
309
- `SELECT COUNT(*) as cnt FROM "${tableName}" WHERE _namespace = ?`,
310
- ["primary"]
311
- )
312
- const count = (existing as any[])[0]?.cnt ?? 0
313
- if (count === 0) {
314
- yield* InfraLogger.logInfo("Seeding data for " + name)
315
- const items = yield* seed
316
- yield* Effect.flatMapOption(
317
- Effect.succeed(toNonEmptyArray([...items])),
318
- (a) => s.bulkSet(a).pipe(Effect.orDie)
319
- )
320
- }
321
- }
340
+ // Eagerly seed primary namespace on initialization
341
+ yield* seedNamespace("primary")
322
342
 
323
343
  return s
324
344
  })