@effect-app/infra 4.0.0-beta.20 → 4.0.0-beta.200

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