@mnci/az-durable 0.1.2 → 0.1.3

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.
@@ -8,7 +8,8 @@
8
8
  * `@typescript-eslint/utils` because this package ships **zero runtime
9
9
  * dependencies**, and a type-only dependency is still a dependency a consumer
10
10
  * must be able to resolve. These cover exactly what the three rules use.
11
- */ /**
11
+ */
12
+ /**
12
13
  * Whether a call expression registers an orchestration.
13
14
  *
14
15
  * @remarks
@@ -25,21 +26,22 @@
25
26
  * @returns `true` when the call registers an orchestration.
26
27
  * @throws Never - pure inspection.
27
28
  * @typeParam None - this function has no generic type parameters.
28
- */ function isOrchestrationRegistration(node) {
29
- var callee = node.callee;
30
- if (callee === undefined) {
31
- return false;
32
- }
33
- if (callee.type === 'Identifier' && callee.name === 'defineOrchestration') {
34
- return true;
35
- }
36
- // df.app.orchestration(...) — match on the trailing property, so any local
37
- // alias for the namespace still matches.
38
- if (callee.type === 'MemberExpression') {
39
- var property = callee.property;
40
- return (property === null || property === void 0 ? void 0 : property.type) === 'Identifier' && property.name === 'orchestration';
41
- }
29
+ */
30
+ function isOrchestrationRegistration(node) {
31
+ const callee = node.callee;
32
+ if (callee === undefined) {
42
33
  return false;
34
+ }
35
+ if (callee.type === 'Identifier' && callee.name === 'defineOrchestration') {
36
+ return true;
37
+ }
38
+ // df.app.orchestration(...) — match on the trailing property, so any local
39
+ // alias for the namespace still matches.
40
+ if (callee.type === 'MemberExpression') {
41
+ const property = callee.property;
42
+ return property?.type === 'Identifier' && property.name === 'orchestration';
43
+ }
44
+ return false;
43
45
  }
44
46
  /**
45
47
  * The name a callee refers to, if it is a plain identifier or member access.
@@ -55,28 +57,30 @@
55
57
  * @returns The identifier or property name, or `undefined`.
56
58
  * @throws Never - pure inspection.
57
59
  * @typeParam None - this function has no generic type parameters.
58
- */ function calleeName(node) {
59
- if (node === undefined) {
60
- return undefined;
61
- }
62
- if (node.type === 'Identifier') {
63
- return node.name;
64
- }
65
- if (node.type === 'MemberExpression') {
66
- var property = node.property;
67
- if ((property === null || property === void 0 ? void 0 : property.type) === 'Identifier') {
68
- return property.name;
69
- }
70
- }
60
+ */
61
+ function calleeName(node) {
62
+ if (node === undefined) {
71
63
  return undefined;
64
+ }
65
+ if (node.type === 'Identifier') {
66
+ return node.name;
67
+ }
68
+ if (node.type === 'MemberExpression') {
69
+ const property = node.property;
70
+ if (property?.type === 'Identifier') {
71
+ return property.name;
72
+ }
73
+ }
74
+ return undefined;
72
75
  }
73
76
 
74
- /** Global reads that differ on every replay, with the replacement to suggest. */ var FORBIDDEN_CALLS = {
75
- 'Date.now': 'now(context).getTime()',
76
- 'Math.random': 'context.df.newGuid(...) or an activity',
77
- 'crypto.randomUUID': 'context.df.newGuid(...)',
78
- fetch: 'an activity — network calls must not run in an orchestrator',
79
- axios: 'an activity — network calls must not run in an orchestrator'
77
+ /** Global reads that differ on every replay, with the replacement to suggest. */
78
+ const FORBIDDEN_CALLS = {
79
+ 'Date.now': 'now(context).getTime()',
80
+ 'Math.random': 'context.df.newGuid(...) or an activity',
81
+ 'crypto.randomUUID': 'context.df.newGuid(...)',
82
+ fetch: 'an activity — network calls must not run in an orchestrator',
83
+ axios: 'an activity — network calls must not run in an orchestrator'
80
84
  };
81
85
  /**
82
86
  * Flags non-deterministic operations inside an orchestration body.
@@ -91,92 +95,88 @@
91
95
  * only half of what a reader needs.
92
96
  *
93
97
  * Heuristic by design — see {@link isOrchestrationRegistration}.
94
- */ var noNondeterministicOrchestrator = {
95
- meta: {
96
- type: 'problem',
97
- docs: {
98
- description: 'Disallow non-deterministic operations inside an orchestration.'
99
- },
100
- schema: [],
101
- messages: {
102
- forbidden: '{{what}} is non-deterministic on replay. Use {{fix}} instead.',
103
- newDate: 'new Date() is non-deterministic on replay. Use now(context) instead.',
104
- processEnv: 'process.env is read at replay time and may differ between deploys. ' + 'Read it in an activity, or pass it as orchestration input.'
105
- }
98
+ */
99
+ const noNondeterministicOrchestrator = {
100
+ meta: {
101
+ type: 'problem',
102
+ docs: {
103
+ description: 'Disallow non-deterministic operations inside an orchestration.'
106
104
  },
107
- create: function create(context) {
108
- var depth = 0;
109
- var enter = function enter(node) {
110
- if (isOrchestrationRegistration(node)) {
111
- depth += 1;
112
- }
113
- };
114
- var exit = function exit(node) {
115
- if (isOrchestrationRegistration(node)) {
116
- depth -= 1;
117
- }
118
- };
119
- return {
120
- CallExpression: function CallExpression(node) {
121
- var _calleeName;
122
- enter(node);
123
- if (depth === 0) {
124
- return;
125
- }
126
- var callee = node.callee;
127
- var object = callee === null || callee === void 0 ? void 0 : callee.object;
128
- var full = (object === null || object === void 0 ? void 0 : object.type) === 'Identifier' ? "".concat(String(object.name), ".").concat(String(calleeName(callee))) : (_calleeName = calleeName(callee)) !== null && _calleeName !== void 0 ? _calleeName : '';
129
- var fix = FORBIDDEN_CALLS[full];
130
- if (fix !== undefined) {
131
- context.report({
132
- node: node,
133
- messageId: 'forbidden',
134
- data: {
135
- what: full,
136
- fix: fix
137
- }
138
- });
139
- }
140
- },
141
- 'CallExpression:exit': exit,
142
- NewExpression: function NewExpression(node) {
143
- var _ref;
144
- if (depth === 0) {
145
- return;
146
- }
147
- var callee = node.callee;
148
- var args = node.arguments;
149
- // `new Date(someInstant)` is fine and common — only the argument-less
150
- // form reads the wall clock.
151
- if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && callee.name === 'Date' && ((_ref = args === null || args === void 0 ? void 0 : args.length) !== null && _ref !== void 0 ? _ref : 0) === 0) {
152
- context.report({
153
- node: node,
154
- messageId: 'newDate'
155
- });
156
- }
157
- },
158
- MemberExpression: function MemberExpression(node) {
159
- if (depth === 0) {
160
- return;
161
- }
162
- var object = node.object;
163
- var property = node.property;
164
- if ((object === null || object === void 0 ? void 0 : object.type) === 'Identifier' && object.name === 'process' && (property === null || property === void 0 ? void 0 : property.type) === 'Identifier' && property.name === 'env') {
165
- context.report({
166
- node: node,
167
- messageId: 'processEnv'
168
- });
169
- }
170
- }
171
- };
105
+ schema: [],
106
+ messages: {
107
+ forbidden: '{{what}} is non-deterministic on replay. Use {{fix}} instead.',
108
+ newDate: 'new Date() is non-deterministic on replay. Use now(context) instead.',
109
+ processEnv: 'process.env is read at replay time and may differ between deploys. ' + 'Read it in an activity, or pass it as orchestration input.'
172
110
  }
111
+ },
112
+ create(context) {
113
+ let depth = 0;
114
+ const enter = node => {
115
+ if (isOrchestrationRegistration(node)) {
116
+ depth += 1;
117
+ }
118
+ };
119
+ const exit = node => {
120
+ if (isOrchestrationRegistration(node)) {
121
+ depth -= 1;
122
+ }
123
+ };
124
+ return {
125
+ CallExpression: node => {
126
+ enter(node);
127
+ if (depth === 0) {
128
+ return;
129
+ }
130
+ const callee = node.callee;
131
+ const object = callee?.object;
132
+ const full = object?.type === 'Identifier' ? `${String(object.name)}.${String(calleeName(callee))}` : calleeName(callee) ?? '';
133
+ const fix = FORBIDDEN_CALLS[full];
134
+ if (fix !== undefined) {
135
+ context.report({
136
+ node,
137
+ messageId: 'forbidden',
138
+ data: {
139
+ what: full,
140
+ fix
141
+ }
142
+ });
143
+ }
144
+ },
145
+ 'CallExpression:exit': exit,
146
+ NewExpression: node => {
147
+ if (depth === 0) {
148
+ return;
149
+ }
150
+ const callee = node.callee;
151
+ const args = node.arguments;
152
+ // `new Date(someInstant)` is fine and common — only the argument-less
153
+ // form reads the wall clock.
154
+ if (callee?.type === 'Identifier' && callee.name === 'Date' && (args?.length ?? 0) === 0) {
155
+ context.report({
156
+ node,
157
+ messageId: 'newDate'
158
+ });
159
+ }
160
+ },
161
+ MemberExpression: node => {
162
+ if (depth === 0) {
163
+ return;
164
+ }
165
+ const object = node.object;
166
+ const property = node.property;
167
+ if (object?.type === 'Identifier' && object.name === 'process' && property?.type === 'Identifier' && property.name === 'env') {
168
+ context.report({
169
+ node,
170
+ messageId: 'processEnv'
171
+ });
172
+ }
173
+ }
174
+ };
175
+ }
173
176
  };
174
177
 
175
- /** SDK handler aliases that erase a handler's real signature. */ var ERASING_TYPES = new Set([
176
- 'ActivityHandler',
177
- 'OrchestrationHandler',
178
- 'FunctionHandler'
179
- ]);
178
+ /** SDK handler aliases that erase a handler's real signature. */
179
+ const ERASING_TYPES = new Set(['ActivityHandler', 'OrchestrationHandler', 'FunctionHandler']);
180
180
  /**
181
181
  * The annotation's type name, if it is a bare type reference.
182
182
  *
@@ -184,13 +184,14 @@
184
184
  * @returns The referenced type's name, or `undefined` when it is not a bare reference.
185
185
  * @throws Never - pure inspection.
186
186
  * @typeParam None - this function has no generic type parameters.
187
- */ function referencedName(annotation) {
188
- var typeAnnotation = annotation === null || annotation === void 0 ? void 0 : annotation.typeAnnotation;
189
- if ((typeAnnotation === null || typeAnnotation === void 0 ? void 0 : typeAnnotation.type) !== 'TSTypeReference') {
190
- return undefined;
191
- }
192
- var typeName = typeAnnotation.typeName;
193
- return (typeName === null || typeName === void 0 ? void 0 : typeName.type) === 'Identifier' ? typeName.name : undefined;
187
+ */
188
+ function referencedName(annotation) {
189
+ const typeAnnotation = annotation?.typeAnnotation;
190
+ if (typeAnnotation?.type !== 'TSTypeReference') {
191
+ return undefined;
192
+ }
193
+ const typeName = typeAnnotation.typeName;
194
+ return typeName?.type === 'Identifier' ? typeName.name : undefined;
194
195
  }
195
196
  /**
196
197
  * Flags annotations that collapse a typed handler back to `any`.
@@ -215,51 +216,44 @@
215
216
  * const withLogging = <I, O>(h: (i: I, c: InvocationContext) => O) =>
216
217
  * (i: I, c: InvocationContext): O => { c.log('...'); return h(i, c) }
217
218
  * ```
218
- */ var noUntypedActivityHandler = {
219
- meta: {
220
- type: 'problem',
221
- docs: {
222
- description: 'Disallow handler annotations that erase inferred types.'
223
- },
224
- schema: [],
225
- messages: {
226
- erased: "Annotating with '{{name}}' erases the handler's real signature — it is an alias for " + '(triggerInput: any, context) => any, so the activity becomes `any` in and `any` out. ' + 'Drop the annotation and let defineActivity infer it.',
227
- erasedWrapper: "A wrapper typed '{{name}}' collapses every handler it wraps to `any`. " + 'Make it generic: <I, O>(h: (i: I, c: InvocationContext) => O) => (i: I, c: InvocationContext): O.'
228
- }
219
+ */
220
+ const noUntypedActivityHandler = {
221
+ meta: {
222
+ type: 'problem',
223
+ docs: {
224
+ description: 'Disallow handler annotations that erase inferred types.'
229
225
  },
230
- create: function create(context) {
231
- return {
232
- // const handler: ActivityHandler = ...
233
- Identifier: function Identifier(node) {
234
- var _node_parent_type, _node_parent, _node_parent_params, _node_parent1;
235
- var name = referencedName(node.typeAnnotation);
236
- if (name === undefined || !ERASING_TYPES.has(name)) {
237
- return;
238
- }
239
- // A function PARAMETER annotated this way is the middleware form, which
240
- // is worse: it erases every handler passed through it, not just one.
241
- var isParameter = ((_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : (_node_parent_type = _node_parent.type) === null || _node_parent_type === void 0 ? void 0 : _node_parent_type.startsWith('TS')) === false && ((_node_parent1 = node.parent) === null || _node_parent1 === void 0 ? void 0 : (_node_parent_params = _node_parent1.params) === null || _node_parent_params === void 0 ? void 0 : _node_parent_params.includes(node)) === true;
242
- context.report({
243
- node: node,
244
- messageId: isParameter ? 'erasedWrapper' : 'erased',
245
- data: {
246
- name: name
247
- }
248
- });
249
- }
250
- };
226
+ schema: [],
227
+ messages: {
228
+ erased: "Annotating with '{{name}}' erases the handler's real signature — it is an alias for " + '(triggerInput: any, context) => any, so the activity becomes `any` in and `any` out. ' + 'Drop the annotation and let defineActivity infer it.',
229
+ erasedWrapper: "A wrapper typed '{{name}}' collapses every handler it wraps to `any`. " + 'Make it generic: <I, O>(h: (i: I, c: InvocationContext) => O) => (i: I, c: InvocationContext): O.'
251
230
  }
231
+ },
232
+ create(context) {
233
+ return {
234
+ // const handler: ActivityHandler = ...
235
+ Identifier: node => {
236
+ const name = referencedName(node.typeAnnotation);
237
+ if (name === undefined || !ERASING_TYPES.has(name)) {
238
+ return;
239
+ }
240
+ // A function PARAMETER annotated this way is the middleware form, which
241
+ // is worse: it erases every handler passed through it, not just one.
242
+ const isParameter = node.parent?.type?.startsWith('TS') === false && node.parent?.params?.includes(node) === true;
243
+ context.report({
244
+ node,
245
+ messageId: isParameter ? 'erasedWrapper' : 'erased',
246
+ data: {
247
+ name
248
+ }
249
+ });
250
+ }
251
+ };
252
+ }
252
253
  };
253
254
 
254
- /** The wrapper calls that must be delegated to, not yielded. */ var DELEGATED = new Set([
255
- 'callActivity',
256
- 'callSubOrchestration',
257
- 'all',
258
- 'any',
259
- 'waitForEvent',
260
- 'sleepFor',
261
- 'sleepUntil'
262
- ]);
255
+ /** The wrapper calls that must be delegated to, not yielded. */
256
+ const DELEGATED = new Set(['callActivity', 'callSubOrchestration', 'all', 'any', 'waitForEvent', 'sleepFor', 'sleepUntil']);
263
257
  /**
264
258
  * Flags `yield callActivity(...)` where `yield *` is meant.
265
259
  *
@@ -278,48 +272,49 @@
278
272
  * So this rule earns its place only by reporting a clearer message than
279
273
  * `TS2345` does. It is in `recommended` for that reason, not because anything
280
274
  * depends on it.
281
- */ var requireYieldStar = {
282
- meta: {
283
- type: 'suggestion',
284
- docs: {
285
- description: 'Require `yield *` when calling a delegating helper.'
286
- },
287
- schema: [],
288
- messages: {
289
- useYieldStar: "Use 'yield *' rather than 'yield' with {{name}}(). Delegation is what carries the " + 'result type; a bare yield does not typecheck, but the compiler error is obscure.'
290
- }
275
+ */
276
+ const requireYieldStar = {
277
+ meta: {
278
+ type: 'suggestion',
279
+ docs: {
280
+ description: 'Require `yield *` when calling a delegating helper.'
291
281
  },
292
- create: function create(context) {
293
- return {
294
- YieldExpression: function YieldExpression(node) {
295
- if (node.delegate === true) {
296
- return;
297
- }
298
- var argument = node.argument;
299
- if ((argument === null || argument === void 0 ? void 0 : argument.type) !== 'CallExpression') {
300
- return;
301
- }
302
- // A BARE IDENTIFIER only. `c.df.callActivity(...)` is the raw SDK call,
303
- // which is correct code and must not be flagged — matching the trailing
304
- // property of a member expression would flag it, which a negative
305
- // fixture caught.
306
- var callee = argument.callee;
307
- if ((callee === null || callee === void 0 ? void 0 : callee.type) !== 'Identifier') {
308
- return;
309
- }
310
- var name = callee.name;
311
- if (DELEGATED.has(name)) {
312
- context.report({
313
- node: node,
314
- messageId: 'useYieldStar',
315
- data: {
316
- name: name
317
- }
318
- });
319
- }
320
- }
321
- };
282
+ schema: [],
283
+ messages: {
284
+ useYieldStar: "Use 'yield *' rather than 'yield' with {{name}}(). Delegation is what carries the " + 'result type; a bare yield does not typecheck, but the compiler error is obscure.'
322
285
  }
286
+ },
287
+ create(context) {
288
+ return {
289
+ YieldExpression: node => {
290
+ if (node.delegate === true) {
291
+ return;
292
+ }
293
+ const argument = node.argument;
294
+ if (argument?.type !== 'CallExpression') {
295
+ return;
296
+ }
297
+ // A BARE IDENTIFIER only. `c.df.callActivity(...)` is the raw SDK call,
298
+ // which is correct code and must not be flagged — matching the trailing
299
+ // property of a member expression would flag it, which a negative
300
+ // fixture caught.
301
+ const callee = argument.callee;
302
+ if (callee?.type !== 'Identifier') {
303
+ return;
304
+ }
305
+ const name = callee.name;
306
+ if (DELEGATED.has(name)) {
307
+ context.report({
308
+ node,
309
+ messageId: 'useYieldStar',
310
+ data: {
311
+ name
312
+ }
313
+ });
314
+ }
315
+ }
316
+ };
317
+ }
323
318
  };
324
319
 
325
320
  /**
@@ -328,10 +323,11 @@
328
323
  * @remarks
329
324
  * The keys are the names a config writes after the `az-durable/` prefix, so
330
325
  * renaming one is a breaking change for any consumer's config.
331
- */ var rules = {
332
- 'no-nondeterministic-orchestrator': noNondeterministicOrchestrator,
333
- 'no-untyped-activity-handler': noUntypedActivityHandler,
334
- 'require-yield-star': requireYieldStar
326
+ */
327
+ const rules = {
328
+ 'no-nondeterministic-orchestrator': noNondeterministicOrchestrator,
329
+ 'no-untyped-activity-handler': noUntypedActivityHandler,
330
+ 'require-yield-star': requireYieldStar
335
331
  };
336
332
  /**
337
333
  * The plugin object, for a flat config's `plugins` map.
@@ -339,8 +335,9 @@
339
335
  * @remarks
340
336
  * Shipped from a separate entry point so lint rules are never a runtime import
341
337
  * of the wrapper itself.
342
- */ var plugin = {
343
- rules: rules
338
+ */
339
+ const plugin = {
340
+ rules
344
341
  };
345
342
  /**
346
343
  * The recommended rule set.
@@ -351,15 +348,16 @@
351
348
  * failures nothing else does — silent replay corruption, and a type collapse
352
349
  * TypeScript accepts as legal. `require-yield-star` is a warning because the
353
350
  * compiler already rejects the code it flags; it only improves the message.
354
- */ var recommended = {
355
- plugins: {
356
- 'az-durable': plugin
357
- },
358
- rules: {
359
- 'az-durable/no-nondeterministic-orchestrator': 'error',
360
- 'az-durable/no-untyped-activity-handler': 'error',
361
- 'az-durable/require-yield-star': 'warn'
362
- }
351
+ */
352
+ const recommended = {
353
+ plugins: {
354
+ 'az-durable': plugin
355
+ },
356
+ rules: {
357
+ 'az-durable/no-nondeterministic-orchestrator': 'error',
358
+ 'az-durable/no-untyped-activity-handler': 'error',
359
+ 'az-durable/require-yield-star': 'warn'
360
+ }
363
361
  };
364
362
 
365
363
  exports.noNondeterministicOrchestrator = noNondeterministicOrchestrator;
@@ -368,3 +366,4 @@ exports.plugin = plugin;
368
366
  exports.recommended = recommended;
369
367
  exports.requireYieldStar = requireYieldStar;
370
368
  exports.rules = rules;
369
+ //# sourceMappingURL=eslint-plugin.cjs.js.map