@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221

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.
Files changed (337) hide show
  1. package/CHANGELOG.md +1648 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +12 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +16 -12
  6. package/dist/Emailer/Sendgrid.d.ts +15 -15
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +20 -16
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/fake.js +2 -2
  11. package/dist/Emailer/service.d.ts +13 -4
  12. package/dist/Emailer/service.d.ts.map +1 -1
  13. package/dist/Emailer/service.js +4 -3
  14. package/dist/Emailer.d.ts +1 -1
  15. package/dist/MainFiberSet.d.ts +12 -9
  16. package/dist/MainFiberSet.d.ts.map +1 -1
  17. package/dist/MainFiberSet.js +7 -3
  18. package/dist/Model/Repository/Registry.d.ts +21 -0
  19. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  20. package/dist/Model/Repository/Registry.js +18 -0
  21. package/dist/Model/Repository/ext.d.ts +35 -16
  22. package/dist/Model/Repository/ext.d.ts.map +1 -1
  23. package/dist/Model/Repository/ext.js +60 -3
  24. package/dist/Model/Repository/internal/internal.d.ts +9 -6
  25. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  26. package/dist/Model/Repository/internal/internal.js +115 -51
  27. package/dist/Model/Repository/legacy.d.ts +4 -2
  28. package/dist/Model/Repository/legacy.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.d.ts +10 -6
  30. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  31. package/dist/Model/Repository/makeRepo.js +5 -2
  32. package/dist/Model/Repository/service.d.ts +32 -24
  33. package/dist/Model/Repository/service.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.d.ts +47 -18
  35. package/dist/Model/Repository/validation.d.ts.map +1 -1
  36. package/dist/Model/Repository/validation.js +6 -6
  37. package/dist/Model/Repository.d.ts +2 -1
  38. package/dist/Model/Repository.d.ts.map +1 -1
  39. package/dist/Model/Repository.js +2 -1
  40. package/dist/Model/dsl.d.ts +6 -5
  41. package/dist/Model/dsl.d.ts.map +1 -1
  42. package/dist/Model/dsl.js +2 -3
  43. package/dist/Model/filter/filterApi.d.ts +5 -5
  44. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  45. package/dist/Model/filter/types/errors.d.ts +1 -1
  46. package/dist/Model/filter/types/fields.d.ts +1 -1
  47. package/dist/Model/filter/types/path/common.d.ts +1 -1
  48. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  49. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  50. package/dist/Model/filter/types/path/eager.js +1 -1
  51. package/dist/Model/filter/types/path/index.d.ts +1 -1
  52. package/dist/Model/filter/types/utils.d.ts +1 -1
  53. package/dist/Model/filter/types/validator.d.ts +1 -1
  54. package/dist/Model/filter/types.d.ts +1 -1
  55. package/dist/Model/query/dsl.d.ts +142 -17
  56. package/dist/Model/query/dsl.d.ts.map +1 -1
  57. package/dist/Model/query/dsl.js +190 -5
  58. package/dist/Model/query/new-kid-interpreter.d.ts +77 -8
  59. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  60. package/dist/Model/query/new-kid-interpreter.js +127 -6
  61. package/dist/Model/query.d.ts +1 -1
  62. package/dist/Model.d.ts +2 -1
  63. package/dist/Model.d.ts.map +1 -1
  64. package/dist/Model.js +2 -1
  65. package/dist/QueueMaker/SQLQueue.d.ts +7 -8
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +135 -117
  68. package/dist/QueueMaker/errors.d.ts +5 -3
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/errors.js +4 -2
  71. package/dist/QueueMaker/memQueue.d.ts +9 -5
  72. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  73. package/dist/QueueMaker/memQueue.js +81 -65
  74. package/dist/QueueMaker/sbqueue.d.ts +8 -4
  75. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  76. package/dist/QueueMaker/sbqueue.js +57 -55
  77. package/dist/QueueMaker/service.d.ts +4 -2
  78. package/dist/QueueMaker/service.d.ts.map +1 -1
  79. package/dist/QueueMaker/service.js +1 -1
  80. package/dist/RequestContext.d.ts +75 -35
  81. package/dist/RequestContext.d.ts.map +1 -1
  82. package/dist/RequestContext.js +14 -14
  83. package/dist/RequestFiberSet.d.ts +10 -7
  84. package/dist/RequestFiberSet.d.ts.map +1 -1
  85. package/dist/RequestFiberSet.js +8 -3
  86. package/dist/Store/ContextMapContainer.d.ts +22 -3
  87. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  88. package/dist/Store/ContextMapContainer.js +17 -3
  89. package/dist/Store/Cosmos/query.d.ts +7 -2
  90. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  91. package/dist/Store/Cosmos/query.js +115 -35
  92. package/dist/Store/Cosmos.d.ts +2 -2
  93. package/dist/Store/Cosmos.d.ts.map +1 -1
  94. package/dist/Store/Cosmos.js +343 -244
  95. package/dist/Store/Disk.d.ts +3 -3
  96. package/dist/Store/Disk.d.ts.map +1 -1
  97. package/dist/Store/Disk.js +76 -36
  98. package/dist/Store/Memory.d.ts +7 -4
  99. package/dist/Store/Memory.d.ts.map +1 -1
  100. package/dist/Store/Memory.js +251 -58
  101. package/dist/Store/SQL/Pg.d.ts +4 -0
  102. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  103. package/dist/Store/SQL/Pg.js +233 -0
  104. package/dist/Store/SQL/query.d.ts +43 -0
  105. package/dist/Store/SQL/query.d.ts.map +1 -0
  106. package/dist/Store/SQL/query.js +478 -0
  107. package/dist/Store/SQL.d.ts +21 -0
  108. package/dist/Store/SQL.d.ts.map +1 -0
  109. package/dist/Store/SQL.js +450 -0
  110. package/dist/Store/codeFilter.d.ts +2 -2
  111. package/dist/Store/codeFilter.d.ts.map +1 -1
  112. package/dist/Store/codeFilter.js +6 -3
  113. package/dist/Store/index.d.ts +6 -3
  114. package/dist/Store/index.d.ts.map +1 -1
  115. package/dist/Store/index.js +18 -4
  116. package/dist/Store/service.d.ts +26 -8
  117. package/dist/Store/service.d.ts.map +1 -1
  118. package/dist/Store/service.js +25 -6
  119. package/dist/Store/utils.d.ts +3 -2
  120. package/dist/Store/utils.d.ts.map +1 -1
  121. package/dist/Store/utils.js +5 -5
  122. package/dist/Store.d.ts +1 -1
  123. package/dist/adapters/SQL/Model.d.ts +32 -43
  124. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  125. package/dist/adapters/SQL/Model.js +30 -39
  126. package/dist/adapters/SQL.d.ts +1 -1
  127. package/dist/adapters/ServiceBus.d.ts +14 -11
  128. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  129. package/dist/adapters/ServiceBus.js +30 -21
  130. package/dist/adapters/cosmos-client.d.ts +5 -3
  131. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  132. package/dist/adapters/cosmos-client.js +5 -3
  133. package/dist/adapters/index.d.ts +8 -2
  134. package/dist/adapters/index.d.ts.map +1 -1
  135. package/dist/adapters/index.js +8 -2
  136. package/dist/adapters/logger.d.ts +2 -2
  137. package/dist/adapters/logger.d.ts.map +1 -1
  138. package/dist/adapters/memQueue.d.ts +5 -3
  139. package/dist/adapters/memQueue.d.ts.map +1 -1
  140. package/dist/adapters/memQueue.js +6 -5
  141. package/dist/adapters/mongo-client.d.ts +4 -3
  142. package/dist/adapters/mongo-client.d.ts.map +1 -1
  143. package/dist/adapters/mongo-client.js +5 -3
  144. package/dist/adapters/redis-client.d.ts +6 -3
  145. package/dist/adapters/redis-client.d.ts.map +1 -1
  146. package/dist/adapters/redis-client.js +7 -3
  147. package/dist/api/ContextProvider.d.ts +12 -8
  148. package/dist/api/ContextProvider.d.ts.map +1 -1
  149. package/dist/api/ContextProvider.js +9 -7
  150. package/dist/api/codec.d.ts +1 -1
  151. package/dist/api/internal/RequestContextMiddleware.d.ts +3 -3
  152. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  153. package/dist/api/internal/RequestContextMiddleware.js +10 -6
  154. package/dist/api/internal/auth.d.ts +45 -7
  155. package/dist/api/internal/auth.d.ts.map +1 -1
  156. package/dist/api/internal/auth.js +162 -29
  157. package/dist/api/internal/events.d.ts +6 -4
  158. package/dist/api/internal/events.d.ts.map +1 -1
  159. package/dist/api/internal/events.js +16 -9
  160. package/dist/api/internal/health.d.ts +1 -1
  161. package/dist/api/layerUtils.d.ts +10 -6
  162. package/dist/api/layerUtils.d.ts.map +1 -1
  163. package/dist/api/layerUtils.js +7 -6
  164. package/dist/api/middlewares.d.ts +1 -1
  165. package/dist/api/reportError.d.ts +2 -2
  166. package/dist/api/reportError.d.ts.map +1 -1
  167. package/dist/api/reportError.js +3 -2
  168. package/dist/api/routing/middleware/RouterMiddleware.d.ts +5 -4
  169. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware/middleware.d.ts +42 -3
  171. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  172. package/dist/api/routing/middleware/middleware.js +53 -17
  173. package/dist/api/routing/middleware.d.ts +1 -2
  174. package/dist/api/routing/middleware.d.ts.map +1 -1
  175. package/dist/api/routing/middleware.js +1 -2
  176. package/dist/api/routing/schema/jwt.d.ts +1 -1
  177. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  178. package/dist/api/routing/schema/jwt.js +3 -2
  179. package/dist/api/routing/tsort.d.ts +1 -1
  180. package/dist/api/routing/tsort.d.ts.map +1 -1
  181. package/dist/api/routing/utils.d.ts +4 -4
  182. package/dist/api/routing/utils.d.ts.map +1 -1
  183. package/dist/api/routing/utils.js +3 -2
  184. package/dist/api/routing.d.ts +84 -37
  185. package/dist/api/routing.d.ts.map +1 -1
  186. package/dist/api/routing.js +115 -45
  187. package/dist/api/setupRequest.d.ts +10 -6
  188. package/dist/api/setupRequest.d.ts.map +1 -1
  189. package/dist/api/setupRequest.js +15 -7
  190. package/dist/api/util.d.ts +1 -1
  191. package/dist/arbs.d.ts +2 -2
  192. package/dist/arbs.d.ts.map +1 -1
  193. package/dist/arbs.js +5 -3
  194. package/dist/errorReporter.d.ts +7 -5
  195. package/dist/errorReporter.d.ts.map +1 -1
  196. package/dist/errorReporter.js +22 -26
  197. package/dist/errors.d.ts +1 -1
  198. package/dist/fileUtil.d.ts +2 -2
  199. package/dist/fileUtil.d.ts.map +1 -1
  200. package/dist/fileUtil.js +2 -2
  201. package/dist/index.d.ts +1 -1
  202. package/dist/logger/jsonLogger.d.ts +2 -2
  203. package/dist/logger/jsonLogger.d.ts.map +1 -1
  204. package/dist/logger/jsonLogger.js +4 -2
  205. package/dist/logger/logFmtLogger.d.ts +2 -2
  206. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  207. package/dist/logger/logFmtLogger.js +2 -2
  208. package/dist/logger/shared.d.ts +2 -2
  209. package/dist/logger/shared.d.ts.map +1 -1
  210. package/dist/logger/shared.js +3 -3
  211. package/dist/logger.d.ts +1 -1
  212. package/dist/logger.d.ts.map +1 -1
  213. package/dist/otel.d.ts +75 -0
  214. package/dist/otel.d.ts.map +1 -0
  215. package/dist/otel.js +65 -0
  216. package/dist/rateLimit.d.ts +12 -4
  217. package/dist/rateLimit.d.ts.map +1 -1
  218. package/dist/rateLimit.js +7 -12
  219. package/dist/test.d.ts +3 -3
  220. package/dist/test.d.ts.map +1 -1
  221. package/dist/test.js +2 -2
  222. package/dist/vitest.d.ts +1 -1
  223. package/examples/query.ts +46 -38
  224. package/package.json +46 -37
  225. package/src/CUPS.ts +15 -11
  226. package/src/Emailer/Sendgrid.ts +21 -15
  227. package/src/Emailer/fake.ts +1 -1
  228. package/src/Emailer/service.ts +13 -3
  229. package/src/MainFiberSet.ts +9 -6
  230. package/src/Model/Repository/Registry.ts +34 -0
  231. package/src/Model/Repository/ext.ts +103 -11
  232. package/src/Model/Repository/internal/internal.ts +231 -149
  233. package/src/Model/Repository/legacy.ts +3 -1
  234. package/src/Model/Repository/makeRepo.ts +15 -10
  235. package/src/Model/Repository/service.ts +35 -23
  236. package/src/Model/Repository/validation.ts +5 -5
  237. package/src/Model/Repository.ts +1 -0
  238. package/src/Model/dsl.ts +5 -4
  239. package/src/Model/filter/types/path/eager.ts +1 -2
  240. package/src/Model/query/dsl.ts +353 -19
  241. package/src/Model/query/new-kid-interpreter.ts +211 -6
  242. package/src/Model.ts +1 -0
  243. package/src/QueueMaker/SQLQueue.ts +150 -153
  244. package/src/QueueMaker/errors.ts +3 -1
  245. package/src/QueueMaker/memQueue.ts +111 -105
  246. package/src/QueueMaker/sbqueue.ts +76 -88
  247. package/src/QueueMaker/service.ts +3 -1
  248. package/src/RequestContext.ts +15 -16
  249. package/src/RequestFiberSet.ts +8 -2
  250. package/src/Store/ContextMapContainer.ts +45 -2
  251. package/src/Store/Cosmos/query.ts +143 -44
  252. package/src/Store/Cosmos.ts +491 -350
  253. package/src/Store/Disk.ts +106 -66
  254. package/src/Store/Memory.ts +285 -87
  255. package/src/Store/SQL/Pg.ts +364 -0
  256. package/src/Store/SQL/query.ts +540 -0
  257. package/src/Store/SQL.ts +736 -0
  258. package/src/Store/codeFilter.ts +5 -2
  259. package/src/Store/index.ts +20 -3
  260. package/src/Store/service.ts +45 -10
  261. package/src/Store/utils.ts +25 -23
  262. package/src/adapters/SQL/Model.ts +42 -41
  263. package/src/adapters/ServiceBus.ts +131 -121
  264. package/src/adapters/cosmos-client.ts +4 -2
  265. package/src/adapters/index.ts +7 -0
  266. package/src/adapters/memQueue.ts +5 -4
  267. package/src/adapters/mongo-client.ts +4 -2
  268. package/src/adapters/redis-client.ts +6 -2
  269. package/src/api/ContextProvider.ts +17 -13
  270. package/src/api/internal/RequestContextMiddleware.ts +16 -5
  271. package/src/api/internal/auth.ts +248 -44
  272. package/src/api/internal/events.ts +19 -10
  273. package/src/api/layerUtils.ts +12 -8
  274. package/src/api/reportError.ts +2 -1
  275. package/src/api/routing/middleware/RouterMiddleware.ts +5 -4
  276. package/src/api/routing/middleware/middleware.ts +60 -15
  277. package/src/api/routing/middleware.ts +0 -2
  278. package/src/api/routing/schema/jwt.ts +2 -1
  279. package/src/api/routing/utils.ts +2 -1
  280. package/src/api/routing.ts +304 -131
  281. package/src/api/setupRequest.ts +31 -8
  282. package/src/arbs.ts +5 -3
  283. package/src/errorReporter.ts +65 -75
  284. package/src/fileUtil.ts +1 -1
  285. package/src/logger/jsonLogger.ts +3 -1
  286. package/src/logger/logFmtLogger.ts +1 -1
  287. package/src/logger/shared.ts +3 -2
  288. package/src/otel.ts +152 -0
  289. package/src/rateLimit.ts +34 -23
  290. package/src/test.ts +2 -2
  291. package/test/auth.test.ts +101 -0
  292. package/test/contextProvider.test.ts +14 -11
  293. package/test/controller.test.ts +25 -29
  294. package/test/dist/auth.test.d.ts.map +1 -0
  295. package/test/dist/contextProvider.test.d.ts.map +1 -1
  296. package/test/dist/controller.test.d.ts.map +1 -1
  297. package/test/dist/date-query.test.d.ts.map +1 -0
  298. package/test/dist/fixtures.d.ts +30 -12
  299. package/test/dist/fixtures.d.ts.map +1 -1
  300. package/test/dist/fixtures.js +17 -10
  301. package/test/dist/query.test.d.ts.map +1 -1
  302. package/test/dist/rawQuery.test.d.ts.map +1 -1
  303. package/test/dist/repository-ext.test.d.ts.map +1 -0
  304. package/test/dist/requires.test.d.ts.map +1 -1
  305. package/test/dist/router-generator.test.d.ts.map +1 -0
  306. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  307. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  308. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  309. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  310. package/test/dist/sql-store.test.d.ts.map +1 -0
  311. package/test/fixtures.ts +16 -9
  312. package/test/layerUtils.test.ts +1 -1
  313. package/test/query.test.ts +819 -38
  314. package/test/rawQuery.test.ts +312 -20
  315. package/test/repository-ext.test.ts +62 -0
  316. package/test/requires.test.ts +10 -5
  317. package/test/router-generator.test.ts +187 -0
  318. package/test/routing-interruptibility.test.ts +66 -0
  319. package/test/rpc-e2e-invalidation.test.ts +256 -0
  320. package/test/rpc-multi-middleware.test.ts +84 -9
  321. package/test/rpc-stream-fullstack.test.ts +304 -0
  322. package/test/sql-store.test.ts +1592 -0
  323. package/test/validateSample.test.ts +17 -12
  324. package/tsconfig.examples.json +1 -1
  325. package/tsconfig.json +0 -1
  326. package/tsconfig.json.bak +2 -2
  327. package/tsconfig.src.json +35 -35
  328. package/tsconfig.test.json +2 -2
  329. package/dist/Operations.d.ts +0 -55
  330. package/dist/Operations.d.ts.map +0 -1
  331. package/dist/Operations.js +0 -102
  332. package/dist/OperationsRepo.d.ts +0 -41
  333. package/dist/OperationsRepo.d.ts.map +0 -1
  334. package/dist/OperationsRepo.js +0 -14
  335. package/eslint.config.mjs +0 -24
  336. package/src/Operations.ts +0 -235
  337. package/src/OperationsRepo.ts +0 -16
@@ -0,0 +1,736 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import type { NonEmptyReadonlyArray } from "effect-app/Array"
4
+ import { toNonEmptyArray } from "effect-app/Array"
5
+ import * as Effect from "effect-app/Effect"
6
+ import * as Option from "effect-app/Option"
7
+ import * as Context from "effect/Context"
8
+ import * as Layer from "effect/Layer"
9
+ import * as LayerMap from "effect/LayerMap"
10
+ import * as Struct from "effect/Struct"
11
+ import { SqlClient } from "effect/unstable/sql"
12
+ import { OptimisticConcurrencyException } from "../errors.js"
13
+ import { InfraLogger } from "../logger.js"
14
+ import type { FieldValues } from "../Model/filter/types.js"
15
+ import type { ComputedProjectionIrExpression } from "../Model/query.js"
16
+ import { annotateDb, type DbSystem } from "../otel.js"
17
+ import { storeId } from "./Memory.js"
18
+ import { type FilterArgs, type PersistenceModelType, type StorageConfig, type Store, type StoreConfig, StoreMaker } from "./service.js"
19
+ import { buildWhereSQLQuery, logQuery, type SQLDialect, sqliteDialect } from "./SQL/query.js"
20
+ import { makeETag } from "./utils.js"
21
+
22
+ export type WithNsTransactionFn = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
23
+
24
+ export class WithNsTransaction
25
+ extends Context.Service<WithNsTransaction, WithNsTransactionFn>()("effect-app/WithNsTransaction")
26
+ {}
27
+
28
+ /** @internal */
29
+ export const parseRow = <Encoded extends FieldValues>(
30
+ row: { id: string; _etag: string | null; data: string },
31
+ idKey: PropertyKey,
32
+ defaultValues: Partial<Encoded>
33
+ ): PersistenceModelType<Encoded> => {
34
+ const data = (typeof row.data === "string" ? JSON.parse(row.data) : row.data) as object
35
+ return { ...defaultValues, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
36
+ }
37
+
38
+ const parseSelectRow = (
39
+ row: Record<string, unknown>,
40
+ idKey: PropertyKey
41
+ ): any => {
42
+ const result: Record<string, unknown> = {}
43
+ for (const [key, value] of Object.entries(row)) {
44
+ if (key === "id") {
45
+ result[idKey as string] = value
46
+ result["id"] = value
47
+ } else if (typeof value === "string") {
48
+ try {
49
+ result[key] = JSON.parse(value)
50
+ } catch {
51
+ result[key] = value
52
+ }
53
+ } else {
54
+ result[key] = value
55
+ }
56
+ }
57
+ return result
58
+ }
59
+
60
+ function makeSQLStoreInt(system: DbSystem, dialect: SQLDialect, jsonColumnType: string) {
61
+ return Effect.fnUntraced(function*({ prefix }: StorageConfig) {
62
+ const sql = yield* SqlClient.SqlClient
63
+ return {
64
+ make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
65
+ name: string,
66
+ idKey: IdKey,
67
+ seed?: Effect.Effect<Iterable<Encoded>, E, R>,
68
+ config?: StoreConfig<Encoded>
69
+ ) {
70
+ type PM = PersistenceModelType<Encoded>
71
+ const tableName = `${prefix}${name}`
72
+ const defaultValues = config?.defaultValues ?? {}
73
+
74
+ const resolveNamespace = !config?.allowNamespace
75
+ ? Effect.succeed("primary")
76
+ : storeId.asEffect().pipe(Effect.map((namespace) => {
77
+ if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
78
+ throw new Error(`Namespace ${namespace} not allowed!`)
79
+ }
80
+ return namespace
81
+ }))
82
+
83
+ const ensureTable = sql
84
+ .unsafe(
85
+ `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))`
86
+ )
87
+ .pipe(
88
+ Effect.andThen(
89
+ sql.unsafe(
90
+ `CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
91
+ )
92
+ ),
93
+ Effect.orDie,
94
+ Effect.asVoid
95
+ )
96
+
97
+ const toRow = (e: PM) => {
98
+ const newE = makeETag(e)
99
+ const id = newE[idKey] as string
100
+ const { _etag, [idKey]: _id, ...rest } = newE as any
101
+ const data = JSON.stringify(rest)
102
+ return { id, _etag: newE._etag!, data, item: newE }
103
+ }
104
+
105
+ const exec = (query: string, params?: readonly unknown[]) => sql.unsafe(query, params as any).pipe(Effect.orDie)
106
+
107
+ const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
108
+ const row = toRow(e)
109
+ if (e._etag) {
110
+ yield* exec(
111
+ `UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ? AND _namespace = ?`,
112
+ [row._etag, row.data, row.id, e._etag, ns]
113
+ )
114
+ const existing = yield* exec(
115
+ `SELECT _etag FROM "${tableName}" WHERE id = ? AND _namespace = ?`,
116
+ [row.id, ns]
117
+ )
118
+ const current = (existing as any[])[0]
119
+ if (!current || current._etag !== row._etag) {
120
+ if (current) {
121
+ return yield* new OptimisticConcurrencyException({
122
+ type: name,
123
+ id: row.id,
124
+ current: current._etag,
125
+ found: e._etag,
126
+ code: 412
127
+ })
128
+ }
129
+ return yield* new OptimisticConcurrencyException({
130
+ type: name,
131
+ id: row.id,
132
+ current: "",
133
+ found: e._etag,
134
+ code: 404
135
+ })
136
+ }
137
+ } else {
138
+ yield* exec(
139
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES (?, ?, ?, ?)`,
140
+ [row.id, ns, row._etag, row.data]
141
+ )
142
+ }
143
+ return row.item
144
+ })
145
+
146
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
147
+ sql
148
+ .withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
149
+ .pipe(
150
+ Effect.orDie,
151
+ Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
152
+ )
153
+
154
+ const ctx = yield* Effect.context<R>()
155
+ const seedCache = new Map<string, Effect.Effect<void>>()
156
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
157
+ yield* ensureTable
158
+ if (!seed) return
159
+ const existing = yield* exec(
160
+ `SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
161
+ [`${tableName}::${ns}`, tableName]
162
+ )
163
+ if ((existing as any[]).length > 0) return
164
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
165
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
166
+ const ne = toNonEmptyArray([...items])
167
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
168
+ yield* exec(
169
+ `INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
170
+ [`${tableName}::${ns}`, tableName]
171
+ )
172
+ })
173
+ const seedNamespace = (ns: string) => {
174
+ let cached = seedCache.get(ns)
175
+ if (!cached) {
176
+ cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
177
+ seedCache.set(ns, cached)
178
+ }
179
+ return cached
180
+ }
181
+ const s: Store<IdKey, Encoded> = {
182
+ seedNamespace: (ns) => seedNamespace(ns),
183
+
184
+ all: resolveNamespace.pipe(
185
+ Effect.flatMap((ns) => {
186
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = ?`
187
+ return exec(sqlText, [ns])
188
+ .pipe(
189
+ Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
190
+ annotateDb({
191
+ operation: "all",
192
+ system,
193
+ collection: tableName,
194
+ namespace: ns,
195
+ entity: name,
196
+ query: sqlText
197
+ })
198
+ )
199
+ })
200
+ ),
201
+
202
+ find: (id) =>
203
+ resolveNamespace.pipe(
204
+ Effect.flatMap((ns) => {
205
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE id = ? AND _namespace = ?`
206
+ return exec(sqlText, [id, ns])
207
+ .pipe(
208
+ Effect.map((rows) => {
209
+ const row = (rows as any[])[0]
210
+ return row
211
+ ? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
212
+ : Option.none()
213
+ }),
214
+ annotateDb({
215
+ operation: "find",
216
+ system,
217
+ collection: tableName,
218
+ namespace: ns,
219
+ entity: name,
220
+ query: sqlText,
221
+ extra: { "app.entity.id": id }
222
+ })
223
+ )
224
+ })
225
+ ),
226
+
227
+ filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
228
+ const filter = f
229
+ .filter
230
+ type M = U extends undefined ? Encoded
231
+ : Pick<Encoded, U>
232
+ return resolveNamespace
233
+ .pipe(Effect
234
+ .flatMap((ns) =>
235
+ Effect
236
+ .sync(() => {
237
+ return buildWhereSQLQuery(
238
+ dialect,
239
+ idKey,
240
+ filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
241
+ tableName,
242
+ defaultValues,
243
+ f
244
+ .select as
245
+ | NonEmptyReadonlyArray<
246
+ string | {
247
+ key: string
248
+ subKeys: readonly string[]
249
+ } | {
250
+ key: string
251
+ computed: ComputedProjectionIrExpression
252
+ }
253
+ >
254
+ | undefined,
255
+ f
256
+ .order,
257
+ f
258
+ .skip,
259
+ f
260
+ .limit,
261
+ ns
262
+ )
263
+ })
264
+ .pipe(
265
+ Effect
266
+ .tap((q) => logQuery(q)),
267
+ Effect.tap((q) => Effect.annotateCurrentSpan({ "db.query.text": q.sql })),
268
+ Effect.flatMap((q) =>
269
+ exec(q.sql, q.params).pipe(
270
+ Effect.map((rows) => {
271
+ if (f.select) {
272
+ return (rows as any[]).map((r) => {
273
+ const selected = parseSelectRow(r, idKey)
274
+ return {
275
+ ...Struct.pick(
276
+ defaultValues as any,
277
+ f.select!.filter((_) => typeof _ === "string") as never[]
278
+ ),
279
+ ...selected
280
+ } as M
281
+ })
282
+ }
283
+ return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
284
+ })
285
+ )
286
+ ),
287
+ annotateDb({
288
+ operation: "filter",
289
+ system,
290
+ collection: tableName,
291
+ namespace: ns,
292
+ entity: name
293
+ })
294
+ )
295
+ ))
296
+ },
297
+
298
+ set: (e) =>
299
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
300
+ setInternal(e, ns).pipe(
301
+ annotateDb({
302
+ operation: "set",
303
+ system,
304
+ collection: tableName,
305
+ namespace: ns,
306
+ entity: name,
307
+ extra: { "app.entity.id": e[idKey] }
308
+ })
309
+ )
310
+ )),
311
+
312
+ batchSet: (items) =>
313
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
314
+ bulkSetInternal(items, ns).pipe(
315
+ annotateDb({
316
+ operation: "batchSet",
317
+ system,
318
+ collection: tableName,
319
+ namespace: ns,
320
+ entity: name
321
+ })
322
+ )
323
+ )),
324
+
325
+ bulkSet: (items) =>
326
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
327
+ bulkSetInternal(items, ns).pipe(
328
+ annotateDb({
329
+ operation: "bulkSet",
330
+ system,
331
+ collection: tableName,
332
+ namespace: ns,
333
+ entity: name
334
+ })
335
+ )
336
+ )),
337
+
338
+ batchRemove: (ids) => {
339
+ const placeholders = ids.map(() => "?").join(", ")
340
+ return resolveNamespace.pipe(Effect.flatMap((ns) => {
341
+ const sqlText = `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ?`
342
+ return exec(sqlText, [...ids, ns])
343
+ .pipe(
344
+ Effect.asVoid,
345
+ annotateDb({
346
+ operation: "batchRemove",
347
+ system,
348
+ collection: tableName,
349
+ namespace: ns,
350
+ entity: name,
351
+ query: sqlText
352
+ })
353
+ )
354
+ }))
355
+ },
356
+
357
+ queryRaw: (query) =>
358
+ s.all.pipe(
359
+ Effect.map(query.memory),
360
+ annotateDb({
361
+ operation: "queryRaw",
362
+ system,
363
+ collection: tableName,
364
+ entity: name
365
+ })
366
+ )
367
+ }
368
+
369
+ // Eagerly seed primary namespace on initialization
370
+ yield* seedNamespace("primary")
371
+
372
+ return s
373
+ })
374
+ }
375
+ })
376
+ }
377
+
378
+ type WithNsSqlFn = <A, E2, R2>(
379
+ ns: string,
380
+ f: (sql: SqlClient.SqlClient) => Effect.Effect<A, E2, R2>
381
+ ) => Effect.Effect<A, E2, R2>
382
+
383
+ function makeSQLiteStorePerNs(
384
+ withNsSql: WithNsSqlFn,
385
+ { prefix }: StorageConfig
386
+ ) {
387
+ return {
388
+ make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
389
+ name: string,
390
+ idKey: IdKey,
391
+ seed?: Effect.Effect<Iterable<Encoded>, E, R>,
392
+ config?: StoreConfig<Encoded>
393
+ ) {
394
+ type PM = PersistenceModelType<Encoded>
395
+ const tableName = `${prefix}${name}`
396
+ const defaultValues = config?.defaultValues ?? {}
397
+
398
+ const resolveNamespace = !config?.allowNamespace
399
+ ? Effect.succeed("primary")
400
+ : storeId.asEffect().pipe(Effect.map((namespace) => {
401
+ if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
402
+ throw new Error(`Namespace ${namespace} not allowed!`)
403
+ }
404
+ return namespace
405
+ }))
406
+
407
+ const toRow = (e: PM) => {
408
+ const newE = makeETag(e)
409
+ const id = newE[idKey] as string
410
+ const { _etag, [idKey]: _id, ...rest } = newE as any
411
+ const data = JSON.stringify(rest)
412
+ return { id, _etag: newE._etag!, data, item: newE }
413
+ }
414
+
415
+ const exec = (ns: string, query: string, params?: readonly unknown[]) =>
416
+ withNsSql(ns, (sql) => sql.unsafe(query, params as any).pipe(Effect.orDie))
417
+
418
+ const ensureTable = (ns: string) =>
419
+ withNsSql(ns, (sql) =>
420
+ sql
421
+ .unsafe(
422
+ `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT NOT NULL PRIMARY KEY, _etag TEXT, data JSON NOT NULL)`
423
+ )
424
+ .pipe(
425
+ Effect.andThen(
426
+ sql.unsafe(
427
+ `CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
428
+ )
429
+ ),
430
+ Effect.orDie,
431
+ Effect.asVoid
432
+ ))
433
+
434
+ const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
435
+ const row = toRow(e)
436
+ if (e._etag) {
437
+ yield* exec(
438
+ ns,
439
+ `UPDATE "${tableName}" SET _etag = ?, data = ? WHERE id = ? AND _etag = ?`,
440
+ [row._etag, row.data, row.id, e._etag]
441
+ )
442
+ const existing = yield* exec(
443
+ ns,
444
+ `SELECT _etag FROM "${tableName}" WHERE id = ?`,
445
+ [row.id]
446
+ )
447
+ const current = (existing as any[])[0]
448
+ if (!current || current._etag !== row._etag) {
449
+ if (current) {
450
+ return yield* new OptimisticConcurrencyException({
451
+ type: name,
452
+ id: row.id,
453
+ current: current._etag,
454
+ found: e._etag,
455
+ code: 412
456
+ })
457
+ }
458
+ return yield* new OptimisticConcurrencyException({
459
+ type: name,
460
+ id: row.id,
461
+ current: "",
462
+ found: e._etag,
463
+ code: 404
464
+ })
465
+ }
466
+ } else {
467
+ yield* exec(
468
+ ns,
469
+ `INSERT INTO "${tableName}" (id, _etag, data) VALUES (?, ?, ?)`,
470
+ [row.id, row._etag, row.data]
471
+ )
472
+ }
473
+ return row.item
474
+ })
475
+
476
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
477
+ withNsSql(ns, (sql) =>
478
+ sql
479
+ .withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
480
+ .pipe(
481
+ Effect.orDie,
482
+ Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
483
+ ))
484
+
485
+ const ctx = yield* Effect.context<R>()
486
+ const seedCache = new Map<string, Effect.Effect<void>>()
487
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
488
+ yield* ensureTable(ns)
489
+ if (!seed) return
490
+ const existing = yield* exec(
491
+ ns,
492
+ `SELECT id FROM "_migrations" WHERE id = ? AND version = ?`,
493
+ [tableName, tableName]
494
+ )
495
+ if ((existing as any[]).length > 0) return
496
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
497
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
498
+ const ne = toNonEmptyArray([...items])
499
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
500
+ yield* exec(
501
+ ns,
502
+ `INSERT INTO "_migrations" (id, version) VALUES (?, ?)`,
503
+ [tableName, tableName]
504
+ )
505
+ })
506
+ const seedNamespace = (ns: string) => {
507
+ let cached = seedCache.get(ns)
508
+ if (!cached) {
509
+ cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
510
+ seedCache.set(ns, cached)
511
+ }
512
+ return cached
513
+ }
514
+
515
+ const s: Store<IdKey, Encoded> = {
516
+ seedNamespace: (ns) => seedNamespace(ns),
517
+
518
+ all: resolveNamespace.pipe(Effect.flatMap((ns) => {
519
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}"`
520
+ return exec(ns, sqlText)
521
+ .pipe(
522
+ Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
523
+ annotateDb({
524
+ operation: "all",
525
+ system: "sqlite",
526
+ collection: tableName,
527
+ namespace: ns,
528
+ entity: name,
529
+ query: sqlText
530
+ })
531
+ )
532
+ })),
533
+
534
+ find: (id) =>
535
+ resolveNamespace.pipe(
536
+ Effect.flatMap((ns) => {
537
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE id = ?`
538
+ return exec(ns, sqlText, [id])
539
+ .pipe(
540
+ Effect.map((rows) => {
541
+ const row = (rows as any[])[0]
542
+ return row
543
+ ? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
544
+ : Option.none()
545
+ }),
546
+ annotateDb({
547
+ operation: "find",
548
+ system: "sqlite",
549
+ collection: tableName,
550
+ namespace: ns,
551
+ entity: name,
552
+ query: sqlText,
553
+ extra: { "app.entity.id": id }
554
+ })
555
+ )
556
+ })
557
+ ),
558
+
559
+ filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
560
+ const filter = f
561
+ .filter
562
+ type M = U extends undefined ? Encoded
563
+ : Pick<Encoded, U>
564
+ return resolveNamespace
565
+ .pipe(Effect
566
+ .flatMap((ns) =>
567
+ Effect
568
+ .sync(() =>
569
+ buildWhereSQLQuery(
570
+ sqliteDialect,
571
+ idKey,
572
+ filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
573
+ tableName,
574
+ defaultValues,
575
+ f
576
+ .select as
577
+ | NonEmptyReadonlyArray<
578
+ string | {
579
+ key: string
580
+ subKeys: readonly string[]
581
+ } | {
582
+ key: string
583
+ computed: ComputedProjectionIrExpression
584
+ }
585
+ >
586
+ | undefined,
587
+ f
588
+ .order as NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }> | undefined,
589
+ f
590
+ .skip,
591
+ f
592
+ .limit
593
+ )
594
+ )
595
+ .pipe(
596
+ Effect
597
+ .tap((q) => logQuery(q)),
598
+ Effect.tap((q) => Effect.annotateCurrentSpan({ "db.query.text": q.sql })),
599
+ Effect.flatMap((q) =>
600
+ exec(ns, q.sql, q.params).pipe(
601
+ Effect.map((rows) => {
602
+ if (f.select) {
603
+ return (rows as any[]).map((r) => {
604
+ const selected = parseSelectRow(r, idKey)
605
+ return {
606
+ ...Struct.pick(
607
+ defaultValues as any,
608
+ f.select!.filter((_) => typeof _ === "string") as never[]
609
+ ),
610
+ ...selected
611
+ } as M
612
+ })
613
+ }
614
+ return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
615
+ })
616
+ )
617
+ ),
618
+ annotateDb({
619
+ operation: "filter",
620
+ system: "sqlite",
621
+ collection: tableName,
622
+ namespace: ns,
623
+ entity: name
624
+ })
625
+ )
626
+ ))
627
+ },
628
+
629
+ set: (e) =>
630
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
631
+ setInternal(e, ns).pipe(
632
+ annotateDb({
633
+ operation: "set",
634
+ system: "sqlite",
635
+ collection: tableName,
636
+ namespace: ns,
637
+ entity: name,
638
+ extra: { "app.entity.id": e[idKey] }
639
+ })
640
+ )
641
+ )),
642
+
643
+ batchSet: (items) =>
644
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
645
+ bulkSetInternal(items, ns).pipe(
646
+ annotateDb({
647
+ operation: "batchSet",
648
+ system: "sqlite",
649
+ collection: tableName,
650
+ namespace: ns,
651
+ entity: name
652
+ })
653
+ )
654
+ )),
655
+
656
+ bulkSet: (items) =>
657
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
658
+ bulkSetInternal(items, ns).pipe(
659
+ annotateDb({
660
+ operation: "bulkSet",
661
+ system: "sqlite",
662
+ collection: tableName,
663
+ namespace: ns,
664
+ entity: name
665
+ })
666
+ )
667
+ )),
668
+
669
+ batchRemove: (ids) => {
670
+ const placeholders = ids.map(() => "?").join(", ")
671
+ return resolveNamespace.pipe(Effect.flatMap((ns) => {
672
+ const sqlText = `DELETE FROM "${tableName}" WHERE id IN (${placeholders})`
673
+ return exec(ns, sqlText, [...ids])
674
+ .pipe(
675
+ Effect.asVoid,
676
+ annotateDb({
677
+ operation: "batchRemove",
678
+ system: "sqlite",
679
+ collection: tableName,
680
+ namespace: ns,
681
+ entity: name,
682
+ query: sqlText
683
+ })
684
+ )
685
+ }))
686
+ },
687
+
688
+ queryRaw: (query) =>
689
+ s.all.pipe(
690
+ Effect.map(query.memory),
691
+ annotateDb({
692
+ operation: "queryRaw",
693
+ system: "sqlite",
694
+ collection: tableName,
695
+ entity: name
696
+ })
697
+ )
698
+ }
699
+
700
+ yield* seedNamespace("primary")
701
+
702
+ return s
703
+ })
704
+ }
705
+ }
706
+
707
+ export function SQLiteStoreLayer(
708
+ cfg: StorageConfig,
709
+ options?: { makeSqlClientLayer?: (namespace: string) => Layer.Layer<SqlClient.SqlClient> }
710
+ ) {
711
+ if (options?.makeSqlClientLayer) {
712
+ return Layer.effectContext(
713
+ Effect.gen(function*() {
714
+ const layerMap = yield* LayerMap.make(
715
+ (namespace: string) => options.makeSqlClientLayer!(namespace),
716
+ { idleTimeToLive: "10 minutes" }
717
+ )
718
+
719
+ const withNsSql: WithNsSqlFn = (ns, f) => SqlClient.SqlClient.use(f).pipe(Effect.provide(layerMap.get(ns)))
720
+
721
+ const storeMaker = makeSQLiteStorePerNs(withNsSql, cfg)
722
+
723
+ const withTransaction: WithNsTransactionFn = (effect) =>
724
+ storeId.asEffect().pipe(
725
+ Effect.flatMap((ns) => withNsSql(ns, (sql) => sql.withTransaction(effect).pipe(Effect.orDie)))
726
+ )
727
+
728
+ return StoreMaker.context(storeMaker).pipe(
729
+ Context.add(WithNsTransaction, withTransaction)
730
+ )
731
+ })
732
+ )
733
+ }
734
+ return StoreMaker
735
+ .toLayer(makeSQLStoreInt("sqlite", sqliteDialect, "JSON")(cfg))
736
+ }