@effect-app/infra 4.0.0-beta.326 → 4.0.0-beta.328
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 +21 -0
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +4 -4
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +7 -6
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +4 -4
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +11 -10
- package/dist/Store/SQL/Pg.js +17 -16
- package/dist/Store/SQL/query.d.ts.map +1 -1
- package/dist/Store/SQL/query.js +4 -4
- package/dist/Store/SQL.d.ts +9 -2
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +28 -29
- package/dist/Store/jsonDocument.d.ts +34 -11
- package/dist/Store/jsonDocument.d.ts.map +1 -1
- package/dist/Store/jsonDocument.js +53 -19
- package/package.json +2 -2
- package/src/Store/Cosmos/query.ts +3 -3
- package/src/Store/Cosmos.ts +11 -6
- package/src/Store/Disk.ts +4 -3
- package/src/Store/Memory.ts +14 -9
- package/src/Store/SQL/Pg.ts +21 -21
- package/src/Store/SQL/query.ts +3 -3
- package/src/Store/SQL.ts +37 -37
- package/src/Store/jsonDocument.ts +73 -18
- package/test/cosmos-query.test.ts +45 -0
- package/test/json-lower.test.ts +15 -1
- package/test/repository-legacy-document.test.ts +96 -31
- package/test/sql-store.test.ts +27 -1
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import type { FieldValues } from "effect-app/Model/filter/types";
|
|
2
|
+
import * as Option from "effect-app/Option";
|
|
2
3
|
import * as S from "effect-app/Schema";
|
|
3
4
|
import type { PersistenceModelType, StoreConfig } from "effect-app/Store";
|
|
5
|
+
import * as Result from "effect/Result";
|
|
4
6
|
export interface JsonDocumentCodec<E extends FieldValues> {
|
|
5
7
|
readonly encode: (doc: PersistenceModelType<E>) => PersistenceModelType<E>;
|
|
6
8
|
readonly decode: (doc: PersistenceModelType<E>) => PersistenceModelType<E>;
|
|
7
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The store *read* boundary decode: a stored JSON document in, either the
|
|
12
|
+
* Encoded document or the `S.SchemaError` saying why it is not one.
|
|
13
|
+
*
|
|
14
|
+
* Synchronous on purpose: it runs once per document over whole result sets, so
|
|
15
|
+
* it must not allocate an Effect per row. Adapters lift the `Result` into their
|
|
16
|
+
* Effect channel once per operation (see {@link decodeStoredMany}).
|
|
17
|
+
*/
|
|
18
|
+
export type StoredDecode<E extends FieldValues> = (doc: PersistenceModelType<E>) => Result.Result<PersistenceModelType<E>, S.SchemaError>;
|
|
8
19
|
/**
|
|
9
20
|
* Encoded document ↔ JSON document. Prefer `Schema.toCodecJson(toEncoded(schema))`
|
|
10
21
|
* when the store has a schema; otherwise lower Date/Map/Set structurally.
|
|
@@ -13,11 +24,10 @@ export interface JsonDocumentCodec<E extends FieldValues> {
|
|
|
13
24
|
* document that does not match the Encoded shape fails loudly rather than being
|
|
14
25
|
* read back half-decoded.
|
|
15
26
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* failure and throws here.
|
|
27
|
+
* This is the **write** path codec: `encode` on the way to the database, and
|
|
28
|
+
* `decode` to hand the just-written document back. The read path uses
|
|
29
|
+
* {@link makeStoredDecode}, which applies `jitM` first and reports a document it
|
|
30
|
+
* does not repair as a typed `S.SchemaError` instead of throwing.
|
|
21
31
|
*
|
|
22
32
|
* `_etag` is infrastructure metadata rather than part of the domain document,
|
|
23
33
|
* so it is split off before the codec runs and re-attached afterwards.
|
|
@@ -26,19 +36,32 @@ export declare const makeJsonDocumentCodec: <E extends FieldValues>(schema?: S.T
|
|
|
26
36
|
/**
|
|
27
37
|
* The store *read* boundary: apply `jitM` to the raw JSON document - after the
|
|
28
38
|
* adapter merged `defaultValues` into it - and only then decode JSON→Encoded
|
|
29
|
-
* with the strict
|
|
39
|
+
* with the strict `Schema.toCodecJson(Schema.toEncoded(schema))` codec.
|
|
30
40
|
*
|
|
31
41
|
* This is the one place a legacy-shaped document can be repaired, and why
|
|
32
42
|
* `jitM` can fix things no schema decode could - an explicit `null` where a
|
|
33
43
|
* `Date` is expected, a key added after the document was written. It runs
|
|
34
|
-
* before any schema decode sees the document
|
|
35
|
-
*
|
|
44
|
+
* before any schema decode sees the document.
|
|
45
|
+
*
|
|
46
|
+
* Nothing here is lenient: a document `jitM` does not repair still fails. It
|
|
47
|
+
* fails as a typed `S.SchemaError` in a `Result` (via `S.decodeUnknownResult`)
|
|
48
|
+
* rather than by throwing, so adapters can turn it into `Effect.fail` and
|
|
49
|
+
* callers - `Repository.validateSample` in particular - can inspect it per
|
|
50
|
+
* document instead of losing the whole run to a defect.
|
|
36
51
|
*
|
|
37
52
|
* `_etag` is infrastructure metadata rather than part of the domain document,
|
|
38
53
|
* so it is split off first and `jitM` never sees it.
|
|
54
|
+
*/
|
|
55
|
+
export declare const makeStoredDecode: <E extends FieldValues>(schema: StoreConfig<E>["schema"], jitM: StoreConfig<E>["jitM"]) => StoredDecode<E>;
|
|
56
|
+
/**
|
|
57
|
+
* Decode a whole result set, failing fast on the first bad document.
|
|
39
58
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
59
|
+
* One synchronous loop and one `Result` for the batch: no Effect per document,
|
|
60
|
+
* and no allocation beyond the decoded array (plus a `Result` wrapper) on the
|
|
61
|
+
* happy path. Adapters lift the returned `Result` with `Effect.fromResult`
|
|
62
|
+
* once, at the end.
|
|
42
63
|
*/
|
|
43
|
-
export declare const
|
|
64
|
+
export declare const decodeStoredMany: <A, B>(docs: readonly A[], decode: (doc: A) => Result.Result<B, S.SchemaError>) => Result.Result<B[], S.SchemaError>;
|
|
65
|
+
/** Decode an optional stored document; `None` short-circuits without decoding. */
|
|
66
|
+
export declare const decodeStoredOption: <A, B>(doc: Option.Option<A>, decode: (doc: A) => Result.Result<B, S.SchemaError>) => Result.Result<Option.Option<B>, S.SchemaError>;
|
|
44
67
|
//# sourceMappingURL=jsonDocument.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonDocument.d.ts","sourceRoot":"","sources":["../../src/Store/jsonDocument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,CAAC,MAAM,mBAAmB,CAAA;AACtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"jsonDocument.d.ts","sourceRoot":"","sources":["../../src/Store/jsonDocument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAA;AAC3C,OAAO,KAAK,CAAC,MAAM,mBAAmB,CAAA;AACtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACzE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,WAAW;IACtD,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,oBAAoB,CAAC,CAAC,CAAC,CAAA;IAC1E,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,oBAAoB,CAAC,CAAC,CAAC,CAAA;CAC3E;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI,CAChD,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC,KACzB,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;AAe1D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,WAAW,WAAW,CAAC,CAAC,GAAG,KAAG,iBAAiB,CAAC,CAAC,CAkBhG,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,WAAW,UAC5C,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAC1B,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAC3B,YAAY,CAAC,CAAC,CAmBhB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,QAC7B,SAAS,CAAC,EAAE,UACV,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,KAClD,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAQlC,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,CAAC,OAChC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,UACb,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,KAClD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CACiD,CAAA"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import * as Option from "effect-app/Option";
|
|
1
2
|
import * as S from "effect-app/Schema";
|
|
3
|
+
import * as Result from "effect/Result";
|
|
2
4
|
import { toJsonQueryValue } from "./utils.js";
|
|
3
5
|
const splitEtag = (doc) => {
|
|
4
6
|
const { _etag, ...rest } = doc;
|
|
5
7
|
return { rest: rest, _etag };
|
|
6
8
|
};
|
|
7
9
|
const joinEtag = (rest, _etag) => (_etag === undefined ? rest : { ...rest, _etag });
|
|
10
|
+
/** JSON document ↔ Encoded document codec for a store's domain schema. */
|
|
11
|
+
const jsonCodec = (schema) => S.toCodecJson(S.toEncoded(schema));
|
|
8
12
|
/**
|
|
9
13
|
* Encoded document ↔ JSON document. Prefer `Schema.toCodecJson(toEncoded(schema))`
|
|
10
14
|
* when the store has a schema; otherwise lower Date/Map/Set structurally.
|
|
@@ -13,18 +17,17 @@ const joinEtag = (rest, _etag) => (_etag === undefined ? rest : { ...rest, _etag
|
|
|
13
17
|
* document that does not match the Encoded shape fails loudly rather than being
|
|
14
18
|
* read back half-decoded.
|
|
15
19
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* failure and throws here.
|
|
20
|
+
* This is the **write** path codec: `encode` on the way to the database, and
|
|
21
|
+
* `decode` to hand the just-written document back. The read path uses
|
|
22
|
+
* {@link makeStoredDecode}, which applies `jitM` first and reports a document it
|
|
23
|
+
* does not repair as a typed `S.SchemaError` instead of throwing.
|
|
21
24
|
*
|
|
22
25
|
* `_etag` is infrastructure metadata rather than part of the domain document,
|
|
23
26
|
* so it is split off before the codec runs and re-attached afterwards.
|
|
24
27
|
*/
|
|
25
28
|
export const makeJsonDocumentCodec = (schema) => {
|
|
26
29
|
if (schema) {
|
|
27
|
-
const codec =
|
|
30
|
+
const codec = jsonCodec(schema);
|
|
28
31
|
return {
|
|
29
32
|
encode: (doc) => {
|
|
30
33
|
const { rest, _etag } = splitEtag(doc);
|
|
@@ -44,30 +47,61 @@ export const makeJsonDocumentCodec = (schema) => {
|
|
|
44
47
|
/**
|
|
45
48
|
* The store *read* boundary: apply `jitM` to the raw JSON document - after the
|
|
46
49
|
* adapter merged `defaultValues` into it - and only then decode JSON→Encoded
|
|
47
|
-
* with the strict
|
|
50
|
+
* with the strict `Schema.toCodecJson(Schema.toEncoded(schema))` codec.
|
|
48
51
|
*
|
|
49
52
|
* This is the one place a legacy-shaped document can be repaired, and why
|
|
50
53
|
* `jitM` can fix things no schema decode could - an explicit `null` where a
|
|
51
54
|
* `Date` is expected, a key added after the document was written. It runs
|
|
52
|
-
* before any schema decode sees the document
|
|
53
|
-
*
|
|
55
|
+
* before any schema decode sees the document.
|
|
56
|
+
*
|
|
57
|
+
* Nothing here is lenient: a document `jitM` does not repair still fails. It
|
|
58
|
+
* fails as a typed `S.SchemaError` in a `Result` (via `S.decodeUnknownResult`)
|
|
59
|
+
* rather than by throwing, so adapters can turn it into `Effect.fail` and
|
|
60
|
+
* callers - `Repository.validateSample` in particular - can inspect it per
|
|
61
|
+
* document instead of losing the whole run to a defect.
|
|
54
62
|
*
|
|
55
63
|
* `_etag` is infrastructure metadata rather than part of the domain document,
|
|
56
64
|
* so it is split off first and `jitM` never sees it.
|
|
57
|
-
*
|
|
58
|
-
* Returns `codec.decode` unchanged when there is no `jitM`; the write path,
|
|
59
|
-
* which must not run migrations, keeps using `codec.decode` directly.
|
|
60
65
|
*/
|
|
61
|
-
export const makeStoredDecode = (
|
|
62
|
-
if (
|
|
63
|
-
|
|
66
|
+
export const makeStoredDecode = (schema, jitM) => {
|
|
67
|
+
if (schema === undefined) {
|
|
68
|
+
// schemaless store: the stored document is already the Encoded document,
|
|
69
|
+
// so there is nothing to decode - only `jitM` to apply.
|
|
70
|
+
if (jitM === undefined)
|
|
71
|
+
return Result.succeed;
|
|
72
|
+
return (doc) => {
|
|
73
|
+
const { rest, _etag } = splitEtag(doc);
|
|
74
|
+
return Result.succeed(joinEtag(jitM(rest), _etag));
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const decode = S.decodeUnknownResult(jsonCodec(schema));
|
|
64
78
|
return (doc) => {
|
|
65
79
|
const { rest, _etag } = splitEtag(doc);
|
|
66
80
|
// adapters type the stored document as the Encoded persistence model, but
|
|
67
81
|
// at this point it is still the raw JSON document that `jitM` is written
|
|
68
|
-
// against - and `jitM` returns JSON, which is what
|
|
69
|
-
const
|
|
70
|
-
return
|
|
82
|
+
// against - and `jitM` returns JSON, which is what the codec consumes.
|
|
83
|
+
const json = jitM === undefined ? rest : jitM(rest);
|
|
84
|
+
return Result.map(decode(json), (decoded) => joinEtag(decoded, _etag));
|
|
71
85
|
};
|
|
72
86
|
};
|
|
73
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Decode a whole result set, failing fast on the first bad document.
|
|
89
|
+
*
|
|
90
|
+
* One synchronous loop and one `Result` for the batch: no Effect per document,
|
|
91
|
+
* and no allocation beyond the decoded array (plus a `Result` wrapper) on the
|
|
92
|
+
* happy path. Adapters lift the returned `Result` with `Effect.fromResult`
|
|
93
|
+
* once, at the end.
|
|
94
|
+
*/
|
|
95
|
+
export const decodeStoredMany = (docs, decode) => {
|
|
96
|
+
const decoded = [];
|
|
97
|
+
for (const doc of docs) {
|
|
98
|
+
const result = decode(doc);
|
|
99
|
+
if (Result.isFailure(result))
|
|
100
|
+
return Result.fail(result.failure);
|
|
101
|
+
decoded.push(result.success);
|
|
102
|
+
}
|
|
103
|
+
return Result.succeed(decoded);
|
|
104
|
+
};
|
|
105
|
+
/** Decode an optional stored document; `None` short-circuits without decoding. */
|
|
106
|
+
export const decodeStoredOption = (doc, decode) => Option.isNone(doc) ? Result.succeed(Option.none()) : Result.map(decode(doc.value), Option.some);
|
|
107
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoianNvbkRvY3VtZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL1N0b3JlL2pzb25Eb2N1bWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssTUFBTSxNQUFNLG1CQUFtQixDQUFBO0FBQzNDLE9BQU8sS0FBSyxDQUFDLE1BQU0sbUJBQW1CLENBQUE7QUFFdEMsT0FBTyxLQUFLLE1BQU0sTUFBTSxlQUFlLENBQUE7QUFDdkMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sWUFBWSxDQUFBO0FBbUI3QyxNQUFNLFNBQVMsR0FBRyxDQUF3QixHQUE0QixFQUFFLEVBQUU7SUFDeEUsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksRUFBRSxHQUFHLEdBQUcsQ0FBQTtJQUM5QixPQUFPLEVBQUUsSUFBSSxFQUFFLElBQVMsRUFBRSxLQUFLLEVBQUUsQ0FBQTtBQUNuQyxDQUFDLENBQUE7QUFFRCxNQUFNLFFBQVEsR0FBRyxDQUNmLElBQU8sRUFDUCxLQUF5QixFQUNBLEVBQUUsQ0FBQyxDQUFDLEtBQUssS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFBO0FBRS9FLDBFQUEwRTtBQUMxRSxNQUFNLFNBQVMsR0FBRyxDQUF3QixNQUFhLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBdUIsQ0FBQTtBQUVwSDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUF3QixNQUFjLEVBQXdCLEVBQUU7SUFDbkcsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUNYLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBSSxNQUFNLENBQUMsQ0FBQTtRQUNsQyxPQUFPO1lBQ0wsTUFBTSxFQUFFLENBQUMsR0FB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-app/infra",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.328",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"proper-lockfile": "^4.1.2",
|
|
17
17
|
"pure-rand": "8.4.0",
|
|
18
18
|
"query-string": "^9.4.0",
|
|
19
|
-
"effect-app": "4.0.0-beta.
|
|
19
|
+
"effect-app": "4.0.0-beta.328"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@azure/cosmos": "^4.9.3",
|
|
@@ -9,7 +9,7 @@ import type { SupportedValues } from "effect-app/Store"
|
|
|
9
9
|
import { assertUnreachable } from "effect-app/utils"
|
|
10
10
|
import { InfraLogger } from "../../logger.ts"
|
|
11
11
|
import { isRelationCheck } from "../codeFilter.ts"
|
|
12
|
-
import { jsonifyFilter, type JsonLower
|
|
12
|
+
import { jsonifyFilter, type JsonLower } from "../utils.ts"
|
|
13
13
|
|
|
14
14
|
export function logQuery(q: {
|
|
15
15
|
query: string
|
|
@@ -64,9 +64,9 @@ export function buildWhereCosmosQuery3(
|
|
|
64
64
|
limit?: number,
|
|
65
65
|
json?: JsonLower
|
|
66
66
|
) {
|
|
67
|
-
|
|
67
|
+
// defaultValues are already JSON-lowered at store construction; a second
|
|
68
|
+
// schema encode of an ISO Date string throws "Expected a valid Date".
|
|
68
69
|
filter = (json?.jsonifyFilter ?? jsonifyFilter)(filter)
|
|
69
|
-
defaultValues = toJson(defaultValues) as Record<string, unknown>
|
|
70
70
|
const statement = (x: FilterR, i: number) => {
|
|
71
71
|
if (x.path === idKey) {
|
|
72
72
|
x = { ...x, path: "id" }
|
package/src/Store/Cosmos.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { DatabaseError, OptimisticConcurrencyException } from "../errors.ts"
|
|
|
19
19
|
import { InfraLogger } from "../logger.ts"
|
|
20
20
|
import { annotateCosmosResponse, annotateDb } from "../otel.ts"
|
|
21
21
|
import { buildWhereCosmosQuery3, logQuery } from "./Cosmos/query.ts"
|
|
22
|
-
import { makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
22
|
+
import { decodeStoredMany, decodeStoredOption, makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
23
23
|
import { makeJsonLower } from "./utils.ts"
|
|
24
24
|
|
|
25
25
|
const makeMapId =
|
|
@@ -99,7 +99,7 @@ const makeCosmosStore = Effect.fnUntraced(function*({ autoscaleMaxThroughput, pr
|
|
|
99
99
|
const mapId = makeMapId<IdKey, Encoded>(idKey)
|
|
100
100
|
const mapReverseId = makeReverseMapId<IdKey, Encoded>(idKey)
|
|
101
101
|
const codec = makeJsonDocumentCodec<Encoded>(config?.schema)
|
|
102
|
-
const decodeStored = makeStoredDecode<Encoded>(
|
|
102
|
+
const decodeStored = makeStoredDecode<Encoded>(config?.schema, config?.jitM)
|
|
103
103
|
const json = makeJsonLower(config)
|
|
104
104
|
const defaultValues = json.toJson(config?.defaultValues ?? {}) as Partial<Encoded>
|
|
105
105
|
// stored JSON -> defaultValues -> jitM -> JSON→Encoded decode
|
|
@@ -458,7 +458,9 @@ const makeCosmosStore = Effect.fnUntraced(function*({ autoscaleMaxThroughput, pr
|
|
|
458
458
|
container.items.query<PMCosmos>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
|
|
459
459
|
)
|
|
460
460
|
yield* annotateFeed(response)
|
|
461
|
-
return
|
|
461
|
+
return yield* Effect.fromResult(
|
|
462
|
+
decodeStoredMany(response.resources, (_) => fromStored(_ as unknown as Encoded))
|
|
463
|
+
)
|
|
462
464
|
})
|
|
463
465
|
.pipe(
|
|
464
466
|
annotateDb({
|
|
@@ -532,7 +534,10 @@ const makeCosmosStore = Effect.fnUntraced(function*({ autoscaleMaxThroughput, pr
|
|
|
532
534
|
container.items.query<{ f: M }>(q, { partitionKey: nsBasePartitionKey(ns) }).fetchAll()
|
|
533
535
|
)
|
|
534
536
|
yield* annotateFeed(response)
|
|
535
|
-
|
|
537
|
+
const decoded = yield* Effect.fromResult(
|
|
538
|
+
decodeStoredMany(response.resources, ({ f }) => fromStored(f as Encoded))
|
|
539
|
+
)
|
|
540
|
+
return decoded as any
|
|
536
541
|
})
|
|
537
542
|
.pipe(
|
|
538
543
|
annotateDb({
|
|
@@ -557,8 +562,8 @@ const makeCosmosStore = Effect.fnUntraced(function*({ autoscaleMaxThroughput, pr
|
|
|
557
562
|
.read<Encoded>()
|
|
558
563
|
)
|
|
559
564
|
yield* annotateItem(response)
|
|
560
|
-
return
|
|
561
|
-
Option.
|
|
565
|
+
return yield* Effect.fromResult(
|
|
566
|
+
decodeStoredOption(Option.fromNullishOr(response.resource), fromStored)
|
|
562
567
|
)
|
|
563
568
|
})
|
|
564
569
|
.pipe(annotateDb({
|
package/src/Store/Disk.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as Console from "effect/Console"
|
|
|
10
10
|
import { flow } from "effect/Function"
|
|
11
11
|
import * as Semaphore from "effect/Semaphore"
|
|
12
12
|
import { annotateDb } from "../otel.ts"
|
|
13
|
-
import { makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
13
|
+
import { decodeStoredMany, makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
14
14
|
import { makeMemoryStoreInt } from "./Memory.ts"
|
|
15
15
|
import { type JsonLower, makeJsonLower } from "./utils.ts"
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValu
|
|
|
30
30
|
const codec = makeJsonDocumentCodec<Encoded>(schema)
|
|
31
31
|
// the file is the raw JSON boundary for this adapter:
|
|
32
32
|
// stored JSON -> jitM -> JSON→Encoded decode
|
|
33
|
-
const decodeStored = makeStoredDecode<Encoded>(
|
|
33
|
+
const decodeStored = makeStoredDecode<Encoded>(schema, jitM)
|
|
34
34
|
return Effect.gen(function*() {
|
|
35
35
|
if (namespace !== "primary") {
|
|
36
36
|
dir = dir + "/" + namespace
|
|
@@ -53,7 +53,8 @@ function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValu
|
|
|
53
53
|
extra: fileExtra
|
|
54
54
|
}),
|
|
55
55
|
Effect.flatMap((x) =>
|
|
56
|
-
Effect.sync(() =>
|
|
56
|
+
Effect.sync(() => JSON.parse(x) as PM[]).pipe(
|
|
57
|
+
Effect.flatMap((docs) => Effect.fromResult(decodeStoredMany(docs, decodeStored))),
|
|
57
58
|
annotateDb({
|
|
58
59
|
operation: "read.parse",
|
|
59
60
|
system: "disk",
|
package/src/Store/Memory.ts
CHANGED
|
@@ -18,7 +18,7 @@ import * as Struct from "effect/Struct"
|
|
|
18
18
|
import { InfraLogger } from "../logger.ts"
|
|
19
19
|
import { annotateDb } from "../otel.ts"
|
|
20
20
|
import { codeFilter, codeFilter3_ } from "./codeFilter.ts"
|
|
21
|
-
import { makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
21
|
+
import { decodeStoredMany, decodeStoredOption, makeJsonDocumentCodec, makeStoredDecode } from "./jsonDocument.ts"
|
|
22
22
|
import { get, jsonifyFilter, type JsonLower, makeJsonLower, makeUpdateETag, toJsonQueryValue } from "./utils.ts"
|
|
23
23
|
|
|
24
24
|
export { get } from "./utils.ts"
|
|
@@ -344,7 +344,7 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
344
344
|
const codec = makeJsonDocumentCodec<Encoded>(schema)
|
|
345
345
|
const encodeDoc = (e: Encoded | PM): PM => codec.encode({ _etag: undefined, ...e })
|
|
346
346
|
// read path: stored JSON -> jitM -> JSON→Encoded decode
|
|
347
|
-
const decodeDoc = makeStoredDecode<Encoded>(
|
|
347
|
+
const decodeDoc = makeStoredDecode<Encoded>(schema, jitM)
|
|
348
348
|
const items_ = yield* seed ?? Effect.sync(() => [])
|
|
349
349
|
const toJson = json?.toJson ?? toJsonQueryValue
|
|
350
350
|
const lowerFilter = json?.jsonifyFilter ?? jsonifyFilter
|
|
@@ -362,7 +362,7 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
362
362
|
const values = Effect.map(Ref.get(store), (s) => s.values())
|
|
363
363
|
|
|
364
364
|
const allStored = Effect.map(values, Array.fromIterable)
|
|
365
|
-
const all = Effect.
|
|
365
|
+
const all = Effect.flatMap(allStored, (rows) => Effect.fromResult(decodeStoredMany(rows, decodeDoc)))
|
|
366
366
|
|
|
367
367
|
const batchSet = (items: NonEmptyReadonlyArray<PM>) =>
|
|
368
368
|
Effect
|
|
@@ -384,7 +384,9 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
384
384
|
)
|
|
385
385
|
),
|
|
386
386
|
Effect
|
|
387
|
-
.
|
|
387
|
+
.flatMap((items) => Effect.fromResult(decodeStoredMany(items, decodeDoc))),
|
|
388
|
+
Effect
|
|
389
|
+
.map((items) => items as unknown as NonEmptyReadonlyArray<PM>),
|
|
388
390
|
withPermit
|
|
389
391
|
)
|
|
390
392
|
|
|
@@ -430,7 +432,7 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
430
432
|
Ref
|
|
431
433
|
.get(store)
|
|
432
434
|
.pipe(
|
|
433
|
-
Effect.
|
|
435
|
+
Effect.flatMap((_) => Effect.fromResult(decodeStoredOption(Option.fromNullishOr(_.get(id)), decodeDoc))),
|
|
434
436
|
annotateDb({
|
|
435
437
|
operation: "find",
|
|
436
438
|
system: "memory",
|
|
@@ -445,10 +447,13 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
445
447
|
.pipe(
|
|
446
448
|
Effect.tap(() => logQuery(f, encodedDefaults)),
|
|
447
449
|
Effect.map(memFilter({ ...f, filter: f.filter ? lowerFilter(f.filter) : f.filter })),
|
|
448
|
-
Effect.
|
|
450
|
+
Effect.flatMap((rows) =>
|
|
449
451
|
f.select
|
|
450
|
-
? rows as (U extends undefined ? Encoded : Pick<Encoded, U>)[]
|
|
451
|
-
:
|
|
452
|
+
? Effect.succeed(rows as (U extends undefined ? Encoded : Pick<Encoded, U>)[])
|
|
453
|
+
: Effect.map(
|
|
454
|
+
Effect.fromResult(decodeStoredMany(rows, decodeDoc)),
|
|
455
|
+
(decoded) => decoded as (U extends undefined ? Encoded : Pick<Encoded, U>)[]
|
|
456
|
+
)
|
|
452
457
|
),
|
|
453
458
|
annotateDb({
|
|
454
459
|
operation: "filter",
|
|
@@ -470,7 +475,7 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
|
|
|
470
475
|
Effect.flatMap((_) => Ref.set(store, _))
|
|
471
476
|
)
|
|
472
477
|
),
|
|
473
|
-
Effect.
|
|
478
|
+
Effect.flatMap((stored) => Effect.fromResult(decodeDoc(stored))),
|
|
474
479
|
withPermit,
|
|
475
480
|
annotateDb({
|
|
476
481
|
operation: "set",
|
package/src/Store/SQL/Pg.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { SqlClient } from "effect/unstable/sql"
|
|
|
12
12
|
import { DatabaseError, OptimisticConcurrencyException } from "../../errors.ts"
|
|
13
13
|
import { InfraLogger } from "../../logger.ts"
|
|
14
14
|
import { annotateDb } from "../../otel.ts"
|
|
15
|
-
import { makeJsonDocumentCodec, makeStoredDecode } from "../jsonDocument.ts"
|
|
15
|
+
import { decodeStoredMany, decodeStoredOption, makeJsonDocumentCodec, makeStoredDecode } from "../jsonDocument.ts"
|
|
16
16
|
import { makeETag, makeJsonLower, toJsonQueryValue } from "../utils.ts"
|
|
17
17
|
import { buildWhereSQLQuery, logQuery, pgDialect } from "./query.ts"
|
|
18
18
|
|
|
@@ -34,17 +34,19 @@ const preserveStoreError = (e: unknown): DatabaseError | OptimisticConcurrencyEx
|
|
|
34
34
|
return toDatabaseError(e)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Row -> raw stored JSON document. Decoding it is a separate step
|
|
39
|
+
* (`makeStoredDecode`), so a document `jitM` does not repair fails as a typed
|
|
40
|
+
* `S.SchemaError` rather than throwing out of here.
|
|
41
|
+
*/
|
|
37
42
|
const parseRow = <Encoded extends FieldValues>(
|
|
38
43
|
row: { id: string; _etag: string | null; data: unknown },
|
|
39
44
|
idKey: PropertyKey,
|
|
40
|
-
defaultValues: Partial<Encoded
|
|
41
|
-
decode: (doc: PersistenceModelType<Encoded>) => PersistenceModelType<Encoded> = (doc) => doc
|
|
45
|
+
defaultValues: Partial<Encoded>
|
|
42
46
|
): PersistenceModelType<Encoded> => {
|
|
43
47
|
const data = (typeof row.data === "string" ? JSON.parse(row.data) : row.data) as object
|
|
44
48
|
const jsonDefaults = toJsonQueryValue(defaultValues) as Partial<Encoded>
|
|
45
|
-
return
|
|
46
|
-
{ ...jsonDefaults, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
|
|
47
|
-
)
|
|
49
|
+
return { ...jsonDefaults, ...data, [idKey]: row.id, _etag: row._etag ?? undefined } as PersistenceModelType<Encoded>
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const parseSelectRow = (
|
|
@@ -79,7 +81,9 @@ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
|
79
81
|
const defaultValues = json.toJson(config?.defaultValues ?? {}) as Partial<Encoded>
|
|
80
82
|
const codec = makeJsonDocumentCodec<Encoded>(config?.schema)
|
|
81
83
|
// read path: stored JSON -> defaultValues -> jitM -> JSON→Encoded decode
|
|
82
|
-
const decodeStored = makeStoredDecode<Encoded>(
|
|
84
|
+
const decodeStored = makeStoredDecode<Encoded>(config?.schema, config?.jitM)
|
|
85
|
+
const decodeRow = (row: { id: string; _etag: string | null; data: unknown }) =>
|
|
86
|
+
decodeStored(parseRow<Encoded>(row, idKey, defaultValues))
|
|
83
87
|
|
|
84
88
|
const resolveNamespace = !config?.allowNamespace
|
|
85
89
|
? Effect.succeed("primary")
|
|
@@ -202,9 +206,7 @@ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
|
202
206
|
const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE _namespace = $1`
|
|
203
207
|
return exec(sqlText, [ns])
|
|
204
208
|
.pipe(
|
|
205
|
-
Effect.
|
|
206
|
-
(rows as any[]).map((r) => parseRow<Encoded>(r, idKey, defaultValues, decodeStored))
|
|
207
|
-
),
|
|
209
|
+
Effect.flatMap((rows) => Effect.fromResult(decodeStoredMany(rows as any[], decodeRow))),
|
|
208
210
|
annotateDb({
|
|
209
211
|
operation: "all",
|
|
210
212
|
system: "postgresql",
|
|
@@ -223,12 +225,9 @@ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
|
223
225
|
const sqlText = `SELECT id, _etag, data FROM "${tableName}" WHERE id = $1 AND _namespace = $2`
|
|
224
226
|
return exec(sqlText, [id, ns])
|
|
225
227
|
.pipe(
|
|
226
|
-
Effect.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
? Option.some(parseRow<Encoded>(row, idKey, defaultValues, decodeStored))
|
|
230
|
-
: Option.none()
|
|
231
|
-
}),
|
|
228
|
+
Effect.flatMap((rows) =>
|
|
229
|
+
Effect.fromResult(decodeStoredOption(Option.fromNullishOr((rows as any[])[0]), decodeRow))
|
|
230
|
+
),
|
|
232
231
|
annotateDb({
|
|
233
232
|
operation: "find",
|
|
234
233
|
system: "postgresql",
|
|
@@ -286,9 +285,9 @@ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
|
286
285
|
Effect.tap((q) => Effect.annotateCurrentSpan({ "db.query.text": q.sql })),
|
|
287
286
|
Effect.flatMap((q) =>
|
|
288
287
|
exec(q.sql, q.params).pipe(
|
|
289
|
-
Effect.
|
|
288
|
+
Effect.flatMap((rows) => {
|
|
290
289
|
if (f.select) {
|
|
291
|
-
return (rows as any[]).map((r) => {
|
|
290
|
+
return Effect.succeed((rows as any[]).map((r) => {
|
|
292
291
|
const selected = parseSelectRow(r, idKey, {})
|
|
293
292
|
return {
|
|
294
293
|
...Struct.pick(
|
|
@@ -297,10 +296,11 @@ const makePgStore = Effect.fnUntraced(function*({ prefix }: StorageConfig) {
|
|
|
297
296
|
),
|
|
298
297
|
...selected
|
|
299
298
|
} as M
|
|
300
|
-
})
|
|
299
|
+
}))
|
|
301
300
|
}
|
|
302
|
-
return
|
|
303
|
-
|
|
301
|
+
return Effect.map(
|
|
302
|
+
Effect.fromResult(decodeStoredMany(rows as any[], decodeRow)),
|
|
303
|
+
(decoded) => decoded as any as M[]
|
|
304
304
|
)
|
|
305
305
|
})
|
|
306
306
|
)
|
package/src/Store/SQL/query.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { AggregateIrExpression, ComputedProjectionIrExpression, ComputedPro
|
|
|
6
6
|
import { assertUnreachable } from "effect-app/utils"
|
|
7
7
|
import { InfraLogger } from "../../logger.ts"
|
|
8
8
|
import { isRelationCheck } from "../codeFilter.ts"
|
|
9
|
-
import { jsonifyFilter, type JsonLower
|
|
9
|
+
import { jsonifyFilter, type JsonLower } from "../utils.ts"
|
|
10
10
|
|
|
11
11
|
export interface SQLDialect {
|
|
12
12
|
readonly jsonExtract: (path: string) => string
|
|
@@ -216,9 +216,9 @@ export function buildWhereSQLQuery(
|
|
|
216
216
|
namespace?: string,
|
|
217
217
|
json?: JsonLower
|
|
218
218
|
) {
|
|
219
|
-
|
|
219
|
+
// defaultValues are already JSON-lowered at store construction; a second
|
|
220
|
+
// schema encode of an ISO Date string throws "Expected a valid Date".
|
|
220
221
|
filter = (json?.jsonifyFilter ?? jsonifyFilter)(filter)
|
|
221
|
-
defaultValues = toJson(defaultValues) as Record<string, unknown>
|
|
222
222
|
const params: unknown[] = []
|
|
223
223
|
let paramIndex = 1
|
|
224
224
|
|