@effect-app/infra 0.47.2 → 0.49.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 +28 -0
- package/_cjs/_ext/Array.cjs.map +1 -1
- package/_cjs/api/defaultErrorHandler.cjs.map +1 -1
- package/_cjs/api/express/makeJsonSchema.cjs +2 -2
- package/_cjs/api/express/makeJsonSchema.cjs.map +1 -1
- package/_cjs/api/express/schema/jwt.cjs.map +1 -1
- package/_cjs/api/express/schema/requestHandler.cjs.map +1 -1
- package/_cjs/api/express/schema/routing.cjs.map +1 -1
- package/_cjs/api/reportError.cjs.map +1 -1
- package/_cjs/api/routing.cjs.map +1 -1
- package/_cjs/api/setupRequest.cjs.map +1 -1
- package/_cjs/api/writeDocs.cjs.map +1 -1
- package/_cjs/rateLimit.cjs.map +1 -1
- package/_cjs/services/Emailer/Sendgrid.cjs.map +1 -1
- package/_cjs/services/Emailer/fake.cjs.map +1 -1
- package/_cjs/services/Operations/live.cjs.map +1 -1
- package/_cjs/services/Operations/service.cjs.map +1 -1
- package/_cjs/services/QueueMaker/errors.cjs.map +1 -1
- package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
- package/_cjs/services/Repository.cjs +8 -8
- package/_cjs/services/Repository.cjs.map +1 -1
- package/_cjs/services/RepositoryBase.cjs +13 -13
- package/_cjs/services/RepositoryBase.cjs.map +1 -1
- package/_cjs/services/RequestContextContainer.cjs.map +1 -1
- package/_cjs/services/Store/Cosmos.cjs +9 -9
- package/_cjs/services/Store/Cosmos.cjs.map +1 -1
- package/_cjs/services/Store/Cosmos.test.cjs.map +1 -1
- package/_cjs/services/Store/Disk.cjs.map +1 -1
- package/_cjs/services/Store/Memory.cjs +42 -42
- package/_cjs/services/Store/Memory.cjs.map +1 -1
- package/_cjs/services/Store/Redis.cjs +13 -13
- package/_cjs/services/Store/Redis.cjs.map +1 -1
- package/_cjs/services/Store/codeFilter.test.cjs.map +1 -1
- package/_cjs/services/Store/index.cjs.map +1 -1
- package/_cjs/services/Store/service.cjs.map +1 -1
- package/_cjs/services/Store/utils.cjs +2 -3
- package/_cjs/services/Store/utils.cjs.map +1 -1
- package/_cjs/services/simpledb/cosmosdb.cjs +12 -4
- package/_cjs/services/simpledb/cosmosdb.cjs.map +1 -1
- package/_cjs/services/simpledb/diskdb.cjs.map +1 -1
- package/_cjs/services/simpledb/memdb.cjs.map +1 -1
- package/_cjs/services/simpledb/mongodb.cjs +15 -5
- package/_cjs/services/simpledb/mongodb.cjs.map +1 -1
- package/_cjs/services/simpledb/redisdb.cjs +13 -7
- package/_cjs/services/simpledb/redisdb.cjs.map +1 -1
- package/_cjs/services/simpledb/simpledb.cjs.map +1 -1
- package/_src/_ext/Array.ts +3 -2
- package/_src/api/defaultErrorHandler.ts +14 -12
- package/_src/api/express/makeJsonSchema.ts +6 -5
- package/_src/api/express/schema/jwt.ts +1 -1
- package/_src/api/express/schema/requestHandler.ts +112 -100
- package/_src/api/express/schema/routing.ts +61 -55
- package/_src/api/reportError.ts +16 -12
- package/_src/api/routing.ts +157 -135
- package/_src/api/setupRequest.ts +3 -1
- package/_src/api/writeDocs.ts +4 -3
- package/_src/rateLimit.ts +21 -14
- package/_src/services/Emailer/Sendgrid.ts +11 -10
- package/_src/services/Emailer/fake.ts +2 -1
- package/_src/services/Operations/live.ts +18 -12
- package/_src/services/Operations/service.ts +26 -17
- package/_src/services/QueueMaker/errors.ts +7 -6
- package/_src/services/QueueMaker/memQueue.ts +13 -11
- package/_src/services/QueueMaker/sbqueue.ts +16 -14
- package/_src/services/Repository.ts +53 -51
- package/_src/services/RepositoryBase.ts +33 -28
- package/_src/services/RequestContextContainer.ts +4 -3
- package/_src/services/Store/Cosmos.test.ts +31 -29
- package/_src/services/Store/Cosmos.ts +203 -180
- package/_src/services/Store/Disk.ts +19 -18
- package/_src/services/Store/Memory.ts +37 -32
- package/_src/services/Store/Redis.ts +31 -27
- package/_src/services/Store/codeFilter.test.ts +26 -16
- package/_src/services/Store/index.ts +26 -24
- package/_src/services/Store/service.ts +5 -5
- package/_src/services/Store/utils.ts +27 -24
- package/_src/services/simpledb/cosmosdb.ts +61 -51
- package/_src/services/simpledb/diskdb.ts +38 -31
- package/_src/services/simpledb/memdb.ts +25 -21
- package/_src/services/simpledb/mongodb.ts +40 -35
- package/_src/services/simpledb/redisdb.ts +66 -55
- package/_src/services/simpledb/simpledb.ts +32 -30
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/Array.js +2 -2
- package/dist/api/defaultErrorHandler.d.ts.map +1 -1
- package/dist/api/defaultErrorHandler.js +5 -5
- package/dist/api/express/makeJsonSchema.d.ts +0 -1
- package/dist/api/express/makeJsonSchema.d.ts.map +1 -1
- package/dist/api/express/makeJsonSchema.js +4 -4
- package/dist/api/express/schema/jwt.js +2 -2
- package/dist/api/express/schema/requestHandler.d.ts.map +1 -1
- package/dist/api/express/schema/requestHandler.js +17 -17
- package/dist/api/express/schema/routing.d.ts.map +1 -1
- package/dist/api/express/schema/routing.js +13 -11
- package/dist/api/reportError.d.ts.map +1 -1
- package/dist/api/reportError.js +6 -6
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +29 -25
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +2 -2
- package/dist/api/writeDocs.d.ts.map +1 -1
- package/dist/api/writeDocs.js +5 -4
- package/dist/rateLimit.d.ts +4 -4
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -4
- package/dist/services/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/services/Emailer/Sendgrid.js +9 -9
- package/dist/services/Emailer/fake.d.ts.map +1 -1
- package/dist/services/Emailer/fake.js +1 -1
- package/dist/services/Operations/live.d.ts.map +1 -1
- package/dist/services/Operations/live.js +11 -8
- package/dist/services/Operations/service.d.ts.map +1 -1
- package/dist/services/Operations/service.js +10 -4
- package/dist/services/QueueMaker/errors.d.ts.map +1 -1
- package/dist/services/QueueMaker/errors.js +5 -5
- package/dist/services/QueueMaker/memQueue.d.ts +1 -1
- package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/memQueue.js +5 -4
- package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/sbqueue.js +5 -5
- package/dist/services/Repository.d.ts +34 -35
- package/dist/services/Repository.d.ts.map +1 -1
- package/dist/services/Repository.js +16 -16
- package/dist/services/RepositoryBase.d.ts +4 -4
- package/dist/services/RepositoryBase.d.ts.map +1 -1
- package/dist/services/RepositoryBase.js +25 -22
- package/dist/services/RequestContextContainer.d.ts.map +1 -1
- package/dist/services/RequestContextContainer.js +3 -3
- package/dist/services/Store/Cosmos.d.ts +3 -3
- package/dist/services/Store/Cosmos.d.ts.map +1 -1
- package/dist/services/Store/Cosmos.js +53 -39
- package/dist/services/Store/Disk.d.ts.map +1 -1
- package/dist/services/Store/Disk.js +18 -18
- package/dist/services/Store/Memory.d.ts +2 -3
- package/dist/services/Store/Memory.d.ts.map +1 -1
- package/dist/services/Store/Memory.js +44 -44
- package/dist/services/Store/Redis.d.ts +4 -4
- package/dist/services/Store/Redis.d.ts.map +1 -1
- package/dist/services/Store/Redis.js +19 -17
- package/dist/services/Store/index.d.ts.map +1 -1
- package/dist/services/Store/index.js +4 -4
- package/dist/services/Store/service.d.ts +4 -5
- package/dist/services/Store/service.d.ts.map +1 -1
- package/dist/services/Store/service.js +3 -3
- package/dist/services/Store/utils.d.ts +2 -2
- package/dist/services/Store/utils.d.ts.map +1 -1
- package/dist/services/Store/utils.js +16 -14
- package/dist/services/simpledb/cosmosdb.d.ts.map +1 -1
- package/dist/services/simpledb/cosmosdb.js +16 -12
- package/dist/services/simpledb/diskdb.d.ts.map +1 -1
- package/dist/services/simpledb/diskdb.js +15 -15
- package/dist/services/simpledb/memdb.d.ts.map +1 -1
- package/dist/services/simpledb/memdb.js +6 -6
- package/dist/services/simpledb/mongodb.d.ts +5 -5
- package/dist/services/simpledb/mongodb.d.ts.map +1 -1
- package/dist/services/simpledb/mongodb.js +9 -9
- package/dist/services/simpledb/redisdb.d.ts +4 -4
- package/dist/services/simpledb/redisdb.d.ts.map +1 -1
- package/dist/services/simpledb/redisdb.js +18 -18
- package/dist/services/simpledb/simpledb.d.ts.map +1 -1
- package/dist/services/simpledb/simpledb.js +9 -9
- package/package.json +11 -11
|
@@ -19,9 +19,9 @@ function makeDiskStoreInt<Id extends string, PM extends PersistenceModelType<Id>
|
|
|
19
19
|
const fsStore = {
|
|
20
20
|
get: fu
|
|
21
21
|
.readTextFile(file)
|
|
22
|
-
.map(x => JSON.parse(x) as PM[])
|
|
22
|
+
.map((x) => JSON.parse(x) as PM[])
|
|
23
23
|
.orDie,
|
|
24
|
-
setRaw: (v: Iterable<PM>) => JSON.stringify([...v], undefined, 2)["|>"](json => fu.writeTextFile(file, json))
|
|
24
|
+
setRaw: (v: Iterable<PM>) => JSON.stringify([...v], undefined, 2)["|>"]((json) => fu.writeTextFile(file, json))
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const store = yield* $(
|
|
@@ -42,19 +42,19 @@ function makeDiskStoreInt<Id extends string, PM extends PersistenceModelType<Id>
|
|
|
42
42
|
...store,
|
|
43
43
|
batchSet: flow(
|
|
44
44
|
store.batchSet,
|
|
45
|
-
t => t.tap(() => flushToDisk.tapErrorCause(err => Effect(console.error(err))).forkDaemon)
|
|
45
|
+
(t) => t.tap(() => flushToDisk.tapErrorCause((err) => Effect(console.error(err))).forkDaemon)
|
|
46
46
|
),
|
|
47
47
|
bulkSet: flow(
|
|
48
48
|
store.bulkSet,
|
|
49
|
-
t => t.tap(() => flushToDisk.tapErrorCause(err => Effect(console.error(err))).forkDaemon)
|
|
49
|
+
(t) => t.tap(() => flushToDisk.tapErrorCause((err) => Effect(console.error(err))).forkDaemon)
|
|
50
50
|
),
|
|
51
51
|
set: flow(
|
|
52
52
|
store.set,
|
|
53
|
-
t => t.tap(() => flushToDisk.tapErrorCause(err => Effect(console.error(err))).forkDaemon)
|
|
53
|
+
(t) => t.tap(() => flushToDisk.tapErrorCause((err) => Effect(console.error(err))).forkDaemon)
|
|
54
54
|
),
|
|
55
55
|
remove: flow(
|
|
56
56
|
store.remove,
|
|
57
|
-
t => t.tap(() => flushToDisk.tapErrorCause(err => Effect(console.error(err))).forkDaemon)
|
|
57
|
+
(t) => t.tap(() => flushToDisk.tapErrorCause((err) => Effect(console.error(err))).forkDaemon)
|
|
58
58
|
)
|
|
59
59
|
} satisfies Store<PM, Id>
|
|
60
60
|
})
|
|
@@ -79,7 +79,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
79
79
|
const storesSem = Semaphore.unsafeMake(1)
|
|
80
80
|
const primary = yield* $(makeDiskStoreInt(prefix, "primary", dir, name, seed))
|
|
81
81
|
const stores = new Map<string, Store<PM, Id>>([["primary", primary]])
|
|
82
|
-
const getStore = !config?.allowNamespace ? Effect.succeed(primary) : storeId.get.flatMap(namespace => {
|
|
82
|
+
const getStore = !config?.allowNamespace ? Effect.succeed(primary) : storeId.get.flatMap((namespace) => {
|
|
83
83
|
const store = stores.get(namespace)
|
|
84
84
|
if (store) {
|
|
85
85
|
return Effect.succeed(store)
|
|
@@ -91,7 +91,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
91
91
|
Effect.suspend(() => {
|
|
92
92
|
const existing = stores.get(namespace)
|
|
93
93
|
if (existing) return Effect(existing)
|
|
94
|
-
return makeDiskStoreInt<Id, PM>(prefix, namespace, dir, name, seed).tap(store =>
|
|
94
|
+
return makeDiskStoreInt<Id, PM>(prefix, namespace, dir, name, seed).tap((store) =>
|
|
95
95
|
Effect.sync(() => stores.set(namespace, store))
|
|
96
96
|
)
|
|
97
97
|
})
|
|
@@ -99,14 +99,14 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
const s: Store<PM, Id> = {
|
|
102
|
-
all: getStore.flatMap(_ => _.all),
|
|
103
|
-
find: (...args) => getStore.flatMap(_ => _.find(...args)),
|
|
104
|
-
filter: (...args) => getStore.flatMap(_ => _.filter(...args)),
|
|
105
|
-
filterJoinSelect: (...args) => getStore.flatMap(_ => _.filterJoinSelect(...args)),
|
|
106
|
-
set: (...args) => getStore.flatMap(_ => _.set(...args)),
|
|
107
|
-
batchSet: (...args) => getStore.flatMap(_ => _.batchSet(...args)),
|
|
108
|
-
bulkSet: (...args) => getStore.flatMap(_ => _.bulkSet(...args)),
|
|
109
|
-
remove: (...args) => getStore.flatMap(_ => _.remove(...args))
|
|
102
|
+
all: getStore.flatMap((_) => _.all),
|
|
103
|
+
find: (...args) => getStore.flatMap((_) => _.find(...args)),
|
|
104
|
+
filter: (...args) => getStore.flatMap((_) => _.filter(...args)),
|
|
105
|
+
filterJoinSelect: (...args) => getStore.flatMap((_) => _.filterJoinSelect(...args)),
|
|
106
|
+
set: (...args) => getStore.flatMap((_) => _.set(...args)),
|
|
107
|
+
batchSet: (...args) => getStore.flatMap((_) => _.batchSet(...args)),
|
|
108
|
+
bulkSet: (...args) => getStore.flatMap((_) => _.bulkSet(...args)),
|
|
109
|
+
remove: (...args) => getStore.flatMap((_) => _.remove(...args))
|
|
110
110
|
}
|
|
111
111
|
return s
|
|
112
112
|
})
|
|
@@ -115,7 +115,8 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export function DiskStoreLive(config: Config<StorageConfig>, dir: string) {
|
|
118
|
-
return config
|
|
119
|
-
.
|
|
118
|
+
return config
|
|
119
|
+
.config
|
|
120
|
+
.flatMap((_) => makeDiskStore(_, dir))
|
|
120
121
|
.toLayer(StoreMaker)
|
|
121
122
|
}
|
|
@@ -6,11 +6,11 @@ import { StoreMaker } from "./service.js"
|
|
|
6
6
|
import { codeFilter, codeFilterJoinSelect, makeETag, makeUpdateETag } from "./utils.js"
|
|
7
7
|
|
|
8
8
|
export function memFilter<T extends { id: string }>(filter: Filter<T>, cursor?: { skip?: number; limit?: number }) {
|
|
9
|
-
return ((c:
|
|
9
|
+
return ((c: T[]): T[] => {
|
|
10
10
|
const skip = cursor?.skip
|
|
11
11
|
const limit = cursor?.limit
|
|
12
12
|
if (!skip && limit === 1) {
|
|
13
|
-
return c.findFirstMap(codeFilter(filter)).map(
|
|
13
|
+
return c.findFirstMap(codeFilter(filter)).map(ReadonlyArray.make).getOrElse(() => [])
|
|
14
14
|
}
|
|
15
15
|
let r = c.filterMap(codeFilter(filter))
|
|
16
16
|
if (skip) {
|
|
@@ -19,17 +19,18 @@ export function memFilter<T extends { id: string }>(filter: Filter<T>, cursor?:
|
|
|
19
19
|
if (limit !== undefined) {
|
|
20
20
|
r = r.take(limit)
|
|
21
21
|
}
|
|
22
|
-
return r
|
|
22
|
+
return r
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const storeId = FiberRef.unsafeMake("primary")
|
|
27
|
-
export const restoreFromRequestContext = RequestContextContainer.get.flatMap(ctx =>
|
|
27
|
+
export const restoreFromRequestContext = RequestContextContainer.get.flatMap((ctx) =>
|
|
28
28
|
storeId.set(ctx.namespace ?? "primary")
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
function logQuery(filter: any, cursor: any) {
|
|
32
|
-
return Effect
|
|
32
|
+
return Effect
|
|
33
|
+
.logDebug("mem query")
|
|
33
34
|
.apply(Effect.logAnnotates({
|
|
34
35
|
query: JSON.stringify(filter, undefined, 2),
|
|
35
36
|
cursor: JSON.stringify(
|
|
@@ -47,47 +48,51 @@ export function makeMemoryStoreInt<Id extends string, PM extends PersistenceMode
|
|
|
47
48
|
return Effect.gen(function*($) {
|
|
48
49
|
const updateETag = makeUpdateETag(name)
|
|
49
50
|
const items_ = yield* $(seed ?? Effect([]))
|
|
50
|
-
const items = new Map(items_.toChunk.map(_ => [_.id, _] as const))
|
|
51
|
+
const items = new Map(items_.toChunk.map((_) => [_.id, _] as const))
|
|
51
52
|
const makeStore = (): ReadonlyMap<Id, PM> => new Map([...items.entries()].map(([id, e]) => [id, makeETag(e)]))
|
|
52
53
|
const store = Ref.unsafeMake(makeStore())
|
|
53
54
|
const sem = Semaphore.unsafeMake(1)
|
|
54
55
|
const withPermit = sem.withPermits(1)
|
|
55
|
-
const values = store.get.map(s => s.values())
|
|
56
|
-
const all = values.map(
|
|
56
|
+
const values = store.get.map((s) => s.values())
|
|
57
|
+
const all = values.map(ReadonlyArray.fromIterable)
|
|
57
58
|
const batchSet = (items: NonEmptyReadonlyArray<PM>) =>
|
|
58
59
|
items
|
|
59
|
-
.forEachEffect(i => s.find(i.id).flatMap(current => updateETag(i, current)))
|
|
60
|
-
.tap(items =>
|
|
61
|
-
store
|
|
62
|
-
.
|
|
60
|
+
.forEachEffect((i) => s.find(i.id).flatMap((current) => updateETag(i, current)))
|
|
61
|
+
.tap((items) =>
|
|
62
|
+
store
|
|
63
|
+
.get
|
|
64
|
+
.map((m) => {
|
|
63
65
|
const mut = m as Map<Id, PM>
|
|
64
|
-
items.forEach(e => mut.set(e.id, e))
|
|
66
|
+
items.forEach((e) => mut.set(e.id, e))
|
|
65
67
|
return mut
|
|
66
68
|
})
|
|
67
|
-
.flatMap(_ => store.set(_))
|
|
69
|
+
.flatMap((_) => store.set(_))
|
|
68
70
|
)
|
|
69
|
-
.map(_ => _
|
|
71
|
+
.map((_) => _ as NonEmptyArray<PM>)
|
|
70
72
|
.apply(withPermit)
|
|
71
73
|
const s: Store<PM, Id> = {
|
|
72
74
|
all,
|
|
73
|
-
find: id => store.get.map(_ => Option.fromNullable(_.get(id))),
|
|
75
|
+
find: (id) => store.get.map((_) => Option.fromNullable(_.get(id))),
|
|
74
76
|
filter: (filter: Filter<PM>, cursor?: { skip?: number; limit?: number }) =>
|
|
75
77
|
all
|
|
76
78
|
.tap(() => logQuery(filter, cursor))
|
|
77
79
|
.map(memFilter(filter, cursor)),
|
|
78
80
|
filterJoinSelect: <T extends object>(filter: FilterJoinSelect) =>
|
|
79
|
-
all.map(c => c.flatMap(codeFilterJoinSelect<PM, T>(filter))),
|
|
80
|
-
set: e =>
|
|
81
|
+
all.map((c) => c.flatMap(codeFilterJoinSelect<PM, T>(filter))),
|
|
82
|
+
set: (e) =>
|
|
81
83
|
s
|
|
82
84
|
.find(e.id)
|
|
83
|
-
.flatMap(current => updateETag(e, current))
|
|
84
|
-
.tap(e => store.get.map(_ => new Map([..._, [e.id, e]])).flatMap(_ => store.set(_)))
|
|
85
|
+
.flatMap((current) => updateETag(e, current))
|
|
86
|
+
.tap((e) => store.get.map((_) => new Map([..._, [e.id, e]])).flatMap((_) => store.set(_)))
|
|
85
87
|
.apply(withPermit),
|
|
86
88
|
batchSet,
|
|
87
89
|
bulkSet: batchSet,
|
|
88
90
|
remove: (e: PM) =>
|
|
89
|
-
store
|
|
90
|
-
.
|
|
91
|
+
store
|
|
92
|
+
.get
|
|
93
|
+
.map((_) => new Map([..._].filter(([_]) => _ !== e.id)))
|
|
94
|
+
.flatMap((_) => store.set(_))
|
|
95
|
+
.apply(withPermit)
|
|
91
96
|
}
|
|
92
97
|
return s
|
|
93
98
|
})
|
|
@@ -103,7 +108,7 @@ export const makeMemoryStore = () => ({
|
|
|
103
108
|
const storesSem = Semaphore.unsafeMake(1)
|
|
104
109
|
const primary = yield* $(makeMemoryStoreInt<Id, PM>(name, seed))
|
|
105
110
|
const stores = new Map([["primary", primary]])
|
|
106
|
-
const getStore = !config?.allowNamespace ? Effect.succeed(primary) : storeId.get.flatMap(namespace => {
|
|
111
|
+
const getStore = !config?.allowNamespace ? Effect.succeed(primary) : storeId.get.flatMap((namespace) => {
|
|
107
112
|
const store = stores.get(namespace)
|
|
108
113
|
if (store) {
|
|
109
114
|
return Effect.succeed(store)
|
|
@@ -114,18 +119,18 @@ export const makeMemoryStore = () => ({
|
|
|
114
119
|
return storesSem.withPermits(1)(Effect.suspend(() => {
|
|
115
120
|
const store = stores.get(namespace)
|
|
116
121
|
if (store) return Effect(store)
|
|
117
|
-
return makeMemoryStoreInt(name, seed).tap(store => Effect.sync(() => stores.set(namespace, store)))
|
|
122
|
+
return makeMemoryStoreInt(name, seed).tap((store) => Effect.sync(() => stores.set(namespace, store)))
|
|
118
123
|
}))
|
|
119
124
|
})
|
|
120
125
|
const s: Store<PM, Id> = {
|
|
121
|
-
all: getStore.flatMap(_ => _.all),
|
|
122
|
-
find: (...args) => getStore.flatMap(_ => _.find(...args)),
|
|
123
|
-
filter: (...args) => getStore.flatMap(_ => _.filter(...args)),
|
|
124
|
-
filterJoinSelect: (...args) => getStore.flatMap(_ => _.filterJoinSelect(...args)),
|
|
125
|
-
set: (...args) => getStore.flatMap(_ => _.set(...args)),
|
|
126
|
-
batchSet: (...args) => getStore.flatMap(_ => _.batchSet(...args)),
|
|
127
|
-
bulkSet: (...args) => getStore.flatMap(_ => _.bulkSet(...args)),
|
|
128
|
-
remove: (...args) => getStore.flatMap(_ => _.remove(...args))
|
|
126
|
+
all: getStore.flatMap((_) => _.all),
|
|
127
|
+
find: (...args) => getStore.flatMap((_) => _.find(...args)),
|
|
128
|
+
filter: (...args) => getStore.flatMap((_) => _.filter(...args)),
|
|
129
|
+
filterJoinSelect: (...args) => getStore.flatMap((_) => _.filterJoinSelect(...args)),
|
|
130
|
+
set: (...args) => getStore.flatMap((_) => _.set(...args)),
|
|
131
|
+
batchSet: (...args) => getStore.flatMap((_) => _.batchSet(...args)),
|
|
132
|
+
bulkSet: (...args) => getStore.flatMap((_) => _.bulkSet(...args)),
|
|
133
|
+
remove: (...args) => getStore.flatMap((_) => _.remove(...args))
|
|
129
134
|
}
|
|
130
135
|
return s
|
|
131
136
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import
|
|
2
|
+
import { RedisClient } from "@effect-app/infra-adapters/redis-client"
|
|
3
3
|
import { NotFoundError } from "../../errors.js"
|
|
4
4
|
import { memFilter } from "./Memory.js"
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ import { codeFilterJoinSelect, makeETag, makeUpdateETag } from "./utils.js"
|
|
|
9
9
|
|
|
10
10
|
export function makeRedisStore({ prefix }: StorageConfig) {
|
|
11
11
|
return Effect.gen(function*($) {
|
|
12
|
-
const redis = yield* $(RedisClient
|
|
12
|
+
const redis = yield* $(RedisClient)
|
|
13
13
|
return {
|
|
14
14
|
make: <Id extends string, PM extends PersistenceModelType<Id>>(
|
|
15
15
|
name: string,
|
|
@@ -20,65 +20,69 @@ export function makeRedisStore({ prefix }: StorageConfig) {
|
|
|
20
20
|
const updateETag = makeUpdateETag(name)
|
|
21
21
|
// Very naive implementation of course.
|
|
22
22
|
const key = `${prefix}${name}`
|
|
23
|
-
const current = yield* $(
|
|
23
|
+
const current = yield* $(redis.get(key).orDie.provideService(RedisClient, redis))
|
|
24
24
|
if (!current.isSome()) {
|
|
25
25
|
const m = yield* $(seed ?? Effect([]))
|
|
26
26
|
yield* $(
|
|
27
|
-
|
|
27
|
+
redis
|
|
28
|
+
.set(key, JSON.stringify({ data: [...m].map((e) => makeETag(e)) }))
|
|
28
29
|
.orDie
|
|
29
|
-
.provideService(RedisClient
|
|
30
|
+
.provideService(RedisClient, redis)
|
|
30
31
|
)
|
|
31
32
|
}
|
|
32
|
-
const get =
|
|
33
|
-
.
|
|
33
|
+
const get = redis
|
|
34
|
+
.get(key)
|
|
35
|
+
.flatMap((x) => x.encaseInEffect(() => new NotFoundError("data", "")))
|
|
34
36
|
.orDie
|
|
35
|
-
.map(x => JSON.parse(x) as { data: readonly PM[] })
|
|
36
|
-
.map(_ => _.data)
|
|
37
|
-
.provideService(RedisClient
|
|
37
|
+
.map((x) => JSON.parse(x) as { data: readonly PM[] })
|
|
38
|
+
.map((_) => _.data)
|
|
39
|
+
.provideService(RedisClient, redis)
|
|
38
40
|
|
|
39
|
-
const set = (i: ReadonlyMap<Id, PM>) =>
|
|
41
|
+
const set = (i: ReadonlyMap<Id, PM>) => redis.set(key, JSON.stringify({ data: [...i.values()] })).orDie
|
|
40
42
|
|
|
41
43
|
const sem = Semaphore.unsafeMake(1)
|
|
42
44
|
const withPermit = sem.withPermits(1)
|
|
43
45
|
|
|
44
|
-
const asMap = get.map(x => new Map(x.map(x => [x.id, x] as const)))
|
|
45
|
-
const all = get.map(
|
|
46
|
+
const asMap = get.map((x) => new Map(x.map((x) => [x.id, x] as const)))
|
|
47
|
+
const all = get.map(ReadonlyArray.fromIterable)
|
|
46
48
|
const batchSet = (items: NonEmptyReadonlyArray<PM>) =>
|
|
47
49
|
items
|
|
48
|
-
.forEachEffect(e => s.find(e.id).flatMap(current => updateETag(e, current)))
|
|
49
|
-
.tap(items =>
|
|
50
|
+
.forEachEffect((e) => s.find(e.id).flatMap((current) => updateETag(e, current)))
|
|
51
|
+
.tap((items) =>
|
|
50
52
|
asMap
|
|
51
|
-
.map(m => {
|
|
53
|
+
.map((m) => {
|
|
52
54
|
const mut = m
|
|
53
|
-
items.forEach(e => mut.set(e.id, e))
|
|
55
|
+
items.forEach((e) => mut.set(e.id, e))
|
|
54
56
|
return mut
|
|
55
57
|
})
|
|
56
58
|
.flatMap(set)
|
|
57
59
|
)
|
|
58
|
-
.map(_ => _
|
|
60
|
+
.map((_) => _ as NonEmptyArray<PM>)
|
|
59
61
|
.apply(withPermit)
|
|
60
|
-
.provideService(RedisClient
|
|
62
|
+
.provideService(RedisClient, redis)
|
|
61
63
|
const s: Store<PM, Id> = {
|
|
62
64
|
all,
|
|
63
65
|
filter: (filter: Filter<PM>, cursor?: { skip?: number; limit?: number }) =>
|
|
64
66
|
all.map(memFilter(filter, cursor)),
|
|
65
67
|
filterJoinSelect: <T extends object>(filter: FilterJoinSelect) =>
|
|
66
|
-
all.map(c => c.flatMap(codeFilterJoinSelect<PM, T>(filter))),
|
|
67
|
-
find: id => asMap.map(_ => Option.fromNullable(_.get(id))),
|
|
68
|
-
set: e =>
|
|
68
|
+
all.map((c) => c.flatMap(codeFilterJoinSelect<PM, T>(filter))),
|
|
69
|
+
find: (id) => asMap.map((_) => Option.fromNullable(_.get(id))),
|
|
70
|
+
set: (e) =>
|
|
69
71
|
s
|
|
70
72
|
.find(e.id)
|
|
71
|
-
.flatMap(current => updateETag(e, current))
|
|
72
|
-
.tap(e => asMap.map(_ => new Map([..._, [e.id, e]])).flatMap(set))
|
|
73
|
+
.flatMap((current) => updateETag(e, current))
|
|
74
|
+
.tap((e) => asMap.map((_) => new Map([..._, [e.id, e]])).flatMap(set))
|
|
73
75
|
.apply(withPermit)
|
|
74
|
-
.provideService(RedisClient
|
|
76
|
+
.provideService(RedisClient, redis),
|
|
75
77
|
batchSet,
|
|
76
78
|
bulkSet: batchSet,
|
|
77
79
|
remove: (e: PM) =>
|
|
78
|
-
asMap
|
|
80
|
+
asMap
|
|
81
|
+
.map((_) => new Map([..._].filter(([_]) => _ !== e.id)))
|
|
82
|
+
.flatMap(set)
|
|
79
83
|
.apply(withPermit)
|
|
80
84
|
.provideService(
|
|
81
|
-
RedisClient
|
|
85
|
+
RedisClient,
|
|
82
86
|
redis
|
|
83
87
|
)
|
|
84
88
|
}
|
|
@@ -20,7 +20,7 @@ export function somethingsWhere(
|
|
|
20
20
|
) => Where | [Where, ...Where[]],
|
|
21
21
|
mode?: "or" | "and"
|
|
22
22
|
) {
|
|
23
|
-
return makeSomethingFilter_(f => {
|
|
23
|
+
return makeSomethingFilter_((f) => {
|
|
24
24
|
const m = makeWhere ? makeWhere(f) : []
|
|
25
25
|
return ({
|
|
26
26
|
mode,
|
|
@@ -44,37 +44,47 @@ type Something = {
|
|
|
44
44
|
|
|
45
45
|
test("works", () => {
|
|
46
46
|
expect(
|
|
47
|
-
somethings
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
somethings
|
|
48
|
+
.filterMap(codeFilter(
|
|
49
|
+
somethingsWhere((_) => _("b", (_) => "b2"))
|
|
50
|
+
))
|
|
51
|
+
.toArray
|
|
50
52
|
)
|
|
51
53
|
.toEqual([somethings[1]])
|
|
52
54
|
|
|
53
55
|
expect(
|
|
54
|
-
somethings
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
somethings
|
|
57
|
+
.filterMap(codeFilter(
|
|
58
|
+
somethingsWhere((_) => _("b", (_) => "b"))
|
|
59
|
+
))
|
|
60
|
+
.toArray
|
|
57
61
|
)
|
|
58
62
|
.toEqual([somethings[0]])
|
|
59
63
|
|
|
60
64
|
expect(
|
|
61
|
-
somethings
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
somethings
|
|
66
|
+
.filterMap(codeFilter(
|
|
67
|
+
somethingsWhere((_) => _("d.-1.a", (_) => "a5"))
|
|
68
|
+
))
|
|
69
|
+
.toArray
|
|
64
70
|
)
|
|
65
71
|
.toEqual([somethings[1]])
|
|
66
72
|
|
|
67
73
|
expect(
|
|
68
|
-
somethings
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
somethings
|
|
75
|
+
.filterMap(codeFilter(
|
|
76
|
+
somethingsWhere((_) => _("d.-1.a", (_) => "a"))
|
|
77
|
+
))
|
|
78
|
+
.toArray
|
|
71
79
|
)
|
|
72
80
|
.toEqual([somethings[0]])
|
|
73
81
|
|
|
74
82
|
expect(
|
|
75
|
-
somethings
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
somethings
|
|
84
|
+
.filterMap(codeFilter(
|
|
85
|
+
somethingsWhere((_) => _("d.-1.a", (_) => _.$isnt("a")))
|
|
86
|
+
))
|
|
87
|
+
.toArray
|
|
78
88
|
)
|
|
79
89
|
.toEqual([somethings[1]])
|
|
80
90
|
})
|
|
@@ -14,32 +14,34 @@ import { StoreMaker } from "./service.js"
|
|
|
14
14
|
* @tsplus static StoreMaker.Ops Live
|
|
15
15
|
*/
|
|
16
16
|
export function StoreMakerLive(config: Config<StorageConfig>) {
|
|
17
|
-
return config
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
return config
|
|
18
|
+
.config
|
|
19
|
+
.flatMap((cfg) => {
|
|
20
|
+
const storageUrl = cfg.url.value
|
|
21
|
+
if (storageUrl.startsWith("mem://")) {
|
|
22
|
+
console.log("Using in memory store")
|
|
23
|
+
return Effect(makeMemoryStore())
|
|
24
|
+
}
|
|
25
|
+
if (storageUrl.startsWith("disk://")) {
|
|
26
|
+
const dir = storageUrl.replace("disk://", "")
|
|
27
|
+
console.log("Using disk store at " + dir)
|
|
28
|
+
return makeDiskStore(cfg, dir)
|
|
29
|
+
}
|
|
30
|
+
if (storageUrl.startsWith("redis://")) {
|
|
31
|
+
console.log("Using Redis store")
|
|
32
|
+
return RedisClient.makeRedisClient(makeRedis(storageUrl)).flatMap((client) =>
|
|
33
|
+
makeRedisStore(cfg).provideService(
|
|
34
|
+
RedisClient.RedisClient,
|
|
35
|
+
client
|
|
36
|
+
)
|
|
34
37
|
)
|
|
35
|
-
|
|
36
|
-
}
|
|
38
|
+
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
console.log("Using Cosmos DB store")
|
|
41
|
+
return CosmosClient.makeCosmosClient(storageUrl, cfg.dbName).flatMap((client) =>
|
|
42
|
+
makeCosmosStore(cfg).provideService(CosmosClient.CosmosClient, client)
|
|
43
|
+
)
|
|
44
|
+
})
|
|
43
45
|
.toLayerScoped(StoreMaker)
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -57,14 +57,14 @@ export type FilterJoinSelect = {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export interface Store<PM extends PersistenceModelType<Id>, Id extends string> {
|
|
60
|
-
all: Effect<never, never,
|
|
60
|
+
all: Effect<never, never, PM[]>
|
|
61
61
|
filter: (
|
|
62
62
|
filter: Filter<PM>,
|
|
63
63
|
cursor?: { limit?: number; skip?: number }
|
|
64
|
-
) => Effect<never, never,
|
|
64
|
+
) => Effect<never, never, PM[]>
|
|
65
65
|
filterJoinSelect: <T extends object>(
|
|
66
66
|
filter: FilterJoinSelect
|
|
67
|
-
) => Effect<never, never,
|
|
67
|
+
) => Effect<never, never, (T & { _rootId: string })[]>
|
|
68
68
|
find: (id: Id) => Effect<never, never, Option<PM>>
|
|
69
69
|
set: (e: PM) => Effect<never, OptimisticConcurrencyException, PM>
|
|
70
70
|
batchSet: (
|
|
@@ -124,8 +124,8 @@ export const makeMap = Effect.sync((): ContextMap => {
|
|
|
124
124
|
// but of course removes confirming of validation rules (which may be okay for a database owned by the app, as we write safely)
|
|
125
125
|
lax: false,
|
|
126
126
|
cache: {
|
|
127
|
-
getOrSetParser: p => parserCache.get(p) ?? setAndReturn(p, i => parserEnv.cache!.getOrSet(i, p)),
|
|
128
|
-
getOrSetParsers: parsers => {
|
|
127
|
+
getOrSetParser: (p) => parserCache.get(p) ?? setAndReturn(p, (i) => parserEnv.cache!.getOrSet(i, p)),
|
|
128
|
+
getOrSetParsers: (parsers) => {
|
|
129
129
|
return Object.entries(parsers).reduce((prev, [k, v]) => {
|
|
130
130
|
prev[k] = parserEnv.cache!.getOrSetParser(v)
|
|
131
131
|
return prev
|
|
@@ -50,20 +50,21 @@ export function codeFilter<E extends { id: string }, NE extends E>(filter: Filte
|
|
|
50
50
|
? Option(x as unknown as NE)
|
|
51
51
|
: Option.none
|
|
52
52
|
: filter.type === "join_find"
|
|
53
|
-
? filter.keys.some(k => {
|
|
53
|
+
? filter.keys.some((k) => {
|
|
54
54
|
const value = get(x, k) as Record<string, unknown>[]
|
|
55
55
|
// we mimic the behavior of cosmosdb; if the shape in db does not match what we're looking for, we imagine false hit
|
|
56
56
|
return (
|
|
57
|
-
value
|
|
58
|
-
value.some(v => compareCaseInsensitive(v[filter.valueKey], filter.value))
|
|
57
|
+
value
|
|
58
|
+
&& value.some((v) => compareCaseInsensitive(v[filter.valueKey], filter.value))
|
|
59
59
|
)
|
|
60
60
|
})
|
|
61
61
|
? Option(x as unknown as NE)
|
|
62
62
|
: Option.none
|
|
63
63
|
// TODO: support mixed or/and
|
|
64
64
|
: filter.mode === "or"
|
|
65
|
-
? filter
|
|
66
|
-
.
|
|
65
|
+
? filter
|
|
66
|
+
.where
|
|
67
|
+
.some((p) =>
|
|
67
68
|
p.t === "in"
|
|
68
69
|
? p.value.includes(get(x, p.key))
|
|
69
70
|
: p.t === "not-in"
|
|
@@ -82,8 +83,9 @@ export function codeFilter<E extends { id: string }, NE extends E>(filter: Filte
|
|
|
82
83
|
)
|
|
83
84
|
? Option(x as unknown as NE)
|
|
84
85
|
: Option.none
|
|
85
|
-
: filter
|
|
86
|
-
.
|
|
86
|
+
: filter
|
|
87
|
+
.where
|
|
88
|
+
.every((p) =>
|
|
87
89
|
p.t === "in"
|
|
88
90
|
? p.value.includes(get(x, p.key))
|
|
89
91
|
: p.t === "not-in"
|
|
@@ -100,12 +102,12 @@ export function codeFilter<E extends { id: string }, NE extends E>(filter: Filte
|
|
|
100
102
|
? p.key.includes(".-1.")
|
|
101
103
|
? (get(x, p.key.split(".-1.")[0]) as any[])
|
|
102
104
|
// TODO: or vs and
|
|
103
|
-
.every(_ => !compareCaseInsensitive(get(_, p.key.split(".-1.")[1]!), p.value))
|
|
105
|
+
.every((_) => !compareCaseInsensitive(get(_, p.key.split(".-1.")[1]!), p.value))
|
|
104
106
|
: !compareCaseInsensitive(get(x, p.key), p.value)
|
|
105
107
|
: p.key.includes(".-1.")
|
|
106
108
|
? (get(x, p.key.split(".-1.")[0]) as any[])
|
|
107
109
|
// TODO: or vs and
|
|
108
|
-
.some(_ => compareCaseInsensitive(get(_, p.key.split(".-1.")[1]!), p.value))
|
|
110
|
+
.some((_) => compareCaseInsensitive(get(_, p.key.split(".-1.")[1]!), p.value))
|
|
109
111
|
: compareCaseInsensitive(get(x, p.key), p.value)
|
|
110
112
|
)
|
|
111
113
|
? Option(x as unknown as NE)
|
|
@@ -116,22 +118,23 @@ export function codeFilterJoinSelect<E extends { id: string }, NE>(
|
|
|
116
118
|
filter: FilterJoinSelect
|
|
117
119
|
) {
|
|
118
120
|
return (x: E) =>
|
|
119
|
-
filter
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
filter
|
|
122
|
+
.keys
|
|
123
|
+
.filterMap((k) => {
|
|
124
|
+
const value = get(x, k)
|
|
125
|
+
// we mimic the behavior of cosmosdb; if the shape in db does not match what we're looking for, we imagine false hit
|
|
126
|
+
return value
|
|
127
|
+
? Option(
|
|
128
|
+
(value as readonly NE[]).filterMap((v) =>
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
compareCaseInsensitive((v as any)[filter.valueKey], filter.value)
|
|
131
|
+
? Option({ ...v, _rootId: x.id })
|
|
132
|
+
: Option.none
|
|
133
|
+
)
|
|
129
134
|
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.flatMap(_ => _)
|
|
134
|
-
.toChunk
|
|
135
|
+
: Option.none
|
|
136
|
+
})
|
|
137
|
+
.flatMap((_) => _)
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
function lowercaseIfString<T>(val: T) {
|