@effect-app/infra 4.0.0-beta.21 → 4.0.0-beta.210

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