@effect-app/infra 2.0.2 → 2.1.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 (110) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/_cjs/api/internal/events.cjs +2 -2
  3. package/_cjs/api/internal/events.cjs.map +1 -1
  4. package/_cjs/fileUtil.cjs +48 -0
  5. package/_cjs/fileUtil.cjs.map +1 -0
  6. package/_cjs/services/CUPS.cjs +118 -0
  7. package/_cjs/services/CUPS.cjs.map +1 -0
  8. package/_cjs/services/QueueMaker/SQLQueue.cjs +1 -1
  9. package/_cjs/services/QueueMaker/SQLQueue.cjs.map +1 -1
  10. package/_cjs/services/QueueMaker/memQueue.cjs +1 -1
  11. package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
  12. package/_cjs/services/QueueMaker/sbqueue.cjs +1 -1
  13. package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
  14. package/_cjs/services/Store/Cosmos.cjs +1 -1
  15. package/_cjs/services/Store/Cosmos.cjs.map +1 -1
  16. package/_cjs/services/Store/Disk.cjs +1 -1
  17. package/_cjs/services/adapters/SQL/Model.cjs +500 -0
  18. package/_cjs/services/adapters/SQL/Model.cjs.map +1 -0
  19. package/_cjs/services/adapters/SQL.cjs +11 -0
  20. package/_cjs/services/adapters/SQL.cjs.map +1 -0
  21. package/_cjs/services/adapters/ServiceBus.cjs +76 -0
  22. package/_cjs/services/adapters/ServiceBus.cjs.map +1 -0
  23. package/_cjs/services/adapters/cosmos-client.cjs +18 -0
  24. package/_cjs/services/adapters/cosmos-client.cjs.map +1 -0
  25. package/_cjs/services/adapters/index.cjs +6 -0
  26. package/_cjs/services/adapters/index.cjs.map +1 -0
  27. package/_cjs/services/adapters/logger.cjs +9 -0
  28. package/_cjs/services/adapters/logger.cjs.map +1 -0
  29. package/_cjs/services/adapters/memQueue.cjs +31 -0
  30. package/_cjs/services/adapters/memQueue.cjs.map +1 -0
  31. package/_cjs/services/adapters/mongo-client.cjs +20 -0
  32. package/_cjs/services/adapters/mongo-client.cjs.map +1 -0
  33. package/_cjs/services/adapters/redis-client.cjs +83 -0
  34. package/_cjs/services/adapters/redis-client.cjs.map +1 -0
  35. package/dist/api/internal/events.d.ts.map +1 -1
  36. package/dist/api/internal/events.js +3 -3
  37. package/dist/fileUtil.d.ts +23 -0
  38. package/dist/fileUtil.d.ts.map +1 -0
  39. package/dist/fileUtil.js +41 -0
  40. package/dist/services/CUPS.d.ts +26 -0
  41. package/dist/services/CUPS.d.ts.map +1 -0
  42. package/dist/services/CUPS.js +111 -0
  43. package/dist/services/QueueMaker/SQLQueue.d.ts.map +1 -1
  44. package/dist/services/QueueMaker/SQLQueue.js +2 -2
  45. package/dist/services/QueueMaker/memQueue.d.ts +1 -1
  46. package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
  47. package/dist/services/QueueMaker/memQueue.js +2 -2
  48. package/dist/services/QueueMaker/sbqueue.d.ts +3 -3
  49. package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
  50. package/dist/services/QueueMaker/sbqueue.js +2 -2
  51. package/dist/services/Repository/ext.d.ts +11 -11
  52. package/dist/services/RepositoryBase.d.ts +6 -6
  53. package/dist/services/Store/Cosmos.d.ts.map +1 -1
  54. package/dist/services/Store/Cosmos.js +2 -2
  55. package/dist/services/Store/Disk.js +2 -2
  56. package/dist/services/adapters/SQL/Model.d.ts +538 -0
  57. package/dist/services/adapters/SQL/Model.d.ts.map +1 -0
  58. package/dist/services/adapters/SQL/Model.js +508 -0
  59. package/dist/services/adapters/SQL.d.ts +2 -0
  60. package/dist/services/adapters/SQL.d.ts.map +1 -0
  61. package/dist/services/adapters/SQL.js +2 -0
  62. package/dist/services/adapters/ServiceBus.d.ts +50 -0
  63. package/dist/services/adapters/ServiceBus.d.ts.map +1 -0
  64. package/dist/services/adapters/ServiceBus.js +73 -0
  65. package/dist/services/adapters/cosmos-client.d.ts +10 -0
  66. package/dist/services/adapters/cosmos-client.d.ts.map +1 -0
  67. package/dist/services/adapters/cosmos-client.js +8 -0
  68. package/dist/services/adapters/index.d.ts +2 -0
  69. package/dist/services/adapters/index.d.ts.map +1 -0
  70. package/dist/services/adapters/index.js +2 -0
  71. package/dist/services/adapters/logger.d.ts +8 -0
  72. package/dist/services/adapters/logger.d.ts.map +1 -0
  73. package/dist/services/adapters/logger.js +3 -0
  74. package/dist/services/adapters/memQueue.d.ts +34 -0
  75. package/dist/services/adapters/memQueue.d.ts.map +1 -0
  76. package/dist/services/adapters/memQueue.js +24 -0
  77. package/dist/services/adapters/mongo-client.d.ts +10 -0
  78. package/dist/services/adapters/mongo-client.d.ts.map +1 -0
  79. package/dist/services/adapters/mongo-client.js +12 -0
  80. package/dist/services/adapters/redis-client.d.ts +29 -0
  81. package/dist/services/adapters/redis-client.d.ts.map +1 -0
  82. package/dist/services/adapters/redis-client.js +93 -0
  83. package/package.json +128 -12
  84. package/src/api/internal/events.ts +2 -2
  85. package/src/fileUtil.ts +85 -0
  86. package/src/services/CUPS.ts +151 -0
  87. package/src/services/QueueMaker/SQLQueue.ts +1 -1
  88. package/src/services/QueueMaker/memQueue.ts +1 -1
  89. package/src/services/QueueMaker/sbqueue.ts +7 -7
  90. package/src/services/Store/Cosmos.ts +1 -1
  91. package/src/services/Store/Disk.ts +1 -1
  92. package/src/services/adapters/SQL/Model.ts +939 -0
  93. package/src/services/adapters/SQL.ts +1 -0
  94. package/src/services/adapters/ServiceBus.ts +140 -0
  95. package/src/services/adapters/cosmos-client.ts +16 -0
  96. package/src/services/adapters/index.ts +0 -0
  97. package/src/services/adapters/logger.ts +3 -0
  98. package/src/services/adapters/memQueue.ts +26 -0
  99. package/src/services/adapters/mongo-client.ts +23 -0
  100. package/src/services/adapters/redis-client.ts +123 -0
  101. package/tsconfig.src.json +0 -3
  102. package/src/services/Store/Redis.ts.bak +0 -88
  103. package/src/services/simpledb/cosmosdb.ts.bak +0 -149
  104. package/src/services/simpledb/diskdb.ts.bak +0 -165
  105. package/src/services/simpledb/index.ts.bak +0 -6
  106. package/src/services/simpledb/memdb.ts.bak +0 -78
  107. package/src/services/simpledb/mongodb.ts.bak +0 -107
  108. package/src/services/simpledb/redisdb.ts.bak +0 -202
  109. package/src/services/simpledb/shared.ts.bak +0 -117
  110. package/src/services/simpledb/simpledb.ts.bak +0 -121
@@ -1,165 +0,0 @@
1
- import { flow, pipe } from "effect-app/Function"
2
- import fs from "fs"
3
- import * as PLF from "proper-lockfile"
4
-
5
- import { pretty } from "effect-app/utils"
6
- import * as fu from "@effect-app/infra-adapters/fileUtil"
7
- import { Effect, Option } from "effect-app"
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
- import type { Version } from "./simpledb.js"
12
-
13
- export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
14
- return <REncode, RDecode, EDecode>(
15
- type: string,
16
- encode: (record: A) => Effect<EA, never, REncode>,
17
- decode: (d: EA) => Effect<A, EDecode, RDecode>,
18
- schemaVersion: string,
19
- makeIndexKey: (r: A) => Index,
20
- dir = "./data.js"
21
- ) => {
22
- initialise(dir)
23
- const globalLock = "global.lock"
24
- const typeLockKey = getIdxName(type, globalLock)
25
- if (!fs.existsSync(typeLockKey)) {
26
- fs.writeFileSync(typeLockKey, "", "utf-8")
27
- }
28
-
29
- return {
30
- find: simpledb.find(find(type), decode, type),
31
- findByIndex: getIdx,
32
- save: simpledb.store(find(type), store, lockRecordOnDisk(type), type)
33
- }
34
-
35
- function store(record: A, currentVersion: Option<Version>) {
36
- const version = currentVersion
37
- .map((cv) => (parseInt(cv) + 1).toString())
38
- .getOrElse(() => "1")
39
- const getData = flow(
40
- encode,
41
- (_) => _.map((data) => pretty({ version, timestamp: new Date(), data }))
42
- )
43
-
44
- const idx = makeIndexKey(record)
45
- return currentVersion.isSome()
46
- ? lockIndex(record)
47
- .zipRight(
48
- readIndex(idx)
49
- .flatMap((x) =>
50
- x[record.id]
51
- ? Effect.fail(() => new Error("Combination already exists, abort"))
52
- : getData(record)
53
- .flatMap((serialised) => fu.writeTextFile(getFilename(type, record.id), serialised))
54
- .zipRight(writeIndex(idx, { ...x, [idx.key]: record.id }))
55
- )
56
- .orDie
57
- )
58
- .scoped
59
- .map(() => ({ version, data: record } as CachedRecord<A>))
60
- : getData(record)
61
- .flatMap((serialised) => fu.writeTextFile(getFilename(type, record.id), serialised))
62
- .map(() => ({ version, data: record } as CachedRecord<A>))
63
- }
64
-
65
- function lockIndex(record: A) {
66
- const index = makeIndexKey(record)
67
- return lockDiskIndex(index)
68
- }
69
-
70
- function lockDiskIndex(_: Index) {
71
- /*
72
- Disk index locks require a file to exist already, hence for now we use a global index lock.
73
- */
74
- // const lockKey = getIdxKey(index)
75
- const lockKey = globalLock
76
- return lockIndexOnDisk(type)(lockKey)
77
- }
78
-
79
- function lockRecordOnDisk(type: string) {
80
- return (id: string) =>
81
- lockFile(getFilename(type, id))
82
- .mapBoth({
83
- onFailure: (err) => new CouldNotAquireDbLockException(type, id, err as Error),
84
- onSuccess: (release) => ({ release })
85
- })
86
- .acquireRelease(
87
- (l) => l.release
88
- )
89
- }
90
-
91
- function lockIndexOnDisk(type: string) {
92
- return (id: string) =>
93
- lockFile(getIdxName(type, id))
94
- .mapBoth({
95
- onFailure: (err) => new CouldNotAquireDbLockException(type, id, err as Error),
96
- onSuccess: (release) => ({ release })
97
- })
98
- .acquireRelease(
99
- (l) => l.release
100
- )
101
- }
102
-
103
- function readFile(filePath: string) {
104
- return fu
105
- .readTextFile(filePath)
106
- .catchAll((err) => Effect.die(new ConnectionException(err as Error)))
107
- }
108
-
109
- function find(type: string) {
110
- return (id: string) => {
111
- return tryRead(getFilename(type, id)).map(
112
- (_) => _.map((s) => JSON.parse(s) as CachedRecord<EA>)
113
- )
114
- }
115
- }
116
-
117
- function getIdx(index: Index) {
118
- return readIndex(index).map((idx) => Option.fromNullable(idx[index.key]))
119
- }
120
-
121
- function readIndex(index: Index) {
122
- return tryRead(getIdxName(type, index.doc)).map(
123
- (_) =>
124
- _.match(
125
- { onNone: () => ({} as Record<string, TKey>), onSome: (x) => JSON.parse(x) as Record<string, TKey> }
126
- )
127
- )
128
- }
129
-
130
- function writeIndex(index: Index, content: Record<string, TKey>) {
131
- return pipe(JSON.stringify(content), (serialised) => fu.writeTextFile(getIdxName(type, index.doc), serialised))
132
- }
133
-
134
- function tryRead(filePath: string) {
135
- return fu
136
- .fileExists(filePath)
137
- .flatMap((exists) => !exists ? Effect.sync(() => Option.none()) : readFile(filePath).map(Option.some))
138
- }
139
-
140
- function getFilename(type: string, id: string) {
141
- return `${dir}/v${schemaVersion}.${getRecordName(type, id)}.json`
142
- }
143
-
144
- function getIdxName(type: string, id: string) {
145
- return `${dir}/v${schemaVersion}.${getIndexName(type, id)}.json`
146
- }
147
- }
148
- }
149
-
150
- function lockFile(fileName: string) {
151
- return Effect.tryPromise(() => PLF.lock(fileName).then(flow(Effect.tryPromise, Effect.orDie)))
152
- }
153
-
154
- // TODO: ugh.
155
- let initialised = false
156
- export function initialise(dir: string) {
157
- if (initialised) {
158
- return
159
- }
160
-
161
- if (!fs.existsSync(dir)) {
162
- fs.mkdirSync(dir)
163
- }
164
- initialised = true
165
- }
@@ -1,6 +0,0 @@
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"
@@ -1,78 +0,0 @@
1
- import { flow } from "effect-app/Function"
2
-
3
- import { Effect, Option } from "effect-app"
4
- import type { Equivalence } from "effect-app"
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
- // When we are in-process, we want to share the same Storage
10
- // Do not try this at home.
11
- const storage = makeMap<string, string>()
12
-
13
- const parseSDB = S.decodeUnknown(SerializedDBRecord)
14
-
15
- export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
16
- return <REncode, RDecode, EDecode>(
17
- type: string,
18
- encode: (record: A) => Effect<EA, never, REncode>,
19
- decode: (d: EA) => Effect<A, EDecode, RDecode>
20
- ) => {
21
- return {
22
- find: simpledb.find(find, decode, type),
23
- findBy,
24
- save: simpledb.store(find, store, bogusLock, type)
25
- }
26
-
27
- function find(id: string) {
28
- return storage
29
- .find(getRecordName(type, id))
30
- .map((_) => _.map((s) => JSON.parse(s)))
31
- .flatMapOpt(parseSDB)
32
- .mapOpt(({ data, version }) => ({
33
- data: JSON.parse(data) as EA,
34
- version
35
- }))
36
- }
37
-
38
- function findBy<V extends Partial<A>>(keys: V, eq: Equivalence<V>) {
39
- // Naive implementation, fine for in memory testing purposes.
40
- return Effect
41
- .gen(function*($) {
42
- for (const [, value] of storage) {
43
- const sdb_ = JSON.parse(value)
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)
48
- .filterOrFail((d) => eq(keys, d as unknown as V), () => "not equals")
49
- .exit
50
- )
51
- if (r.isSuccess()) {
52
- return r.value
53
- }
54
- }
55
- return null
56
- })
57
- .map(Option.fromNullable)
58
- }
59
-
60
- function store(record: A, currentVersion: Option<Version>) {
61
- const version = currentVersion
62
- .map((cv) => (parseInt(cv) + 1).toString())
63
- .getOrElse(() => "1")
64
-
65
- const getData = flow(
66
- encode,
67
- (_) => _.map(JSON.stringify).map((data) => JSON.stringify({ version, timestamp: new Date(), data }))
68
- )
69
- return getData(record)
70
- .flatMap((serialised) => storage.set(getRecordName(type, record.id), serialised))
71
- .map(() => ({ version, data: record } as CachedRecord<A>))
72
- }
73
- }
74
- }
75
-
76
- function bogusLock() {
77
- return Effect.unit.acquireRelease(() => Effect.unit)
78
- }
@@ -1,107 +0,0 @@
1
- import { MongoClient } from "@effect-app/infra-adapters/mongo-client"
2
- import { Effect, Option } from "effect-app"
3
- import type { IndexDescription, InsertOneOptions } from "mongodb"
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
- MongoClient
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<EA, never, REncode>,
23
- decode: (d: EA) => Effect<A, EDecode, RDecode>,
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 MongoClient
35
- .flatMap(({ db }) =>
36
- Effect.tryPromise(() =>
37
- db
38
- .collection(type)
39
- .findOne<{ _id: TKey; version: Version; data: EA }>({ _id: { equals: id } })
40
- )
41
- )
42
- .map(Option.fromNullable)
43
- .mapOpt(({ data, version }) => ({ version, data } as CachedRecord<EA>))
44
- }
45
-
46
- function findBy(keys: Record<string, string>) {
47
- return MongoClient
48
- .flatMap(({ db }) =>
49
- Effect.tryPromise(() => db.collection(type).findOne<{ _id: TKey }>(keys, { projection: { _id: 1 } }))
50
- )
51
- .map(Option.fromNullable)
52
- .mapOpt(({ _id }) => _id)
53
- }
54
-
55
- function store(record: A, currentVersion: Option<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* $(MongoClient)
62
- const data = yield* $(encode(record))
63
- yield* $(
64
- currentVersion.match(
65
- {
66
- onNone: () =>
67
- Effect
68
- .tryPromise(() =>
69
- db
70
- .collection(type)
71
- .insertOne(
72
- { _id: record.id as any, version, timestamp: new Date(), data },
73
- {
74
- checkKeys: false // support for keys with `.` and `$`. NOTE: you can write them, read them, but NOT query for them.
75
- } as InsertOneOptions
76
- )
77
- )
78
- .asUnit
79
- .orDie,
80
- onSome: (currentVersion) =>
81
- Effect
82
- .tryPromise(() =>
83
- db.collection(type).replaceOne(
84
- { _id: record.id as any, version: currentVersion },
85
- {
86
- version,
87
- timestamp: new Date(),
88
- data
89
- },
90
- { upsert: false }
91
- )
92
- )
93
- .orDie
94
- .flatMap((x) => {
95
- if (!x.modifiedCount) {
96
- return new OptimisticLockException(type, record.id)
97
- }
98
- return Effect.unit
99
- })
100
- }
101
- )
102
- )
103
- return { version, data: record } as CachedRecord<A>
104
- })
105
- }
106
- }
107
- }
@@ -1,202 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- import { flow } from "effect-app/Function"
4
- import { RedisClient } from "@effect-app/infra-adapters/redis-client"
5
- import { Effect } from "effect-app"
6
- import type { Option } from "effect-app"
7
- import * as S from "effect-app/Schema"
8
- import type { Lock } from "redlock"
9
- import type { CachedRecord, DBRecord, Index } from "./shared.js"
10
- import { ConnectionException, CouldNotAquireDbLockException, getIndexName, getRecordName } from "./shared.js"
11
- import * as simpledb from "./simpledb.js"
12
-
13
- const ttl = 10 * 1000
14
-
15
- export function createContext<TKey extends string, EA, A extends DBRecord<TKey>>() {
16
- return <REncode, RDecode, EDecode>(
17
- type: string,
18
- encode: (record: A) => Effect<EA, never, REncode>,
19
- decode: (d: EA) => Effect<A, EDecode, RDecode>,
20
- schemaVersion: string,
21
- makeIndexKey: (r: A) => Index
22
- ) => {
23
- const getData = flow(encode, (_) => _.map(JSON.stringify))
24
- return {
25
- find: simpledb.find(find, decode, type),
26
- findByIndex: getIdx,
27
- save: simpledb.store(find, store, lockRedisRecord, type)
28
- }
29
-
30
- function find(id: string) {
31
- return Effect
32
- .flatMap(RedisClient, (_) => _.hmgetAll(getKey(id)))
33
- .flatMapOpt((v) =>
34
- RedisSerializedDBRecord
35
- .decodeUnknown(v)
36
- .map(({ data, version }) => ({
37
- data: JSON.parse(data) as EA,
38
- version
39
- }))
40
- .mapError((e) => new ConnectionException(new Error(e.toString())))
41
- )
42
- .orDie
43
- }
44
- function store(record: A, currentVersion: Option<string>) {
45
- const version = currentVersion
46
- .map((cv) => (parseInt(cv) + 1).toString())
47
- .getOrElse(() => "1")
48
- return currentVersion.match(
49
- {
50
- onNone: () =>
51
- lockIndex(record)
52
- .zipRight(
53
- getIndex(record)
54
- .zipRightOpt(
55
- Effect.fail(() => new Error("Combination already exists, abort"))
56
- )
57
- .zipRight(getData(record))
58
- // TODO: instead use MULTI & EXEC to make it in one command?
59
- .flatMap((data) =>
60
- hmSetRec(
61
- getKey(record.id),
62
- new RedisSerializedDBRecord({
63
- version,
64
- timestamp: new Date(),
65
- data
66
- })
67
- )
68
- )
69
- .zipRight(setIndex(record))
70
- .orDie
71
- .map(() => ({ version, data: record } as CachedRecord<A>))
72
- )
73
- .scoped,
74
- onSome: () =>
75
- getData(record)
76
- .flatMap((data) =>
77
- hmSetRec(
78
- getKey(record.id),
79
- new RedisSerializedDBRecord({
80
- version,
81
- timestamp: new Date(),
82
- data
83
- })
84
- )
85
- )
86
- .orDie
87
- .map(() => ({ version, data: record } as CachedRecord<A>))
88
- }
89
- )
90
- }
91
-
92
- function getIndex(record: A) {
93
- const index = makeIndexKey(record)
94
- return getIdx(index)
95
- }
96
-
97
- function setIndex(record: A) {
98
- const index = makeIndexKey(record)
99
- return setIdx(index, record)
100
- }
101
-
102
- function lockIndex(record: A) {
103
- const index = makeIndexKey(record)
104
- return lockRedisIdx(index)
105
- }
106
-
107
- function getIdx(index: Index) {
108
- return Effect.flatMap(RedisClient, (_) => _.hget(getIdxKey(index), index.key).map((_) => _.map((i) => i as TKey)))
109
- }
110
-
111
- function setIdx(index: Index, r: A) {
112
- return Effect.flatMap(RedisClient, (_) => _.hset(getIdxKey(index), index.key, r.id))
113
- }
114
-
115
- function lockRedisIdx(index: Index) {
116
- const lockKey = getIdxLockKey(index)
117
- // acquire
118
- return Effect
119
- .flatMap(
120
- RedisClient,
121
- ({ lock }) => Effect.tryPromise(() => lock.lock(lockKey, ttl) as unknown as Promise<Lock>)
122
- )
123
- .mapBoth({
124
- onFailure: (err) => new CouldNotAquireDbLockException(type, lockKey, err as Error),
125
- // release
126
- onSuccess: (lock) => ({
127
- release: Effect
128
- .tryPromise(() => lock.unlock() as unknown as Promise<void>)
129
- .orDie
130
- })
131
- })
132
- .acquireRelease(
133
- (l) => l.release
134
- )
135
- }
136
-
137
- function lockRedisRecord(id: string) {
138
- // acquire
139
- return Effect
140
- .flatMap(RedisClient, ({ lock }) =>
141
- Effect.tryPromise(
142
- () => lock.lock(getLockKey(id), ttl) as unknown as Promise<Lock>
143
- ))
144
- .mapBoth({
145
- onFailure: (err) => new CouldNotAquireDbLockException(type, id, err as Error),
146
- // release
147
- onSuccess: (lock) => ({
148
- // TODO
149
- release: Effect
150
- .tryPromise(() => lock.unlock() as unknown as Promise<void>)
151
- .orDie
152
- })
153
- })
154
- .acquireRelease(
155
- (l) => l.release
156
- )
157
- }
158
-
159
- function getKey(id: string) {
160
- return `v${schemaVersion}.${getRecordName(type, id)}`
161
- }
162
-
163
- function getLockKey(id: string) {
164
- return `v${schemaVersion}.locks.${getRecordName(type, id)}`
165
- }
166
-
167
- function getIdxKey(index: Index) {
168
- return `v${schemaVersion}.${getIndexName(type, index.doc)}`
169
- }
170
- function getIdxLockKey(index: Index) {
171
- return `v${schemaVersion}.locks.${getIndexName(type, index.doc)}_${index.key}`
172
- }
173
- }
174
-
175
- function hmSetRec(key: string, val: RedisSerializedDBRecord) {
176
- const enc = S.encodeSync(RedisSerializedDBRecord)(val)
177
- return Effect.flatMap(RedisClient, ({ client }) =>
178
- Effect
179
- .async<void, ConnectionException>((res) => {
180
- client.hmset(
181
- key,
182
- "version",
183
- enc.version,
184
- "timestamp",
185
- enc.timestamp,
186
- "data",
187
- enc.data,
188
- (err) =>
189
- err
190
- ? res(new ConnectionException(err))
191
- : res(Effect.sync(() => void 0))
192
- )
193
- })
194
- .uninterruptible)
195
- }
196
- }
197
-
198
- export class RedisSerializedDBRecord extends S.Class<RedisSerializedDBRecord>()({
199
- version: S.string,
200
- timestamp: S.Date,
201
- data: S.string
202
- }) {}
@@ -1,117 +0,0 @@
1
- import { Data, Effect, Option } from "effect-app"
2
- import * as S from "effect-app/Schema"
3
-
4
- export class CouldNotAquireDbLockException
5
- extends Data.TaggedError("CouldNotAquireDbLockException")<{ type: string; id: string; error: Error; message: string }>
6
- {
7
- constructor(type: string, id: string, error: Error) {
8
- super({ type, id, error, message: `Couldn't lock db record ${type}: ${id}` })
9
- }
10
- }
11
-
12
- export class OptimisticLockException
13
- extends Data.TaggedError("OptimisticLockException")<{ type: string; id: string; message: string }>
14
- {
15
- constructor(type: string, id: string) {
16
- super({ type, id, message: `Existing ${type} ${id} record changed` })
17
- }
18
- }
19
-
20
- export class ConnectionException extends Data.TaggedError("ConnectionException")<{ cause: Error; message: string }> {
21
- readonly _errorTag = "ConnectionException"
22
- constructor(cause: Error) {
23
- super({ cause, message: "A connection error ocurred" })
24
- }
25
- }
26
-
27
- export interface DBRecord<TKey extends string> {
28
- id: TKey
29
- }
30
-
31
- export class SerializedDBRecord extends S.Class<SerializedDBRecord>()({
32
- version: S.string,
33
- timestamp: S.Date,
34
- data: S.string
35
- }) {}
36
-
37
- // unknown -> string -> SDB?
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- export function makeSerialisedDBRecord(s: S.Schema<any>) {
40
- return S.Struct({
41
- version: S.number,
42
- timestamp: S.Date,
43
- data: s
44
- })
45
- }
46
-
47
- export interface CachedRecord<T> {
48
- version: string
49
- data: T
50
- }
51
-
52
- export interface Index {
53
- doc: string
54
- key: string
55
- }
56
-
57
- export function getIndexName(type: string, id: string) {
58
- return `${type}-idx_${id}`
59
- }
60
-
61
- export function getRecordName(type: string, id: string) {
62
- return `${type}-r_${id}`
63
- }
64
-
65
- export function makeMap<TKey, T>() {
66
- const map = new Map<TKey, T>()
67
- return {
68
- find: (k: TKey) => Effect.sync(() => Option.fromNullable(map.get(k))),
69
- [Symbol.iterator]: () => map[Symbol.iterator](),
70
- set: (k: TKey, v: T) =>
71
- Effect.sync(() => {
72
- map.set(k, v)
73
- })
74
- } as EffectMap<TKey, T>
75
- }
76
-
77
- export interface EffectMap<TKey, T> {
78
- [Symbol.iterator](): IterableIterator<[TKey, T]>
79
- find: (k: TKey) => Effect<Option<T>>
80
- set: (k: TKey, v: T) => Effect<void>
81
- }
82
-
83
- // export function encodeOnlyWhenStrictMatch<A, E>(
84
- // encode: S.HasEncoder<A, E>["encode_"],
85
- // v: A
86
- // ) {
87
- // const e1 = Sync.run(encode(v, "strict"))
88
- // const e2 = Sync.run(encode(v, "classic"))
89
- // try {
90
- // assert.deepStrictEqual(e1, e2)
91
- // } catch (err) {
92
- // throw new Error(
93
- // // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
94
- // "The strict encoding of these objects does not match the classic encoding of these objects. This means that there is a chance of a data-loss, and is probably a programming error\n" +
95
- // err
96
- // )
97
- // }
98
- // return e1
99
- // }
100
-
101
- // export function decodeOnlyWhenStrictMatch<A, E>(
102
- // decode: S.HasDecoder<A, E>["decode_"],
103
- // u: unknown
104
- // ) {
105
- // return pipe(
106
- // decode(u, "strict"),
107
- // Sync.tap((v) =>
108
- // pipe(
109
- // decode(u),
110
- // Sync.tap((v2) => {
111
- // assert.deepStrictEqual(v, v2)
112
- // return Sync.succeed(v2)
113
- // })
114
- // )
115
- // )
116
- // )
117
- // }