@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 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
- The parts of Azure's wire contract that both a real client and a fake one have to agree on: the OData filter
9
- clause vocabulary shared by Table Storage and Search, the entity key casing convention, and the service limits.
10
- No SDK client is constructed here and no credential is read — this package is pure data and pure functions, so
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
- - 📖 [Documentation](#documentation)
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="documentation">📖 Documentation</a>
28
-
29
- We highly recommend you take a look at the [documentation](https://github.com/Esposter/Esposter/tree/main/openspec/specs) to level up.
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 expressed against a property of `T`, and it is what the OData string is
34
- built from rather than the string being assembled by hand. `BinaryOperator` covers the comparisons Table
35
- Storage understands; `SearchOperator` covers the collection predicates only Azure Search does; `UnaryOperator`
36
- is how clauses are combined.
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 way the target service expects — Table Storage wants a
49
- `datetime'<iso>'` literal for a `Date`, Search wants the bare ISO string, and `isTableFilter` selects between
50
- them. Strings go through `escapeValue`, which doubles embedded single quotes so a value can never close its own
51
- literal and append filter syntax of its own.
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 and `serializeSearchClauses` into a
60
- Search one; the two differ only in how a `Date` renders, which is why they share a single core. Clauses on the
61
- same key are grouped — a range pair is joined with `and`, anything else with `or` — so a caller never assembles
62
- the boolean structure by hand. `deserializeClause` is the inverse, and is what lets a fake client evaluate a
63
- filter it was handed rather than pattern-matching the string.
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
- Null is where the two services diverge and the helpers carry the difference: Table Storage cannot compare
76
- against null at all, so `getTableNullClause` expresses "is null" as a negated `NaN` comparison, while Search
77
- supports it directly through `getSearchNullClause` / `getSearchNonNullClause`. `getPartitionKeyFilter` is the
78
- one-clause case worth naming, since a read, a count and a purge of the same entity must all start from it.
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, and `CompositeKeyPropertyNames`
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` are the service's own
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 RangeOperators: (BinaryOperator | SearchOperator)[];
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/RangeOperators.ts
71
- const RangeOperators = [
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/deserializeValue.ts
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: groups.operator.toLowerCase(),
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.3",
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": "tsc"
44
+ "typecheck": "tsrs --fast"
45
45
  },
46
46
  "dependencies": {
47
- "@esposter/shared": "4.3.3"
47
+ "@esposter/shared": "4.5.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@esposter/configuration": "4.3.3",
51
- "tsdown": "^0.23.1",
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": "955bbc130ad9b28877fd4ae8efc73a045141ce01"
61
+ "gitHead": "e30c001f3b4b15693e75fb53092d62a97ade4d2b"
62
62
  }