@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/CHANGELOG.md +14 -0
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +44 -32
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +65 -54
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +65 -54
- package/package.json +2 -2
- package/src/Store/Cosmos.ts +73 -42
- package/src/Store/SQL/Pg.ts +112 -80
- package/src/Store/SQL.ts +112 -80
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
252
|
-
.
|
|
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
|
-
|
|
265
|
-
.
|
|
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
|
|
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
|
-
|
|
302
|
-
|
|
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
|
})
|