@effect-app/infra 4.0.0-beta.16 → 4.0.0-beta.161
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 +1060 -0
- package/dist/CUPS.d.ts +15 -7
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +10 -12
- package/dist/Emailer/Sendgrid.d.ts +14 -14
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +16 -15
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/service.d.ts +9 -3
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/Emailer.d.ts +1 -1
- package/dist/MainFiberSet.d.ts +5 -5
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/Registry.d.ts +20 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +17 -0
- package/dist/Model/Repository/ext.d.ts +33 -15
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +54 -2
- package/dist/Model/Repository/internal/internal.d.ts +6 -6
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +43 -32
- package/dist/Model/Repository/legacy.d.ts +1 -1
- package/dist/Model/Repository/makeRepo.d.ts +7 -6
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +5 -1
- package/dist/Model/Repository/service.d.ts +28 -23
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +142 -17
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository/validation.js +5 -5
- package/dist/Model/Repository.d.ts +2 -1
- package/dist/Model/Repository.d.ts.map +1 -1
- package/dist/Model/Repository.js +2 -1
- package/dist/Model/dsl.d.ts +4 -4
- package/dist/Model/dsl.d.ts.map +1 -1
- package/dist/Model/filter/filterApi.d.ts +5 -5
- package/dist/Model/filter/filterApi.d.ts.map +1 -1
- package/dist/Model/filter/types/errors.d.ts +1 -1
- package/dist/Model/filter/types/fields.d.ts +1 -1
- package/dist/Model/filter/types/path/common.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
- package/dist/Model/filter/types/path/index.d.ts +1 -1
- package/dist/Model/filter/types/utils.d.ts +1 -1
- package/dist/Model/filter/types/validator.d.ts +1 -1
- package/dist/Model/filter/types.d.ts +1 -1
- package/dist/Model/query/dsl.d.ts +1 -1
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.d.ts +6 -6
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +3 -3
- package/dist/Model/query.d.ts +1 -1
- package/dist/Model.d.ts +2 -1
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +2 -1
- package/dist/Operations.d.ts +6 -6
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +56 -59
- package/dist/OperationsRepo.d.ts +11 -29
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +5 -7
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +105 -114
- package/dist/QueueMaker/errors.d.ts +2 -2
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts +7 -4
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +51 -62
- package/dist/QueueMaker/sbqueue.d.ts +6 -3
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +37 -53
- package/dist/QueueMaker/service.d.ts +1 -1
- package/dist/RequestContext.d.ts +114 -26
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +7 -7
- package/dist/RequestFiberSet.d.ts +7 -7
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +5 -5
- package/dist/Store/ContextMapContainer.d.ts +19 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +13 -3
- package/dist/Store/Cosmos/query.d.ts +1 -1
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +8 -10
- package/dist/Store/Cosmos.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +308 -242
- package/dist/Store/Disk.d.ts +2 -2
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +25 -22
- package/dist/Store/Memory.d.ts +4 -4
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +27 -22
- package/dist/Store/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +189 -0
- package/dist/Store/SQL/query.d.ts +38 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +367 -0
- package/dist/Store/SQL.d.ts +20 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +381 -0
- package/dist/Store/codeFilter.d.ts +1 -1
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +2 -1
- package/dist/Store/index.d.ts +5 -2
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +15 -3
- package/dist/Store/service.d.ts +17 -6
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- package/dist/Store/utils.d.ts +1 -1
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/Store.d.ts +1 -1
- package/dist/adapters/SQL/Model.d.ts +28 -42
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +2 -2
- package/dist/adapters/SQL.d.ts +1 -1
- package/dist/adapters/ServiceBus.d.ts +9 -9
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +13 -15
- package/dist/adapters/cosmos-client.d.ts +3 -3
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/index.d.ts +8 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +8 -2
- package/dist/adapters/logger.d.ts +1 -1
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +3 -3
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +3 -3
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +7 -7
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/codec.d.ts +1 -1
- package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
- package/dist/api/internal/RequestContextMiddleware.js +2 -2
- package/dist/api/internal/auth.d.ts +44 -6
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +160 -29
- package/dist/api/internal/events.d.ts +3 -3
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +9 -7
- package/dist/api/internal/health.d.ts +1 -1
- package/dist/api/layerUtils.d.ts +6 -6
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/middlewares.d.ts +1 -1
- package/dist/api/reportError.d.ts +1 -1
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +39 -3
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +45 -14
- package/dist/api/routing/middleware.d.ts +1 -2
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +1 -2
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/tsort.d.ts +1 -1
- package/dist/api/routing/tsort.d.ts.map +1 -1
- package/dist/api/routing/utils.d.ts +3 -3
- package/dist/api/routing/utils.d.ts.map +1 -1
- package/dist/api/routing.d.ts +12 -14
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +17 -6
- package/dist/api/setupRequest.d.ts +8 -5
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +12 -7
- package/dist/api/util.d.ts +1 -1
- package/dist/arbs.d.ts +1 -1
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +5 -3
- package/dist/errorReporter.d.ts +4 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +16 -23
- package/dist/errors.d.ts +1 -1
- package/dist/fileUtil.d.ts +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts +1 -1
- package/dist/logger/logFmtLogger.d.ts +1 -1
- package/dist/logger/shared.d.ts +1 -1
- package/dist/logger/shared.js +2 -2
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.d.ts +9 -3
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -11
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/vitest.d.ts +1 -1
- package/eslint.config.mjs +3 -3
- package/examples/query.ts +39 -35
- package/package.json +42 -28
- package/src/CUPS.ts +9 -11
- package/src/Emailer/Sendgrid.ts +17 -14
- package/src/Emailer/service.ts +8 -2
- package/src/MainFiberSet.ts +3 -3
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +93 -6
- package/src/Model/Repository/internal/internal.ts +97 -88
- package/src/Model/Repository/makeRepo.ts +12 -10
- package/src/Model/Repository/service.ts +31 -22
- package/src/Model/Repository/validation.ts +4 -4
- package/src/Model/Repository.ts +1 -0
- package/src/Model/dsl.ts +3 -3
- package/src/Model/query/new-kid-interpreter.ts +2 -2
- package/src/Model.ts +1 -0
- package/src/Operations.ts +78 -113
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +121 -151
- package/src/QueueMaker/memQueue.ts +82 -103
- package/src/QueueMaker/sbqueue.ts +56 -86
- package/src/RequestContext.ts +8 -8
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos/query.ts +9 -11
- package/src/Store/Cosmos.ts +437 -343
- package/src/Store/Disk.ts +52 -49
- package/src/Store/Memory.ts +54 -48
- package/src/Store/SQL/Pg.ts +318 -0
- package/src/Store/SQL/query.ts +409 -0
- package/src/Store/SQL.ts +668 -0
- package/src/Store/codeFilter.ts +1 -0
- package/src/Store/index.ts +17 -2
- package/src/Store/service.ts +31 -7
- package/src/Store/utils.ts +23 -22
- package/src/adapters/SQL/Model.ts +10 -4
- package/src/adapters/ServiceBus.ts +111 -115
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/index.ts +7 -0
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +11 -11
- package/src/api/internal/RequestContextMiddleware.ts +1 -1
- package/src/api/internal/auth.ts +246 -44
- package/src/api/internal/events.ts +12 -8
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +52 -12
- package/src/api/routing/middleware.ts +0 -2
- package/src/api/routing.ts +21 -7
- package/src/api/setupRequest.ts +28 -8
- package/src/arbs.ts +4 -2
- package/src/errorReporter.ts +58 -72
- package/src/logger/shared.ts +1 -1
- package/src/rateLimit.ts +30 -22
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +18 -14
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/date-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +26 -12
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +12 -10
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/router-generator.test.d.ts.map +1 -0
- package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +11 -9
- package/test/query.test.ts +216 -34
- package/test/rawQuery.test.ts +23 -19
- package/test/repository-ext.test.ts +60 -0
- package/test/requires.test.ts +6 -6
- package/test/router-generator.test.ts +180 -0
- package/test/routing-interruptibility.test.ts +63 -0
- package/test/rpc-multi-middleware.test.ts +78 -9
- package/test/sql-store.test.ts +1064 -0
- package/test/validateSample.test.ts +15 -12
- package/tsconfig.examples.json +1 -1
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +35 -35
- package/tsconfig.test.json +2 -2
package/src/Store/SQL.ts
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { Context, Layer, LayerMap } from "effect"
|
|
4
|
+
import { Effect, type NonEmptyReadonlyArray, Option, Struct } from "effect-app"
|
|
5
|
+
import { toNonEmptyArray } from "effect-app/Array"
|
|
6
|
+
import { SqlClient } from "effect/unstable/sql"
|
|
7
|
+
import { OptimisticConcurrencyException } from "../errors.js"
|
|
8
|
+
import { InfraLogger } from "../logger.js"
|
|
9
|
+
import type { FieldValues } from "../Model/filter/types.js"
|
|
10
|
+
import { storeId } from "./Memory.js"
|
|
11
|
+
import { type FilterArgs, type PersistenceModelType, type StorageConfig, type Store, type StoreConfig, StoreMaker } from "./service.js"
|
|
12
|
+
import { buildWhereSQLQuery, logQuery, type SQLDialect, sqliteDialect } from "./SQL/query.js"
|
|
13
|
+
import { makeETag } from "./utils.js"
|
|
14
|
+
|
|
15
|
+
export type WithNsTransactionFn = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
|
|
16
|
+
|
|
17
|
+
export class WithNsTransaction
|
|
18
|
+
extends Context.Service<WithNsTransaction, WithNsTransactionFn>()("effect-app/WithNsTransaction")
|
|
19
|
+
{}
|
|
20
|
+
|
|
21
|
+
/** @internal */
|
|
22
|
+
export const parseRow = <Encoded extends FieldValues>(
|
|
23
|
+
row: { id: string; _etag: string | null; data: string },
|
|
24
|
+
idKey: PropertyKey,
|
|
25
|
+
defaultValues: Partial<Encoded>
|
|
26
|
+
): PersistenceModelType<Encoded> => {
|
|
27
|
+
const data = (typeof row.data === "string" ? JSON.parse(row.data) : row.data) as object
|
|
28
|
+
return { ...defaultValues, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parseSelectRow = (
|
|
32
|
+
row: Record<string, unknown>,
|
|
33
|
+
idKey: PropertyKey
|
|
34
|
+
): any => {
|
|
35
|
+
const result: Record<string, unknown> = {}
|
|
36
|
+
for (const [key, value] of Object.entries(row)) {
|
|
37
|
+
if (key === "id") {
|
|
38
|
+
result[idKey as string] = value
|
|
39
|
+
result["id"] = value
|
|
40
|
+
} else if (typeof value === "string") {
|
|
41
|
+
try {
|
|
42
|
+
result[key] = JSON.parse(value)
|
|
43
|
+
} catch {
|
|
44
|
+
result[key] = value
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
result[key] = value
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeSQLStoreInt(dialect: SQLDialect, jsonColumnType: string) {
|
|
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
|
+
) {
|
|
63
|
+
type PM = PersistenceModelType<Encoded>
|
|
64
|
+
const tableName = `${prefix}${name}`
|
|
65
|
+
const defaultValues = config?.defaultValues ?? {}
|
|
66
|
+
|
|
67
|
+
const resolveNamespace = !config?.allowNamespace
|
|
68
|
+
? Effect.succeed("primary")
|
|
69
|
+
: storeId.asEffect().pipe(Effect.map((namespace) => {
|
|
70
|
+
if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
|
|
71
|
+
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
72
|
+
}
|
|
73
|
+
return namespace
|
|
74
|
+
}))
|
|
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
|
+
|
|
90
|
+
const toRow = (e: PM) => {
|
|
91
|
+
const newE = makeETag(e)
|
|
92
|
+
const id = newE[idKey] as string
|
|
93
|
+
const { _etag, [idKey]: _id, ...rest } = newE as any
|
|
94
|
+
const data = JSON.stringify(rest)
|
|
95
|
+
return { id, _etag: newE._etag!, data, item: newE }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const exec = (query: string, params?: readonly unknown[]) => sql.unsafe(query, params as any).pipe(Effect.orDie)
|
|
99
|
+
|
|
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) {
|
|
114
|
+
return yield* new OptimisticConcurrencyException({
|
|
115
|
+
type: name,
|
|
116
|
+
id: row.id,
|
|
117
|
+
current: current._etag,
|
|
118
|
+
found: e._etag,
|
|
119
|
+
code: 412
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
return yield* new OptimisticConcurrencyException({
|
|
123
|
+
type: name,
|
|
124
|
+
id: row.id,
|
|
125
|
+
current: "",
|
|
126
|
+
found: e._etag,
|
|
127
|
+
code: 404
|
|
128
|
+
})
|
|
129
|
+
}
|
|
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
|
+
})
|
|
138
|
+
|
|
139
|
+
const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
|
|
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
|
+
)
|
|
146
|
+
|
|
147
|
+
const ctx = yield* Effect.context<R>()
|
|
148
|
+
const seedCache = new Map<string, Effect.Effect<void>>()
|
|
149
|
+
const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
|
|
150
|
+
yield* ensureTable
|
|
151
|
+
if (!seed) return
|
|
152
|
+
const existing = yield* exec(
|
|
153
|
+
`SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
|
|
154
|
+
[`${tableName}::${ns}`, tableName]
|
|
155
|
+
)
|
|
156
|
+
if ((existing as any[]).length > 0) return
|
|
157
|
+
yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
|
|
158
|
+
const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
|
|
159
|
+
const ne = toNonEmptyArray([...items])
|
|
160
|
+
if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
|
|
161
|
+
yield* exec(
|
|
162
|
+
`INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
|
|
163
|
+
[`${tableName}::${ns}`, tableName]
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
const seedNamespace = (ns: string) => {
|
|
167
|
+
let cached = seedCache.get(ns)
|
|
168
|
+
if (!cached) {
|
|
169
|
+
cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
|
|
170
|
+
seedCache.set(ns, cached)
|
|
171
|
+
}
|
|
172
|
+
return cached
|
|
173
|
+
}
|
|
174
|
+
const s: Store<IdKey, Encoded> = {
|
|
175
|
+
seedNamespace: (ns) => seedNamespace(ns),
|
|
176
|
+
|
|
177
|
+
all: resolveNamespace.pipe(
|
|
178
|
+
Effect.flatMap((ns) =>
|
|
179
|
+
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`, [ns])
|
|
180
|
+
.pipe(
|
|
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
|
+
}
|
|
188
|
+
}, { captureStackTrace: false })
|
|
189
|
+
)
|
|
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
|
+
),
|
|
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,
|
|
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) => logQuery(q)),
|
|
263
|
+
Effect.flatMap((q) =>
|
|
264
|
+
exec(q.sql, q.params).pipe(
|
|
265
|
+
Effect.map((rows) => {
|
|
266
|
+
if (f.select) {
|
|
267
|
+
return (rows as any[]).map((r) => {
|
|
268
|
+
const selected = parseSelectRow(r, idKey)
|
|
269
|
+
return {
|
|
270
|
+
...Struct.pick(
|
|
271
|
+
defaultValues as any,
|
|
272
|
+
f.select!.filter((_) => typeof _ === "string") as never[]
|
|
273
|
+
),
|
|
274
|
+
...selected
|
|
275
|
+
} as M
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
|
|
279
|
+
})
|
|
280
|
+
)
|
|
281
|
+
),
|
|
282
|
+
Effect.withSpan("SQL.filter [effect-app/infra/Store]", {
|
|
283
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
284
|
+
}, { captureStackTrace: false })
|
|
285
|
+
)
|
|
286
|
+
))
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
set: (e) =>
|
|
290
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
291
|
+
setInternal(e, ns).pipe(
|
|
292
|
+
Effect.withSpan("SQL.set [effect-app/infra/Store]", {
|
|
293
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
|
|
294
|
+
}, { captureStackTrace: false })
|
|
295
|
+
)
|
|
296
|
+
)),
|
|
297
|
+
|
|
298
|
+
batchSet: (items) =>
|
|
299
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
300
|
+
bulkSetInternal(items, ns).pipe(
|
|
301
|
+
Effect.withSpan("SQL.batchSet [effect-app/infra/Store]", {
|
|
302
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
303
|
+
}, { captureStackTrace: false })
|
|
304
|
+
)
|
|
305
|
+
)),
|
|
306
|
+
|
|
307
|
+
bulkSet: (items) =>
|
|
308
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
309
|
+
bulkSetInternal(items, ns).pipe(
|
|
310
|
+
Effect.withSpan("SQL.bulkSet [effect-app/infra/Store]", {
|
|
311
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
312
|
+
}, { captureStackTrace: false })
|
|
313
|
+
)
|
|
314
|
+
)),
|
|
315
|
+
|
|
316
|
+
batchRemove: (ids) => {
|
|
317
|
+
const placeholders = ids.map(() => "?").join(", ")
|
|
318
|
+
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
319
|
+
exec(
|
|
320
|
+
`DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`,
|
|
321
|
+
[...ids, ns]
|
|
322
|
+
)
|
|
323
|
+
.pipe(
|
|
324
|
+
Effect.asVoid,
|
|
325
|
+
Effect.withSpan("SQL.batchRemove [effect-app/infra/Store]", {
|
|
326
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
327
|
+
}, { captureStackTrace: false })
|
|
328
|
+
)
|
|
329
|
+
))
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
queryRaw: (query) =>
|
|
333
|
+
s.all.pipe(
|
|
334
|
+
Effect.map(query.memory),
|
|
335
|
+
Effect.withSpan("SQL.queryRaw [effect-app/infra/Store]", {
|
|
336
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
337
|
+
}, { captureStackTrace: false })
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Eagerly seed primary namespace on initialization
|
|
342
|
+
yield* seedNamespace("primary")
|
|
343
|
+
|
|
344
|
+
return s
|
|
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
|
+
})
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function SQLiteStoreLayer(
|
|
640
|
+
cfg: StorageConfig,
|
|
641
|
+
options?: { makeSqlClientLayer?: (namespace: string) => Layer.Layer<SqlClient.SqlClient> }
|
|
642
|
+
) {
|
|
643
|
+
if (options?.makeSqlClientLayer) {
|
|
644
|
+
return Layer.effectContext(
|
|
645
|
+
Effect.gen(function*() {
|
|
646
|
+
const layerMap = yield* LayerMap.make(
|
|
647
|
+
(namespace: string) => options.makeSqlClientLayer!(namespace),
|
|
648
|
+
{ idleTimeToLive: "10 minutes" }
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
const withNsSql: WithNsSqlFn = (ns, f) => SqlClient.SqlClient.use(f).pipe(Effect.provide(layerMap.get(ns)))
|
|
652
|
+
|
|
653
|
+
const storeMaker = makeSQLiteStorePerNs(withNsSql, cfg)
|
|
654
|
+
|
|
655
|
+
const withTransaction: WithNsTransactionFn = (effect) =>
|
|
656
|
+
storeId.asEffect().pipe(
|
|
657
|
+
Effect.flatMap((ns) => withNsSql(ns, (sql) => sql.withTransaction(effect).pipe(Effect.orDie)))
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
return StoreMaker.context(storeMaker).pipe(
|
|
661
|
+
Context.add(WithNsTransaction, withTransaction)
|
|
662
|
+
)
|
|
663
|
+
})
|
|
664
|
+
)
|
|
665
|
+
}
|
|
666
|
+
return StoreMaker
|
|
667
|
+
.toLayer(makeSQLStoreInt(sqliteDialect, "JSON")(cfg))
|
|
668
|
+
}
|
package/src/Store/codeFilter.ts
CHANGED
|
@@ -186,6 +186,7 @@ export const codeFilter3_ = <E>(state: readonly FilterResult[], sut: E): boolean
|
|
|
186
186
|
const statements: any[] = [] // must be defined here to be used by eval.
|
|
187
187
|
// always put everything inside a root scope.
|
|
188
188
|
const s = codeFilter3__([{ t: "where-scope", result: state, relation: "some" }], sut, statements, null, false)
|
|
189
|
+
// oxlint-disable-next-line no-eval
|
|
189
190
|
return eval(s)
|
|
190
191
|
}
|
|
191
192
|
|