@effect-app/infra 0.7.0 → 0.8.0

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 (284) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/_cjs/errors.cjs +21 -22
  3. package/_cjs/errors.cjs.map +1 -1
  4. package/_cjs/lib/api/authorization.jwt.cjs +1 -1
  5. package/_cjs/{vendor/Cache/Cache/operations/makeWith.cjs → lib/api/codec.cjs} +16 -13
  6. package/_cjs/lib/api/codec.cjs.map +1 -0
  7. package/_cjs/{services/memQueue.cjs → lib/api/express/makeJsonSchema.cjs} +42 -22
  8. package/_cjs/lib/api/express/makeJsonSchema.cjs.map +1 -0
  9. package/_cjs/lib/api/express/makeOpenApiSpecs.cjs +66 -0
  10. package/_cjs/lib/api/express/makeOpenApiSpecs.cjs.map +1 -0
  11. package/_cjs/lib/api/express/schema/jwt.cjs +43 -0
  12. package/_cjs/lib/api/express/schema/jwt.cjs.map +1 -0
  13. package/_cjs/lib/api/express/schema/requestHandler.cjs +166 -0
  14. package/_cjs/lib/api/express/schema/requestHandler.cjs.map +1 -0
  15. package/_cjs/lib/api/express/schema/routing.cjs +121 -0
  16. package/_cjs/lib/api/express/schema/routing.cjs.map +1 -0
  17. package/_cjs/lib/api/routing.cjs +28 -28
  18. package/_cjs/lib/api/routing.cjs.map +1 -1
  19. package/_cjs/lib/api/writeDocs.cjs +4 -4
  20. package/_cjs/lib/api/writeDocs.cjs.map +1 -1
  21. package/_cjs/services/Store/Disk.cjs +1 -1
  22. package/_cjs/services/simpledb/cosmosdb.cjs +98 -0
  23. package/_cjs/services/simpledb/cosmosdb.cjs.map +1 -0
  24. package/_cjs/services/simpledb/diskdb.cjs +118 -0
  25. package/_cjs/services/simpledb/diskdb.cjs.map +1 -0
  26. package/_cjs/services/simpledb/fileutil.cjs +36 -0
  27. package/_cjs/services/simpledb/fileutil.cjs.map +1 -0
  28. package/_cjs/{vendor/Cache/Cache/_internal/CacheState.cjs → services/simpledb/index.cjs} +31 -24
  29. package/_cjs/services/simpledb/index.cjs.map +1 -0
  30. package/_cjs/services/simpledb/memdb.cjs +74 -0
  31. package/_cjs/services/simpledb/memdb.cjs.map +1 -0
  32. package/_cjs/services/simpledb/mongodb.cjs +86 -0
  33. package/_cjs/services/simpledb/mongodb.cjs.map +1 -0
  34. package/_cjs/services/simpledb/redisdb.cjs +120 -0
  35. package/_cjs/services/simpledb/redisdb.cjs.map +1 -0
  36. package/_cjs/services/simpledb/shared.cjs +80 -0
  37. package/_cjs/services/simpledb/shared.cjs.map +1 -0
  38. package/_cjs/services/simpledb/simpledb.cjs +77 -0
  39. package/_cjs/services/simpledb/simpledb.cjs.map +1 -0
  40. package/_src/_global.ext.ts +1 -1
  41. package/_src/errors.ts +12 -3
  42. package/_src/lib/api/app.ts.bak +125 -0
  43. package/_src/lib/api/authorization.jwt.ts +1 -1
  44. package/_src/lib/api/codec.ts +27 -0
  45. package/_src/lib/api/errors.ts.bak +20 -0
  46. package/_src/lib/api/express/makeJsonSchema.ts +49 -0
  47. package/_src/lib/api/express/makeOpenApiSpecs.ts +60 -0
  48. package/_src/lib/api/express/routing.ts.bak +460 -0
  49. package/_src/lib/api/express/schema/jwt.ts +38 -0
  50. package/_src/lib/api/express/schema/requestHandler.ts +360 -0
  51. package/_src/lib/api/express/schema/routing.ts +202 -0
  52. package/_src/lib/api/routing.ts +5 -10
  53. package/_src/lib/api/writeDocs.ts +4 -5
  54. package/_src/services/Store/Disk.ts +1 -1
  55. package/_src/services/simpledb/cosmosdb.ts +134 -0
  56. package/_src/services/simpledb/diskdb.ts +158 -0
  57. package/_src/services/simpledb/fileutil.ts +28 -0
  58. package/_src/services/simpledb/index.ts +6 -0
  59. package/_src/services/simpledb/memdb.ts +79 -0
  60. package/_src/services/simpledb/mongodb.ts +99 -0
  61. package/_src/services/simpledb/redisdb.ts +182 -0
  62. package/_src/services/simpledb/shared.ts +117 -0
  63. package/_src/services/simpledb/simpledb.ts +117 -0
  64. package/dist/_global.ext.d.ts +1 -1
  65. package/dist/_global.ext.d.ts.map +1 -1
  66. package/dist/errors.d.ts +11 -2
  67. package/dist/errors.d.ts.map +1 -1
  68. package/dist/errors.js +17 -3
  69. package/dist/lib/api/authorization.jwt.d.ts +1 -1
  70. package/dist/lib/api/authorization.jwt.d.ts.map +1 -1
  71. package/dist/lib/api/authorization.jwt.js +2 -2
  72. package/dist/lib/api/codec.d.ts +7 -0
  73. package/dist/lib/api/codec.d.ts.map +1 -0
  74. package/dist/lib/api/codec.js +19 -0
  75. package/dist/lib/api/express/makeJsonSchema.d.ts +30 -0
  76. package/dist/lib/api/express/makeJsonSchema.d.ts.map +1 -0
  77. package/dist/lib/api/express/makeJsonSchema.js +40 -0
  78. package/dist/lib/api/express/makeOpenApiSpecs.d.ts +55 -0
  79. package/dist/lib/api/express/makeOpenApiSpecs.d.ts.map +1 -0
  80. package/dist/lib/api/express/makeOpenApiSpecs.js +56 -0
  81. package/dist/lib/api/express/schema/jwt.d.ts +5 -0
  82. package/dist/lib/api/express/schema/jwt.d.ts.map +1 -0
  83. package/dist/lib/api/express/schema/jwt.js +32 -0
  84. package/dist/lib/api/express/schema/requestHandler.d.ts +71 -0
  85. package/dist/lib/api/express/schema/requestHandler.d.ts.map +1 -0
  86. package/dist/lib/api/express/schema/requestHandler.js +140 -0
  87. package/dist/lib/api/express/schema/routing.d.ts +45 -0
  88. package/dist/lib/api/express/schema/routing.d.ts.map +1 -0
  89. package/dist/lib/api/express/schema/routing.js +95 -0
  90. package/dist/lib/api/routing.d.ts +4 -4
  91. package/dist/lib/api/routing.d.ts.map +1 -1
  92. package/dist/lib/api/routing.js +29 -28
  93. package/dist/lib/api/writeDocs.d.ts +1 -1
  94. package/dist/lib/api/writeDocs.d.ts.map +1 -1
  95. package/dist/lib/api/writeDocs.js +5 -5
  96. package/dist/services/Store/Disk.js +2 -2
  97. package/dist/services/simpledb/cosmosdb.d.ts +13 -0
  98. package/dist/services/simpledb/cosmosdb.d.ts.map +1 -0
  99. package/dist/services/simpledb/cosmosdb.js +88 -0
  100. package/dist/services/simpledb/diskdb.d.ts +12 -0
  101. package/dist/services/simpledb/diskdb.d.ts.map +1 -0
  102. package/dist/services/simpledb/diskdb.js +96 -0
  103. package/dist/services/simpledb/fileutil.d.ts +7 -0
  104. package/dist/services/simpledb/fileutil.d.ts.map +1 -0
  105. package/dist/services/simpledb/fileutil.js +25 -0
  106. package/dist/services/simpledb/index.d.ts +7 -0
  107. package/dist/services/simpledb/index.d.ts.map +1 -0
  108. package/dist/services/simpledb/index.js +7 -0
  109. package/dist/services/simpledb/memdb.d.ts +12 -0
  110. package/dist/services/simpledb/memdb.d.ts.map +1 -0
  111. package/dist/services/simpledb/memdb.js +55 -0
  112. package/dist/services/simpledb/mongodb.d.ts +13 -0
  113. package/dist/services/simpledb/mongodb.d.ts.map +1 -0
  114. package/dist/services/simpledb/mongodb.js +55 -0
  115. package/dist/services/simpledb/redisdb.d.ts +25 -0
  116. package/dist/services/simpledb/redisdb.d.ts.map +1 -0
  117. package/dist/services/simpledb/redisdb.js +105 -0
  118. package/dist/services/simpledb/shared.d.ts +63 -0
  119. package/dist/services/simpledb/shared.d.ts.map +1 -0
  120. package/dist/services/simpledb/shared.js +65 -0
  121. package/dist/services/simpledb/simpledb.d.ts +23 -0
  122. package/dist/services/simpledb/simpledb.d.ts.map +1 -0
  123. package/dist/services/simpledb/simpledb.js +56 -0
  124. package/package.json +126 -238
  125. package/_cjs/ext.cjs +0 -4
  126. package/_cjs/ext.cjs.map +0 -1
  127. package/_cjs/lib/fileUtil.cjs +0 -36
  128. package/_cjs/lib/fileUtil.cjs.map +0 -1
  129. package/_cjs/services/CUPS/service.cjs +0 -15
  130. package/_cjs/services/CUPS/service.cjs.map +0 -1
  131. package/_cjs/services/CUPS.cjs +0 -113
  132. package/_cjs/services/CUPS.cjs.map +0 -1
  133. package/_cjs/services/Cache.cjs +0 -39
  134. package/_cjs/services/Cache.cjs.map +0 -1
  135. package/_cjs/services/ServiceBus.cjs +0 -69
  136. package/_cjs/services/ServiceBus.cjs.map +0 -1
  137. package/_cjs/services/memQueue.cjs.map +0 -1
  138. package/_cjs/vendor/Cache/Cache/_internal/AtomicBoolean.cjs +0 -14
  139. package/_cjs/vendor/Cache/Cache/_internal/AtomicBoolean.cjs.map +0 -1
  140. package/_cjs/vendor/Cache/Cache/_internal/AtomicNumber.cjs +0 -29
  141. package/_cjs/vendor/Cache/Cache/_internal/AtomicNumber.cjs.map +0 -1
  142. package/_cjs/vendor/Cache/Cache/_internal/AtomicReference.cjs +0 -32
  143. package/_cjs/vendor/Cache/Cache/_internal/AtomicReference.cjs.map +0 -1
  144. package/_cjs/vendor/Cache/Cache/_internal/CacheInternal.cjs +0 -266
  145. package/_cjs/vendor/Cache/Cache/_internal/CacheInternal.cjs.map +0 -1
  146. package/_cjs/vendor/Cache/Cache/_internal/CacheState.cjs.map +0 -1
  147. package/_cjs/vendor/Cache/Cache/_internal/KeySet.cjs +0 -67
  148. package/_cjs/vendor/Cache/Cache/_internal/KeySet.cjs.map +0 -1
  149. package/_cjs/vendor/Cache/Cache/_internal/MapKey.cjs +0 -44
  150. package/_cjs/vendor/Cache/Cache/_internal/MapKey.cjs.map +0 -1
  151. package/_cjs/vendor/Cache/Cache/_internal/MapValue.cjs +0 -33
  152. package/_cjs/vendor/Cache/Cache/_internal/MapValue.cjs.map +0 -1
  153. package/_cjs/vendor/Cache/Cache/definition.cjs +0 -13
  154. package/_cjs/vendor/Cache/Cache/definition.cjs.map +0 -1
  155. package/_cjs/vendor/Cache/Cache/operations/make.cjs +0 -20
  156. package/_cjs/vendor/Cache/Cache/operations/make.cjs.map +0 -1
  157. package/_cjs/vendor/Cache/Cache/operations/makeWith.cjs.map +0 -1
  158. package/_cjs/vendor/Cache/Cache/operations.cjs +0 -28
  159. package/_cjs/vendor/Cache/Cache/operations.cjs.map +0 -1
  160. package/_cjs/vendor/Cache/Cache.cjs +0 -28
  161. package/_cjs/vendor/Cache/Cache.cjs.map +0 -1
  162. package/_cjs/vendor/Cache/CacheStats/definition.cjs +0 -13
  163. package/_cjs/vendor/Cache/CacheStats/definition.cjs.map +0 -1
  164. package/_cjs/vendor/Cache/CacheStats.cjs +0 -17
  165. package/_cjs/vendor/Cache/CacheStats.cjs.map +0 -1
  166. package/_cjs/vendor/Cache/EntryStats/definition.cjs +0 -11
  167. package/_cjs/vendor/Cache/EntryStats/definition.cjs.map +0 -1
  168. package/_cjs/vendor/Cache/EntryStats.cjs +0 -17
  169. package/_cjs/vendor/Cache/EntryStats.cjs.map +0 -1
  170. package/_cjs/vendor/Cache/Lookup/definition.cjs +0 -6
  171. package/_cjs/vendor/Cache/Lookup/definition.cjs.map +0 -1
  172. package/_cjs/vendor/Cache/Lookup.cjs +0 -17
  173. package/_cjs/vendor/Cache/Lookup.cjs.map +0 -1
  174. package/_cjs/vendor/Cache/index.cjs +0 -17
  175. package/_cjs/vendor/Cache/index.cjs.map +0 -1
  176. package/_src/ext.ts +0 -1
  177. package/_src/lib/fileUtil.ts +0 -56
  178. package/_src/services/CUPS/service.ts +0 -16
  179. package/_src/services/CUPS.ts +0 -105
  180. package/_src/services/Cache.ts +0 -45
  181. package/_src/services/ServiceBus.ts +0 -111
  182. package/_src/services/memQueue.ts +0 -30
  183. package/_src/vendor/Cache/Cache/_internal/AtomicBoolean.ts +0 -7
  184. package/_src/vendor/Cache/Cache/_internal/AtomicNumber.ts +0 -28
  185. package/_src/vendor/Cache/Cache/_internal/AtomicReference.ts +0 -30
  186. package/_src/vendor/Cache/Cache/_internal/CacheInternal.ts +0 -293
  187. package/_src/vendor/Cache/Cache/_internal/CacheState.ts +0 -56
  188. package/_src/vendor/Cache/Cache/_internal/KeySet.ts +0 -58
  189. package/_src/vendor/Cache/Cache/_internal/MapKey.ts +0 -43
  190. package/_src/vendor/Cache/Cache/_internal/MapValue.ts +0 -39
  191. package/_src/vendor/Cache/Cache/definition.ts +0 -112
  192. package/_src/vendor/Cache/Cache/operations/make.ts +0 -17
  193. package/_src/vendor/Cache/Cache/operations/makeWith.ts +0 -32
  194. package/_src/vendor/Cache/Cache/operations.ts +0 -4
  195. package/_src/vendor/Cache/Cache.ts +0 -4
  196. package/_src/vendor/Cache/CacheStats/definition.ts +0 -23
  197. package/_src/vendor/Cache/CacheStats.ts +0 -3
  198. package/_src/vendor/Cache/EntryStats/definition.ts +0 -19
  199. package/_src/vendor/Cache/EntryStats.ts +0 -3
  200. package/_src/vendor/Cache/Lookup/definition.ts +0 -15
  201. package/_src/vendor/Cache/Lookup.ts +0 -3
  202. package/_src/vendor/Cache/global.ts.bak +0 -23
  203. package/_src/vendor/Cache/index.ts +0 -4
  204. package/dist/ext.d.ts +0 -2
  205. package/dist/ext.d.ts.map +0 -1
  206. package/dist/ext.js +0 -2
  207. package/dist/lib/fileUtil.d.ts +0 -22
  208. package/dist/lib/fileUtil.d.ts.map +0 -1
  209. package/dist/lib/fileUtil.js +0 -23
  210. package/dist/services/CUPS/service.d.ts +0 -19
  211. package/dist/services/CUPS/service.d.ts.map +0 -1
  212. package/dist/services/CUPS/service.js +0 -5
  213. package/dist/services/CUPS.d.ts +0 -10
  214. package/dist/services/CUPS.d.ts.map +0 -1
  215. package/dist/services/CUPS.js +0 -89
  216. package/dist/services/Cache.d.ts +0 -28
  217. package/dist/services/Cache.d.ts.map +0 -1
  218. package/dist/services/Cache.js +0 -28
  219. package/dist/services/ServiceBus.d.ts +0 -26
  220. package/dist/services/ServiceBus.d.ts.map +0 -1
  221. package/dist/services/ServiceBus.js +0 -53
  222. package/dist/services/memQueue.d.ts +0 -18
  223. package/dist/services/memQueue.d.ts.map +0 -1
  224. package/dist/services/memQueue.js +0 -24
  225. package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.d.ts +0 -5
  226. package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.d.ts.map +0 -1
  227. package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.js +0 -7
  228. package/dist/vendor/Cache/Cache/_internal/AtomicNumber.d.ts +0 -8
  229. package/dist/vendor/Cache/Cache/_internal/AtomicNumber.d.ts.map +0 -1
  230. package/dist/vendor/Cache/Cache/_internal/AtomicNumber.js +0 -22
  231. package/dist/vendor/Cache/Cache/_internal/AtomicReference.d.ts +0 -10
  232. package/dist/vendor/Cache/Cache/_internal/AtomicReference.d.ts.map +0 -1
  233. package/dist/vendor/Cache/Cache/_internal/AtomicReference.js +0 -25
  234. package/dist/vendor/Cache/Cache/_internal/CacheInternal.d.ts +0 -51
  235. package/dist/vendor/Cache/Cache/_internal/CacheInternal.d.ts.map +0 -1
  236. package/dist/vendor/Cache/Cache/_internal/CacheInternal.js +0 -253
  237. package/dist/vendor/Cache/Cache/_internal/CacheState.d.ts +0 -33
  238. package/dist/vendor/Cache/Cache/_internal/CacheState.d.ts.map +0 -1
  239. package/dist/vendor/Cache/Cache/_internal/CacheState.js +0 -21
  240. package/dist/vendor/Cache/Cache/_internal/KeySet.d.ts +0 -20
  241. package/dist/vendor/Cache/Cache/_internal/KeySet.d.ts.map +0 -1
  242. package/dist/vendor/Cache/Cache/_internal/KeySet.js +0 -61
  243. package/dist/vendor/Cache/Cache/_internal/MapKey.d.ts +0 -24
  244. package/dist/vendor/Cache/Cache/_internal/MapKey.d.ts.map +0 -1
  245. package/dist/vendor/Cache/Cache/_internal/MapKey.js +0 -37
  246. package/dist/vendor/Cache/Cache/_internal/MapValue.d.ts +0 -32
  247. package/dist/vendor/Cache/Cache/_internal/MapValue.d.ts.map +0 -1
  248. package/dist/vendor/Cache/Cache/_internal/MapValue.js +0 -24
  249. package/dist/vendor/Cache/Cache/definition.d.ts +0 -100
  250. package/dist/vendor/Cache/Cache/definition.d.ts.map +0 -1
  251. package/dist/vendor/Cache/Cache/definition.js +0 -5
  252. package/dist/vendor/Cache/Cache/operations/make.d.ts +0 -12
  253. package/dist/vendor/Cache/Cache/operations/make.d.ts.map +0 -1
  254. package/dist/vendor/Cache/Cache/operations/make.js +0 -12
  255. package/dist/vendor/Cache/Cache/operations/makeWith.d.ts +0 -14
  256. package/dist/vendor/Cache/Cache/operations/makeWith.d.ts.map +0 -1
  257. package/dist/vendor/Cache/Cache/operations/makeWith.js +0 -15
  258. package/dist/vendor/Cache/Cache/operations.d.ts +0 -3
  259. package/dist/vendor/Cache/Cache/operations.d.ts.map +0 -1
  260. package/dist/vendor/Cache/Cache/operations.js +0 -4
  261. package/dist/vendor/Cache/Cache.d.ts +0 -3
  262. package/dist/vendor/Cache/Cache.d.ts.map +0 -1
  263. package/dist/vendor/Cache/Cache.js +0 -4
  264. package/dist/vendor/Cache/CacheStats/definition.d.ts +0 -19
  265. package/dist/vendor/Cache/CacheStats/definition.d.ts.map +0 -1
  266. package/dist/vendor/Cache/CacheStats/definition.js +0 -6
  267. package/dist/vendor/Cache/CacheStats.d.ts +0 -2
  268. package/dist/vendor/Cache/CacheStats.d.ts.map +0 -1
  269. package/dist/vendor/Cache/CacheStats.js +0 -3
  270. package/dist/vendor/Cache/EntryStats/definition.d.ts +0 -17
  271. package/dist/vendor/Cache/EntryStats/definition.d.ts.map +0 -1
  272. package/dist/vendor/Cache/EntryStats/definition.js +0 -4
  273. package/dist/vendor/Cache/EntryStats.d.ts +0 -2
  274. package/dist/vendor/Cache/EntryStats.d.ts.map +0 -1
  275. package/dist/vendor/Cache/EntryStats.js +0 -3
  276. package/dist/vendor/Cache/Lookup/definition.d.ts +0 -17
  277. package/dist/vendor/Cache/Lookup/definition.d.ts.map +0 -1
  278. package/dist/vendor/Cache/Lookup/definition.js +0 -2
  279. package/dist/vendor/Cache/Lookup.d.ts +0 -2
  280. package/dist/vendor/Cache/Lookup.d.ts.map +0 -1
  281. package/dist/vendor/Cache/Lookup.js +0 -3
  282. package/dist/vendor/Cache/index.d.ts +0 -5
  283. package/dist/vendor/Cache/index.d.ts.map +0 -1
  284. package/dist/vendor/Cache/index.js +0 -5
@@ -0,0 +1,134 @@
1
+ import type { IndexingPolicy } from "@azure/cosmos"
2
+ import { typedKeysOf } from "@effect-app/core/utils"
3
+
4
+ import * as Cosmos from "@effect-app/infra-adapters/cosmos-client"
5
+ import type { CachedRecord, DBRecord } from "./shared.js"
6
+ import { OptimisticLockException } from "./shared.js"
7
+ import * as simpledb from "./simpledb.js"
8
+ import type { Version } from "./simpledb.js"
9
+
10
+ class CosmosDbOperationError {
11
+ constructor(readonly message: string) {}
12
+ }
13
+
14
+ const setup = (type: string, indexingPolicy: IndexingPolicy) =>
15
+ Cosmos.db.tap(db =>
16
+ Effect.tryPromise(() =>
17
+ db.containers
18
+ .create({ id: type, indexingPolicy })
19
+ .catch(err => console.warn(err))
20
+ )
21
+ )
22
+ // TODO: Error if current indexingPolicy does not match
23
+ // Effect.flatMap((db) => Effect.tryPromise(() => db.container(type).(indexes)))
24
+ export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
25
+ return <REncode, RDecode, EDecode>(
26
+ type: string,
27
+ encode: (record: A) => Effect<REncode, never, EA>,
28
+ decode: (d: EA) => Effect<RDecode, EDecode, A>,
29
+ // schemaVersion: string,
30
+ indexes: IndexingPolicy
31
+ ) => {
32
+ return setup(type, indexes).map(() => ({
33
+ find: simpledb.find(find, decode, type),
34
+ findBy,
35
+ save: simpledb.storeDirectly(store, type)
36
+ }))
37
+
38
+ function find(id: string) {
39
+ return Cosmos.db
40
+ .flatMap(db => Effect.tryPromise(() => db.container(type).item(id).read<{ data: EA }>()))
41
+ .map(i => Opt.fromNullable(i.resource))
42
+ .map(
43
+ _ =>
44
+ _.map(
45
+ ({ _etag, data }) => ({ version: _etag, data } as CachedRecord<EA>)
46
+ )
47
+ )
48
+ }
49
+
50
+ function findBy(parameters: Record<string, string>) {
51
+ return Cosmos.db
52
+ .flatMap(db =>
53
+ Effect.tryPromise(() =>
54
+ db
55
+ .container(type)
56
+ .items.query({
57
+ query: `
58
+ SELECT TOP 1 ${type}.id
59
+ FROM ${type} i
60
+ WHERE (
61
+ ${
62
+ typedKeysOf(parameters)
63
+ .map(k => `i.${k} = @${k}`)
64
+ .join(" and ")
65
+ }
66
+ )
67
+ `,
68
+ parameters: typedKeysOf(parameters).map(p => ({
69
+ name: `@${p}`,
70
+ value: parameters[p]!
71
+ }))
72
+ })
73
+ .fetchAll()
74
+ )
75
+ )
76
+ .map(x => x.resources.head)
77
+ .map(_ => _.map(_ => _.id))
78
+ }
79
+
80
+ function store(record: A, currentVersion: Opt<Version>) {
81
+ return Effect.gen(function*($) {
82
+ const version = "_etag" // we get this from the etag anyway.
83
+
84
+ const db = yield* $(Cosmos.db)
85
+ const data = yield* $(encode(record))
86
+
87
+ yield* $(
88
+ currentVersion.match(
89
+ () =>
90
+ Effect.tryPromise(() =>
91
+ db.container(type).items.create({
92
+ id: record.id,
93
+ timestamp: new Date(),
94
+ data
95
+ })
96
+ ).asUnit.orDie,
97
+ currentVersion =>
98
+ Effect.tryPromise(() =>
99
+ db
100
+ .container(type)
101
+ .item(record.id)
102
+ .replace(
103
+ {
104
+ id: record.id,
105
+ timestamp: new Date(),
106
+ data
107
+ },
108
+ {
109
+ accessCondition: {
110
+ type: "IfMatch",
111
+ condition: currentVersion
112
+ }
113
+ }
114
+ )
115
+ ).orDie.flatMap(x => {
116
+ if (x.statusCode === 412) {
117
+ return Effect.fail(new OptimisticLockException(type, record.id))
118
+ }
119
+ if (x.statusCode > 299 || x.statusCode < 200) {
120
+ return Effect.die(
121
+ new CosmosDbOperationError(
122
+ "not able to update record: " + x.statusCode
123
+ )
124
+ )
125
+ }
126
+ return Effect.unit
127
+ })
128
+ )
129
+ )
130
+ return { version, data: record } as CachedRecord<A>
131
+ })
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,158 @@
1
+ import { flow, pipe } from "@effect-app/core/Function"
2
+ import fs from "fs"
3
+ import * as PLF from "proper-lockfile"
4
+
5
+ import { pretty } from "@effect-app/core/utils"
6
+ import * as fu from "./fileutil.js"
7
+ import type { CachedRecord, DBRecord, Index } from "./shared.js"
8
+ import { ConnectionException, CouldNotAquireDbLockException, getIndexName, getRecordName } from "./shared.js"
9
+ import * as simpledb from "./simpledb.js"
10
+ import type { Version } from "./simpledb.js"
11
+
12
+ export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
13
+ return <REncode, RDecode, EDecode>(
14
+ type: string,
15
+ encode: (record: A) => Effect<REncode, never, EA>,
16
+ decode: (d: EA) => Effect<RDecode, EDecode, A>,
17
+ schemaVersion: string,
18
+ makeIndexKey: (r: A) => Index,
19
+ dir = "./data.js"
20
+ ) => {
21
+ initialise(dir)
22
+ const globalLock = "global.lock"
23
+ const typeLockKey = getIdxName(type, globalLock)
24
+ if (!fs.existsSync(typeLockKey)) {
25
+ fs.writeFileSync(typeLockKey, "", "utf-8")
26
+ }
27
+
28
+ return {
29
+ find: simpledb.find(find(type), decode, type),
30
+ findByIndex: getIdx,
31
+ save: simpledb.store(find(type), store, lockRecordOnDisk(type), type)
32
+ }
33
+
34
+ function store(record: A, currentVersion: Opt<Version>) {
35
+ const version = currentVersion
36
+ .map(cv => (parseInt(cv) + 1).toString())
37
+ .getOrElse(() => "1")
38
+ const getData = flow(
39
+ encode,
40
+ _ => _.map(data => pretty({ version, timestamp: new Date(), data }))
41
+ )
42
+
43
+ const idx = makeIndexKey(record)
44
+ return currentVersion.isSome()
45
+ ? lockIndex(record)
46
+ .zipRight(
47
+ readIndex(idx).flatMap(x =>
48
+ x[record.id]
49
+ ? Effect.fail(() => new Error("Combination already exists, abort"))
50
+ : getData(record)
51
+ .flatMap(serialised => fu.writeTextFile(getFilename(type, record.id), serialised))
52
+ .zipRight(writeIndex(idx, { ...x, [idx.key]: record.id }))
53
+ ).orDie
54
+ )
55
+ .scoped.map(() => ({ version, data: record } as CachedRecord<A>))
56
+ : getData(record)
57
+ .flatMap(serialised => fu.writeTextFile(getFilename(type, record.id), serialised))
58
+ .map(() => ({ version, data: record } as CachedRecord<A>))
59
+ }
60
+
61
+ function lockIndex(record: A) {
62
+ const index = makeIndexKey(record)
63
+ return lockDiskIndex(index)
64
+ }
65
+
66
+ function lockDiskIndex(_: Index) {
67
+ /*
68
+ Disk index locks require a file to exist already, hence for now we use a global index lock.
69
+ */
70
+ // const lockKey = getIdxKey(index)
71
+ const lockKey = globalLock
72
+ return lockIndexOnDisk(type)(lockKey)
73
+ }
74
+
75
+ function lockRecordOnDisk(type: string) {
76
+ return (id: string) =>
77
+ lockFile(getFilename(type, id)).mapBoth(
78
+ err => new CouldNotAquireDbLockException(type, id, err as Error),
79
+ release => ({ release })
80
+ ).acquireRelease(
81
+ l => l.release
82
+ )
83
+ }
84
+
85
+ function lockIndexOnDisk(type: string) {
86
+ return (id: string) =>
87
+ lockFile(getIdxName(type, id)).mapBoth(
88
+ err => new CouldNotAquireDbLockException(type, id, err as Error),
89
+ release => ({ release })
90
+ ).acquireRelease(
91
+ l => l.release
92
+ )
93
+ }
94
+
95
+ function readFile(filePath: string) {
96
+ return fu
97
+ .readTextFile(filePath)
98
+ .catchAll(err => Effect.die(new ConnectionException(err as Error)))
99
+ }
100
+
101
+ function find(type: string) {
102
+ return (id: string) => {
103
+ return tryRead(getFilename(type, id)).map(
104
+ _ => _.map(s => JSON.parse(s) as CachedRecord<EA>)
105
+ )
106
+ }
107
+ }
108
+
109
+ function getIdx(index: Index) {
110
+ return readIndex(index).map(idx => Opt.fromNullable(idx[index.key]))
111
+ }
112
+
113
+ function readIndex(index: Index) {
114
+ return tryRead(getIdxName(type, index.doc)).map(
115
+ _ =>
116
+ _.match(
117
+ () => ({} as Record<string, TKey>),
118
+ x => JSON.parse(x) as Record<string, TKey>
119
+ )
120
+ )
121
+ }
122
+
123
+ function writeIndex(index: Index, content: Record<string, TKey>) {
124
+ return pipe(JSON.stringify(content), serialised => fu.writeTextFile(getIdxName(type, index.doc), serialised))
125
+ }
126
+
127
+ function tryRead(filePath: string) {
128
+ return fu
129
+ .fileExists(filePath)
130
+ .flatMap(exists => !exists ? Effect(Opt.none) : readFile(filePath).map(Opt.some))
131
+ }
132
+
133
+ function getFilename(type: string, id: string) {
134
+ return `${dir}/v${schemaVersion}.${getRecordName(type, id)}.json`
135
+ }
136
+
137
+ function getIdxName(type: string, id: string) {
138
+ return `${dir}/v${schemaVersion}.${getIndexName(type, id)}.json`
139
+ }
140
+ }
141
+ }
142
+
143
+ function lockFile(fileName: string) {
144
+ return Effect.tryPromise(() => PLF.lock(fileName).then(flow(Effect.tryPromise, _ => _.orDie)))
145
+ }
146
+
147
+ // TODO: ugh.
148
+ let initialised = false
149
+ export function initialise(dir: string) {
150
+ if (initialised) {
151
+ return
152
+ }
153
+
154
+ if (!fs.existsSync(dir)) {
155
+ fs.mkdirSync(dir)
156
+ }
157
+ initialised = true
158
+ }
@@ -0,0 +1,28 @@
1
+ import fs from "fs"
2
+ import { promisify } from "util"
3
+
4
+ const readFile = promisify(fs.readFile)
5
+ const writeFile = promisify(fs.writeFile)
6
+ const rename = promisify(fs.rename)
7
+ const exists = promisify(fs.exists)
8
+ // const mkdir = promisify(fs.mkdir)
9
+ // const unlinkFile = promisify(fs.unlink)
10
+
11
+ /**
12
+ * Safe write file to .tmp and then rename
13
+ */
14
+ export function writeTextFile(fileName: string, content: string) {
15
+ const tmp = fileName + ".tmp"
16
+ return (
17
+ Effect.tryPromise(() => writeFile(tmp, content, "utf-8")) >
18
+ Effect.tryPromise(() => rename(tmp, fileName))
19
+ ).orDie
20
+ }
21
+
22
+ export function fileExists(fileName: string) {
23
+ return Effect.tryPromise(() => exists(fileName)).orDie
24
+ }
25
+
26
+ export function readTextFile(fileName: string) {
27
+ return Effect.tryPromise(() => readFile(fileName, "utf-8"))
28
+ }
@@ -0,0 +1,6 @@
1
+ export * as disk from "./diskdb.js"
2
+ export * as mem from "./memdb.js"
3
+ export * as mongo from "./mongodb.js"
4
+ export * as redis from "./redisdb.js"
5
+ export * from "./shared.js"
6
+ export * as SDB from "./simpledb.js"
@@ -0,0 +1,79 @@
1
+ import { flow } from "@effect-app/core/Function"
2
+ import * as MO from "@effect-app/schema"
3
+ import type * as Eq from "@effect-ts/core/Equal"
4
+
5
+ import type { CachedRecord, DBRecord } from "./shared.js"
6
+ import { getRecordName, makeMap, SerializedDBRecord } from "./shared.js"
7
+ import * as simpledb from "./simpledb.js"
8
+ import type { Version } from "./simpledb.js"
9
+
10
+ // When we are in-process, we want to share the same Storage
11
+ // Do not try this at home.
12
+ const storage = makeMap<string, string>()
13
+
14
+ const parseSDB = SerializedDBRecord.Parser >= MO.condemnFail
15
+
16
+ export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
17
+ return <REncode, RDecode, EDecode>(
18
+ type: string,
19
+ encode: (record: A) => Effect<REncode, never, EA>,
20
+ decode: (d: EA) => Effect<RDecode, EDecode, A>
21
+ ) => {
22
+ return {
23
+ find: simpledb.find(find, decode, type),
24
+ findBy,
25
+ save: simpledb.store(find, store, bogusLock, type)
26
+ }
27
+
28
+ function find(id: string) {
29
+ return storage
30
+ .find(getRecordName(type, id))
31
+ .map(_ => _.map(s => JSON.parse(s) as unknown))
32
+ .flatMapOpt(parseSDB)
33
+ .mapOpt(({ data, version }) => ({
34
+ data: JSON.parse(data) as EA,
35
+ version
36
+ }))
37
+ }
38
+
39
+ function findBy<V extends Partial<A>>(keys: V, eq: Eq.Equal<V>) {
40
+ // Naive implementation, fine for in memory testing purposes.
41
+ return Effect.gen(function*($) {
42
+ for (const [, value] of storage) {
43
+ const sdb_ = JSON.parse(value) as unknown
44
+ const sdb = yield* $(parseSDB(sdb_))
45
+ const cr = { data: JSON.parse(sdb.data) as EA, version: sdb.version }
46
+ const r = yield* $(
47
+ decode(cr.data).flatMap(d =>
48
+ eq.equals(keys, d as unknown as V)
49
+ ? Effect(d)
50
+ : Effect.fail("not equals")
51
+ ).exit
52
+ )
53
+ if (r.isSuccess()) {
54
+ return r.value
55
+ }
56
+ }
57
+ return null
58
+ }).map(Opt.fromNullable)
59
+ }
60
+
61
+ function store(record: A, currentVersion: Opt<Version>) {
62
+ const version = currentVersion
63
+ .map(cv => (parseInt(cv) + 1).toString())
64
+ .getOrElse(() => "1")
65
+
66
+ const getData = flow(
67
+ encode,
68
+ _ => _.map(JSON.stringify).map(data => JSON.stringify({ version, timestamp: new Date(), data }))
69
+ )
70
+ return getData(record)
71
+ .flatMap(serialised => storage.set(getRecordName(type, record.id), serialised))
72
+ .map(() => ({ version, data: record } as CachedRecord<A>))
73
+ }
74
+ }
75
+ }
76
+
77
+ function bogusLock() {
78
+ return Effect.unit.acquireRelease(() => Effect.unit)
79
+ }
@@ -0,0 +1,99 @@
1
+ import type { IndexDescription, InsertOneOptions } from "mongodb"
2
+
3
+ import * as Mongo from "@effect-app/infra-adapters/mongo-client"
4
+ import type { CachedRecord, DBRecord } from "./shared.js"
5
+ import { OptimisticLockException } from "./shared.js"
6
+ import * as simpledb from "./simpledb.js"
7
+ import type { Version } from "./simpledb.js"
8
+
9
+ // const makeFromIndexKeys = (indexKeys: string[], unique: boolean) => indexKeys.reduce((prev, cur) => {
10
+ // prev[cur] = 1
11
+ // return prev
12
+ // }, {} as Record<string, number>)
13
+
14
+ const setup = (type: string, indexes: IndexDescription[]) =>
15
+ Mongo.db
16
+ .tap(db => Effect.tryPromise(() => db.createCollection(type).catch(err => console.warn(err))))
17
+ .flatMap(db => Effect.tryPromise(() => db.collection(type).createIndexes(indexes)))
18
+
19
+ export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
20
+ return <REncode, RDecode, EDecode>(
21
+ type: string,
22
+ encode: (record: A) => Effect<REncode, never, EA>,
23
+ decode: (d: EA) => Effect<RDecode, EDecode, A>,
24
+ // schemaVersion: string,
25
+ indexes: IndexDescription[]
26
+ ) => {
27
+ return setup(type, indexes).map(() => ({
28
+ find: simpledb.find(find, decode, type),
29
+ findBy,
30
+ save: simpledb.storeDirectly(store, type)
31
+ }))
32
+
33
+ function find(id: string) {
34
+ return Mongo.db
35
+ .flatMap(db =>
36
+ Effect.tryPromise(() =>
37
+ db
38
+ .collection(type)
39
+ .findOne<{ _id: TKey; version: Version; data: EA }>({ _id: id })
40
+ )
41
+ )
42
+ .map(Opt.fromNullable)
43
+ .mapOpt(({ data, version }) => ({ version, data } as CachedRecord<EA>))
44
+ }
45
+
46
+ function findBy(keys: Record<string, string>) {
47
+ return Mongo.db
48
+ .flatMap(db =>
49
+ Effect.tryPromise(() => db.collection(type).findOne<{ _id: TKey }>(keys, { projection: { _id: 1 } }))
50
+ )
51
+ .map(Opt.fromNullable)
52
+ .mapOpt(({ _id }) => _id)
53
+ }
54
+
55
+ function store(record: A, currentVersion: Opt<Version>) {
56
+ return Effect.gen(function*($) {
57
+ const version = currentVersion
58
+ .map(cv => (parseInt(cv) + 1).toString())
59
+ .getOrElse(() => "1")
60
+
61
+ const db = yield* $(Mongo.db)
62
+ const data = yield* $(encode(record))
63
+ yield* $(
64
+ currentVersion.match(
65
+ () =>
66
+ Effect.tryPromise(() =>
67
+ db
68
+ .collection(type)
69
+ .insertOne(
70
+ { _id: record.id as any, version, timestamp: new Date(), data },
71
+ {
72
+ checkKeys: false // support for keys with `.` and `$`. NOTE: you can write them, read them, but NOT query for them.
73
+ } as InsertOneOptions
74
+ )
75
+ ).asUnit.orDie,
76
+ currentVersion =>
77
+ Effect.tryPromise(() =>
78
+ db.collection(type).replaceOne(
79
+ { _id: record.id, version: currentVersion },
80
+ {
81
+ version,
82
+ timestamp: new Date(),
83
+ data
84
+ },
85
+ { upsert: false }
86
+ )
87
+ ).orDie.flatMap(x => {
88
+ if (!x.modifiedCount) {
89
+ return Effect.fail(new OptimisticLockException(type, record.id))
90
+ }
91
+ return Effect.unit
92
+ })
93
+ )
94
+ )
95
+ return { version, data: record } as CachedRecord<A>
96
+ })
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,182 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { flow, pipe } from "@effect-app/core/Function"
4
+ import * as MO from "@effect-app/schema"
5
+ import type { Lock } from "redlock"
6
+
7
+ import * as RED from "@effect-app/infra-adapters/redis-client"
8
+ import type { CachedRecord, DBRecord, Index } from "./shared.js"
9
+ import { ConnectionException, CouldNotAquireDbLockException, getIndexName, getRecordName } from "./shared.js"
10
+ import * as simpledb from "./simpledb.js"
11
+
12
+ const ttl = 10 * 1000
13
+
14
+ export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
15
+ return <REncode, RDecode, EDecode>(
16
+ type: string,
17
+ encode: (record: A) => Effect<REncode, never, EA>,
18
+ decode: (d: EA) => Effect<RDecode, EDecode, A>,
19
+ schemaVersion: string,
20
+ makeIndexKey: (r: A) => Index
21
+ ) => {
22
+ const getData = flow(encode, _ => _.map(JSON.stringify))
23
+ return {
24
+ find: simpledb.find(find, decode, type),
25
+ findByIndex: getIdx,
26
+ save: simpledb.store(find, store, lockRedisRecord, type)
27
+ }
28
+
29
+ function find(id: string) {
30
+ return RED.hmgetAll(getKey(id)).flatMapOpt(v =>
31
+ pipe(
32
+ RedisSerializedDBRecord.Parser,
33
+ MO.condemnFail
34
+ )(v)
35
+ .map(({ data, version }) => ({
36
+ data: JSON.parse(data) as EA,
37
+ version
38
+ }))
39
+ .mapError(e => new ConnectionException(new Error(e.toString())))
40
+ ).orDie
41
+ }
42
+ function store(record: A, currentVersion: Opt<string>) {
43
+ const version = currentVersion
44
+ .map(cv => (parseInt(cv) + 1).toString())
45
+ .getOrElse(() => "1")
46
+ return currentVersion.match(
47
+ () =>
48
+ lockIndex(record).zipRight(
49
+ getIndex(record)
50
+ .zipRightOpt(
51
+ Effect.fail(() => new Error("Combination already exists, abort"))
52
+ )
53
+ .zipRight(getData(record))
54
+ // TODO: instead use MULTI & EXEC to make it in one command?
55
+ .flatMap(data =>
56
+ hmSetRec(getKey(record.id), {
57
+ version,
58
+ timestamp: new Date(),
59
+ data
60
+ })
61
+ )
62
+ .zipRight(setIndex(record))
63
+ .orDie.map(() => ({ version, data: record } as CachedRecord<A>))
64
+ ).scoped,
65
+ () =>
66
+ getData(record)
67
+ .flatMap(data =>
68
+ hmSetRec(getKey(record.id), {
69
+ version,
70
+ timestamp: new Date(),
71
+ data
72
+ })
73
+ )
74
+ .orDie.map(() => ({ version, data: record } as CachedRecord<A>))
75
+ )
76
+ }
77
+
78
+ function getIndex(record: A) {
79
+ const index = makeIndexKey(record)
80
+ return getIdx(index)
81
+ }
82
+
83
+ function setIndex(record: A) {
84
+ const index = makeIndexKey(record)
85
+ return setIdx(index, record)
86
+ }
87
+
88
+ function lockIndex(record: A) {
89
+ const index = makeIndexKey(record)
90
+ return lockRedisIdx(index)
91
+ }
92
+
93
+ function getIdx(index: Index) {
94
+ return RED.hget(getIdxKey(index), index.key).map(_ => _.map(i => i as TKey))
95
+ }
96
+
97
+ function setIdx(index: Index, r: A) {
98
+ return RED.hset(getIdxKey(index), index.key, r.id)
99
+ }
100
+
101
+ function lockRedisIdx(index: Index) {
102
+ const lockKey = getIdxLockKey(index)
103
+ // acquire
104
+ return RED.lock
105
+ .flatMap(lock => Effect.tryPromise(() => lock.lock(lockKey, ttl) as unknown as Promise<Lock>))
106
+ .mapBoth(
107
+ err => new CouldNotAquireDbLockException(type, lockKey, err as Error),
108
+ // release
109
+ lock => ({
110
+ release: Effect.tryPromise(() => lock.unlock() as unknown as Promise<void>)
111
+ .orDie
112
+ })
113
+ ).acquireRelease(
114
+ l => l.release
115
+ )
116
+ }
117
+
118
+ function lockRedisRecord(id: string) {
119
+ // acquire
120
+ return RED.lock
121
+ .flatMap(lock =>
122
+ Effect.tryPromise(
123
+ () => lock.lock(getLockKey(id), ttl) as unknown as Promise<Lock>
124
+ )
125
+ )
126
+ .mapBoth(
127
+ err => new CouldNotAquireDbLockException(type, id, err as Error),
128
+ // release
129
+ lock => ({
130
+ // TODO
131
+ release: Effect.tryPromise(() => lock.unlock() as unknown as Promise<void>)
132
+ .orDie
133
+ })
134
+ ).acquireRelease(
135
+ l => l.release
136
+ )
137
+ }
138
+
139
+ function getKey(id: string) {
140
+ return `v${schemaVersion}.${getRecordName(type, id)}`
141
+ }
142
+
143
+ function getLockKey(id: string) {
144
+ return `v${schemaVersion}.locks.${getRecordName(type, id)}`
145
+ }
146
+
147
+ function getIdxKey(index: Index) {
148
+ return `v${schemaVersion}.${getIndexName(type, index.doc)}`
149
+ }
150
+ function getIdxLockKey(index: Index) {
151
+ return `v${schemaVersion}.locks.${getIndexName(type, index.doc)}_${index.key}`
152
+ }
153
+ }
154
+
155
+ function hmSetRec(key: string, val: RedisSerializedDBRecord) {
156
+ const enc = RedisSerializedDBRecord.Encoder(val)
157
+ return RED.client.flatMap(
158
+ client =>
159
+ Effect.async<never, ConnectionException, void>(res => {
160
+ client.hmset(
161
+ key,
162
+ "version",
163
+ enc.version,
164
+ "timestamp",
165
+ enc.timestamp,
166
+ "data",
167
+ enc.data,
168
+ err =>
169
+ err
170
+ ? res(Effect.fail(new ConnectionException(err)))
171
+ : res(Effect(void 0))
172
+ )
173
+ }).uninterruptible
174
+ )
175
+ }
176
+ }
177
+
178
+ export class RedisSerializedDBRecord extends MO.Model<RedisSerializedDBRecord>()({
179
+ version: MO.prop(MO.string),
180
+ timestamp: MO.prop(MO.date),
181
+ data: MO.prop(MO.string)
182
+ }) {}