@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.
- package/CHANGELOG.md +13 -0
- package/_cjs/errors.cjs +21 -22
- package/_cjs/errors.cjs.map +1 -1
- package/_cjs/lib/api/authorization.jwt.cjs +1 -1
- package/_cjs/{vendor/Cache/Cache/operations/makeWith.cjs → lib/api/codec.cjs} +16 -13
- package/_cjs/lib/api/codec.cjs.map +1 -0
- package/_cjs/{services/memQueue.cjs → lib/api/express/makeJsonSchema.cjs} +42 -22
- package/_cjs/lib/api/express/makeJsonSchema.cjs.map +1 -0
- package/_cjs/lib/api/express/makeOpenApiSpecs.cjs +66 -0
- package/_cjs/lib/api/express/makeOpenApiSpecs.cjs.map +1 -0
- package/_cjs/lib/api/express/schema/jwt.cjs +43 -0
- package/_cjs/lib/api/express/schema/jwt.cjs.map +1 -0
- package/_cjs/lib/api/express/schema/requestHandler.cjs +166 -0
- package/_cjs/lib/api/express/schema/requestHandler.cjs.map +1 -0
- package/_cjs/lib/api/express/schema/routing.cjs +121 -0
- package/_cjs/lib/api/express/schema/routing.cjs.map +1 -0
- package/_cjs/lib/api/routing.cjs +28 -28
- package/_cjs/lib/api/routing.cjs.map +1 -1
- package/_cjs/lib/api/writeDocs.cjs +4 -4
- package/_cjs/lib/api/writeDocs.cjs.map +1 -1
- package/_cjs/services/Store/Disk.cjs +1 -1
- package/_cjs/services/simpledb/cosmosdb.cjs +98 -0
- package/_cjs/services/simpledb/cosmosdb.cjs.map +1 -0
- package/_cjs/services/simpledb/diskdb.cjs +118 -0
- package/_cjs/services/simpledb/diskdb.cjs.map +1 -0
- package/_cjs/services/simpledb/fileutil.cjs +36 -0
- package/_cjs/services/simpledb/fileutil.cjs.map +1 -0
- package/_cjs/{vendor/Cache/Cache/_internal/CacheState.cjs → services/simpledb/index.cjs} +31 -24
- package/_cjs/services/simpledb/index.cjs.map +1 -0
- package/_cjs/services/simpledb/memdb.cjs +74 -0
- package/_cjs/services/simpledb/memdb.cjs.map +1 -0
- package/_cjs/services/simpledb/mongodb.cjs +86 -0
- package/_cjs/services/simpledb/mongodb.cjs.map +1 -0
- package/_cjs/services/simpledb/redisdb.cjs +120 -0
- package/_cjs/services/simpledb/redisdb.cjs.map +1 -0
- package/_cjs/services/simpledb/shared.cjs +80 -0
- package/_cjs/services/simpledb/shared.cjs.map +1 -0
- package/_cjs/services/simpledb/simpledb.cjs +77 -0
- package/_cjs/services/simpledb/simpledb.cjs.map +1 -0
- package/_src/_global.ext.ts +1 -1
- package/_src/errors.ts +12 -3
- package/_src/lib/api/app.ts.bak +125 -0
- package/_src/lib/api/authorization.jwt.ts +1 -1
- package/_src/lib/api/codec.ts +27 -0
- package/_src/lib/api/errors.ts.bak +20 -0
- package/_src/lib/api/express/makeJsonSchema.ts +49 -0
- package/_src/lib/api/express/makeOpenApiSpecs.ts +60 -0
- package/_src/lib/api/express/routing.ts.bak +460 -0
- package/_src/lib/api/express/schema/jwt.ts +38 -0
- package/_src/lib/api/express/schema/requestHandler.ts +360 -0
- package/_src/lib/api/express/schema/routing.ts +202 -0
- package/_src/lib/api/routing.ts +5 -10
- package/_src/lib/api/writeDocs.ts +4 -5
- package/_src/services/Store/Disk.ts +1 -1
- package/_src/services/simpledb/cosmosdb.ts +134 -0
- package/_src/services/simpledb/diskdb.ts +158 -0
- package/_src/services/simpledb/fileutil.ts +28 -0
- package/_src/services/simpledb/index.ts +6 -0
- package/_src/services/simpledb/memdb.ts +79 -0
- package/_src/services/simpledb/mongodb.ts +99 -0
- package/_src/services/simpledb/redisdb.ts +182 -0
- package/_src/services/simpledb/shared.ts +117 -0
- package/_src/services/simpledb/simpledb.ts +117 -0
- package/dist/_global.ext.d.ts +1 -1
- package/dist/_global.ext.d.ts.map +1 -1
- package/dist/errors.d.ts +11 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -3
- package/dist/lib/api/authorization.jwt.d.ts +1 -1
- package/dist/lib/api/authorization.jwt.d.ts.map +1 -1
- package/dist/lib/api/authorization.jwt.js +2 -2
- package/dist/lib/api/codec.d.ts +7 -0
- package/dist/lib/api/codec.d.ts.map +1 -0
- package/dist/lib/api/codec.js +19 -0
- package/dist/lib/api/express/makeJsonSchema.d.ts +30 -0
- package/dist/lib/api/express/makeJsonSchema.d.ts.map +1 -0
- package/dist/lib/api/express/makeJsonSchema.js +40 -0
- package/dist/lib/api/express/makeOpenApiSpecs.d.ts +55 -0
- package/dist/lib/api/express/makeOpenApiSpecs.d.ts.map +1 -0
- package/dist/lib/api/express/makeOpenApiSpecs.js +56 -0
- package/dist/lib/api/express/schema/jwt.d.ts +5 -0
- package/dist/lib/api/express/schema/jwt.d.ts.map +1 -0
- package/dist/lib/api/express/schema/jwt.js +32 -0
- package/dist/lib/api/express/schema/requestHandler.d.ts +71 -0
- package/dist/lib/api/express/schema/requestHandler.d.ts.map +1 -0
- package/dist/lib/api/express/schema/requestHandler.js +140 -0
- package/dist/lib/api/express/schema/routing.d.ts +45 -0
- package/dist/lib/api/express/schema/routing.d.ts.map +1 -0
- package/dist/lib/api/express/schema/routing.js +95 -0
- package/dist/lib/api/routing.d.ts +4 -4
- package/dist/lib/api/routing.d.ts.map +1 -1
- package/dist/lib/api/routing.js +29 -28
- package/dist/lib/api/writeDocs.d.ts +1 -1
- package/dist/lib/api/writeDocs.d.ts.map +1 -1
- package/dist/lib/api/writeDocs.js +5 -5
- package/dist/services/Store/Disk.js +2 -2
- package/dist/services/simpledb/cosmosdb.d.ts +13 -0
- package/dist/services/simpledb/cosmosdb.d.ts.map +1 -0
- package/dist/services/simpledb/cosmosdb.js +88 -0
- package/dist/services/simpledb/diskdb.d.ts +12 -0
- package/dist/services/simpledb/diskdb.d.ts.map +1 -0
- package/dist/services/simpledb/diskdb.js +96 -0
- package/dist/services/simpledb/fileutil.d.ts +7 -0
- package/dist/services/simpledb/fileutil.d.ts.map +1 -0
- package/dist/services/simpledb/fileutil.js +25 -0
- package/dist/services/simpledb/index.d.ts +7 -0
- package/dist/services/simpledb/index.d.ts.map +1 -0
- package/dist/services/simpledb/index.js +7 -0
- package/dist/services/simpledb/memdb.d.ts +12 -0
- package/dist/services/simpledb/memdb.d.ts.map +1 -0
- package/dist/services/simpledb/memdb.js +55 -0
- package/dist/services/simpledb/mongodb.d.ts +13 -0
- package/dist/services/simpledb/mongodb.d.ts.map +1 -0
- package/dist/services/simpledb/mongodb.js +55 -0
- package/dist/services/simpledb/redisdb.d.ts +25 -0
- package/dist/services/simpledb/redisdb.d.ts.map +1 -0
- package/dist/services/simpledb/redisdb.js +105 -0
- package/dist/services/simpledb/shared.d.ts +63 -0
- package/dist/services/simpledb/shared.d.ts.map +1 -0
- package/dist/services/simpledb/shared.js +65 -0
- package/dist/services/simpledb/simpledb.d.ts +23 -0
- package/dist/services/simpledb/simpledb.d.ts.map +1 -0
- package/dist/services/simpledb/simpledb.js +56 -0
- package/package.json +126 -238
- package/_cjs/ext.cjs +0 -4
- package/_cjs/ext.cjs.map +0 -1
- package/_cjs/lib/fileUtil.cjs +0 -36
- package/_cjs/lib/fileUtil.cjs.map +0 -1
- package/_cjs/services/CUPS/service.cjs +0 -15
- package/_cjs/services/CUPS/service.cjs.map +0 -1
- package/_cjs/services/CUPS.cjs +0 -113
- package/_cjs/services/CUPS.cjs.map +0 -1
- package/_cjs/services/Cache.cjs +0 -39
- package/_cjs/services/Cache.cjs.map +0 -1
- package/_cjs/services/ServiceBus.cjs +0 -69
- package/_cjs/services/ServiceBus.cjs.map +0 -1
- package/_cjs/services/memQueue.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/AtomicBoolean.cjs +0 -14
- package/_cjs/vendor/Cache/Cache/_internal/AtomicBoolean.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/AtomicNumber.cjs +0 -29
- package/_cjs/vendor/Cache/Cache/_internal/AtomicNumber.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/AtomicReference.cjs +0 -32
- package/_cjs/vendor/Cache/Cache/_internal/AtomicReference.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/CacheInternal.cjs +0 -266
- package/_cjs/vendor/Cache/Cache/_internal/CacheInternal.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/CacheState.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/KeySet.cjs +0 -67
- package/_cjs/vendor/Cache/Cache/_internal/KeySet.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/MapKey.cjs +0 -44
- package/_cjs/vendor/Cache/Cache/_internal/MapKey.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/_internal/MapValue.cjs +0 -33
- package/_cjs/vendor/Cache/Cache/_internal/MapValue.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/definition.cjs +0 -13
- package/_cjs/vendor/Cache/Cache/definition.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/operations/make.cjs +0 -20
- package/_cjs/vendor/Cache/Cache/operations/make.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/operations/makeWith.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache/operations.cjs +0 -28
- package/_cjs/vendor/Cache/Cache/operations.cjs.map +0 -1
- package/_cjs/vendor/Cache/Cache.cjs +0 -28
- package/_cjs/vendor/Cache/Cache.cjs.map +0 -1
- package/_cjs/vendor/Cache/CacheStats/definition.cjs +0 -13
- package/_cjs/vendor/Cache/CacheStats/definition.cjs.map +0 -1
- package/_cjs/vendor/Cache/CacheStats.cjs +0 -17
- package/_cjs/vendor/Cache/CacheStats.cjs.map +0 -1
- package/_cjs/vendor/Cache/EntryStats/definition.cjs +0 -11
- package/_cjs/vendor/Cache/EntryStats/definition.cjs.map +0 -1
- package/_cjs/vendor/Cache/EntryStats.cjs +0 -17
- package/_cjs/vendor/Cache/EntryStats.cjs.map +0 -1
- package/_cjs/vendor/Cache/Lookup/definition.cjs +0 -6
- package/_cjs/vendor/Cache/Lookup/definition.cjs.map +0 -1
- package/_cjs/vendor/Cache/Lookup.cjs +0 -17
- package/_cjs/vendor/Cache/Lookup.cjs.map +0 -1
- package/_cjs/vendor/Cache/index.cjs +0 -17
- package/_cjs/vendor/Cache/index.cjs.map +0 -1
- package/_src/ext.ts +0 -1
- package/_src/lib/fileUtil.ts +0 -56
- package/_src/services/CUPS/service.ts +0 -16
- package/_src/services/CUPS.ts +0 -105
- package/_src/services/Cache.ts +0 -45
- package/_src/services/ServiceBus.ts +0 -111
- package/_src/services/memQueue.ts +0 -30
- package/_src/vendor/Cache/Cache/_internal/AtomicBoolean.ts +0 -7
- package/_src/vendor/Cache/Cache/_internal/AtomicNumber.ts +0 -28
- package/_src/vendor/Cache/Cache/_internal/AtomicReference.ts +0 -30
- package/_src/vendor/Cache/Cache/_internal/CacheInternal.ts +0 -293
- package/_src/vendor/Cache/Cache/_internal/CacheState.ts +0 -56
- package/_src/vendor/Cache/Cache/_internal/KeySet.ts +0 -58
- package/_src/vendor/Cache/Cache/_internal/MapKey.ts +0 -43
- package/_src/vendor/Cache/Cache/_internal/MapValue.ts +0 -39
- package/_src/vendor/Cache/Cache/definition.ts +0 -112
- package/_src/vendor/Cache/Cache/operations/make.ts +0 -17
- package/_src/vendor/Cache/Cache/operations/makeWith.ts +0 -32
- package/_src/vendor/Cache/Cache/operations.ts +0 -4
- package/_src/vendor/Cache/Cache.ts +0 -4
- package/_src/vendor/Cache/CacheStats/definition.ts +0 -23
- package/_src/vendor/Cache/CacheStats.ts +0 -3
- package/_src/vendor/Cache/EntryStats/definition.ts +0 -19
- package/_src/vendor/Cache/EntryStats.ts +0 -3
- package/_src/vendor/Cache/Lookup/definition.ts +0 -15
- package/_src/vendor/Cache/Lookup.ts +0 -3
- package/_src/vendor/Cache/global.ts.bak +0 -23
- package/_src/vendor/Cache/index.ts +0 -4
- package/dist/ext.d.ts +0 -2
- package/dist/ext.d.ts.map +0 -1
- package/dist/ext.js +0 -2
- package/dist/lib/fileUtil.d.ts +0 -22
- package/dist/lib/fileUtil.d.ts.map +0 -1
- package/dist/lib/fileUtil.js +0 -23
- package/dist/services/CUPS/service.d.ts +0 -19
- package/dist/services/CUPS/service.d.ts.map +0 -1
- package/dist/services/CUPS/service.js +0 -5
- package/dist/services/CUPS.d.ts +0 -10
- package/dist/services/CUPS.d.ts.map +0 -1
- package/dist/services/CUPS.js +0 -89
- package/dist/services/Cache.d.ts +0 -28
- package/dist/services/Cache.d.ts.map +0 -1
- package/dist/services/Cache.js +0 -28
- package/dist/services/ServiceBus.d.ts +0 -26
- package/dist/services/ServiceBus.d.ts.map +0 -1
- package/dist/services/ServiceBus.js +0 -53
- package/dist/services/memQueue.d.ts +0 -18
- package/dist/services/memQueue.d.ts.map +0 -1
- package/dist/services/memQueue.js +0 -24
- package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.d.ts +0 -5
- package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/AtomicBoolean.js +0 -7
- package/dist/vendor/Cache/Cache/_internal/AtomicNumber.d.ts +0 -8
- package/dist/vendor/Cache/Cache/_internal/AtomicNumber.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/AtomicNumber.js +0 -22
- package/dist/vendor/Cache/Cache/_internal/AtomicReference.d.ts +0 -10
- package/dist/vendor/Cache/Cache/_internal/AtomicReference.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/AtomicReference.js +0 -25
- package/dist/vendor/Cache/Cache/_internal/CacheInternal.d.ts +0 -51
- package/dist/vendor/Cache/Cache/_internal/CacheInternal.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/CacheInternal.js +0 -253
- package/dist/vendor/Cache/Cache/_internal/CacheState.d.ts +0 -33
- package/dist/vendor/Cache/Cache/_internal/CacheState.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/CacheState.js +0 -21
- package/dist/vendor/Cache/Cache/_internal/KeySet.d.ts +0 -20
- package/dist/vendor/Cache/Cache/_internal/KeySet.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/KeySet.js +0 -61
- package/dist/vendor/Cache/Cache/_internal/MapKey.d.ts +0 -24
- package/dist/vendor/Cache/Cache/_internal/MapKey.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/MapKey.js +0 -37
- package/dist/vendor/Cache/Cache/_internal/MapValue.d.ts +0 -32
- package/dist/vendor/Cache/Cache/_internal/MapValue.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/_internal/MapValue.js +0 -24
- package/dist/vendor/Cache/Cache/definition.d.ts +0 -100
- package/dist/vendor/Cache/Cache/definition.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/definition.js +0 -5
- package/dist/vendor/Cache/Cache/operations/make.d.ts +0 -12
- package/dist/vendor/Cache/Cache/operations/make.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/operations/make.js +0 -12
- package/dist/vendor/Cache/Cache/operations/makeWith.d.ts +0 -14
- package/dist/vendor/Cache/Cache/operations/makeWith.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/operations/makeWith.js +0 -15
- package/dist/vendor/Cache/Cache/operations.d.ts +0 -3
- package/dist/vendor/Cache/Cache/operations.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache/operations.js +0 -4
- package/dist/vendor/Cache/Cache.d.ts +0 -3
- package/dist/vendor/Cache/Cache.d.ts.map +0 -1
- package/dist/vendor/Cache/Cache.js +0 -4
- package/dist/vendor/Cache/CacheStats/definition.d.ts +0 -19
- package/dist/vendor/Cache/CacheStats/definition.d.ts.map +0 -1
- package/dist/vendor/Cache/CacheStats/definition.js +0 -6
- package/dist/vendor/Cache/CacheStats.d.ts +0 -2
- package/dist/vendor/Cache/CacheStats.d.ts.map +0 -1
- package/dist/vendor/Cache/CacheStats.js +0 -3
- package/dist/vendor/Cache/EntryStats/definition.d.ts +0 -17
- package/dist/vendor/Cache/EntryStats/definition.d.ts.map +0 -1
- package/dist/vendor/Cache/EntryStats/definition.js +0 -4
- package/dist/vendor/Cache/EntryStats.d.ts +0 -2
- package/dist/vendor/Cache/EntryStats.d.ts.map +0 -1
- package/dist/vendor/Cache/EntryStats.js +0 -3
- package/dist/vendor/Cache/Lookup/definition.d.ts +0 -17
- package/dist/vendor/Cache/Lookup/definition.d.ts.map +0 -1
- package/dist/vendor/Cache/Lookup/definition.js +0 -2
- package/dist/vendor/Cache/Lookup.d.ts +0 -2
- package/dist/vendor/Cache/Lookup.d.ts.map +0 -1
- package/dist/vendor/Cache/Lookup.js +0 -3
- package/dist/vendor/Cache/index.d.ts +0 -5
- package/dist/vendor/Cache/index.d.ts.map +0 -1
- 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,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
|
+
}) {}
|