@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,364 @@
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 Struct from "effect/Struct"
8
+ import { SqlClient } from "effect/unstable/sql"
9
+ import { OptimisticConcurrencyException } from "../../errors.js"
10
+ import { InfraLogger } from "../../logger.js"
11
+ import type { FieldValues } from "../../Model/filter/types.js"
12
+ import type { ComputedProjectionIrExpression } from "../../Model/query.js"
13
+ import { annotateDb } from "../../otel.js"
14
+ import { storeId } from "../Memory.js"
15
+ import { type FilterArgs, type PersistenceModelType, type StorageConfig, type Store, type StoreConfig, StoreMaker } from "../service.js"
16
+ import { makeETag } from "../utils.js"
17
+ import { buildWhereSQLQuery, logQuery, pgDialect } from "./query.js"
18
+
19
+ const parseRow = <Encoded extends FieldValues>(
20
+ row: { id: string; _etag: string | null; data: unknown },
21
+ idKey: PropertyKey,
22
+ defaultValues: Partial<Encoded>
23
+ ): PersistenceModelType<Encoded> => {
24
+ const data = (typeof row.data === "string" ? JSON.parse(row.data) : row.data) as object
25
+ return { ...defaultValues, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
26
+ }
27
+
28
+ const parseSelectRow = (
29
+ row: Record<string, unknown>,
30
+ idKey: PropertyKey,
31
+ defaultValues: Record<string, unknown>
32
+ ): any => {
33
+ const result: Record<string, unknown> = { ...defaultValues }
34
+ for (const [key, value] of Object.entries(row)) {
35
+ if (key === "id") {
36
+ result[idKey as string] = value
37
+ result["id"] = value
38
+ } else {
39
+ result[key] = value
40
+ }
41
+ }
42
+ return result
43
+ }
44
+
45
+ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
46
+ const sql = yield* SqlClient.SqlClient
47
+ return {
48
+ make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
49
+ name: string,
50
+ idKey: IdKey,
51
+ seed?: Effect.Effect<Iterable<Encoded>, E, R>,
52
+ config?: StoreConfig<Encoded>
53
+ ) {
54
+ type PM = PersistenceModelType<Encoded>
55
+ const tableName = `${prefix}${name}`
56
+ const defaultValues = config?.defaultValues ?? {}
57
+
58
+ const resolveNamespace = !config?.allowNamespace
59
+ ? Effect.succeed("primary")
60
+ : storeId.asEffect().pipe(Effect.map((namespace) => {
61
+ if (namespace !== "primary" && !config.allowNamespace!(namespace)) {
62
+ throw new Error(`Namespace ${namespace} not allowed!`)
63
+ }
64
+ return namespace
65
+ }))
66
+
67
+ const ensureTable = sql
68
+ .unsafe(
69
+ `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))`
70
+ )
71
+ .pipe(
72
+ Effect.andThen(
73
+ sql.unsafe(
74
+ `CREATE TABLE IF NOT EXISTS "_migrations" (id TEXT NOT NULL, version TEXT NOT NULL, PRIMARY KEY (id, version))`
75
+ )
76
+ ),
77
+ Effect.orDie,
78
+ Effect.asVoid
79
+ )
80
+
81
+ const toRow = (e: PM) => {
82
+ const newE = makeETag(e)
83
+ const id = newE[idKey] as string
84
+ const { _etag, [idKey]: _id, ...rest } = newE as any
85
+ const data = JSON.stringify(rest)
86
+ return { id, _etag: newE._etag!, data, item: newE }
87
+ }
88
+
89
+ const exec = (query: string, params?: readonly unknown[]) => sql.unsafe(query, params as any).pipe(Effect.orDie)
90
+
91
+ const setInternal = Effect.fnUntraced(function*(e: PM, ns: string) {
92
+ const row = toRow(e)
93
+ if (e._etag) {
94
+ yield* exec(
95
+ `UPDATE "${tableName}" SET _etag = $1, data = $2 WHERE id = $3 AND _etag = $4 AND _namespace = $5`,
96
+ [row._etag, row.data, row.id, e._etag, ns]
97
+ )
98
+ const existing = yield* exec(
99
+ `SELECT _etag FROM "${tableName}" WHERE id = $1 AND _namespace = $2`,
100
+ [row.id, ns]
101
+ )
102
+ const current = (existing as any[])[0]
103
+ if (!current || current._etag !== row._etag) {
104
+ if (current) {
105
+ return yield* new OptimisticConcurrencyException({
106
+ type: name,
107
+ id: row.id,
108
+ current: current._etag,
109
+ found: e._etag,
110
+ code: 412
111
+ })
112
+ }
113
+ return yield* new OptimisticConcurrencyException({
114
+ type: name,
115
+ id: row.id,
116
+ current: "",
117
+ found: e._etag,
118
+ code: 404
119
+ })
120
+ }
121
+ } else {
122
+ yield* exec(
123
+ `INSERT INTO "${tableName}" (id, _namespace, _etag, data) VALUES ($1, $2, $3, $4)`,
124
+ [row.id, ns, row._etag, row.data]
125
+ )
126
+ }
127
+ return row.item
128
+ })
129
+
130
+ const bulkSetInternal = (items: NonEmptyReadonlyArray<PM>, ns: string) =>
131
+ sql
132
+ .withTransaction(Effect.forEach(items, (e) => setInternal(e, ns)))
133
+ .pipe(
134
+ Effect.orDie,
135
+ Effect.map((_) => _ as unknown as NonEmptyReadonlyArray<PM>)
136
+ )
137
+
138
+ const ctx = yield* Effect.context<R>()
139
+ const seedCache = new Map<string, Effect.Effect<void>>()
140
+ const makeSeedEffect = Effect.fnUntraced(function*(ns: string) {
141
+ yield* ensureTable
142
+ if (!seed) return
143
+ const existing = yield* exec(
144
+ `SELECT id FROM "_migrations" WHERE id = $1 AND version = $2`,
145
+ [`${tableName}::${ns}`, tableName]
146
+ )
147
+ if ((existing as any[]).length > 0) return
148
+ yield* InfraLogger.logInfo(`Seeding data for ${name} (namespace: ${ns})`)
149
+ const items = yield* seed.pipe(Effect.provide(ctx), Effect.orDie)
150
+ const ne = toNonEmptyArray([...items])
151
+ if (Option.isSome(ne)) yield* bulkSetInternal(ne.value, ns)
152
+ yield* exec(
153
+ `INSERT INTO "_migrations" (id, version) VALUES ($1, $2)`,
154
+ [`${tableName}::${ns}`, tableName]
155
+ )
156
+ })
157
+ const seedNamespace = (ns: string) => {
158
+ let cached = seedCache.get(ns)
159
+ if (!cached) {
160
+ cached = Effect.cached(Effect.uninterruptible(makeSeedEffect(ns))).pipe(Effect.runSync)
161
+ seedCache.set(ns, cached)
162
+ }
163
+ return cached
164
+ }
165
+ const s: Store<IdKey, Encoded> = {
166
+ seedNamespace: (ns) => seedNamespace(ns),
167
+
168
+ all: resolveNamespace.pipe(
169
+ Effect.flatMap((ns) => {
170
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = $1`
171
+ return exec(sqlText, [ns])
172
+ .pipe(
173
+ Effect.map((rows) => (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues))),
174
+ annotateDb({
175
+ operation: "all",
176
+ system: "postgresql",
177
+ collection: tableName,
178
+ namespace: ns,
179
+ entity: name,
180
+ query: sqlText
181
+ })
182
+ )
183
+ })
184
+ ),
185
+
186
+ find: (id) =>
187
+ resolveNamespace.pipe(Effect
188
+ .flatMap((ns) => {
189
+ const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE id = $1 AND _namespace = $2`
190
+ return exec(sqlText, [id, ns])
191
+ .pipe(
192
+ Effect.map((rows) => {
193
+ const row = (rows as any[])[0]
194
+ return row
195
+ ? Option.some(parseRow<Encoded>(row, idKey, defaultValues))
196
+ : Option.none()
197
+ }),
198
+ annotateDb({
199
+ operation: "find",
200
+ system: "postgresql",
201
+ collection: tableName,
202
+ namespace: ns,
203
+ entity: name,
204
+ query: sqlText,
205
+ extra: { "app.entity.id": id }
206
+ })
207
+ )
208
+ })),
209
+
210
+ filter: <U extends keyof Encoded = never>(f: FilterArgs<Encoded, U>) => {
211
+ const filter = f
212
+ .filter
213
+ type M = U extends undefined ? Encoded : Pick<Encoded, U>
214
+ return resolveNamespace.pipe(Effect.flatMap((ns) =>
215
+ Effect
216
+ .sync(() => {
217
+ const q = buildWhereSQLQuery(
218
+ pgDialect,
219
+ idKey,
220
+ filter ? [{ t: "where-scope", result: filter, relation: "some" }] : [],
221
+ tableName,
222
+ defaultValues,
223
+ f.select as
224
+ | NonEmptyReadonlyArray<
225
+ string | {
226
+ key: string
227
+ subKeys: readonly string[]
228
+ } | {
229
+ key: string
230
+ computed: ComputedProjectionIrExpression
231
+ }
232
+ >
233
+ | undefined,
234
+ f.order,
235
+ f.skip,
236
+ f.limit
237
+ )
238
+ const nsPlaceholder = pgDialect.placeholder(q.params.length + 1)
239
+ const hasWhere = q.sql.includes("WHERE")
240
+ const nsSql = hasWhere
241
+ ? q.sql.replace("WHERE", `WHERE _namespace = ${nsPlaceholder} AND`)
242
+ : q.sql.replace(
243
+ `FROM "${tableName}"`,
244
+ `FROM "${tableName}" WHERE _namespace = ${nsPlaceholder}`
245
+ )
246
+ return { sql: nsSql, params: [...q.params, ns] }
247
+ })
248
+ .pipe(
249
+ Effect.tap((q) => logQuery(q)),
250
+ Effect.tap((q) => Effect.annotateCurrentSpan({ "db.query.text": q.sql })),
251
+ Effect.flatMap((q) =>
252
+ exec(q.sql, q.params).pipe(
253
+ Effect.map((rows) => {
254
+ if (f.select) {
255
+ return (rows as any[]).map((r) => {
256
+ const selected = parseSelectRow(r, idKey, {})
257
+ return {
258
+ ...Struct.pick(
259
+ defaultValues as any,
260
+ f.select!.filter((_) => typeof _ === "string") as never[]
261
+ ),
262
+ ...selected
263
+ } as M
264
+ })
265
+ }
266
+ return (rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues) as any as M)
267
+ })
268
+ )
269
+ ),
270
+ annotateDb({
271
+ operation: "filter",
272
+ system: "postgresql",
273
+ collection: tableName,
274
+ namespace: ns,
275
+ entity: name
276
+ })
277
+ )
278
+ ))
279
+ },
280
+
281
+ set: (e) =>
282
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
283
+ setInternal(e, ns).pipe(
284
+ annotateDb({
285
+ operation: "set",
286
+ system: "postgresql",
287
+ collection: tableName,
288
+ namespace: ns,
289
+ entity: name,
290
+ extra: { "app.entity.id": e[idKey] }
291
+ })
292
+ )
293
+ )),
294
+
295
+ batchSet: (items) =>
296
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
297
+ bulkSetInternal(items, ns).pipe(
298
+ annotateDb({
299
+ operation: "batchSet",
300
+ system: "postgresql",
301
+ collection: tableName,
302
+ namespace: ns,
303
+ entity: name
304
+ })
305
+ )
306
+ )),
307
+
308
+ bulkSet: (items) =>
309
+ resolveNamespace.pipe(Effect.flatMap((ns) =>
310
+ bulkSetInternal(items, ns).pipe(
311
+ annotateDb({
312
+ operation: "bulkSet",
313
+ system: "postgresql",
314
+ collection: tableName,
315
+ namespace: ns,
316
+ entity: name
317
+ })
318
+ )
319
+ )),
320
+
321
+ batchRemove: (ids) => {
322
+ const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ")
323
+ const nsPlaceholder = `$${ids.length + 1}`
324
+ return resolveNamespace.pipe(Effect.flatMap((ns) => {
325
+ const sqlText = `DELETE FROM "${tableName}" WHERE id IN (${placeholders}) AND _namespace = ${nsPlaceholder}`
326
+ return exec(sqlText, [...ids, ns])
327
+ .pipe(
328
+ Effect.asVoid,
329
+ annotateDb({
330
+ operation: "batchRemove",
331
+ system: "postgresql",
332
+ collection: tableName,
333
+ namespace: ns,
334
+ entity: name,
335
+ query: sqlText
336
+ })
337
+ )
338
+ }))
339
+ },
340
+
341
+ queryRaw: (query) =>
342
+ s.all.pipe(
343
+ Effect.map(query.memory),
344
+ annotateDb({
345
+ operation: "queryRaw",
346
+ system: "postgresql",
347
+ collection: tableName,
348
+ entity: name
349
+ })
350
+ )
351
+ }
352
+
353
+ // Eagerly seed primary namespace on initialization
354
+ yield* seedNamespace("primary")
355
+
356
+ return s
357
+ })
358
+ }
359
+ })
360
+
361
+ export function PgStoreLayer(cfg: StorageConfig) {
362
+ return StoreMaker
363
+ .toLayer(makePgStore(cfg))
364
+ }