@effect-app/infra 4.0.0-beta.20 → 4.0.0-beta.200
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 +1378 -0
- package/_check.sh +1 -1
- 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 +10 -4
- 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 +9 -9
- 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/eager.js +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 +16 -16
- 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/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 +112 -26
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +7 -8
- 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 +10 -12
- package/dist/Store/Cosmos.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +318 -240
- 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 +18 -7
- 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 +11 -11
- 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 +2 -2
- 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 +8 -8
- 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 +50 -4
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +79 -17
- 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 +25 -26
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +83 -35
- 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 +20 -25
- 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 +2 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +1 -1
- package/dist/vitest.d.ts +1 -1
- package/examples/query.ts +39 -35
- package/package.json +41 -37
- package/src/CUPS.ts +9 -11
- package/src/Emailer/Sendgrid.ts +17 -14
- package/src/Emailer/service.ts +9 -3
- package/src/MainFiberSet.ts +5 -6
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +96 -10
- 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/filter/types/path/eager.ts +1 -2
- package/src/Model/query/dsl.ts +18 -18
- package/src/Model/query/new-kid-interpreter.ts +2 -2
- package/src/Model.ts +1 -0
- 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 -10
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos/query.ts +16 -20
- package/src/Store/Cosmos.ts +452 -342
- 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 +32 -8
- package/src/Store/utils.ts +23 -22
- package/src/adapters/SQL/Model.ts +10 -4
- package/src/adapters/ServiceBus.ts +112 -116
- 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 +12 -13
- 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 +112 -15
- package/src/api/routing/middleware.ts +0 -2
- package/src/api/routing.ts +142 -63
- package/src/api/setupRequest.ts +28 -8
- package/src/arbs.ts +4 -2
- package/src/errorReporter.ts +62 -74
- package/src/logger/shared.ts +1 -1
- package/src/rateLimit.ts +30 -22
- package/src/test.ts +1 -1
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +18 -16
- 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/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-e2e-invalidation.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
- 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-e2e-invalidation.test.ts +507 -0
- package/test/rpc-multi-middleware.test.ts +78 -9
- package/test/rpc-stream-fullstack.test.ts +325 -0
- 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/dist/Operations.d.ts +0 -55
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -102
- package/dist/OperationsRepo.d.ts +0 -41
- package/dist/OperationsRepo.d.ts.map +0 -1
- package/dist/OperationsRepo.js +0 -14
- package/eslint.config.mjs +0 -24
- package/src/Operations.ts +0 -235
- package/src/OperationsRepo.ts +0 -16
package/src/Store/Disk.ts
CHANGED
|
@@ -66,11 +66,10 @@ function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValu
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// lock file for cross-process coordination during initialization
|
|
69
|
-
const lockFile = file + ".lock"
|
|
70
69
|
|
|
71
70
|
// wrap initialization in file lock to prevent race conditions in multi-worker setups
|
|
72
71
|
const store = yield* fu.withFileLock(
|
|
73
|
-
|
|
72
|
+
file,
|
|
74
73
|
Effect.gen(function*() {
|
|
75
74
|
const shouldSeed = !(fs.existsSync(file))
|
|
76
75
|
|
|
@@ -133,61 +132,65 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
133
132
|
fs.mkdirSync(dir)
|
|
134
133
|
}
|
|
135
134
|
return {
|
|
136
|
-
make:
|
|
135
|
+
make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
|
|
137
136
|
name: string,
|
|
138
137
|
idKey: IdKey,
|
|
139
138
|
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
140
139
|
config?: StoreConfig<Encoded>
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
140
|
+
) {
|
|
141
|
+
const primary = yield* makeDiskStoreInt(prefix, idKey, "primary", dir, name, seed, config?.defaultValues)
|
|
142
|
+
const stores = new Map<string, Store<IdKey, Encoded>>([["primary", primary]])
|
|
143
|
+
const ctx = yield* Effect.context<R>()
|
|
144
|
+
const semaphores = new Map<string, Semaphore.Semaphore>()
|
|
145
|
+
const getSem = (ns: string) => {
|
|
146
|
+
let sem = semaphores.get(ns)
|
|
147
|
+
if (!sem) {
|
|
148
|
+
sem = Semaphore.makeUnsafe(1)
|
|
149
|
+
semaphores.set(ns, sem)
|
|
150
|
+
}
|
|
151
|
+
return sem
|
|
152
|
+
}
|
|
153
|
+
const ensureStore = (namespace: string) =>
|
|
154
|
+
getSem(namespace).withPermits(1)(
|
|
155
|
+
Effect.suspend(() => {
|
|
156
|
+
const existing = stores.get(namespace)
|
|
157
|
+
if (existing) return Effect.succeed(existing)
|
|
158
|
+
if (config?.allowNamespace && !config.allowNamespace(namespace)) {
|
|
155
159
|
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
156
160
|
}
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
dir,
|
|
166
|
-
name,
|
|
167
|
-
seed,
|
|
168
|
-
config?.defaultValues
|
|
169
|
-
)
|
|
170
|
-
.pipe(
|
|
171
|
-
Effect.orDie,
|
|
172
|
-
Effect.provide(ctx),
|
|
173
|
-
Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
|
|
174
|
-
)
|
|
175
|
-
})
|
|
161
|
+
return makeDiskStoreInt<IdKey, Encoded, R, E>(
|
|
162
|
+
prefix,
|
|
163
|
+
idKey,
|
|
164
|
+
namespace,
|
|
165
|
+
dir,
|
|
166
|
+
name,
|
|
167
|
+
seed,
|
|
168
|
+
config?.defaultValues
|
|
176
169
|
)
|
|
177
|
-
|
|
170
|
+
.pipe(
|
|
171
|
+
Effect.orDie,
|
|
172
|
+
Effect.provide(ctx),
|
|
173
|
+
Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
|
|
174
|
+
)
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
const getStore = !config?.allowNamespace
|
|
178
|
+
? Effect.succeed(primary)
|
|
179
|
+
: storeId.asEffect().pipe(Effect.flatMap((namespace) => ensureStore(namespace)))
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
const s: Store<IdKey, Encoded> = {
|
|
182
|
+
seedNamespace: (namespace) => ensureStore(namespace).pipe(Effect.asVoid),
|
|
183
|
+
all: Effect.flatMap(getStore, (_) => _.all),
|
|
184
|
+
find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
|
|
185
|
+
filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
|
|
186
|
+
set: (...args) => Effect.flatMap(getStore, (_) => _.set(...args)),
|
|
187
|
+
batchSet: (...args) => Effect.flatMap(getStore, (_) => _.batchSet(...args)),
|
|
188
|
+
bulkSet: (...args) => Effect.flatMap(getStore, (_) => _.bulkSet(...args)),
|
|
189
|
+
batchRemove: (...args) => Effect.flatMap(getStore, (_) => _.batchRemove(...args)),
|
|
190
|
+
queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args))
|
|
191
|
+
}
|
|
192
|
+
return s
|
|
193
|
+
})
|
|
191
194
|
}
|
|
192
195
|
})
|
|
193
196
|
}
|
package/src/Store/Memory.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
|
|
3
|
-
import { Array, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore,
|
|
3
|
+
import { Array, Context, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, Struct } from "effect-app"
|
|
4
4
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
5
5
|
import { get } from "effect-app/utils"
|
|
6
6
|
import { InfraLogger } from "../logger.js"
|
|
@@ -27,7 +27,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
|
|
|
27
27
|
n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys as never[]))
|
|
28
28
|
})
|
|
29
29
|
return n as M
|
|
30
|
-
})
|
|
30
|
+
})
|
|
31
31
|
}
|
|
32
32
|
const skip = f?.skip
|
|
33
33
|
const limit = f?.limit
|
|
@@ -72,7 +72,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const defaultNs: NonEmptyString255 = NonEmptyString255("primary")
|
|
75
|
-
export class storeId extends
|
|
75
|
+
export class storeId extends Context.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs }) {}
|
|
76
76
|
|
|
77
77
|
function logQuery(f: FilterArgs<any, any>, defaultValues?: any) {
|
|
78
78
|
return InfraLogger
|
|
@@ -151,6 +151,8 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
151
151
|
withPermit
|
|
152
152
|
)
|
|
153
153
|
const s: Store<IdKey, Encoded> = {
|
|
154
|
+
seedNamespace: () => Effect.void,
|
|
155
|
+
|
|
154
156
|
queryRaw: (query) =>
|
|
155
157
|
all
|
|
156
158
|
.pipe(
|
|
@@ -250,56 +252,60 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
export const makeMemoryStore = () => ({
|
|
253
|
-
make:
|
|
255
|
+
make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
|
|
254
256
|
modelName: string,
|
|
255
257
|
idKey: IdKey,
|
|
256
258
|
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
257
259
|
config?: StoreConfig<Encoded>
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (store) {
|
|
275
|
-
return Effect.succeed(store)
|
|
276
|
-
}
|
|
277
|
-
if (!config.allowNamespace!(namespace)) {
|
|
278
|
-
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
279
|
-
}
|
|
280
|
-
return storesSem.withPermits(1)(Effect.suspend(() => {
|
|
281
|
-
const store = stores.get(namespace)
|
|
282
|
-
if (store) return Effect.sync(() => store)
|
|
283
|
-
return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
|
|
284
|
-
.pipe(
|
|
285
|
-
Effect.orDie,
|
|
286
|
-
Effect.provide(ctx),
|
|
287
|
-
Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
|
|
288
|
-
)
|
|
289
|
-
}))
|
|
290
|
-
}))
|
|
291
|
-
const s: Store<IdKey, Encoded> = {
|
|
292
|
-
all: Effect.flatMap(getStore, (_) => _.all),
|
|
293
|
-
queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args)),
|
|
294
|
-
find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
|
|
295
|
-
filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
|
|
296
|
-
set: (...args) => Effect.flatMap(getStore, (_) => _.set(...args)),
|
|
297
|
-
batchSet: (...args) => Effect.flatMap(getStore, (_) => _.batchSet(...args)),
|
|
298
|
-
bulkSet: (...args) => Effect.flatMap(getStore, (_) => _.bulkSet(...args)),
|
|
299
|
-
batchRemove: (...args) => Effect.flatMap(getStore, (_) => _.batchRemove(...args))
|
|
260
|
+
) {
|
|
261
|
+
const primary = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
|
|
262
|
+
modelName,
|
|
263
|
+
idKey,
|
|
264
|
+
"primary",
|
|
265
|
+
seed,
|
|
266
|
+
config?.defaultValues
|
|
267
|
+
)
|
|
268
|
+
const ctx = yield* Effect.context<R>()
|
|
269
|
+
const stores = new Map([["primary", primary]])
|
|
270
|
+
const semaphores = new Map<string, Semaphore.Semaphore>()
|
|
271
|
+
const getSem = (ns: string) => {
|
|
272
|
+
let sem = semaphores.get(ns)
|
|
273
|
+
if (!sem) {
|
|
274
|
+
sem = Semaphore.makeUnsafe(1)
|
|
275
|
+
semaphores.set(ns, sem)
|
|
300
276
|
}
|
|
301
|
-
return
|
|
302
|
-
}
|
|
277
|
+
return sem
|
|
278
|
+
}
|
|
279
|
+
const ensureStore = (namespace: string) =>
|
|
280
|
+
getSem(namespace).withPermits(1)(Effect.suspend(() => {
|
|
281
|
+
const store = stores.get(namespace)
|
|
282
|
+
if (store) return Effect.succeed(store)
|
|
283
|
+
if (config?.allowNamespace && !config.allowNamespace(namespace)) {
|
|
284
|
+
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
285
|
+
}
|
|
286
|
+
return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
|
|
287
|
+
.pipe(
|
|
288
|
+
Effect.orDie,
|
|
289
|
+
Effect.provide(ctx),
|
|
290
|
+
Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
|
|
291
|
+
)
|
|
292
|
+
}))
|
|
293
|
+
const getStore = !config?.allowNamespace
|
|
294
|
+
? Effect.succeed(primary)
|
|
295
|
+
: storeId.asEffect().pipe(Effect.flatMap((namespace) => ensureStore(namespace)))
|
|
296
|
+
const s: Store<IdKey, Encoded> = {
|
|
297
|
+
seedNamespace: (namespace) => ensureStore(namespace).pipe(Effect.asVoid),
|
|
298
|
+
all: Effect.flatMap(getStore, (_) => _.all),
|
|
299
|
+
queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args)),
|
|
300
|
+
find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
|
|
301
|
+
filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
|
|
302
|
+
set: (...args) => Effect.flatMap(getStore, (_) => _.set(...args)),
|
|
303
|
+
batchSet: (...args) => Effect.flatMap(getStore, (_) => _.batchSet(...args)),
|
|
304
|
+
bulkSet: (...args) => Effect.flatMap(getStore, (_) => _.bulkSet(...args)),
|
|
305
|
+
batchRemove: (...args) => Effect.flatMap(getStore, (_) => _.batchRemove(...args))
|
|
306
|
+
}
|
|
307
|
+
return s
|
|
308
|
+
})
|
|
303
309
|
})
|
|
304
310
|
|
|
305
311
|
export const MemoryStoreLive = StoreMaker.toLayer(Effect.sync(() => makeMemoryStore()))
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { Effect, type NonEmptyReadonlyArray, Option, Struct } from "effect-app"
|
|
4
|
+
import { toNonEmptyArray } from "effect-app/Array"
|
|
5
|
+
import { SqlClient } from "effect/unstable/sql"
|
|
6
|
+
import { OptimisticConcurrencyException } from "../../errors.js"
|
|
7
|
+
import { InfraLogger } from "../../logger.js"
|
|
8
|
+
import type { FieldValues } from "../../Model/filter/types.js"
|
|
9
|
+
import { storeId } from "../Memory.js"
|
|
10
|
+
import { type FilterArgs, type PersistenceModelType, type StorageConfig, type Store, type StoreConfig, StoreMaker } from "../service.js"
|
|
11
|
+
import { makeETag } from "../utils.js"
|
|
12
|
+
import { buildWhereSQLQuery, logQuery, pgDialect } from "./query.js"
|
|
13
|
+
|
|
14
|
+
const parseRow = <Encoded extends FieldValues>(
|
|
15
|
+
row: { id: string; _etag: string | null; data: unknown },
|
|
16
|
+
idKey: PropertyKey,
|
|
17
|
+
defaultValues: Partial<Encoded>
|
|
18
|
+
): PersistenceModelType<Encoded> => {
|
|
19
|
+
const data = (typeof row.data === "string" ? JSON.parse(row.data) : row.data) as object
|
|
20
|
+
return { ...defaultValues, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const parseSelectRow = (
|
|
24
|
+
row: Record<string, unknown>,
|
|
25
|
+
idKey: PropertyKey,
|
|
26
|
+
defaultValues: Record<string, unknown>
|
|
27
|
+
): any => {
|
|
28
|
+
const result: Record<string, unknown> = { ...defaultValues }
|
|
29
|
+
for (const [key, value] of Object.entries(row)) {
|
|
30
|
+
if (key === "id") {
|
|
31
|
+
result[idKey as string] = value
|
|
32
|
+
result["id"] = value
|
|
33
|
+
} else {
|
|
34
|
+
result[key] = value
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
41
|
+
const sql = yield* SqlClient.SqlClient
|
|
42
|
+
return {
|
|
43
|
+
make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
44
|
+
name: string,
|
|
45
|
+
idKey: IdKey,
|
|
46
|
+
seed?: Effect.Effect<Iterable<Encoded>, E, R>,
|
|
47
|
+
config?: StoreConfig<Encoded>
|
|
48
|
+
) {
|
|
49
|
+
type PM = PersistenceModelType<Encoded>
|
|
50
|
+
const tableName = `${prefix}${name}`
|
|
51
|
+
const defaultValues = config?.defaultValues ?? {}
|
|
52
|
+
|
|
53
|
+
const resolveNamespace = !config?.allowNamespace
|
|
54
|
+
? Effect.succeed("primary")
|
|
55
|
+
: storeId.asEffect().pipe(Effect.map((namespace) => {
|
|
56
|
+
if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
|
|
57
|
+
throw new Error(`Namespace ${namespace} not allowed!`)
|
|
58
|
+
}
|
|
59
|
+
return namespace
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
const ensureTable = sql
|
|
63
|
+
.unsafe(
|
|
64
|
+
`CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL, _namespace TEXT NOT NULL DEFAULT 'primary', _etag TEXT, data JSONB NOT NULL, PRIMARY KEY (id, _namespace))`
|
|
65
|
+
)
|
|
66
|
+
.pipe(
|
|
67
|
+
Effect.andThen(
|
|
68
|
+
sql.unsafe(
|
|
69
|
+
`CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
Effect.orDie,
|
|
73
|
+
Effect.asVoid
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const toRow = (e: PM) => {
|
|
77
|
+
const newE = makeETag(e)
|
|
78
|
+
const id = newE[idKey] as string
|
|
79
|
+
const { _etag, [idKey]: _id, ...rest } = newE as any
|
|
80
|
+
const data = JSON.stringify(rest)
|
|
81
|
+
return { id, _etag: newE._etag!, data, item: newE }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const exec = (query: string, params?: readonly unknown[]) => sql.unsafe(query, params as any).pipe(Effect.orDie)
|
|
85
|
+
|
|
86
|
+
const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
|
|
87
|
+
const row = toRow(e)
|
|
88
|
+
if (e._etag) {
|
|
89
|
+
yield* exec(
|
|
90
|
+
`UPDATE "${tableName}" SET _etag = $1, data = $2 WHERE id = $3 AND _etag = $4 AND _namespace = $5`,
|
|
91
|
+
[row._etag, row.data, row.id, e._etag, ns]
|
|
92
|
+
)
|
|
93
|
+
const existing = yield* exec(
|
|
94
|
+
`SELECT _etag FROM "${tableName}" WHERE id = $1 AND _namespace = $2`,
|
|
95
|
+
[row.id, ns]
|
|
96
|
+
)
|
|
97
|
+
const current = (existing as any[])[0]
|
|
98
|
+
if (!current || current._etag !== row._etag) {
|
|
99
|
+
if (current) {
|
|
100
|
+
return yield* new OptimisticConcurrencyException({
|
|
101
|
+
type: name,
|
|
102
|
+
id: row.id,
|
|
103
|
+
current: current._etag,
|
|
104
|
+
found: e._etag,
|
|
105
|
+
code: 412
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
return yield* new OptimisticConcurrencyException({
|
|
109
|
+
type: name,
|
|
110
|
+
id: row.id,
|
|
111
|
+
current: "",
|
|
112
|
+
found: e._etag,
|
|
113
|
+
code: 404
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
yield* exec(
|
|
118
|
+
`INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES ($1, $2, $3, $4)`,
|
|
119
|
+
[row.id, ns, row._etag, row.data]
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
return row.item
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
|
|
126
|
+
sql
|
|
127
|
+
.withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
|
|
128
|
+
.pipe(
|
|
129
|
+
Effect.orDie,
|
|
130
|
+
Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const ctx = yield* Effect.context<R>()
|
|
134
|
+
const seedCache = new Map<string, Effect.Effect<void>>()
|
|
135
|
+
const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
|
|
136
|
+
yield* ensureTable
|
|
137
|
+
if (!seed) return
|
|
138
|
+
const existing = yield* exec(
|
|
139
|
+
`SELECT id FROM "_migrations" WHERE id = $1 AND version = $2`,
|
|
140
|
+
[`${tableName}::${ns}`, tableName]
|
|
141
|
+
)
|
|
142
|
+
if ((existing as any[]).length > 0) return
|
|
143
|
+
yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
|
|
144
|
+
const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
|
|
145
|
+
const ne = toNonEmptyArray([...items])
|
|
146
|
+
if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
|
|
147
|
+
yield* exec(
|
|
148
|
+
`INSERT INTO "_migrations" (id, version) VALUES ($1, $2)`,
|
|
149
|
+
[`${tableName}::${ns}`, tableName]
|
|
150
|
+
)
|
|
151
|
+
})
|
|
152
|
+
const seedNamespace = (ns: string) => {
|
|
153
|
+
let cached = seedCache.get(ns)
|
|
154
|
+
if (!cached) {
|
|
155
|
+
cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
|
|
156
|
+
seedCache.set(ns, cached)
|
|
157
|
+
}
|
|
158
|
+
return cached
|
|
159
|
+
}
|
|
160
|
+
const s: Store<IdKey, Encoded> = {
|
|
161
|
+
seedNamespace: (ns) => seedNamespace(ns),
|
|
162
|
+
|
|
163
|
+
all: resolveNamespace.pipe(
|
|
164
|
+
Effect.flatMap((ns) =>
|
|
165
|
+
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = $1`, [ns])
|
|
166
|
+
.pipe(
|
|
167
|
+
Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
|
|
168
|
+
Effect.withSpan("PgSQL.all [effect-app/infra/Store]", {
|
|
169
|
+
attributes: {
|
|
170
|
+
"repository.table_name": tableName,
|
|
171
|
+
"repository.model_name": name,
|
|
172
|
+
"repository.namespace": ns
|
|
173
|
+
}
|
|
174
|
+
}, { captureStackTrace: false })
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
),
|
|
178
|
+
|
|
179
|
+
find: (id) =>
|
|
180
|
+
resolveNamespace.pipe(Effect
|
|
181
|
+
.flatMap((ns) =>
|
|
182
|
+
exec(`SELECT id, _etag, data FROM "${tableName}" WHERE id = $1 AND _namespace = $2`, [id, ns])
|
|
183
|
+
.pipe(
|
|
184
|
+
Effect.map((rows) => {
|
|
185
|
+
const row = (rows as any[])[0]
|
|
186
|
+
return row
|
|
187
|
+
? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
|
|
188
|
+
: Option.none()
|
|
189
|
+
}),
|
|
190
|
+
Effect.withSpan("PgSQL.find [effect-app/infra/Store]", {
|
|
191
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id }
|
|
192
|
+
}, { captureStackTrace: false })
|
|
193
|
+
)
|
|
194
|
+
)),
|
|
195
|
+
|
|
196
|
+
filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
|
|
197
|
+
const filter = f
|
|
198
|
+
.filter
|
|
199
|
+
type M = U extends undefined ? Encoded : Pick<Encoded, U>
|
|
200
|
+
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
201
|
+
Effect
|
|
202
|
+
.sync(() => {
|
|
203
|
+
const q = buildWhereSQLQuery(
|
|
204
|
+
pgDialect,
|
|
205
|
+
idKey,
|
|
206
|
+
filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
|
|
207
|
+
tableName,
|
|
208
|
+
defaultValues,
|
|
209
|
+
f.select as
|
|
210
|
+
| NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }>
|
|
211
|
+
| undefined,
|
|
212
|
+
f.order,
|
|
213
|
+
f.skip,
|
|
214
|
+
f.limit
|
|
215
|
+
)
|
|
216
|
+
const nsPlaceholder = pgDialect.placeholder(q.params.length + 1)
|
|
217
|
+
const hasWhere = q.sql.includes("WHERE")
|
|
218
|
+
const nsSql = hasWhere
|
|
219
|
+
? q.sql.replace("WHERE", `WHERE _namespace = ${nsPlaceholder} AND`)
|
|
220
|
+
: q.sql.replace(
|
|
221
|
+
`FROM "${tableName}"`,
|
|
222
|
+
`FROM "${tableName}" WHERE _namespace = ${nsPlaceholder}`
|
|
223
|
+
)
|
|
224
|
+
return { sql: nsSql, params: [...q.params, ns] }
|
|
225
|
+
})
|
|
226
|
+
.pipe(
|
|
227
|
+
Effect.tap((q) => logQuery(q)),
|
|
228
|
+
Effect.flatMap((q) =>
|
|
229
|
+
exec(q.sql, q.params).pipe(
|
|
230
|
+
Effect.map((rows) => {
|
|
231
|
+
if (f.select) {
|
|
232
|
+
return (rows as any[]).map((r) => {
|
|
233
|
+
const selected = parseSelectRow(r, idKey, {})
|
|
234
|
+
return {
|
|
235
|
+
...Struct.pick(
|
|
236
|
+
defaultValues as any,
|
|
237
|
+
f.select!.filter((_) => typeof _ === "string") as never[]
|
|
238
|
+
),
|
|
239
|
+
...selected
|
|
240
|
+
} as M
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
),
|
|
247
|
+
Effect.withSpan("PgSQL.filter [effect-app/infra/Store]", {
|
|
248
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
249
|
+
}, { captureStackTrace: false })
|
|
250
|
+
)
|
|
251
|
+
))
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
set: (e) =>
|
|
255
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
256
|
+
setInternal(e, ns).pipe(
|
|
257
|
+
Effect.withSpan("PgSQL.set [effect-app/infra/Store]", {
|
|
258
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name, id: e[idKey] }
|
|
259
|
+
}, { captureStackTrace: false })
|
|
260
|
+
)
|
|
261
|
+
)),
|
|
262
|
+
|
|
263
|
+
batchSet: (items) =>
|
|
264
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
265
|
+
bulkSetInternal(items, ns).pipe(
|
|
266
|
+
Effect.withSpan("PgSQL.batchSet [effect-app/infra/Store]", {
|
|
267
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
268
|
+
}, { captureStackTrace: false })
|
|
269
|
+
)
|
|
270
|
+
)),
|
|
271
|
+
|
|
272
|
+
bulkSet: (items) =>
|
|
273
|
+
resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
274
|
+
bulkSetInternal(items, ns).pipe(
|
|
275
|
+
Effect.withSpan("PgSQL.bulkSet [effect-app/infra/Store]", {
|
|
276
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
277
|
+
}, { captureStackTrace: false })
|
|
278
|
+
)
|
|
279
|
+
)),
|
|
280
|
+
|
|
281
|
+
batchRemove: (ids) => {
|
|
282
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ")
|
|
283
|
+
const nsPlaceholder = `$${ids.length + 1}`
|
|
284
|
+
return resolveNamespace.pipe(Effect.flatMap((ns) =>
|
|
285
|
+
exec(
|
|
286
|
+
`DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ${nsPlaceholder}`,
|
|
287
|
+
[...ids, ns]
|
|
288
|
+
)
|
|
289
|
+
.pipe(
|
|
290
|
+
Effect.asVoid,
|
|
291
|
+
Effect.withSpan("PgSQL.batchRemove [effect-app/infra/Store]", {
|
|
292
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
293
|
+
}, { captureStackTrace: false })
|
|
294
|
+
)
|
|
295
|
+
))
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
queryRaw: (query) =>
|
|
299
|
+
s.all.pipe(
|
|
300
|
+
Effect.map(query.memory),
|
|
301
|
+
Effect.withSpan("PgSQL.queryRaw [effect-app/infra/Store]", {
|
|
302
|
+
attributes: { "repository.table_name": tableName, "repository.model_name": name }
|
|
303
|
+
}, { captureStackTrace: false })
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Eagerly seed primary namespace on initialization
|
|
308
|
+
yield* seedNamespace("primary")
|
|
309
|
+
|
|
310
|
+
return s
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
export function PgStoreLayer(cfg: StorageConfig) {
|
|
316
|
+
return StoreMaker
|
|
317
|
+
.toLayer(makePgStore(cfg))
|
|
318
|
+
}
|