@esposter/azure 4.4.0 → 4.5.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/README.md +30 -19
- package/dist/index.d.ts +165 -0
- package/dist/index.js +176 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[![NPM downloads][badge-npm-downloads]][url-npm]
|
|
6
6
|
[![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
It holds the Azure wire conventions that a real client and a fake client must both follow. They are the OData filter clause vocabulary shared by Table Storage and Search, the entity key casing convention and the service limits.
|
|
9
9
|
|
|
10
10
|
Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs).
|
|
11
11
|
|
|
@@ -19,6 +19,8 @@ Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/
|
|
|
19
19
|
|
|
20
20
|
## <a name="getting-started">🚀 Getting Started</a>
|
|
21
21
|
|
|
22
|
+
Install the package:
|
|
23
|
+
|
|
22
24
|
```bash
|
|
23
25
|
pnpm add @esposter/azure
|
|
24
26
|
```
|
|
@@ -42,10 +44,11 @@ pnpm add @esposter/azure
|
|
|
42
44
|
|
|
43
45
|
### Clauses
|
|
44
46
|
|
|
45
|
-
A `Clause<T>` is a filter condition
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
A `Clause<T>` is a filter condition on a property of `T`. The OData string is built from clauses. Do not assemble the string by hand.
|
|
48
|
+
|
|
49
|
+
- `BinaryOperator` holds the comparisons that Table Storage understands.
|
|
50
|
+
- `SearchOperator` holds the collection predicates that only Azure Search supports.
|
|
51
|
+
- `UnaryOperator` combines clauses.
|
|
49
52
|
|
|
50
53
|
```ts
|
|
51
54
|
import type { Clause } from "@esposter/azure";
|
|
@@ -57,10 +60,12 @@ const clause: Clause<{ createdAt: Date }> = { key: "createdAt", operator: Binary
|
|
|
57
60
|
|
|
58
61
|
### Values
|
|
59
62
|
|
|
60
|
-
`serializeValue` renders a `SerializableValue` the
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
`serializeValue` renders a `SerializableValue` in the format that the target service expects. `isTableFilter` selects the format.
|
|
64
|
+
|
|
65
|
+
- Table Storage: a `Date` renders as a `datetime'<iso>'` literal.
|
|
66
|
+
- Search: a `Date` renders as the bare ISO string.
|
|
67
|
+
|
|
68
|
+
Strings go through `escapeValue`. It doubles each embedded single quote, so a value cannot close its own literal or add filter syntax of its own.
|
|
64
69
|
|
|
65
70
|
```ts
|
|
66
71
|
import { escapeValue, serializeValue } from "@esposter/azure";
|
|
@@ -68,10 +73,14 @@ import { escapeValue, serializeValue } from "@esposter/azure";
|
|
|
68
73
|
|
|
69
74
|
### Filters
|
|
70
75
|
|
|
71
|
-
`serializeClauses` turns a clause array into a Table Storage filter string
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
`serializeClauses` turns a clause array into a Table Storage filter string. `serializeSearchClauses` turns a clause array into a Search filter string. The two differ only in how a `Date` renders.
|
|
77
|
+
|
|
78
|
+
Clauses on the same key are grouped:
|
|
79
|
+
|
|
80
|
+
- A range pair is joined with `and`.
|
|
81
|
+
- Any other group is joined with `or`.
|
|
82
|
+
|
|
83
|
+
Do not assemble the boolean structure by hand. `deserializeClause` is the inverse.
|
|
75
84
|
|
|
76
85
|
```ts
|
|
77
86
|
import { BinaryOperator, CompositeKeyPropertyNames, getTableNullClause, serializeClauses } from "@esposter/azure";
|
|
@@ -83,13 +92,16 @@ const filter = serializeClauses([
|
|
|
83
92
|
]);
|
|
84
93
|
```
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
The two services handle null differently. The helpers handle the difference.
|
|
96
|
+
|
|
97
|
+
- `getTableNullClause` expresses "is null" for Table Storage.
|
|
98
|
+
- Search supports null directly, through `getSearchNullClause` / `getSearchNonNullClause`.
|
|
99
|
+
|
|
100
|
+
`getPartitionKeyFilter` builds the one-clause filter that selects everything under one partition key. A read, a count and a purge of the same entity must all start from this filter.
|
|
87
101
|
|
|
88
102
|
### Keys
|
|
89
103
|
|
|
90
|
-
`CompositeKey` is the `partitionKey`/`rowKey` pair every Table entity carries
|
|
91
|
-
is how those names are referred to without restating the strings. `serializeKey` and `deserializeKey` convert
|
|
92
|
-
at the boundary.
|
|
104
|
+
`CompositeKey` is the `partitionKey`/`rowKey` pair that every Table entity carries. Refer to these names through `CompositeKeyPropertyNames`. Do not restate the strings. `serializeKey` and `deserializeKey` convert keys at the boundary.
|
|
93
105
|
|
|
94
106
|
```ts
|
|
95
107
|
import { CompositeKey, CompositeKeyPropertyNames, serializeKey } from "@esposter/azure";
|
|
@@ -97,8 +109,7 @@ import { CompositeKey, CompositeKeyPropertyNames, serializeKey } from "@esposter
|
|
|
97
109
|
|
|
98
110
|
### Limits
|
|
99
111
|
|
|
100
|
-
`AZURE_MAX_BATCH_SIZE`, `AZURE_MAX_PAGE_SIZE` and `AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS
|
|
101
|
-
ceilings, named so a caller paginating, batching or enqueueing does not hard-code them.
|
|
112
|
+
These constants hold the maximum values that the service allows: `AZURE_MAX_BATCH_SIZE`, `AZURE_MAX_PAGE_SIZE` and `AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS`. Use them when you paginate, batch or enqueue. Do not hard-code these values.
|
|
102
113
|
|
|
103
114
|
## <a name="license">⚖️ License</a>
|
|
104
115
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { PropertyNames } from "@esposter/shared";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { TableEntity } from "@azure/data-tables";
|
|
4
|
+
export declare enum SearchOperator {
|
|
5
|
+
ArrayAny = "arrayAny",
|
|
6
|
+
ArrayContains = "arrayContains"
|
|
7
|
+
}
|
|
8
|
+
export declare enum BinaryOperator {
|
|
9
|
+
Eq = "eq",
|
|
10
|
+
Ge = "ge",
|
|
11
|
+
Gt = "gt",
|
|
12
|
+
Le = "le",
|
|
13
|
+
Lt = "lt",
|
|
14
|
+
Ne = "ne"
|
|
15
|
+
}
|
|
16
|
+
export declare const BinaryOperators: readonly BinaryOperator[];
|
|
17
|
+
export type SerializableValue = boolean | Date | null | number | string;
|
|
18
|
+
export declare const serializableValueSchema: z.ZodType<SerializableValue, SerializableValue>;
|
|
19
|
+
export type Clause<T extends object> = {
|
|
20
|
+
key: keyof T & string;
|
|
21
|
+
not?: boolean;
|
|
22
|
+
} & ({
|
|
23
|
+
operator: BinaryOperator;
|
|
24
|
+
value: SerializableValue;
|
|
25
|
+
} | {
|
|
26
|
+
operator: SearchOperator.ArrayAny;
|
|
27
|
+
} | {
|
|
28
|
+
operator: SearchOperator.ArrayContains;
|
|
29
|
+
value: SerializableValue[];
|
|
30
|
+
});
|
|
31
|
+
export declare enum UnaryOperator {
|
|
32
|
+
And = "and",
|
|
33
|
+
Not = "not",
|
|
34
|
+
Or = "or"
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
Omit any index signatures from the given object type, leaving only explicitly defined properties.
|
|
38
|
+
|
|
39
|
+
This is the counterpart of `PickIndexSignature`.
|
|
40
|
+
|
|
41
|
+
Use-cases:
|
|
42
|
+
- Remove overly permissive signatures from third-party types.
|
|
43
|
+
|
|
44
|
+
This type was taken from this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
|
|
45
|
+
|
|
46
|
+
It relies on the fact that an empty object (`{}`) is assignable to an object with just an index signature, like `Record<string, unknown>`, but not to an object with explicitly defined keys, like `Record<'foo' | 'bar', unknown>`.
|
|
47
|
+
|
|
48
|
+
(The actual value type, `unknown`, is irrelevant and could be any type. Only the key type matters.)
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
const indexed: Record<string, unknown> = {}; // Allowed
|
|
52
|
+
|
|
53
|
+
// @ts-expect-error
|
|
54
|
+
const keyed: Record<'foo', unknown> = {}; // Error
|
|
55
|
+
// TS2739: Type '{}' is missing the following properties from type 'Record<"foo" | "bar", unknown>': foo, bar
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Instead of causing a type error like the above, you can also use a [conditional type](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) to test whether a type is assignable to another:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
type Indexed = {} extends Record<string, unknown>
|
|
62
|
+
? '✅ `{}` is assignable to `Record<string, unknown>`'
|
|
63
|
+
: '❌ `{}` is NOT assignable to `Record<string, unknown>`';
|
|
64
|
+
|
|
65
|
+
type IndexedResult = Indexed;
|
|
66
|
+
//=> '✅ `{}` is assignable to `Record<string, unknown>`'
|
|
67
|
+
|
|
68
|
+
type Keyed = {} extends Record<'foo' | 'bar', unknown>
|
|
69
|
+
? '✅ `{}` is assignable to `Record<\'foo\' | \'bar\', unknown>`'
|
|
70
|
+
: '❌ `{}` is NOT assignable to `Record<\'foo\' | \'bar\', unknown>`';
|
|
71
|
+
|
|
72
|
+
type KeyedResult = Keyed;
|
|
73
|
+
//=> '❌ `{}` is NOT assignable to `Record<\'foo\' | \'bar\', unknown>`'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Using a [mapped type](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#further-exploration), you can then check for each `KeyType` of `ObjectType`...
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
type OmitIndexSignature<ObjectType> = {
|
|
80
|
+
[KeyType in keyof ObjectType // Map each key of `ObjectType`...
|
|
81
|
+
]: ObjectType[KeyType]; // ...to its original value, i.e. `OmitIndexSignature<Foo> == Foo`.
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
...whether an empty object (`{}`) would be assignable to an object with that `KeyType` (`Record<KeyType, unknown>`)...
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
type OmitIndexSignature<ObjectType> = {
|
|
89
|
+
[KeyType in keyof ObjectType
|
|
90
|
+
// Is `{}` assignable to `Record<KeyType, unknown>`?
|
|
91
|
+
as {} extends Record<KeyType, unknown>
|
|
92
|
+
? never // ✅ `{}` is assignable to `Record<KeyType, unknown>`
|
|
93
|
+
: KeyType // ❌ `{}` is NOT assignable to `Record<KeyType, unknown>`
|
|
94
|
+
]: ObjectType[KeyType];
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If `{}` is assignable, it means that `KeyType` is an index signature and we want to remove it. If it is not assignable, `KeyType` is a "real" key and we want to keep it.
|
|
99
|
+
|
|
100
|
+
@example
|
|
101
|
+
```
|
|
102
|
+
import type {OmitIndexSignature} from 'type-fest';
|
|
103
|
+
|
|
104
|
+
type Example = {
|
|
105
|
+
// These index signatures will be removed.
|
|
106
|
+
[x: string]: any;
|
|
107
|
+
[x: number]: any;
|
|
108
|
+
[x: symbol]: any;
|
|
109
|
+
[x: `head-${string}`]: string;
|
|
110
|
+
[x: `${string}-tail`]: string;
|
|
111
|
+
[x: `head-${string}-tail`]: string;
|
|
112
|
+
[x: `${bigint}`]: string;
|
|
113
|
+
[x: `embedded-${number}`]: string;
|
|
114
|
+
|
|
115
|
+
// These explicitly defined keys will remain.
|
|
116
|
+
foo: 'bar';
|
|
117
|
+
qux?: 'baz';
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type ExampleWithoutIndexSignatures = OmitIndexSignature<Example>;
|
|
121
|
+
//=> {foo: 'bar'; qux?: 'baz'}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
@see {@link PickIndexSignature}
|
|
125
|
+
@category Object
|
|
126
|
+
*/
|
|
127
|
+
type OmitIndexSignature<ObjectType> = { [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown> ? never : KeyType]: ObjectType[KeyType]; };
|
|
128
|
+
export declare class CompositeKey implements OmitIndexSignature<TableEntity> {
|
|
129
|
+
partitionKey: string;
|
|
130
|
+
rowKey: string;
|
|
131
|
+
}
|
|
132
|
+
export declare const CompositeKeyPropertyNames: PropertyNames<CompositeKey>;
|
|
133
|
+
export declare const getSearchNonNullClause: <T extends object>(key: keyof T & string) => Clause<T>;
|
|
134
|
+
export declare const getSearchNullClause: <T extends object>(key: keyof T & string) => Clause<T>;
|
|
135
|
+
export declare const AZURE_MAX_BATCH_SIZE = 100;
|
|
136
|
+
export declare const AZURE_MAX_PAGE_SIZE = 1e3;
|
|
137
|
+
export declare const SERIALIZABLE_VALUE_MAX_LENGTH = 100;
|
|
138
|
+
export declare const AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS: number;
|
|
139
|
+
export declare const AZURE_MAX_STRING_PROPERTY_LENGTH: number;
|
|
140
|
+
export declare const MAX_BLOB_BATCH_DELETIONS = 256;
|
|
141
|
+
export declare const MAX_EVENT_GRID_PUBLISH_REQUEST_BYTES: number;
|
|
142
|
+
export declare const MAX_EVENT_GRID_PUBLISH_BYTES: number;
|
|
143
|
+
export declare const MAX_EVENT_GRID_PUBLISH_EVENT_COUNT = 5e3;
|
|
144
|
+
export declare const KEYS_TO_CAPITALIZE: ReadonlySet<string>;
|
|
145
|
+
export declare const KEYS_TO_UNCAPITALIZE: ReadonlySet<string>;
|
|
146
|
+
export declare const deserializeKey: (key: string) => string;
|
|
147
|
+
export declare const getTableNullClause: <T extends object>(key: keyof T & string) => Clause<T>;
|
|
148
|
+
export declare const serializeKey: (key: string) => string;
|
|
149
|
+
export declare const checkIsBinaryOperator: (value: string) => value is BinaryOperator;
|
|
150
|
+
export declare const DATETIME_LITERAL_PREFIX = "datetime'";
|
|
151
|
+
export declare const CLAUSE_REGEX: RegExp;
|
|
152
|
+
export declare const deserializeClause: (serializedClause: string) => Extract<Clause<Record<string, unknown>>, {
|
|
153
|
+
operator: BinaryOperator;
|
|
154
|
+
}>;
|
|
155
|
+
export declare const deserializeValue: (serializedValue: string) => SerializableValue;
|
|
156
|
+
export declare const escapeValue: (value: string) => string;
|
|
157
|
+
export declare const getPartitionKeyFilter: (partitionKey: CompositeKey["partitionKey"]) => string;
|
|
158
|
+
export declare const parseIsoDate: (isoValue: string) => Date | undefined;
|
|
159
|
+
export declare const RangeOperators: (BinaryOperator | SearchOperator)[];
|
|
160
|
+
export declare const serializeClause: (clause: Clause<Record<string, unknown>>, isTableFilter?: boolean) => string;
|
|
161
|
+
export declare const serializeClauses: (clauses: Clause<Record<string, unknown>>[]) => string;
|
|
162
|
+
export declare const serializeClausesCore: (clauses: Clause<Record<string, unknown>>[], isTableFilter: boolean) => string;
|
|
163
|
+
export declare const serializeSearchClauses: (clauses: Clause<Record<string, unknown>>[]) => string;
|
|
164
|
+
export declare const serializeValue: (value: SerializableValue, isTableFilter?: boolean) => string;
|
|
165
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { InvalidOperationError, KIBIBYTE, NotFoundError, Operation, capitalize, getPropertyNames, getResult, normalizeString, takeOne, uncapitalize } from "@esposter/shared";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// src/models/search/SearchOperator.ts
|
|
4
|
+
let SearchOperator = /* @__PURE__ */ function(SearchOperator) {
|
|
5
|
+
SearchOperator["ArrayAny"] = "arrayAny";
|
|
6
|
+
SearchOperator["ArrayContains"] = "arrayContains";
|
|
7
|
+
return SearchOperator;
|
|
8
|
+
}({});
|
|
9
|
+
// src/models/shared/BinaryOperator.ts
|
|
10
|
+
let BinaryOperator = /* @__PURE__ */ function(BinaryOperator) {
|
|
11
|
+
BinaryOperator["Eq"] = "eq";
|
|
12
|
+
BinaryOperator["Ge"] = "ge";
|
|
13
|
+
BinaryOperator["Gt"] = "gt";
|
|
14
|
+
BinaryOperator["Le"] = "le";
|
|
15
|
+
BinaryOperator["Lt"] = "lt";
|
|
16
|
+
BinaryOperator["Ne"] = "ne";
|
|
17
|
+
return BinaryOperator;
|
|
18
|
+
}({});
|
|
19
|
+
const BinaryOperators = Object.values(BinaryOperator);
|
|
20
|
+
// src/services/shared/constants.ts
|
|
21
|
+
const AZURE_MAX_BATCH_SIZE = 100;
|
|
22
|
+
const AZURE_MAX_PAGE_SIZE = 1e3;
|
|
23
|
+
const SERIALIZABLE_VALUE_MAX_LENGTH = 100;
|
|
24
|
+
const AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS = Temporal.Duration.from({ days: 7 }).total("milliseconds");
|
|
25
|
+
const AZURE_MAX_STRING_PROPERTY_LENGTH = 32768;
|
|
26
|
+
const MAX_BLOB_BATCH_DELETIONS = 256;
|
|
27
|
+
const MAX_EVENT_GRID_PUBLISH_REQUEST_BYTES = 1024 * KIBIBYTE;
|
|
28
|
+
const MAX_EVENT_GRID_PUBLISH_BYTES = MAX_EVENT_GRID_PUBLISH_REQUEST_BYTES / 2;
|
|
29
|
+
const MAX_EVENT_GRID_PUBLISH_EVENT_COUNT = 5e3;
|
|
30
|
+
// src/models/shared/SerializableValue.ts
|
|
31
|
+
const serializableValueSchema = z.union([
|
|
32
|
+
z.boolean(),
|
|
33
|
+
z.date(),
|
|
34
|
+
z.null(),
|
|
35
|
+
z.number(),
|
|
36
|
+
z.string().min(1).max(100)
|
|
37
|
+
]);
|
|
38
|
+
// src/models/shared/UnaryOperator.ts
|
|
39
|
+
let UnaryOperator = /* @__PURE__ */ function(UnaryOperator) {
|
|
40
|
+
UnaryOperator["And"] = "and";
|
|
41
|
+
UnaryOperator["Not"] = "not";
|
|
42
|
+
UnaryOperator["Or"] = "or";
|
|
43
|
+
return UnaryOperator;
|
|
44
|
+
}({});
|
|
45
|
+
// src/models/table/CompositeKey.ts
|
|
46
|
+
var CompositeKey = class {};
|
|
47
|
+
const CompositeKeyPropertyNames = getPropertyNames();
|
|
48
|
+
// src/services/search/getSearchNonNullClause.ts
|
|
49
|
+
const getSearchNonNullClause = (key) => ({
|
|
50
|
+
key,
|
|
51
|
+
operator: "ne",
|
|
52
|
+
value: null
|
|
53
|
+
});
|
|
54
|
+
// src/services/search/getSearchNullClause.ts
|
|
55
|
+
const getSearchNullClause = (key) => ({
|
|
56
|
+
key,
|
|
57
|
+
operator: "eq",
|
|
58
|
+
value: null
|
|
59
|
+
});
|
|
60
|
+
// src/services/table/constants.ts
|
|
61
|
+
const KEYS_TO_CAPITALIZE = /* @__PURE__ */ new Set([CompositeKeyPropertyNames.partitionKey, CompositeKeyPropertyNames.rowKey]);
|
|
62
|
+
const KEYS_TO_UNCAPITALIZE = /* @__PURE__ */ new Set([capitalize(CompositeKeyPropertyNames.partitionKey), capitalize(CompositeKeyPropertyNames.rowKey)]);
|
|
63
|
+
// src/services/table/deserializeKey.ts
|
|
64
|
+
const deserializeKey = (key) => KEYS_TO_UNCAPITALIZE.has(key) ? uncapitalize(key) : key;
|
|
65
|
+
// src/services/table/getTableNullClause.ts
|
|
66
|
+
const getTableNullClause = (key) => ({
|
|
67
|
+
key,
|
|
68
|
+
not: true,
|
|
69
|
+
operator: "ne",
|
|
70
|
+
value: NaN
|
|
71
|
+
});
|
|
72
|
+
// src/services/table/serializeKey.ts
|
|
73
|
+
const serializeKey = (key) => KEYS_TO_CAPITALIZE.has(key) ? capitalize(key) : key;
|
|
74
|
+
// src/services/transformer/checkIsBinaryOperator.ts
|
|
75
|
+
const checkIsBinaryOperator = (value) => BinaryOperators.some((binaryOperator) => binaryOperator === value);
|
|
76
|
+
// src/services/transformer/constants.ts
|
|
77
|
+
const DATETIME_LITERAL_PREFIX = "datetime'";
|
|
78
|
+
const VALUE_REGEX = [
|
|
79
|
+
true,
|
|
80
|
+
false,
|
|
81
|
+
String.raw`${DATETIME_LITERAL_PREFIX}[^']*'`,
|
|
82
|
+
String.raw`\d{4}-\d{2}-\d{2}T[^\s]+`,
|
|
83
|
+
String(null),
|
|
84
|
+
String.raw`-?\d+(?:\.\d+)?`,
|
|
85
|
+
String(NaN),
|
|
86
|
+
"'(?:[^']|'')*'"
|
|
87
|
+
].join("|");
|
|
88
|
+
const CLAUSE_REGEX = new RegExp(String.raw`^(?<not>not\s+)?(?<key>[A-Za-z0-9_]*)\s*(?<operator>${BinaryOperators.join("|")})\s+(?<value>${VALUE_REGEX})$`, "iu");
|
|
89
|
+
// src/services/transformer/parseIsoDate.ts
|
|
90
|
+
const parseIsoDate = (isoValue) => getResult(() => new Date(Temporal.Instant.from(isoValue).epochMilliseconds)).unwrapOr(void 0);
|
|
91
|
+
// src/services/transformer/deserializeValue.ts
|
|
92
|
+
const deserializeValue = (serializedValue) => {
|
|
93
|
+
if (serializedValue === String(true)) return true;
|
|
94
|
+
else if (serializedValue === String(false)) return false;
|
|
95
|
+
else if (serializedValue === String(null)) return null;
|
|
96
|
+
else if (Number.isFinite(Number(serializedValue))) return Number(serializedValue);
|
|
97
|
+
else if (serializedValue === String(NaN)) return NaN;
|
|
98
|
+
else if (serializedValue.startsWith("datetime'") && serializedValue.endsWith("'")) return parseIsoDate(serializedValue.slice(9, -1)) ?? serializedValue;
|
|
99
|
+
else if (serializedValue.startsWith("'") && serializedValue.endsWith("'")) return serializedValue.slice(1, -1).replaceAll("''", "'");
|
|
100
|
+
else return parseIsoDate(serializedValue) ?? serializedValue;
|
|
101
|
+
};
|
|
102
|
+
// src/services/transformer/deserializeClause.ts
|
|
103
|
+
const deserializeClause = (serializedClause) => {
|
|
104
|
+
const normalizedClause = normalizeString(serializedClause);
|
|
105
|
+
const match = CLAUSE_REGEX.exec(normalizedClause);
|
|
106
|
+
if (!match) throw new NotFoundError(deserializeClause.name, normalizedClause);
|
|
107
|
+
const groups = match.groups;
|
|
108
|
+
if (!groups) throw new NotFoundError(deserializeClause.name, normalizedClause);
|
|
109
|
+
const operator = groups.operator.toLowerCase();
|
|
110
|
+
if (!checkIsBinaryOperator(operator)) throw new NotFoundError(deserializeClause.name, normalizedClause);
|
|
111
|
+
return {
|
|
112
|
+
key: deserializeKey(groups.key),
|
|
113
|
+
not: Boolean(groups.not),
|
|
114
|
+
operator,
|
|
115
|
+
value: deserializeValue(groups.value)
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
// src/services/transformer/escapeValue.ts
|
|
119
|
+
const escapeValue = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
120
|
+
// src/services/transformer/RangeOperators.ts
|
|
121
|
+
const RangeOperators = [
|
|
122
|
+
"ge",
|
|
123
|
+
"gt",
|
|
124
|
+
"le",
|
|
125
|
+
"lt"
|
|
126
|
+
];
|
|
127
|
+
// src/services/transformer/serializeValue.ts
|
|
128
|
+
const serializeValue = (value, isTableFilter = false) => {
|
|
129
|
+
if (value instanceof Date) return isTableFilter ? `${DATETIME_LITERAL_PREFIX}${value.toISOString()}'` : value.toISOString();
|
|
130
|
+
else if (typeof value === "string") return escapeValue(value);
|
|
131
|
+
else return String(value);
|
|
132
|
+
};
|
|
133
|
+
// src/services/transformer/serializeClause.ts
|
|
134
|
+
const serializeClause = (clause, isTableFilter = false) => {
|
|
135
|
+
const keys = clause.key.split("/").map((key) => serializeKey(key));
|
|
136
|
+
const clauseKey = keys.join("/");
|
|
137
|
+
if (clause.operator === "arrayAny") return `${clauseKey}/any()`;
|
|
138
|
+
else if (clause.operator === "arrayContains") {
|
|
139
|
+
const serializedValue = serializeValue(clause.value.join(","));
|
|
140
|
+
if (keys.length === 1) return `${clauseKey}/any(x: search.in(x, ${serializedValue}))`;
|
|
141
|
+
else if (keys.length === 2) {
|
|
142
|
+
const [collectionName, propertyName] = keys;
|
|
143
|
+
return `${collectionName}/any(x: search.in(x/${propertyName}, ${serializedValue}))`;
|
|
144
|
+
} else throw new InvalidOperationError(Operation.Read, serializeClause.name, clause.key);
|
|
145
|
+
}
|
|
146
|
+
const { not, operator, value } = clause;
|
|
147
|
+
const baseClause = `${clauseKey} ${operator} ${serializeValue(value, isTableFilter)}`;
|
|
148
|
+
return not ? `not(${baseClause})` : baseClause;
|
|
149
|
+
};
|
|
150
|
+
// src/services/transformer/serializeClausesCore.ts
|
|
151
|
+
const serializeClausesCore = (clauses, isTableFilter) => {
|
|
152
|
+
if (clauses.length === 0) return "";
|
|
153
|
+
else if (clauses.length === 1) return serializeClause(takeOne(clauses), isTableFilter);
|
|
154
|
+
else {
|
|
155
|
+
const groupedClauses = Object.groupBy(clauses, ({ key }) => key);
|
|
156
|
+
const groupedStrings = [];
|
|
157
|
+
for (const clauseGroup of Object.values(groupedClauses)) if (clauseGroup.length === 1) groupedStrings.push(serializeClause(takeOne(clauseGroup), isTableFilter));
|
|
158
|
+
else {
|
|
159
|
+
const serializedClauses = clauseGroup.map((clause) => serializeClause(clause, isTableFilter));
|
|
160
|
+
const groupedString = clauseGroup.some(({ operator }) => RangeOperators.includes(operator)) ? serializedClauses.join(` and `) : `(${serializedClauses.join(` or `)})`;
|
|
161
|
+
groupedStrings.push(groupedString);
|
|
162
|
+
}
|
|
163
|
+
return groupedStrings.join(` and `);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
// src/services/transformer/serializeClauses.ts
|
|
167
|
+
const serializeClauses = (clauses) => serializeClausesCore(clauses, true);
|
|
168
|
+
// src/services/transformer/getPartitionKeyFilter.ts
|
|
169
|
+
const getPartitionKeyFilter = (partitionKey) => serializeClauses([{
|
|
170
|
+
key: CompositeKeyPropertyNames.partitionKey,
|
|
171
|
+
operator: "eq",
|
|
172
|
+
value: partitionKey
|
|
173
|
+
}]);
|
|
174
|
+
// src/services/transformer/serializeSearchClauses.ts
|
|
175
|
+
const serializeSearchClauses = (clauses) => serializeClausesCore(clauses, false);
|
|
176
|
+
export { AZURE_MAX_BATCH_SIZE, AZURE_MAX_PAGE_SIZE, AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS, AZURE_MAX_STRING_PROPERTY_LENGTH, BinaryOperator, BinaryOperators, CLAUSE_REGEX, CompositeKey, CompositeKeyPropertyNames, DATETIME_LITERAL_PREFIX, KEYS_TO_CAPITALIZE, KEYS_TO_UNCAPITALIZE, MAX_BLOB_BATCH_DELETIONS, MAX_EVENT_GRID_PUBLISH_BYTES, MAX_EVENT_GRID_PUBLISH_EVENT_COUNT, MAX_EVENT_GRID_PUBLISH_REQUEST_BYTES, RangeOperators, SERIALIZABLE_VALUE_MAX_LENGTH, SearchOperator, UnaryOperator, checkIsBinaryOperator, deserializeClause, deserializeKey, deserializeValue, escapeValue, getPartitionKeyFilter, getSearchNonNullClause, getSearchNullClause, getTableNullClause, parseIsoDate, serializableValueSchema, serializeClause, serializeClauses, serializeClausesCore, serializeKey, serializeSearchClauses, serializeValue };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esposter/azure",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "The Azure wire conventions Esposter's clients and its mocks both speak — OData filter clauses, entity key casing, and service limits.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"azure",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"typecheck": "tsrs --fast"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@esposter/shared": "4.
|
|
47
|
+
"@esposter/shared": "4.5.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@esposter/configuration": "4.
|
|
51
|
-
"tsdown": "0.23.
|
|
50
|
+
"@esposter/configuration": "4.5.0",
|
|
51
|
+
"tsdown": "^0.23.5",
|
|
52
52
|
"type-fest": "^5.10.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"inlinedDependencies": {
|
|
59
59
|
"type-fest": "5.10.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "e30c001f3b4b15693e75fb53092d62a97ade4d2b"
|
|
62
62
|
}
|