@effect-app/infra 4.0.0-beta.121 → 4.0.0-beta.123
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 +17 -0
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +8 -10
- package/dist/Model/Repository/ext.d.ts +15 -3
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +25 -2
- package/dist/Model/Repository/internal/internal.d.ts +1 -1
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +9 -8
- package/dist/Model/Repository/makeRepo.d.ts +3 -3
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/service.d.ts +21 -21
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.d.ts +2 -2
- package/dist/Operations.d.ts +2 -2
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +54 -57
- package/dist/OperationsRepo.d.ts +2 -2
- package/dist/QueueMaker/SQLQueue.d.ts +2 -3
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +104 -115
- package/dist/QueueMaker/memQueue.d.ts +2 -2
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +51 -62
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +34 -50
- package/dist/Store/ContextMapContainer.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +304 -306
- package/dist/Store/Disk.d.ts +1 -1
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +2 -2
- package/dist/Store/Memory.d.ts +1 -1
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +2 -2
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +147 -149
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +6 -6
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +7 -9
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +1 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +2 -2
- package/dist/errorReporter.d.ts +3 -3
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +16 -23
- package/package.json +14 -14
- package/src/CUPS.ts +7 -9
- package/src/Model/Repository/ext.ts +71 -6
- package/src/Model/Repository/internal/internal.ts +13 -25
- package/src/Model/Repository/makeRepo.ts +4 -4
- package/src/Model/Repository/service.ts +22 -21
- package/src/Operations.ts +76 -111
- package/src/QueueMaker/SQLQueue.ts +119 -150
- package/src/QueueMaker/memQueue.ts +81 -102
- package/src/QueueMaker/sbqueue.ts +51 -81
- package/src/Store/Cosmos.ts +481 -484
- package/src/Store/Disk.ts +52 -53
- package/src/Store/Memory.ts +49 -50
- package/src/Store/SQL/Pg.ts +247 -250
- package/src/Store/SQL.ts +420 -426
- package/src/Store/utils.ts +23 -22
- package/src/adapters/ServiceBus.ts +106 -110
- package/src/api/internal/auth.ts +8 -6
- package/src/api/routing/middleware/middleware.ts +10 -11
- package/src/errorReporter.ts +58 -72
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/repository-ext.test.ts +58 -0
package/src/Store/SQL.ts
CHANGED
|
@@ -51,323 +51,15 @@ const parseSelectRow = (
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
|
|
54
|
-
return ({ prefix }: StorageConfig)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
) =>
|
|
64
|
-
Effect.gen(function*() {
|
|
65
|
-
type PM = PersistenceModelType<Encoded>
|
|
66
|
-
const tableName = `${prefix}${name}`
|
|
67
|
-
const defaultValues = config?.defaultValues ?? {}
|
|
68
|
-
|
|
69
|
-
const resolveNamespace = !config?.allowNamespace
|
|
70
|
-
? Effect.succeed("primary")
|
|
71
|
-
: storeId.asEffect().pipe(Effect.map((namespace) => {
|
|
72
|
-
if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
|
|
73
|
-
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
74
|
-
}
|
|
75
|
-
return namespace
|
|
76
|
-
}))
|
|
77
|
-
|
|
78
|
-
const ensureTable = sql
|
|
79
|
-
.unsafe(
|
|
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
|
-
)
|
|
82
|
-
.pipe(
|
|
83
|
-
Effect.andThen(
|
|
84
|
-
sql.unsafe(
|
|
85
|
-
`CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
|
|
86
|
-
)
|
|
87
|
-
),
|
|
88
|
-
Effect.orDie,
|
|
89
|
-
Effect.asVoid
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
const toRow = (e: PM) => {
|
|
93
|
-
const newE = makeETag(e)
|
|
94
|
-
const id = newE[idKey] as string
|
|
95
|
-
const { _etag, [idKey]: _id, ...rest } = newE as any
|
|
96
|
-
const data = JSON.stringify(rest)
|
|
97
|
-
return { id, _etag: newE._etag!, data, item: newE }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const exec = (query: string, params?: readonly unknown[]) =>
|
|
101
|
-
sql.unsafe(query, params as any).pipe(Effect.orDie)
|
|
102
|
-
|
|
103
|
-
const setInternal = (e: PM, ns: string) =>
|
|
104
|
-
Effect.gen(function*() {
|
|
105
|
-
const row = toRow(e)
|
|
106
|
-
if (e._etag) {
|
|
107
|
-
yield* exec(
|
|
108
|
-
`UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ? AND _namespace = ?`,
|
|
109
|
-
[row._etag, row.data, row.id, e._etag, ns]
|
|
110
|
-
)
|
|
111
|
-
const existing = yield* exec(
|
|
112
|
-
`SELECT _etag FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
|
|
113
|
-
[row.id, ns]
|
|
114
|
-
)
|
|
115
|
-
const current = (existing as any[])[0]
|
|
116
|
-
if (!current || current._etag !== row._etag) {
|
|
117
|
-
if (current) {
|
|
118
|
-
return yield* new OptimisticConcurrencyException({
|
|
119
|
-
type: name,
|
|
120
|
-
id: row.id,
|
|
121
|
-
current: current._etag,
|
|
122
|
-
found: e._etag,
|
|
123
|
-
code: 412
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
return yield* new OptimisticConcurrencyException({
|
|
127
|
-
type: name,
|
|
128
|
-
id: row.id,
|
|
129
|
-
current: "",
|
|
130
|
-
found: e._etag,
|
|
131
|
-
code: 404
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
yield* exec(
|
|
136
|
-
`INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
|
|
137
|
-
[row.id, ns, row._etag, row.data]
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
return row.item
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
|
|
144
|
-
sql
|
|
145
|
-
.withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
|
|
146
|
-
.pipe(
|
|
147
|
-
Effect.orDie,
|
|
148
|
-
Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
const ctx = yield* Effect.context<R>()
|
|
152
|
-
const seedCache = new Map<string, Effect.Effect<void>>()
|
|
153
|
-
const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
|
|
154
|
-
yield* ensureTable
|
|
155
|
-
if (!seed) return
|
|
156
|
-
const existing = yield* exec(
|
|
157
|
-
`SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
|
|
158
|
-
[`${tableName}::${ns}`, tableName]
|
|
159
|
-
)
|
|
160
|
-
if ((existing as any[]).length > 0) return
|
|
161
|
-
yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
|
|
162
|
-
const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
|
|
163
|
-
const ne = toNonEmptyArray([...items])
|
|
164
|
-
if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
|
|
165
|
-
yield* exec(
|
|
166
|
-
`INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
|
|
167
|
-
[`${tableName}::${ns}`, tableName]
|
|
168
|
-
)
|
|
169
|
-
})
|
|
170
|
-
const seedNamespace = (ns: string) => {
|
|
171
|
-
let cached = seedCache.get(ns)
|
|
172
|
-
if (!cached) {
|
|
173
|
-
cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
|
|
174
|
-
seedCache.set(ns, cached)
|
|
175
|
-
}
|
|
176
|
-
return cached
|
|
177
|
-
}
|
|
178
|
-
const s: Store<IdKey, Encoded> = {
|
|
179
|
-
seedNamespace: (ns) => seedNamespace(ns),
|
|
180
|
-
|
|
181
|
-
all: resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
182
|
-
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
|
|
183
|
-
.pipe(
|
|
184
|
-
Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
|
|
185
|
-
Effect.withSpan("SQL.all [effect-app/infra/Store]", {
|
|
186
|
-
attributes: {
|
|
187
|
-
"repository.table_name": tableName,
|
|
188
|
-
"repository.model_name": name,
|
|
189
|
-
"repository.namespace": ns
|
|
190
|
-
}
|
|
191
|
-
}, { captureStackTrace: false })
|
|
192
|
-
)
|
|
193
|
-
)),
|
|
194
|
-
|
|
195
|
-
find: (id) =>
|
|
196
|
-
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
197
|
-
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`, [id, ns])
|
|
198
|
-
.pipe(
|
|
199
|
-
Effect.map((rows) => {
|
|
200
|
-
const row = (rows as any[])[0]
|
|
201
|
-
return row
|
|
202
|
-
? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
|
|
203
|
-
: Option.none()
|
|
204
|
-
}),
|
|
205
|
-
Effect.withSpan("SQL.find [effect-app/infra/Store]", {
|
|
206
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name, id }
|
|
207
|
-
}, { captureStackTrace: false })
|
|
208
|
-
)
|
|
209
|
-
)),
|
|
210
|
-
|
|
211
|
-
filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
|
|
212
|
-
const filter = f
|
|
213
|
-
.filter
|
|
214
|
-
type M = U extends undefined ? Encoded
|
|
215
|
-
: Pick<Encoded, U>
|
|
216
|
-
return resolveNamespace
|
|
217
|
-
.pipe(Effect
|
|
218
|
-
.flatMap((ns) =>
|
|
219
|
-
Effect
|
|
220
|
-
.sync(() => {
|
|
221
|
-
const q = buildWhereSQLQuery(
|
|
222
|
-
dialect,
|
|
223
|
-
idKey,
|
|
224
|
-
filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
|
|
225
|
-
tableName,
|
|
226
|
-
defaultValues,
|
|
227
|
-
f
|
|
228
|
-
.select as
|
|
229
|
-
| NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }>
|
|
230
|
-
| undefined,
|
|
231
|
-
f
|
|
232
|
-
.order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
|
|
233
|
-
f
|
|
234
|
-
.skip,
|
|
235
|
-
f
|
|
236
|
-
.limit
|
|
237
|
-
)
|
|
238
|
-
const hasWhere = q
|
|
239
|
-
.sql
|
|
240
|
-
.includes("WHERE")
|
|
241
|
-
const nsSql = hasWhere
|
|
242
|
-
? q
|
|
243
|
-
.sql
|
|
244
|
-
.replace("WHERE", `WHERE _namespace = ? AND`)
|
|
245
|
-
: q
|
|
246
|
-
.sql
|
|
247
|
-
.replace(
|
|
248
|
-
`FROM "${tableName}"`,
|
|
249
|
-
`FROM "${tableName}" WHERE _namespace = ?`
|
|
250
|
-
)
|
|
251
|
-
return {
|
|
252
|
-
sql: nsSql,
|
|
253
|
-
params: [
|
|
254
|
-
ns,
|
|
255
|
-
...q
|
|
256
|
-
.params
|
|
257
|
-
]
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
.pipe(
|
|
261
|
-
Effect
|
|
262
|
-
.tap((q) =>
|
|
263
|
-
logQuery(q)
|
|
264
|
-
),
|
|
265
|
-
Effect.flatMap((q) =>
|
|
266
|
-
exec(q.sql, q.params).pipe(
|
|
267
|
-
Effect.map((rows) => {
|
|
268
|
-
if (f.select) {
|
|
269
|
-
return (rows as any[]).map((r) => {
|
|
270
|
-
const selected = parseSelectRow(r, idKey)
|
|
271
|
-
return {
|
|
272
|
-
...Struct.pick(
|
|
273
|
-
defaultValues as any,
|
|
274
|
-
f.select!.filter((_) => typeof _ === "string") as never[]
|
|
275
|
-
),
|
|
276
|
-
...selected
|
|
277
|
-
} as M
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
return (rows as any[]).map((r) =>
|
|
281
|
-
parseRow<Encoded>(r, idKey, defaultValues) as any as M
|
|
282
|
-
)
|
|
283
|
-
})
|
|
284
|
-
)
|
|
285
|
-
),
|
|
286
|
-
Effect.withSpan("SQL.filter [effect-app/infra/Store]", {
|
|
287
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
288
|
-
}, { captureStackTrace: false })
|
|
289
|
-
)
|
|
290
|
-
))
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
set: (e) =>
|
|
294
|
-
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
295
|
-
setInternal(e, ns).pipe(
|
|
296
|
-
Effect.withSpan("SQL.set [effect-app/infra/Store]", {
|
|
297
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
|
|
298
|
-
}, { captureStackTrace: false })
|
|
299
|
-
)
|
|
300
|
-
)),
|
|
301
|
-
|
|
302
|
-
batchSet: (items) =>
|
|
303
|
-
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
304
|
-
bulkSetInternal(items, ns).pipe(
|
|
305
|
-
Effect.withSpan("SQL.batchSet [effect-app/infra/Store]", {
|
|
306
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
307
|
-
}, { captureStackTrace: false })
|
|
308
|
-
)
|
|
309
|
-
)),
|
|
310
|
-
|
|
311
|
-
bulkSet: (items) =>
|
|
312
|
-
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
313
|
-
bulkSetInternal(items, ns).pipe(
|
|
314
|
-
Effect.withSpan("SQL.bulkSet [effect-app/infra/Store]", {
|
|
315
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
316
|
-
}, { captureStackTrace: false })
|
|
317
|
-
)
|
|
318
|
-
)),
|
|
319
|
-
|
|
320
|
-
batchRemove: (ids) => {
|
|
321
|
-
const placeholders = ids.map(() => "?").join(", ")
|
|
322
|
-
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
323
|
-
exec(
|
|
324
|
-
`DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
|
|
325
|
-
[...ids, ns]
|
|
326
|
-
)
|
|
327
|
-
.pipe(
|
|
328
|
-
Effect.asVoid,
|
|
329
|
-
Effect.withSpan("SQL.batchRemove [effect-app/infra/Store]", {
|
|
330
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
331
|
-
}, { captureStackTrace: false })
|
|
332
|
-
)
|
|
333
|
-
))
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
queryRaw: (query) =>
|
|
337
|
-
s.all.pipe(
|
|
338
|
-
Effect.map(query.memory),
|
|
339
|
-
Effect.withSpan("SQL.queryRaw [effect-app/infra/Store]", {
|
|
340
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
341
|
-
}, { captureStackTrace: false })
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Eagerly seed primary namespace on initialization
|
|
346
|
-
yield* seedNamespace("primary")
|
|
347
|
-
|
|
348
|
-
return s
|
|
349
|
-
})
|
|
350
|
-
}
|
|
351
|
-
})
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
type WithNsSqlFn = <A, E2, R2>(
|
|
355
|
-
ns: string,
|
|
356
|
-
f: (sql: SqlClient.SqlClient) => Effect.Effect<A, E2, R2>
|
|
357
|
-
) => Effect.Effect<A, E2, R2>
|
|
358
|
-
|
|
359
|
-
function makeSQLiteStorePerNs(
|
|
360
|
-
withNsSql: WithNsSqlFn,
|
|
361
|
-
{ prefix }: StorageConfig
|
|
362
|
-
) {
|
|
363
|
-
return {
|
|
364
|
-
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
365
|
-
name: string,
|
|
366
|
-
idKey: IdKey,
|
|
367
|
-
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
368
|
-
config?: StoreConfig<Encoded>
|
|
369
|
-
) =>
|
|
370
|
-
Effect.gen(function*() {
|
|
54
|
+
return Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
55
|
+
const sql = yield* SqlClient.SqlClient
|
|
56
|
+
return {
|
|
57
|
+
make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
58
|
+
name: string,
|
|
59
|
+
idKey: IdKey,
|
|
60
|
+
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
61
|
+
config?: StoreConfig<Encoded>
|
|
62
|
+
) {
|
|
371
63
|
type PM = PersistenceModelType<Encoded>
|
|
372
64
|
const tableName = `${prefix}${name}`
|
|
373
65
|
const defaultValues = config?.defaultValues ?? {}
|
|
@@ -381,6 +73,20 @@ function makeSQLiteStorePerNs(
|
|
|
381
73
|
return namespace
|
|
382
74
|
}))
|
|
383
75
|
|
|
76
|
+
const ensureTable = sql
|
|
77
|
+
.unsafe(
|
|
78
|
+
`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))`
|
|
79
|
+
)
|
|
80
|
+
.pipe(
|
|
81
|
+
Effect.andThen(
|
|
82
|
+
sql.unsafe(
|
|
83
|
+
`CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
Effect.orDie,
|
|
87
|
+
Effect.asVoid
|
|
88
|
+
)
|
|
89
|
+
|
|
384
90
|
const toRow = (e: PM) => {
|
|
385
91
|
const newE = makeETag(e)
|
|
386
92
|
const id = newE[idKey] as string
|
|
@@ -389,86 +95,63 @@ function makeSQLiteStorePerNs(
|
|
|
389
95
|
return { id, _etag: newE._etag!, data, item: newE }
|
|
390
96
|
}
|
|
391
97
|
|
|
392
|
-
const exec = (
|
|
393
|
-
withNsSql(ns, (sql) => sql.unsafe(query, params as any).pipe(Effect.orDie))
|
|
98
|
+
const exec = (query: string, params?: readonly unknown[]) => sql.unsafe(query, params as any).pipe(Effect.orDie)
|
|
394
99
|
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
))
|
|
410
|
-
|
|
411
|
-
const setInternal = (e: PM, ns: string) =>
|
|
412
|
-
Effect.gen(function*() {
|
|
413
|
-
const row = toRow(e)
|
|
414
|
-
if (e._etag) {
|
|
415
|
-
yield* exec(
|
|
416
|
-
ns,
|
|
417
|
-
`UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ?`,
|
|
418
|
-
[row._etag, row.data, row.id, e._etag]
|
|
419
|
-
)
|
|
420
|
-
const existing = yield* exec(
|
|
421
|
-
ns,
|
|
422
|
-
`SELECT _etag FROM "${tableName}" WHERE id = ?`,
|
|
423
|
-
[row.id]
|
|
424
|
-
)
|
|
425
|
-
const current = (existing as any[])[0]
|
|
426
|
-
if (!current || current._etag !== row._etag) {
|
|
427
|
-
if (current) {
|
|
428
|
-
return yield* new OptimisticConcurrencyException({
|
|
429
|
-
type: name,
|
|
430
|
-
id: row.id,
|
|
431
|
-
current: current._etag,
|
|
432
|
-
found: e._etag,
|
|
433
|
-
code: 412
|
|
434
|
-
})
|
|
435
|
-
}
|
|
100
|
+
const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
|
|
101
|
+
const row = toRow(e)
|
|
102
|
+
if (e._etag) {
|
|
103
|
+
yield* exec(
|
|
104
|
+
`UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ? AND _namespace = ?`,
|
|
105
|
+
[row._etag, row.data, row.id, e._etag, ns]
|
|
106
|
+
)
|
|
107
|
+
const existing = yield* exec(
|
|
108
|
+
`SELECT _etag FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
|
|
109
|
+
[row.id, ns]
|
|
110
|
+
)
|
|
111
|
+
const current = (existing as any[])[0]
|
|
112
|
+
if (!current || current._etag !== row._etag) {
|
|
113
|
+
if (current) {
|
|
436
114
|
return yield* new OptimisticConcurrencyException({
|
|
437
115
|
type: name,
|
|
438
116
|
id: row.id,
|
|
439
|
-
current:
|
|
117
|
+
current: current._etag,
|
|
440
118
|
found: e._etag,
|
|
441
|
-
code:
|
|
119
|
+
code: 412
|
|
442
120
|
})
|
|
443
121
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
122
|
+
return yield* new OptimisticConcurrencyException({
|
|
123
|
+
type: name,
|
|
124
|
+
id: row.id,
|
|
125
|
+
current: "",
|
|
126
|
+
found: e._etag,
|
|
127
|
+
code: 404
|
|
128
|
+
})
|
|
450
129
|
}
|
|
451
|
-
|
|
452
|
-
|
|
130
|
+
} else {
|
|
131
|
+
yield* exec(
|
|
132
|
+
`INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
|
|
133
|
+
[row.id, ns, row._etag, row.data]
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
return row.item
|
|
137
|
+
})
|
|
453
138
|
|
|
454
139
|
const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
))
|
|
140
|
+
sql
|
|
141
|
+
.withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
|
|
142
|
+
.pipe(
|
|
143
|
+
Effect.orDie,
|
|
144
|
+
Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
|
|
145
|
+
)
|
|
462
146
|
|
|
463
147
|
const ctx = yield* Effect.context<R>()
|
|
464
148
|
const seedCache = new Map<string, Effect.Effect<void>>()
|
|
465
149
|
const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
|
|
466
|
-
yield* ensureTable
|
|
150
|
+
yield* ensureTable
|
|
467
151
|
if (!seed) return
|
|
468
152
|
const existing = yield* exec(
|
|
469
|
-
ns,
|
|
470
153
|
`SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
|
|
471
|
-
[tableName
|
|
154
|
+
[`${tableName}::${ns}`, tableName]
|
|
472
155
|
)
|
|
473
156
|
if ((existing as any[]).length > 0) return
|
|
474
157
|
yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
|
|
@@ -476,9 +159,8 @@ function makeSQLiteStorePerNs(
|
|
|
476
159
|
const ne = toNonEmptyArray([...items])
|
|
477
160
|
if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
|
|
478
161
|
yield* exec(
|
|
479
|
-
ns,
|
|
480
162
|
`INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
|
|
481
|
-
[tableName
|
|
163
|
+
[`${tableName}::${ns}`, tableName]
|
|
482
164
|
)
|
|
483
165
|
})
|
|
484
166
|
const seedNamespace = (ns: string) => {
|
|
@@ -489,39 +171,42 @@ function makeSQLiteStorePerNs(
|
|
|
489
171
|
}
|
|
490
172
|
return cached
|
|
491
173
|
}
|
|
492
|
-
|
|
493
174
|
const s: Store<IdKey, Encoded> = {
|
|
494
175
|
seedNamespace: (ns) => seedNamespace(ns),
|
|
495
176
|
|
|
496
|
-
all: resolveNamespace.pipe(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
|
|
500
|
-
Effect.withSpan("SQLite.all [effect-app/infra/Store]", {
|
|
501
|
-
attributes: {
|
|
502
|
-
"repository.table_name": tableName,
|
|
503
|
-
"repository.model_name": name,
|
|
504
|
-
"repository.namespace": ns
|
|
505
|
-
}
|
|
506
|
-
}, { captureStackTrace: false })
|
|
507
|
-
)
|
|
508
|
-
)),
|
|
509
|
-
|
|
510
|
-
find: (id) =>
|
|
511
|
-
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
512
|
-
exec(ns, `SELECT id, _etag, data FROM "${tableName}" WHERE id = ?`, [id])
|
|
177
|
+
all: resolveNamespace.pipe(
|
|
178
|
+
Effect.flatMap((ns) =>
|
|
179
|
+
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
|
|
513
180
|
.pipe(
|
|
514
|
-
Effect.map((rows) =>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
attributes: { "repository.table_name": tableName, "repository.model_name": name, id }
|
|
181
|
+
Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
|
|
182
|
+
Effect.withSpan("SQL.all [effect-app/infra/Store]", {
|
|
183
|
+
attributes: {
|
|
184
|
+
"repository.table_name": tableName,
|
|
185
|
+
"repository.model_name": name,
|
|
186
|
+
"repository.namespace": ns
|
|
187
|
+
}
|
|
522
188
|
}, { captureStackTrace: false })
|
|
523
189
|
)
|
|
524
|
-
)
|
|
190
|
+
)
|
|
191
|
+
),
|
|
192
|
+
|
|
193
|
+
find: (id) =>
|
|
194
|
+
resolveNamespace.pipe(
|
|
195
|
+
Effect.flatMap((ns) =>
|
|
196
|
+
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`, [id, ns])
|
|
197
|
+
.pipe(
|
|
198
|
+
Effect.map((rows) => {
|
|
199
|
+
const row = (rows as any[])[0]
|
|
200
|
+
return row
|
|
201
|
+
? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
|
|
202
|
+
: Option.none()
|
|
203
|
+
}),
|
|
204
|
+
Effect.withSpan("SQL.find [effect-app/infra/Store]", {
|
|
205
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id }
|
|
206
|
+
}, { captureStackTrace: false })
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
),
|
|
525
210
|
|
|
526
211
|
filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
|
|
527
212
|
const filter = f
|
|
@@ -532,9 +217,9 @@ function makeSQLiteStorePerNs(
|
|
|
532
217
|
.pipe(Effect
|
|
533
218
|
.flatMap((ns) =>
|
|
534
219
|
Effect
|
|
535
|
-
.sync(() =>
|
|
536
|
-
buildWhereSQLQuery(
|
|
537
|
-
|
|
220
|
+
.sync(() => {
|
|
221
|
+
const q = buildWhereSQLQuery(
|
|
222
|
+
dialect,
|
|
538
223
|
idKey,
|
|
539
224
|
filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
|
|
540
225
|
tableName,
|
|
@@ -550,14 +235,33 @@ function makeSQLiteStorePerNs(
|
|
|
550
235
|
f
|
|
551
236
|
.limit
|
|
552
237
|
)
|
|
553
|
-
|
|
238
|
+
const hasWhere = q
|
|
239
|
+
.sql
|
|
240
|
+
.includes("WHERE")
|
|
241
|
+
const nsSql = hasWhere
|
|
242
|
+
? q
|
|
243
|
+
.sql
|
|
244
|
+
.replace("WHERE", `WHERE _namespace = ? AND`)
|
|
245
|
+
: q
|
|
246
|
+
.sql
|
|
247
|
+
.replace(
|
|
248
|
+
`FROM "${tableName}"`,
|
|
249
|
+
`FROM "${tableName}" WHERE _namespace = ?`
|
|
250
|
+
)
|
|
251
|
+
return {
|
|
252
|
+
sql: nsSql,
|
|
253
|
+
params: [
|
|
254
|
+
ns,
|
|
255
|
+
...q
|
|
256
|
+
.params
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
})
|
|
554
260
|
.pipe(
|
|
555
261
|
Effect
|
|
556
|
-
.tap((q) =>
|
|
557
|
-
logQuery(q)
|
|
558
|
-
),
|
|
262
|
+
.tap((q) => logQuery(q)),
|
|
559
263
|
Effect.flatMap((q) =>
|
|
560
|
-
exec(
|
|
264
|
+
exec(q.sql, q.params).pipe(
|
|
561
265
|
Effect.map((rows) => {
|
|
562
266
|
if (f.select) {
|
|
563
267
|
return (rows as any[]).map((r) => {
|
|
@@ -575,7 +279,7 @@ function makeSQLiteStorePerNs(
|
|
|
575
279
|
})
|
|
576
280
|
)
|
|
577
281
|
),
|
|
578
|
-
Effect.withSpan("
|
|
282
|
+
Effect.withSpan("SQL.filter [effect-app/infra/Store]", {
|
|
579
283
|
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
580
284
|
}, { captureStackTrace: false })
|
|
581
285
|
)
|
|
@@ -585,7 +289,7 @@ function makeSQLiteStorePerNs(
|
|
|
585
289
|
set: (e) =>
|
|
586
290
|
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
587
291
|
setInternal(e, ns).pipe(
|
|
588
|
-
Effect.withSpan("
|
|
292
|
+
Effect.withSpan("SQL.set [effect-app/infra/Store]", {
|
|
589
293
|
attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
|
|
590
294
|
}, { captureStackTrace: false })
|
|
591
295
|
)
|
|
@@ -594,7 +298,7 @@ function makeSQLiteStorePerNs(
|
|
|
594
298
|
batchSet: (items) =>
|
|
595
299
|
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
596
300
|
bulkSetInternal(items, ns).pipe(
|
|
597
|
-
Effect.withSpan("
|
|
301
|
+
Effect.withSpan("SQL.batchSet [effect-app/infra/Store]", {
|
|
598
302
|
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
599
303
|
}, { captureStackTrace: false })
|
|
600
304
|
)
|
|
@@ -603,7 +307,7 @@ function makeSQLiteStorePerNs(
|
|
|
603
307
|
bulkSet: (items) =>
|
|
604
308
|
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
605
309
|
bulkSetInternal(items, ns).pipe(
|
|
606
|
-
Effect.withSpan("
|
|
310
|
+
Effect.withSpan("SQL.bulkSet [effect-app/infra/Store]", {
|
|
607
311
|
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
608
312
|
}, { captureStackTrace: false })
|
|
609
313
|
)
|
|
@@ -613,13 +317,12 @@ function makeSQLiteStorePerNs(
|
|
|
613
317
|
const placeholders = ids.map(() => "?").join(", ")
|
|
614
318
|
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
615
319
|
exec(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
[...ids]
|
|
320
|
+
`DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
|
|
321
|
+
[...ids, ns]
|
|
619
322
|
)
|
|
620
323
|
.pipe(
|
|
621
324
|
Effect.asVoid,
|
|
622
|
-
Effect.withSpan("
|
|
325
|
+
Effect.withSpan("SQL.batchRemove [effect-app/infra/Store]", {
|
|
623
326
|
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
624
327
|
}, { captureStackTrace: false })
|
|
625
328
|
)
|
|
@@ -629,16 +332,307 @@ function makeSQLiteStorePerNs(
|
|
|
629
332
|
queryRaw: (query) =>
|
|
630
333
|
s.all.pipe(
|
|
631
334
|
Effect.map(query.memory),
|
|
632
|
-
Effect.withSpan("
|
|
335
|
+
Effect.withSpan("SQL.queryRaw [effect-app/infra/Store]", {
|
|
633
336
|
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
634
337
|
}, { captureStackTrace: false })
|
|
635
338
|
)
|
|
636
339
|
}
|
|
637
340
|
|
|
341
|
+
// Eagerly seed primary namespace on initialization
|
|
638
342
|
yield* seedNamespace("primary")
|
|
639
343
|
|
|
640
344
|
return s
|
|
641
345
|
})
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
type WithNsSqlFn = <A, E2, R2>(
|
|
351
|
+
ns: string,
|
|
352
|
+
f: (sql: SqlClient.SqlClient) => Effect.Effect<A, E2, R2>
|
|
353
|
+
) => Effect.Effect<A, E2, R2>
|
|
354
|
+
|
|
355
|
+
function makeSQLiteStorePerNs(
|
|
356
|
+
withNsSql: WithNsSqlFn,
|
|
357
|
+
{ prefix }: StorageConfig
|
|
358
|
+
) {
|
|
359
|
+
return {
|
|
360
|
+
make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
361
|
+
name: string,
|
|
362
|
+
idKey: IdKey,
|
|
363
|
+
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
364
|
+
config?: StoreConfig<Encoded>
|
|
365
|
+
) {
|
|
366
|
+
type PM = PersistenceModelType<Encoded>
|
|
367
|
+
const tableName = `${prefix}${name}`
|
|
368
|
+
const defaultValues = config?.defaultValues ?? {}
|
|
369
|
+
|
|
370
|
+
const resolveNamespace = !config?.allowNamespace
|
|
371
|
+
? Effect.succeed("primary")
|
|
372
|
+
: storeId.asEffect().pipe(Effect.map((namespace) => {
|
|
373
|
+
if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
|
|
374
|
+
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
375
|
+
}
|
|
376
|
+
return namespace
|
|
377
|
+
}))
|
|
378
|
+
|
|
379
|
+
const toRow = (e: PM) => {
|
|
380
|
+
const newE = makeETag(e)
|
|
381
|
+
const id = newE[idKey] as string
|
|
382
|
+
const { _etag, [idKey]: _id, ...rest } = newE as any
|
|
383
|
+
const data = JSON.stringify(rest)
|
|
384
|
+
return { id, _etag: newE._etag!, data, item: newE }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const exec = (ns: string, query: string, params?: readonly unknown[]) =>
|
|
388
|
+
withNsSql(ns, (sql) => sql.unsafe(query, params as any).pipe(Effect.orDie))
|
|
389
|
+
|
|
390
|
+
const ensureTable = (ns: string) =>
|
|
391
|
+
withNsSql(ns, (sql) =>
|
|
392
|
+
sql
|
|
393
|
+
.unsafe(
|
|
394
|
+
`CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL PRIMARY KEY, _etag TEXT, data JSON NOT NULL)`
|
|
395
|
+
)
|
|
396
|
+
.pipe(
|
|
397
|
+
Effect.andThen(
|
|
398
|
+
sql.unsafe(
|
|
399
|
+
`CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
|
|
400
|
+
)
|
|
401
|
+
),
|
|
402
|
+
Effect.orDie,
|
|
403
|
+
Effect.asVoid
|
|
404
|
+
))
|
|
405
|
+
|
|
406
|
+
const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
|
|
407
|
+
const row = toRow(e)
|
|
408
|
+
if (e._etag) {
|
|
409
|
+
yield* exec(
|
|
410
|
+
ns,
|
|
411
|
+
`UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ?`,
|
|
412
|
+
[row._etag, row.data, row.id, e._etag]
|
|
413
|
+
)
|
|
414
|
+
const existing = yield* exec(
|
|
415
|
+
ns,
|
|
416
|
+
`SELECT _etag FROM "${tableName}" WHERE id = ?`,
|
|
417
|
+
[row.id]
|
|
418
|
+
)
|
|
419
|
+
const current = (existing as any[])[0]
|
|
420
|
+
if (!current || current._etag !== row._etag) {
|
|
421
|
+
if (current) {
|
|
422
|
+
return yield* new OptimisticConcurrencyException({
|
|
423
|
+
type: name,
|
|
424
|
+
id: row.id,
|
|
425
|
+
current: current._etag,
|
|
426
|
+
found: e._etag,
|
|
427
|
+
code: 412
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
return yield* new OptimisticConcurrencyException({
|
|
431
|
+
type: name,
|
|
432
|
+
id: row.id,
|
|
433
|
+
current: "",
|
|
434
|
+
found: e._etag,
|
|
435
|
+
code: 404
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
yield* exec(
|
|
440
|
+
ns,
|
|
441
|
+
`INSERT INTO "${tableName}" (id, _etag, data) VALUES (?, ?, ?)`,
|
|
442
|
+
[row.id, row._etag, row.data]
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
return row.item
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
|
|
449
|
+
withNsSql(ns, (sql) =>
|
|
450
|
+
sql
|
|
451
|
+
.withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
|
|
452
|
+
.pipe(
|
|
453
|
+
Effect.orDie,
|
|
454
|
+
Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
|
|
455
|
+
))
|
|
456
|
+
|
|
457
|
+
const ctx = yield* Effect.context<R>()
|
|
458
|
+
const seedCache = new Map<string, Effect.Effect<void>>()
|
|
459
|
+
const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
|
|
460
|
+
yield* ensureTable(ns)
|
|
461
|
+
if (!seed) return
|
|
462
|
+
const existing = yield* exec(
|
|
463
|
+
ns,
|
|
464
|
+
`SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
|
|
465
|
+
[tableName, tableName]
|
|
466
|
+
)
|
|
467
|
+
if ((existing as any[]).length > 0) return
|
|
468
|
+
yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
|
|
469
|
+
const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
|
|
470
|
+
const ne = toNonEmptyArray([...items])
|
|
471
|
+
if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
|
|
472
|
+
yield* exec(
|
|
473
|
+
ns,
|
|
474
|
+
`INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
|
|
475
|
+
[tableName, tableName]
|
|
476
|
+
)
|
|
477
|
+
})
|
|
478
|
+
const seedNamespace = (ns: string) => {
|
|
479
|
+
let cached = seedCache.get(ns)
|
|
480
|
+
if (!cached) {
|
|
481
|
+
cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
|
|
482
|
+
seedCache.set(ns, cached)
|
|
483
|
+
}
|
|
484
|
+
return cached
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const s: Store<IdKey, Encoded> = {
|
|
488
|
+
seedNamespace: (ns) => seedNamespace(ns),
|
|
489
|
+
|
|
490
|
+
all: resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
491
|
+
exec(ns, `SELECT id, _etag, data FROM "${tableName}"`)
|
|
492
|
+
.pipe(
|
|
493
|
+
Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
|
|
494
|
+
Effect.withSpan("SQLite.all [effect-app/infra/Store]", {
|
|
495
|
+
attributes: {
|
|
496
|
+
"repository.table_name": tableName,
|
|
497
|
+
"repository.model_name": name,
|
|
498
|
+
"repository.namespace": ns
|
|
499
|
+
}
|
|
500
|
+
}, { captureStackTrace: false })
|
|
501
|
+
)
|
|
502
|
+
)),
|
|
503
|
+
|
|
504
|
+
find: (id) =>
|
|
505
|
+
resolveNamespace.pipe(
|
|
506
|
+
Effect.flatMap((ns) =>
|
|
507
|
+
exec(ns, `SELECT id, _etag, data FROM "${tableName}" WHERE id = ?`, [id])
|
|
508
|
+
.pipe(
|
|
509
|
+
Effect.map((rows) => {
|
|
510
|
+
const row = (rows as any[])[0]
|
|
511
|
+
return row
|
|
512
|
+
? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
|
|
513
|
+
: Option.none()
|
|
514
|
+
}),
|
|
515
|
+
Effect.withSpan("SQLite.find [effect-app/infra/Store]", {
|
|
516
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id }
|
|
517
|
+
}, { captureStackTrace: false })
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
),
|
|
521
|
+
|
|
522
|
+
filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
|
|
523
|
+
const filter = f
|
|
524
|
+
.filter
|
|
525
|
+
type M = U extends undefined ? Encoded
|
|
526
|
+
: Pick<Encoded, U>
|
|
527
|
+
return resolveNamespace
|
|
528
|
+
.pipe(Effect
|
|
529
|
+
.flatMap((ns) =>
|
|
530
|
+
Effect
|
|
531
|
+
.sync(() =>
|
|
532
|
+
buildWhereSQLQuery(
|
|
533
|
+
sqliteDialect,
|
|
534
|
+
idKey,
|
|
535
|
+
filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
|
|
536
|
+
tableName,
|
|
537
|
+
defaultValues,
|
|
538
|
+
f
|
|
539
|
+
.select as
|
|
540
|
+
| NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }>
|
|
541
|
+
| undefined,
|
|
542
|
+
f
|
|
543
|
+
.order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
|
|
544
|
+
f
|
|
545
|
+
.skip,
|
|
546
|
+
f
|
|
547
|
+
.limit
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
.pipe(
|
|
551
|
+
Effect
|
|
552
|
+
.tap((q) => logQuery(q)),
|
|
553
|
+
Effect.flatMap((q) =>
|
|
554
|
+
exec(ns, q.sql, q.params).pipe(
|
|
555
|
+
Effect.map((rows) => {
|
|
556
|
+
if (f.select) {
|
|
557
|
+
return (rows as any[]).map((r) => {
|
|
558
|
+
const selected = parseSelectRow(r, idKey)
|
|
559
|
+
return {
|
|
560
|
+
...Struct.pick(
|
|
561
|
+
defaultValues as any,
|
|
562
|
+
f.select!.filter((_) => typeof _ === "string") as never[]
|
|
563
|
+
),
|
|
564
|
+
...selected
|
|
565
|
+
} as M
|
|
566
|
+
})
|
|
567
|
+
}
|
|
568
|
+
return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
|
|
569
|
+
})
|
|
570
|
+
)
|
|
571
|
+
),
|
|
572
|
+
Effect.withSpan("SQLite.filter [effect-app/infra/Store]", {
|
|
573
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
574
|
+
}, { captureStackTrace: false })
|
|
575
|
+
)
|
|
576
|
+
))
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
set: (e) =>
|
|
580
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
581
|
+
setInternal(e, ns).pipe(
|
|
582
|
+
Effect.withSpan("SQLite.set [effect-app/infra/Store]", {
|
|
583
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
|
|
584
|
+
}, { captureStackTrace: false })
|
|
585
|
+
)
|
|
586
|
+
)),
|
|
587
|
+
|
|
588
|
+
batchSet: (items) =>
|
|
589
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
590
|
+
bulkSetInternal(items, ns).pipe(
|
|
591
|
+
Effect.withSpan("SQLite.batchSet [effect-app/infra/Store]", {
|
|
592
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
593
|
+
}, { captureStackTrace: false })
|
|
594
|
+
)
|
|
595
|
+
)),
|
|
596
|
+
|
|
597
|
+
bulkSet: (items) =>
|
|
598
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
599
|
+
bulkSetInternal(items, ns).pipe(
|
|
600
|
+
Effect.withSpan("SQLite.bulkSet [effect-app/infra/Store]", {
|
|
601
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
602
|
+
}, { captureStackTrace: false })
|
|
603
|
+
)
|
|
604
|
+
)),
|
|
605
|
+
|
|
606
|
+
batchRemove: (ids) => {
|
|
607
|
+
const placeholders = ids.map(() => "?").join(", ")
|
|
608
|
+
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
609
|
+
exec(
|
|
610
|
+
ns,
|
|
611
|
+
`DELETE FROM "${tableName}" WHERE id IN (${placeholders})`,
|
|
612
|
+
[...ids]
|
|
613
|
+
)
|
|
614
|
+
.pipe(
|
|
615
|
+
Effect.asVoid,
|
|
616
|
+
Effect.withSpan("SQLite.batchRemove [effect-app/infra/Store]", {
|
|
617
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
618
|
+
}, { captureStackTrace: false })
|
|
619
|
+
)
|
|
620
|
+
))
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
queryRaw: (query) =>
|
|
624
|
+
s.all.pipe(
|
|
625
|
+
Effect.map(query.memory),
|
|
626
|
+
Effect.withSpan("SQLite.queryRaw [effect-app/infra/Store]", {
|
|
627
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
628
|
+
}, { captureStackTrace: false })
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
yield* seedNamespace("primary")
|
|
633
|
+
|
|
634
|
+
return s
|
|
635
|
+
})
|
|
642
636
|
}
|
|
643
637
|
}
|
|
644
638
|
|