@effect-app/infra 4.0.0-beta.84 → 4.0.0-beta.86

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/src/Store/SQL.ts CHANGED
@@ -86,8 +86,100 @@ 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 seedMarkerId = `__seed_marker__`
90
+
91
+ const setInternal = (e: PM, ns: string) =>
92
+ Effect.gen(function*() {
93
+ const row = toRow(e)
94
+ if (e._etag) {
95
+ yield* exec(
96
+ `UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ? AND _namespace = ?`,
97
+ [row._etag, row.data, row.id, e._etag, ns]
98
+ )
99
+ const existing = yield* exec(
100
+ `SELECT _etag FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
101
+ [row.id, ns]
102
+ )
103
+ const current = (existing as any[])[0]
104
+ if (!current || current._etag !== row._etag) {
105
+ if (current) {
106
+ return yield* new OptimisticConcurrencyException({
107
+ type: name,
108
+ id: row.id,
109
+ current: current._etag,
110
+ found: e._etag,
111
+ code: 412
112
+ })
113
+ }
114
+ return yield* new OptimisticConcurrencyException({
115
+ type: name,
116
+ id: row.id,
117
+ current: "",
118
+ found: e._etag,
119
+ code: 404
120
+ })
121
+ }
122
+ } else {
123
+ yield* exec(
124
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
125
+ [row.id, ns, row._etag, row.data]
126
+ )
127
+ }
128
+ return row.item
129
+ })
130
+
131
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
132
+ sql
133
+ .withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
134
+ .pipe(
135
+ Effect.orDie,
136
+ Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
137
+ )
138
+
139
+ const ctx = yield* Effect.context<R>()
140
+ const seedCache = new Map<string, Effect.Effect<void>>()
141
+ const makeSeedEffect = (ns: string) =>
142
+ exec(
143
+ `SELECT id FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
144
+ [seedMarkerId, `__seed__::${ns}`]
145
+ )
146
+ .pipe(
147
+ Effect.flatMap((existing) => {
148
+ if ((existing as any[]).length > 0) return Effect.void
149
+ return InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`).pipe(
150
+ Effect.andThen(seed!),
151
+ Effect.flatMap((items) =>
152
+ Effect.flatMapOption(
153
+ Effect.succeed(toNonEmptyArray([...items])),
154
+ (a) => bulkSetInternal(a, ns)
155
+ )
156
+ ),
157
+ Effect.andThen(
158
+ exec(
159
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
160
+ [seedMarkerId, `__seed__::${ns}`, null, JSON.stringify({ _marker: true })]
161
+ )
162
+ ),
163
+ Effect.provide(ctx),
164
+ Effect.orDie
165
+ )
166
+ })
167
+ )
168
+ const seedNamespace = Effect.fn("seedNamespace")(function*(ns: string) {
169
+ if (!seed) return
170
+ let cached = seedCache.get(ns)
171
+ if (!cached) {
172
+ cached = yield* Effect.cached(makeSeedEffect(ns))
173
+ seedCache.set(ns, cached)
174
+ }
175
+ yield* cached
176
+ })
177
+ const resolveAndSeed = resolveNamespace.pipe(
178
+ Effect.tap((ns) => seedNamespace(ns))
179
+ )
180
+
89
181
  const s: Store<IdKey, Encoded> = {
90
- all: resolveNamespace.pipe(Effect.flatMap((ns) =>
182
+ all: resolveAndSeed.pipe(Effect.flatMap((ns) =>
91
183
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
92
184
  .pipe(
93
185
  Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
@@ -102,7 +194,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
102
194
  )),
103
195
 
104
196
  find: (id) =>
105
- resolveNamespace.pipe(Effect.flatMap((ns) =>
197
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
106
198
  exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`, [id, ns])
107
199
  .pipe(
108
200
  Effect.map((rows) => {
@@ -122,7 +214,7 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
122
214
  .filter
123
215
  type M = U extends undefined ? Encoded
124
216
  : Pick<Encoded, U>
125
- return resolveNamespace
217
+ return resolveAndSeed
126
218
  .pipe(Effect
127
219
  .flatMap((ns) =>
128
220
  Effect
@@ -200,82 +292,35 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
200
292
  },
201
293
 
202
294
  set: (e) =>
203
- resolveNamespace.pipe(Effect.flatMap((ns) =>
204
- Effect
205
- .gen(function*() {
206
- const row = toRow(e)
207
- if (e._etag) {
208
- yield* exec(
209
- `UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ? AND _namespace = ?`,
210
- [row._etag, row.data, row.id, e._etag, ns]
211
- )
212
- const existing = yield* exec(
213
- `SELECT _etag FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
214
- [row.id, ns]
215
- )
216
- const current = (existing as any[])[0]
217
- if (!current || current._etag !== row._etag) {
218
- if (current) {
219
- return yield* new OptimisticConcurrencyException({
220
- type: name,
221
- id: row.id,
222
- current: current._etag,
223
- found: e._etag,
224
- code: 412
225
- })
226
- }
227
- return yield* new OptimisticConcurrencyException({
228
- type: name,
229
- id: row.id,
230
- current: "",
231
- found: e._etag,
232
- code: 404
233
- })
234
- }
235
- } else {
236
- yield* exec(
237
- `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
238
- [row.id, ns, row._etag, row.data]
239
- )
240
- }
241
- return row.item
242
- })
243
- .pipe(
244
- Effect.withSpan("SQL.set [effect-app/infra/Store]", {
245
- attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
246
- }, { captureStackTrace: false })
247
- )
295
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
296
+ setInternal(e, ns).pipe(
297
+ Effect.withSpan("SQL.set [effect-app/infra/Store]", {
298
+ attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
299
+ }, { captureStackTrace: false })
300
+ )
248
301
  )),
249
302
 
250
303
  batchSet: (items) =>
251
- sql
252
- .withTransaction(
253
- Effect.forEach(items, (e) => s.set(e))
254
- )
255
- .pipe(
256
- Effect.orDie,
257
- Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>),
304
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
305
+ bulkSetInternal(items, ns).pipe(
258
306
  Effect.withSpan("SQL.batchSet [effect-app/infra/Store]", {
259
307
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
260
308
  }, { captureStackTrace: false })
261
- ),
309
+ )
310
+ )),
262
311
 
263
312
  bulkSet: (items) =>
264
- sql
265
- .withTransaction(
266
- Effect.forEach(items, (e) => s.set(e))
267
- )
268
- .pipe(
269
- Effect.orDie,
270
- Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>),
313
+ resolveAndSeed.pipe(Effect.flatMap((ns) =>
314
+ bulkSetInternal(items, ns).pipe(
271
315
  Effect.withSpan("SQL.bulkSet [effect-app/infra/Store]", {
272
316
  attributes: { "repository.table_name": tableName, "repository.model_name": name }
273
317
  }, { captureStackTrace: false })
274
- ),
318
+ )
319
+ )),
275
320
 
276
321
  batchRemove: (ids) => {
277
322
  const placeholders = ids.map(() => "?").join(", ")
278
- return resolveNamespace.pipe(Effect.flatMap((ns) =>
323
+ return resolveAndSeed.pipe(Effect.flatMap((ns) =>
279
324
  exec(
280
325
  `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
281
326
  [...ids, ns]
@@ -298,21 +343,8 @@ function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
298
343
  )
299
344
  }
300
345
 
301
- if (seed) {
302
- const existing = yield* exec(
303
- `SELECT COUNT(*) as cnt FROM "${tableName}" WHERE _namespace = ?`,
304
- ["primary"]
305
- )
306
- const count = (existing as any[])[0]?.cnt ?? 0
307
- if (count === 0) {
308
- yield* InfraLogger.logInfo("Seeding data for " + name)
309
- const items = yield* seed
310
- yield* Effect.flatMapOption(
311
- Effect.succeed(toNonEmptyArray([...items])),
312
- (a) => s.bulkSet(a).pipe(Effect.orDie)
313
- )
314
- }
315
- }
346
+ // Eagerly seed primary namespace on initialization
347
+ yield* seedNamespace("primary")
316
348
 
317
349
  return s
318
350
  })