@esposter/azure 4.3.3 → 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 +49 -42
- package/dist/index.d.ts +8 -2
- package/dist/index.js +20 -11
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -5,35 +5,50 @@
|
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
it runs anywhere.
|
|
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
|
+
|
|
10
|
+
Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs).
|
|
12
11
|
|
|
13
12
|
## Table of Contents
|
|
14
13
|
|
|
15
14
|
- 🚀 [Getting Started](#getting-started)
|
|
16
|
-
-
|
|
15
|
+
- 🧩 [Key Exports](#key-exports)
|
|
17
16
|
- ⚖️ [License](#license)
|
|
18
17
|
|
|
19
18
|
---
|
|
20
19
|
|
|
21
20
|
## <a name="getting-started">🚀 Getting Started</a>
|
|
22
21
|
|
|
22
|
+
Install the package:
|
|
23
|
+
|
|
23
24
|
```bash
|
|
24
25
|
pnpm add @esposter/azure
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
## <a name="
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
## <a name="key-exports">🧩 Key Exports</a>
|
|
29
|
+
|
|
30
|
+
| Export | Kind | Purpose |
|
|
31
|
+
| --------------------------- | -------- | ---------------------------------------------------------------------------- |
|
|
32
|
+
| `AZURE_MAX_PAGE_SIZE` | const | Largest page size the service returns in one call |
|
|
33
|
+
| `BinaryOperator` | enum | Comparison operators Table Storage understands |
|
|
34
|
+
| `Clause` | type | A filter condition on one property, the unit OData strings are built from |
|
|
35
|
+
| `CompositeKey` | class | The `partitionKey` and `rowKey` pair every Table entity carries |
|
|
36
|
+
| `CompositeKeyPropertyNames` | const | The `CompositeKey` property names, so callers never restate the strings |
|
|
37
|
+
| `deserializeClause` | function | Parses a serialized filter clause back into a `Clause`, throwing on no match |
|
|
38
|
+
| `escapeValue` | function | Quotes a string as an OData literal, doubling embedded single quotes |
|
|
39
|
+
| `getPartitionKeyFilter` | function | The filter selecting everything under one partition key |
|
|
40
|
+
| `getTableNullClause` | function | The Table Storage "is null" clause, a negated `NaN` comparison |
|
|
41
|
+
| `SearchOperator` | enum | Collection predicates only Azure Search supports |
|
|
42
|
+
| `serializeClauses` | function | Serializes clauses into a Table Storage OData filter string |
|
|
43
|
+
| `serializeValue` | function | Renders one `SerializableValue` as a literal for Table or Search |
|
|
30
44
|
|
|
31
45
|
### Clauses
|
|
32
46
|
|
|
33
|
-
A `Clause<T>` is a filter condition
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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.
|
|
37
52
|
|
|
38
53
|
```ts
|
|
39
54
|
import type { Clause } from "@esposter/azure";
|
|
@@ -45,10 +60,12 @@ const clause: Clause<{ createdAt: Date }> = { key: "createdAt", operator: Binary
|
|
|
45
60
|
|
|
46
61
|
### Values
|
|
47
62
|
|
|
48
|
-
`serializeValue` renders a `SerializableValue` the
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
52
69
|
|
|
53
70
|
```ts
|
|
54
71
|
import { escapeValue, serializeValue } from "@esposter/azure";
|
|
@@ -56,11 +73,14 @@ import { escapeValue, serializeValue } from "@esposter/azure";
|
|
|
56
73
|
|
|
57
74
|
### Filters
|
|
58
75
|
|
|
59
|
-
`serializeClauses` turns a clause array into a Table Storage filter string
|
|
60
|
-
|
|
61
|
-
same key are grouped
|
|
62
|
-
|
|
63
|
-
|
|
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.
|
|
64
84
|
|
|
65
85
|
```ts
|
|
66
86
|
import { BinaryOperator, CompositeKeyPropertyNames, getTableNullClause, serializeClauses } from "@esposter/azure";
|
|
@@ -72,17 +92,16 @@ const filter = serializeClauses([
|
|
|
72
92
|
]);
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
79
101
|
|
|
80
102
|
### Keys
|
|
81
103
|
|
|
82
|
-
`CompositeKey` is the `partitionKey`/`rowKey` pair every Table entity carries
|
|
83
|
-
is how those names are referred to without restating the strings. The wire spells those two capitalized and
|
|
84
|
-
JavaScript does not, so `serializeKey` and `deserializeKey` convert at the boundary and nothing in between has
|
|
85
|
-
to know.
|
|
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.
|
|
86
105
|
|
|
87
106
|
```ts
|
|
88
107
|
import { CompositeKey, CompositeKeyPropertyNames, serializeKey } from "@esposter/azure";
|
|
@@ -90,19 +109,7 @@ import { CompositeKey, CompositeKeyPropertyNames, serializeKey } from "@esposter
|
|
|
90
109
|
|
|
91
110
|
### Limits
|
|
92
111
|
|
|
93
|
-
`AZURE_MAX_BATCH_SIZE`, `AZURE_MAX_PAGE_SIZE` and `AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS
|
|
94
|
-
ceilings, named so a caller paginating, batching or enqueueing does not hard-code them.
|
|
95
|
-
|
|
96
|
-
### Commands
|
|
97
|
-
|
|
98
|
-
Run from `packages/azure/`:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
pnpm build # compile to dist/
|
|
102
|
-
pnpm test # vitest watch mode (coverage is run from the repo root)
|
|
103
|
-
pnpm lint:fix # auto-fix lint
|
|
104
|
-
pnpm typecheck # type check
|
|
105
|
-
```
|
|
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.
|
|
106
113
|
|
|
107
114
|
## <a name="license">⚖️ License</a>
|
|
108
115
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { PropertyNames } from "@esposter/shared";
|
|
2
|
+
import { z } from "zod";
|
|
3
3
|
import { TableEntity } from "@azure/data-tables";
|
|
4
4
|
export declare enum SearchOperator {
|
|
5
5
|
ArrayAny = "arrayAny",
|
|
@@ -137,12 +137,16 @@ export declare const AZURE_MAX_PAGE_SIZE = 1e3;
|
|
|
137
137
|
export declare const SERIALIZABLE_VALUE_MAX_LENGTH = 100;
|
|
138
138
|
export declare const AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS: number;
|
|
139
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;
|
|
140
144
|
export declare const KEYS_TO_CAPITALIZE: ReadonlySet<string>;
|
|
141
145
|
export declare const KEYS_TO_UNCAPITALIZE: ReadonlySet<string>;
|
|
142
146
|
export declare const deserializeKey: (key: string) => string;
|
|
143
147
|
export declare const getTableNullClause: <T extends object>(key: keyof T & string) => Clause<T>;
|
|
144
148
|
export declare const serializeKey: (key: string) => string;
|
|
145
|
-
export declare const
|
|
149
|
+
export declare const checkIsBinaryOperator: (value: string) => value is BinaryOperator;
|
|
146
150
|
export declare const DATETIME_LITERAL_PREFIX = "datetime'";
|
|
147
151
|
export declare const CLAUSE_REGEX: RegExp;
|
|
148
152
|
export declare const deserializeClause: (serializedClause: string) => Extract<Clause<Record<string, unknown>>, {
|
|
@@ -151,6 +155,8 @@ export declare const deserializeClause: (serializedClause: string) => Extract<Cl
|
|
|
151
155
|
export declare const deserializeValue: (serializedValue: string) => SerializableValue;
|
|
152
156
|
export declare const escapeValue: (value: string) => string;
|
|
153
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)[];
|
|
154
160
|
export declare const serializeClause: (clause: Clause<Record<string, unknown>>, isTableFilter?: boolean) => string;
|
|
155
161
|
export declare const serializeClauses: (clauses: Clause<Record<string, unknown>>[]) => string;
|
|
156
162
|
export declare const serializeClausesCore: (clauses: Clause<Record<string, unknown>>[], isTableFilter: boolean) => string;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { InvalidOperationError, KIBIBYTE, NotFoundError, Operation, capitalize, getPropertyNames, getResult, normalizeString, takeOne, uncapitalize } from "@esposter/shared";
|
|
1
2
|
import { z } from "zod";
|
|
2
|
-
import { InvalidOperationError, NotFoundError, Operation, capitalize, getPropertyNames, getResult, normalizeString, takeOne, uncapitalize } from "@esposter/shared";
|
|
3
3
|
// src/models/search/SearchOperator.ts
|
|
4
4
|
let SearchOperator = /* @__PURE__ */ function(SearchOperator) {
|
|
5
5
|
SearchOperator["ArrayAny"] = "arrayAny";
|
|
@@ -23,6 +23,10 @@ const AZURE_MAX_PAGE_SIZE = 1e3;
|
|
|
23
23
|
const SERIALIZABLE_VALUE_MAX_LENGTH = 100;
|
|
24
24
|
const AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS = Temporal.Duration.from({ days: 7 }).total("milliseconds");
|
|
25
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;
|
|
26
30
|
// src/models/shared/SerializableValue.ts
|
|
27
31
|
const serializableValueSchema = z.union([
|
|
28
32
|
z.boolean(),
|
|
@@ -67,13 +71,8 @@ const getTableNullClause = (key) => ({
|
|
|
67
71
|
});
|
|
68
72
|
// src/services/table/serializeKey.ts
|
|
69
73
|
const serializeKey = (key) => KEYS_TO_CAPITALIZE.has(key) ? capitalize(key) : key;
|
|
70
|
-
// src/services/transformer/
|
|
71
|
-
const
|
|
72
|
-
"ge",
|
|
73
|
-
"gt",
|
|
74
|
-
"le",
|
|
75
|
-
"lt"
|
|
76
|
-
];
|
|
74
|
+
// src/services/transformer/checkIsBinaryOperator.ts
|
|
75
|
+
const checkIsBinaryOperator = (value) => BinaryOperators.some((binaryOperator) => binaryOperator === value);
|
|
77
76
|
// src/services/transformer/constants.ts
|
|
78
77
|
const DATETIME_LITERAL_PREFIX = "datetime'";
|
|
79
78
|
const VALUE_REGEX = [
|
|
@@ -87,8 +86,9 @@ const VALUE_REGEX = [
|
|
|
87
86
|
"'(?:[^']|'')*'"
|
|
88
87
|
].join("|");
|
|
89
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");
|
|
90
|
-
// src/services/transformer/
|
|
89
|
+
// src/services/transformer/parseIsoDate.ts
|
|
91
90
|
const parseIsoDate = (isoValue) => getResult(() => new Date(Temporal.Instant.from(isoValue).epochMilliseconds)).unwrapOr(void 0);
|
|
91
|
+
// src/services/transformer/deserializeValue.ts
|
|
92
92
|
const deserializeValue = (serializedValue) => {
|
|
93
93
|
if (serializedValue === String(true)) return true;
|
|
94
94
|
else if (serializedValue === String(false)) return false;
|
|
@@ -106,15 +106,24 @@ const deserializeClause = (serializedClause) => {
|
|
|
106
106
|
if (!match) throw new NotFoundError(deserializeClause.name, normalizedClause);
|
|
107
107
|
const groups = match.groups;
|
|
108
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);
|
|
109
111
|
return {
|
|
110
112
|
key: deserializeKey(groups.key),
|
|
111
113
|
not: Boolean(groups.not),
|
|
112
|
-
operator
|
|
114
|
+
operator,
|
|
113
115
|
value: deserializeValue(groups.value)
|
|
114
116
|
};
|
|
115
117
|
};
|
|
116
118
|
// src/services/transformer/escapeValue.ts
|
|
117
119
|
const escapeValue = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
120
|
+
// src/services/transformer/RangeOperators.ts
|
|
121
|
+
const RangeOperators = [
|
|
122
|
+
"ge",
|
|
123
|
+
"gt",
|
|
124
|
+
"le",
|
|
125
|
+
"lt"
|
|
126
|
+
];
|
|
118
127
|
// src/services/transformer/serializeValue.ts
|
|
119
128
|
const serializeValue = (value, isTableFilter = false) => {
|
|
120
129
|
if (value instanceof Date) return isTableFilter ? `${DATETIME_LITERAL_PREFIX}${value.toISOString()}'` : value.toISOString();
|
|
@@ -164,4 +173,4 @@ const getPartitionKeyFilter = (partitionKey) => serializeClauses([{
|
|
|
164
173
|
}]);
|
|
165
174
|
// src/services/transformer/serializeSearchClauses.ts
|
|
166
175
|
const serializeSearchClauses = (clauses) => serializeClausesCore(clauses, false);
|
|
167
|
-
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, RangeOperators, SERIALIZABLE_VALUE_MAX_LENGTH, SearchOperator, UnaryOperator, deserializeClause, deserializeKey, deserializeValue, escapeValue, getPartitionKeyFilter, getSearchNonNullClause, getSearchNullClause, getTableNullClause, serializableValueSchema, serializeClause, serializeClauses, serializeClausesCore, serializeKey, serializeSearchClauses, serializeValue };
|
|
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",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"lint": "eslint .",
|
|
42
42
|
"lint:fix": "eslint --fix .",
|
|
43
43
|
"test": "vitest",
|
|
44
|
-
"typecheck": "
|
|
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
|
}
|