@invinite-org/chartlang-compiler 1.3.0 → 1.4.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/analysis/extractDependencyGraph.d.ts.map +1 -1
  3. package/dist/analysis/extractDependencyGraph.js +9 -1
  4. package/dist/analysis/extractDependencyGraph.js.map +1 -1
  5. package/dist/analysis/extractInputs.d.ts.map +1 -1
  6. package/dist/analysis/extractInputs.js +2 -0
  7. package/dist/analysis/extractInputs.js.map +1 -1
  8. package/dist/analysis/extractRequestedIntervals.d.ts +29 -9
  9. package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -1
  10. package/dist/analysis/extractRequestedIntervals.js +173 -42
  11. package/dist/analysis/extractRequestedIntervals.js.map +1 -1
  12. package/dist/analysis/index.d.ts +1 -0
  13. package/dist/analysis/index.d.ts.map +1 -1
  14. package/dist/analysis/index.js +1 -0
  15. package/dist/analysis/index.js.map +1 -1
  16. package/dist/analysis/stateArrayCapacity.d.ts +58 -0
  17. package/dist/analysis/stateArrayCapacity.d.ts.map +1 -0
  18. package/dist/analysis/stateArrayCapacity.js +108 -0
  19. package/dist/analysis/stateArrayCapacity.js.map +1 -0
  20. package/dist/api.d.ts.map +1 -1
  21. package/dist/api.js +12 -3
  22. package/dist/api.js.map +1 -1
  23. package/dist/diagnostics.d.ts +6 -2
  24. package/dist/diagnostics.d.ts.map +1 -1
  25. package/dist/diagnostics.js.map +1 -1
  26. package/dist/manifest.d.ts +2 -1
  27. package/dist/manifest.d.ts.map +1 -1
  28. package/dist/manifest.js +4 -0
  29. package/dist/manifest.js.map +1 -1
  30. package/dist/program.d.ts.map +1 -1
  31. package/dist/program.js +57 -1
  32. package/dist/program.js.map +1 -1
  33. package/dist/transformers/callsiteIdInjection.d.ts.map +1 -1
  34. package/dist/transformers/callsiteIdInjection.js +8 -1
  35. package/dist/transformers/callsiteIdInjection.js.map +1 -1
  36. package/dist/transformers/plotKindFromCallsite.d.ts +3 -0
  37. package/dist/transformers/plotKindFromCallsite.d.ts.map +1 -1
  38. package/dist/transformers/plotKindFromCallsite.js +7 -0
  39. package/dist/transformers/plotKindFromCallsite.js.map +1 -1
  40. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"extractRequestedIntervals.d.ts","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAG7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACnC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,mBAAmB,EAAE,aAAa,CAAC,4BAA4B,CAAC,CAAC;CACpE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,EACxC,mBAAmB,UAAQ,GAC5B,eAAe,CAuCjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,GACzC,aAAa,CAAC,MAAM,CAAC,CAEvB"}
1
+ {"version":3,"file":"extractRequestedIntervals.d.ts","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,aAAa,EAClB,KAAK,4BAA4B,EACpC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAG7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACnC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IACpC,mBAAmB,EAAE,aAAa,CAAC,4BAA4B,CAAC,CAAC;CACpE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,EACxC,mBAAmB,UAAQ,GAC5B,eAAe,CAgDjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,EACrD,WAAW,EAAE,iBAAiB,EAAE,EAChC,UAAU,GAAE,MAA4B,GACzC,aAAa,CAAC,MAAM,CAAC,CAEvB"}
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
2
  // See the LICENSE file in the repo root for full license text.
3
+ import { feedKey, } from "@invinite-org/chartlang-core";
3
4
  import ts from "typescript";
4
5
  import { createDiagnostic } from "../diagnostics.js";
5
6
  import { callsiteIdFor } from "../transformers/callsiteIdInjection.js";
@@ -7,11 +8,22 @@ import { resolveCalleeName } from "../transformers/resolveCallee.js";
7
8
  import { validateSecurityExpr } from "./validateSecurityExpr.js";
8
9
  /**
9
10
  * Walk a script's AST and collect every static `interval` argument to
10
- * `request.security({ interval: ... })` and `request.lowerTf(...)`, plus every
11
- * `request.security` *expression* callsite (a second arrow/function argument).
12
- * Dynamic intervals emit `request-security-interval-not-literal` (for
13
- * `request.security`) or `request-lower-tf-interval-not-literal` (for
14
- * `request.lowerTf`) and are excluded.
11
+ * `request.security({ interval: ... })` and `request.lowerTf(...)`, every
12
+ * distinct requested `(symbol?, interval)` feed (`request.security` only
13
+ * `request.lowerTf` has no symbol), plus every `request.security` *expression*
14
+ * callsite (a second arrow/function argument). Dynamic intervals emit
15
+ * `request-security-interval-not-literal` (for `request.security`) or
16
+ * `request-lower-tf-interval-not-literal` (for `request.lowerTf`); a dynamic
17
+ * `request.security` symbol emits `request-security-symbol-not-literal`. Either
18
+ * dynamic axis is excluded.
19
+ *
20
+ * The `symbol` opt is read the same three ways `interval` is — a string literal,
21
+ * an `inputs.<enum>` access (expanded to all options), or an `inputs.<name>`
22
+ * `input.symbol` default literal — and the cartesian product of resolved
23
+ * symbols × intervals is deduped into `feeds` via the shared
24
+ * `feedKey(symbol, interval)`. A symbol-omitted (or empty-literal) feed keeps its
25
+ * interval in `intervals` (the main-symbol projection); a present-symbol feed
26
+ * does not.
15
27
  *
16
28
  * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}
17
29
  * keyed by the same `slotId` the callsite-id transformer injects (via the
@@ -23,30 +35,37 @@ import { validateSecurityExpr } from "./validateSecurityExpr.js";
23
35
  * @since 0.7
24
36
  * @stable
25
37
  * @example
26
- * // const { intervals, securityExpressions } =
38
+ * // const { intervals, feeds, securityExpressions } =
27
39
  * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);
28
40
  * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;
29
41
  * void fn;
30
42
  */
31
43
  export function extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath = sourceFile.fileName, validateExpressions = false) {
32
44
  const intervals = new Set();
45
+ // Keyed by `feedKey(symbol, interval)` so the dedup format matches the
46
+ // runtime/host stream key exactly and the sort below is byte-stable.
47
+ const feeds = new Map();
33
48
  const securityExpressions = [];
34
49
  const visit = (node) => {
35
50
  if (ts.isCallExpression(node)) {
36
51
  const calleeName = resolveCalleeName(node, checker);
37
52
  if (calleeName === "request.security" || calleeName === "request.lowerTf") {
38
- readRequestInterval(node, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals);
53
+ readRequestInterval(node, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals, feeds);
39
54
  }
40
55
  if (calleeName === "request.security") {
41
- readSecurityExpression(node, sourceFile, sourcePath, checker, diagnostics, validateExpressions, securityExpressions);
56
+ readSecurityExpression(node, sourceFile, sourcePath, checker, diagnostics, validateExpressions, inputs, securityExpressions);
42
57
  }
43
58
  }
44
59
  ts.forEachChild(node, visit);
45
60
  };
46
61
  ts.forEachChild(sourceFile, visit);
47
62
  securityExpressions.sort((a, b) => a.slotId.localeCompare(b.slotId));
63
+ const sortedFeeds = Array.from(feeds.entries())
64
+ .sort(([a], [b]) => a.localeCompare(b))
65
+ .map(([, feed]) => feed);
48
66
  return Object.freeze({
49
67
  intervals: Object.freeze(Array.from(intervals).sort()),
68
+ feeds: Object.freeze(sortedFeeds),
50
69
  securityExpressions: Object.freeze(securityExpressions.slice()),
51
70
  });
52
71
  }
@@ -72,12 +91,15 @@ export function extractRequestedIntervals(sourceFile, checker, inputs, diagnosti
72
91
  * Detect and record a `request.security` expression callsite — a second
73
92
  * argument that is an arrow or function expression. Mints the descriptor's
74
93
  * `slotId` via `callsiteIdFor` (lockstep with the injector), reads the literal
75
- * `interval` and the callback's single parameter name, and when
76
- * `validate` runs the capture check. A callsite whose interval is not a
77
- * compile-time literal already emitted `request-security-interval-not-literal`
78
- * via `readRequestInterval`; it is skipped here (no descriptor).
94
+ * `interval`, the literal `symbol` (string literal or `input.symbol` default
95
+ * an `input.enum`/dynamic symbol can't anchor a single expression clock, so it
96
+ * is omitted, mirroring how an `input.enum` interval can't anchor one), and the
97
+ * callback's single parameter name, and when `validate` — runs the capture
98
+ * check. A callsite whose interval is not a compile-time literal already emitted
99
+ * `request-security-interval-not-literal` via `readRequestInterval`; it is
100
+ * skipped here (no descriptor).
79
101
  */
80
- function readSecurityExpression(call, sourceFile, sourcePath, checker, diagnostics, validate, out) {
102
+ function readSecurityExpression(call, sourceFile, sourcePath, checker, diagnostics, validate, inputs, out) {
81
103
  const callback = call.arguments[1];
82
104
  if (callback === undefined ||
83
105
  !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
@@ -86,28 +108,30 @@ function readSecurityExpression(call, sourceFile, sourcePath, checker, diagnosti
86
108
  if (validate) {
87
109
  validateSecurityExpr(callback, checker, diagnostics, sourcePath);
88
110
  }
89
- const interval = readLiteralInterval(call);
111
+ const opts = call.arguments[0];
112
+ if (opts === undefined || !ts.isObjectLiteralExpression(opts))
113
+ return;
114
+ const interval = readLiteralInterval(opts);
90
115
  if (interval === null)
91
116
  return;
117
+ const symbol = readLiteralSymbol(opts, inputs);
92
118
  const firstParam = callback.parameters[0];
93
119
  const paramName = firstParam !== undefined && ts.isIdentifier(firstParam.name) ? firstParam.name.text : "";
94
120
  out.push(Object.freeze({
95
121
  slotId: callsiteIdFor(sourceFile, call, sourcePath),
122
+ ...(symbol === undefined ? {} : { symbol }),
96
123
  interval,
97
124
  paramName,
98
125
  }));
99
126
  }
100
127
  /**
101
- * Read the literal `interval` string off a `request.security` call's opts
102
- * object, or `null` when it is absent or non-literal. Only string-literal
103
- * intervals key an expression unit; an `input.enum` interval expands to
104
- * multiple intervals for the requested-interval list but cannot anchor a
105
- * single expression clock, so it is treated as non-literal here.
128
+ * Read the literal `interval` string off a `request.security` opts object, or
129
+ * `null` when it is absent or non-literal. Only string-literal intervals key an
130
+ * expression unit; an `input.enum` interval expands to multiple intervals for
131
+ * the requested-interval list but cannot anchor a single expression clock, so it
132
+ * is treated as non-literal here.
106
133
  */
107
- function readLiteralInterval(call) {
108
- const opts = call.arguments[0];
109
- if (opts === undefined || !ts.isObjectLiteralExpression(opts))
110
- return null;
134
+ function readLiteralInterval(opts) {
111
135
  const intervalProperty = opts.properties
112
136
  .filter(ts.isPropertyAssignment)
113
137
  .find((property) => ts.isIdentifier(property.name) && property.name.text === "interval");
@@ -116,7 +140,39 @@ function readLiteralInterval(call) {
116
140
  const initializer = intervalProperty.initializer;
117
141
  return ts.isStringLiteral(initializer) ? initializer.text : null;
118
142
  }
119
- function readRequestInterval(call, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals) {
143
+ /**
144
+ * Read the literal `symbol` off a `request.security` opts object for the
145
+ * expression-descriptor anchor: a string literal or an `input.symbol` default
146
+ * resolves to a concrete symbol; an empty literal, an `input.enum`/dynamic
147
+ * symbol, or an absent property resolves to `undefined` (the chart symbol —
148
+ * an enum/dynamic symbol can't anchor a single expression clock). Never pushes
149
+ * a diagnostic; `readRequestInterval` already reported any dynamic symbol.
150
+ */
151
+ function readLiteralSymbol(opts, inputs) {
152
+ const resolved = resolveOptString(opts, "symbol", inputs);
153
+ if (resolved.kind === "literal" || resolved.kind === "input-default") {
154
+ return resolved.value === "" ? undefined : resolved.value;
155
+ }
156
+ return undefined;
157
+ }
158
+ function resolveOptString(opts, propName, inputs) {
159
+ const property = opts.properties
160
+ .filter(ts.isPropertyAssignment)
161
+ .find((p) => ts.isIdentifier(p.name) && p.name.text === propName);
162
+ if (property === undefined)
163
+ return { kind: "absent" };
164
+ const initializer = property.initializer;
165
+ if (ts.isStringLiteral(initializer))
166
+ return { kind: "literal", value: initializer.text };
167
+ const enumOptions = getInputsEnumOptions(initializer, inputs);
168
+ if (enumOptions !== null)
169
+ return { kind: "enum", values: enumOptions };
170
+ const symbolDefault = getInputSymbolDefault(initializer, inputs);
171
+ if (symbolDefault !== null)
172
+ return { kind: "input-default", value: symbolDefault };
173
+ return { kind: "dynamic", node: initializer };
174
+ }
175
+ function readRequestInterval(call, calleeName, sourceFile, sourcePath, inputs, diagnostics, intervals, feeds) {
120
176
  const opts = call.arguments[0];
121
177
  if (opts === undefined || !ts.isObjectLiteralExpression(opts))
122
178
  return;
@@ -125,27 +181,102 @@ function readRequestInterval(call, calleeName, sourceFile, sourcePath, inputs, d
125
181
  .find((property) => ts.isIdentifier(property.name) && property.name.text === "interval");
126
182
  if (intervalProperty === undefined)
127
183
  return;
128
- const initializer = intervalProperty.initializer;
129
- if (ts.isStringLiteral(initializer)) {
130
- intervals.add(initializer.text);
131
- return;
184
+ const resolvedIntervals = resolveIntervals(intervalProperty.initializer, inputs);
185
+ if (resolvedIntervals === null) {
186
+ diagnostics.push(createDiagnostic({
187
+ severity: "error",
188
+ code: calleeName === "request.lowerTf"
189
+ ? "request-lower-tf-interval-not-literal"
190
+ : "request-security-interval-not-literal",
191
+ message: `${calleeName}({ interval }) must be a string literal or input.enum value`,
192
+ file: sourcePath,
193
+ node: intervalProperty.initializer,
194
+ sourceFile,
195
+ }));
132
196
  }
133
- const enumOptions = getInputsEnumOptions(initializer, inputs);
134
- if (enumOptions !== null) {
135
- for (const option of enumOptions)
136
- intervals.add(option);
197
+ // `request.lowerTf` has no symbol dimension: it only ever feeds intervals
198
+ // (the chart-symbol HTF projection), never `feeds`. Preserve its existing
199
+ // interval-only behavior exactly.
200
+ if (calleeName === "request.lowerTf") {
201
+ for (const interval of resolvedIntervals ?? [])
202
+ intervals.add(interval);
137
203
  return;
138
204
  }
139
- diagnostics.push(createDiagnostic({
140
- severity: "error",
141
- code: calleeName === "request.lowerTf"
142
- ? "request-lower-tf-interval-not-literal"
143
- : "request-security-interval-not-literal",
144
- message: `${calleeName}({ interval }) must be a string literal or input.enum value`,
145
- file: sourcePath,
146
- node: initializer,
147
- sourceFile,
148
- }));
205
+ const resolvedSymbols = resolveSymbols(opts, inputs, sourceFile, sourcePath, diagnostics);
206
+ for (const symbol of resolvedSymbols) {
207
+ for (const interval of resolvedIntervals ?? []) {
208
+ // A symbol-omitted (chart-symbol) feed keeps its interval in the
209
+ // main-symbol projection; a present-symbol feed does not.
210
+ if (symbol === undefined)
211
+ intervals.add(interval);
212
+ feeds.set(feedKey(symbol, interval), {
213
+ ...(symbol === undefined ? {} : { symbol }),
214
+ interval,
215
+ });
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * Resolve a `request.*` `interval` initializer to its concrete interval list —
221
+ * a single-element list for a string literal, all options for an `inputs.<enum>`
222
+ * access — or `null` for a genuinely-dynamic interval (the caller pushes the
223
+ * appropriate diagnostic). `interval` never uses the `input.symbol`-default path:
224
+ * `input.interval` is the main-chart interval, not a feed interval.
225
+ */
226
+ function resolveIntervals(initializer, inputs) {
227
+ if (ts.isStringLiteral(initializer))
228
+ return [initializer.text];
229
+ return getInputsEnumOptions(initializer, inputs);
230
+ }
231
+ /**
232
+ * Resolve a `request.security` opts object's `symbol` axis to the list of
233
+ * requested symbols (`undefined` ⇒ the chart's own symbol): `[undefined]` when
234
+ * absent or an empty literal, `[value]` for a string literal or `input.symbol`
235
+ * default, all options for an `inputs.<enum>` access, or `[]` (excluded, after
236
+ * pushing `request-security-symbol-not-literal`) for a dynamic symbol.
237
+ */
238
+ function resolveSymbols(opts, inputs, sourceFile, sourcePath, diagnostics) {
239
+ const resolved = resolveOptString(opts, "symbol", inputs);
240
+ switch (resolved.kind) {
241
+ case "absent":
242
+ return [undefined];
243
+ case "literal":
244
+ // An empty-literal symbol collapses to the chart symbol, matching
245
+ // `feedKey`'s empty-collapse.
246
+ return [resolved.value === "" ? undefined : resolved.value];
247
+ case "input-default":
248
+ return [resolved.value];
249
+ case "enum":
250
+ return resolved.values;
251
+ default:
252
+ diagnostics.push(createDiagnostic({
253
+ severity: "error",
254
+ code: "request-security-symbol-not-literal",
255
+ message: "request.security({ symbol }) must be a string literal, an input.symbol default, or an input.enum value",
256
+ file: sourcePath,
257
+ node: resolved.node,
258
+ sourceFile,
259
+ }));
260
+ return [];
261
+ }
262
+ }
263
+ /**
264
+ * Resolve an `inputs.<name>` access whose descriptor is an `input.symbol` to its
265
+ * `defaultValue` string, or `null` when the access is not an `inputs.<name>`
266
+ * property access, the descriptor is missing / not a `symbol` kind, or its
267
+ * `defaultValue` is not a string.
268
+ */
269
+ function getInputSymbolDefault(expr, inputs) {
270
+ if (!ts.isPropertyAccessExpression(expr) ||
271
+ !ts.isIdentifier(expr.expression) ||
272
+ expr.expression.text !== "inputs") {
273
+ return null;
274
+ }
275
+ const descriptor = inputs[expr.name.text];
276
+ if (descriptor === undefined || descriptor.kind !== "symbol")
277
+ return null;
278
+ const defaultValue = descriptor.defaultValue;
279
+ return typeof defaultValue === "string" ? defaultValue : null;
149
280
  }
150
281
  function getInputsEnumOptions(expr, inputs) {
151
282
  if (!ts.isPropertyAccessExpression(expr) ||
@@ -1 +1 @@
1
- {"version":3,"file":"extractRequestedIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport type { SecurityExpressionDescriptor } from \"@invinite-org/chartlang-core\";\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { callsiteIdFor } from \"../transformers/callsiteIdInjection.js\";\nimport { resolveCalleeName } from \"../transformers/resolveCallee.js\";\nimport type { ExtractedDescriptor } from \"./extractInputs.js\";\nimport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\n\n/**\n * Combined result of the `request.*` analysis pass: the sorted, deduped list\n * of requested intervals plus one {@link SecurityExpressionDescriptor} per\n * `request.security({ interval }, (bar) => …)` expression callsite (sorted by\n * `slotId`).\n *\n * @since 0.7\n * @stable\n * @example\n * const r: RequestAnalysis = { intervals: [\"1W\"], securityExpressions: [] };\n * void r;\n */\nexport type RequestAnalysis = Readonly<{\n intervals: ReadonlyArray<string>;\n securityExpressions: ReadonlyArray<SecurityExpressionDescriptor>;\n}>;\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`, plus every\n * `request.security` *expression* callsite (a second arrow/function argument).\n * Dynamic intervals emit `request-security-interval-not-literal` (for\n * `request.security`) or `request-lower-tf-interval-not-literal` (for\n * `request.lowerTf`) and are excluded.\n *\n * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}\n * keyed by the same `slotId` the callsite-id transformer injects (via the\n * shared `callsiteIdFor` helper) so the runtime can match the manifest entry\n * to the inlined callback. When `validateExpressions` is `true`, each callback\n * is also run through {@link validateSecurityExpr}, pushing\n * `request-security-expr-captures-local` for any out-of-subset reference.\n *\n * @since 0.7\n * @stable\n * @example\n * // const { intervals, securityExpressions } =\n * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);\n * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;\n * void fn;\n */\nexport function extractRequestAnalysis(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n validateExpressions = false,\n): RequestAnalysis {\n const intervals = new Set<string>();\n const securityExpressions: SecurityExpressionDescriptor[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const calleeName = resolveCalleeName(node, checker);\n if (calleeName === \"request.security\" || calleeName === \"request.lowerTf\") {\n readRequestInterval(\n node,\n calleeName,\n sourceFile,\n sourcePath,\n inputs,\n diagnostics,\n intervals,\n );\n }\n if (calleeName === \"request.security\") {\n readSecurityExpression(\n node,\n sourceFile,\n sourcePath,\n checker,\n diagnostics,\n validateExpressions,\n securityExpressions,\n );\n }\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n securityExpressions.sort((a, b) => a.slotId.localeCompare(b.slotId));\n return Object.freeze({\n intervals: Object.freeze(Array.from(intervals).sort()),\n securityExpressions: Object.freeze(securityExpressions.slice()),\n });\n}\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic\n * arguments emit `request-security-interval-not-literal` (for `request.security`)\n * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are\n * excluded. Thin delegate over {@link extractRequestAnalysis} kept for callers\n * that only need the interval list.\n *\n * @since 0.4\n * @example\n * // const intervals = extractRequestedIntervals(sf, checker, inputs, diagnostics);\n * // intervals === [\"1D\", \"5m\"];\n * const fn: typeof extractRequestedIntervals = extractRequestedIntervals;\n * void fn;\n */\nexport function extractRequestedIntervals(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n): ReadonlyArray<string> {\n return extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath).intervals;\n}\n\n/**\n * Detect and record a `request.security` expression callsite — a second\n * argument that is an arrow or function expression. Mints the descriptor's\n * `slotId` via `callsiteIdFor` (lockstep with the injector), reads the literal\n * `interval` and the callback's single parameter name, and — when\n * `validate` — runs the capture check. A callsite whose interval is not a\n * compile-time literal already emitted `request-security-interval-not-literal`\n * via `readRequestInterval`; it is skipped here (no descriptor).\n */\nfunction readSecurityExpression(\n call: ts.CallExpression,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n checker: ts.TypeChecker,\n diagnostics: CompileDiagnostic[],\n validate: boolean,\n out: SecurityExpressionDescriptor[],\n): void {\n const callback = call.arguments[1];\n if (\n callback === undefined ||\n !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))\n ) {\n return;\n }\n if (validate) {\n validateSecurityExpr(callback, checker, diagnostics, sourcePath);\n }\n const interval = readLiteralInterval(call);\n if (interval === null) return;\n const firstParam = callback.parameters[0];\n const paramName =\n firstParam !== undefined && ts.isIdentifier(firstParam.name) ? firstParam.name.text : \"\";\n out.push(\n Object.freeze({\n slotId: callsiteIdFor(sourceFile, call, sourcePath),\n interval,\n paramName,\n }),\n );\n}\n\n/**\n * Read the literal `interval` string off a `request.security` call's opts\n * object, or `null` when it is absent or non-literal. Only string-literal\n * intervals key an expression unit; an `input.enum` interval expands to\n * multiple intervals for the requested-interval list but cannot anchor a\n * single expression clock, so it is treated as non-literal here.\n */\nfunction readLiteralInterval(call: ts.CallExpression): string | null {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return null;\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return null;\n const initializer = intervalProperty.initializer;\n return ts.isStringLiteral(initializer) ? initializer.text : null;\n}\n\nfunction readRequestInterval(\n call: ts.CallExpression,\n calleeName: \"request.security\" | \"request.lowerTf\",\n sourceFile: ts.SourceFile,\n sourcePath: string,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n intervals: Set<string>,\n): void {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return;\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return;\n\n const initializer = intervalProperty.initializer;\n if (ts.isStringLiteral(initializer)) {\n intervals.add(initializer.text);\n return;\n }\n\n const enumOptions = getInputsEnumOptions(initializer, inputs);\n if (enumOptions !== null) {\n for (const option of enumOptions) intervals.add(option);\n return;\n }\n\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code:\n calleeName === \"request.lowerTf\"\n ? \"request-lower-tf-interval-not-literal\"\n : \"request-security-interval-not-literal\",\n message: `${calleeName}({ interval }) must be a string literal or input.enum value`,\n file: sourcePath,\n node: initializer,\n sourceFile,\n }),\n );\n}\n\nfunction getInputsEnumOptions(\n expr: ts.Expression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): ReadonlyArray<string> | null {\n if (\n !ts.isPropertyAccessExpression(expr) ||\n !ts.isIdentifier(expr.expression) ||\n expr.expression.text !== \"inputs\"\n ) {\n return null;\n }\n const descriptor = inputs[expr.name.text];\n if (descriptor === undefined || descriptor.kind !== \"enum\") return null;\n const options = descriptor.options;\n if (!Array.isArray(options)) return null;\n const strings: string[] = [];\n for (const option of options) {\n if (typeof option !== \"string\") return null;\n strings.push(option);\n }\n return Object.freeze(strings);\n}\n"]}
1
+ {"version":3,"file":"extractRequestedIntervals.js","sourceRoot":"","sources":["../../src/analysis/extractRequestedIntervals.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport {\n feedKey,\n type RequestedFeed,\n type SecurityExpressionDescriptor,\n} from \"@invinite-org/chartlang-core\";\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { callsiteIdFor } from \"../transformers/callsiteIdInjection.js\";\nimport { resolveCalleeName } from \"../transformers/resolveCallee.js\";\nimport type { ExtractedDescriptor } from \"./extractInputs.js\";\nimport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\n\n/**\n * Combined result of the `request.*` analysis pass: the sorted, deduped list\n * of requested intervals (the **main-symbol** projection), the sorted, deduped\n * list of requested `(symbol?, interval)` {@link RequestedFeed | feeds} (the\n * superset), plus one {@link SecurityExpressionDescriptor} per\n * `request.security({ interval }, (bar) => …)` expression callsite (sorted by\n * `slotId`).\n *\n * `intervals` keeps its exact existing meaning — the symbol-omitted\n * (chart-symbol) higher-timeframe intervals — so existing manifests stay\n * byte-identical. `feeds` adds the symbol dimension: one entry per distinct\n * `(symbol, interval)` pair, deduped + ordered by the shared\n * `feedKey(symbol, interval)` so the printed manifest is byte-stable.\n *\n * @since 0.7\n * @stable\n * @example\n * const r: RequestAnalysis = { intervals: [\"1W\"], feeds: [], securityExpressions: [] };\n * void r;\n */\nexport type RequestAnalysis = Readonly<{\n intervals: ReadonlyArray<string>;\n feeds: ReadonlyArray<RequestedFeed>;\n securityExpressions: ReadonlyArray<SecurityExpressionDescriptor>;\n}>;\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`, every\n * distinct requested `(symbol?, interval)` feed (`request.security` only —\n * `request.lowerTf` has no symbol), plus every `request.security` *expression*\n * callsite (a second arrow/function argument). Dynamic intervals emit\n * `request-security-interval-not-literal` (for `request.security`) or\n * `request-lower-tf-interval-not-literal` (for `request.lowerTf`); a dynamic\n * `request.security` symbol emits `request-security-symbol-not-literal`. Either\n * dynamic axis is excluded.\n *\n * The `symbol` opt is read the same three ways `interval` is — a string literal,\n * an `inputs.<enum>` access (expanded to all options), or an `inputs.<name>`\n * `input.symbol` default literal — and the cartesian product of resolved\n * symbols × intervals is deduped into `feeds` via the shared\n * `feedKey(symbol, interval)`. A symbol-omitted (or empty-literal) feed keeps its\n * interval in `intervals` (the main-symbol projection); a present-symbol feed\n * does not.\n *\n * Each expression callsite is recorded as a {@link SecurityExpressionDescriptor}\n * keyed by the same `slotId` the callsite-id transformer injects (via the\n * shared `callsiteIdFor` helper) so the runtime can match the manifest entry\n * to the inlined callback. When `validateExpressions` is `true`, each callback\n * is also run through {@link validateSecurityExpr}, pushing\n * `request-security-expr-captures-local` for any out-of-subset reference.\n *\n * @since 0.7\n * @stable\n * @example\n * // const { intervals, feeds, securityExpressions } =\n * // extractRequestAnalysis(sf, checker, inputs, diagnostics, path, true);\n * const fn: typeof extractRequestAnalysis = extractRequestAnalysis;\n * void fn;\n */\nexport function extractRequestAnalysis(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n validateExpressions = false,\n): RequestAnalysis {\n const intervals = new Set<string>();\n // Keyed by `feedKey(symbol, interval)` so the dedup format matches the\n // runtime/host stream key exactly and the sort below is byte-stable.\n const feeds = new Map<string, RequestedFeed>();\n const securityExpressions: SecurityExpressionDescriptor[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node)) {\n const calleeName = resolveCalleeName(node, checker);\n if (calleeName === \"request.security\" || calleeName === \"request.lowerTf\") {\n readRequestInterval(\n node,\n calleeName,\n sourceFile,\n sourcePath,\n inputs,\n diagnostics,\n intervals,\n feeds,\n );\n }\n if (calleeName === \"request.security\") {\n readSecurityExpression(\n node,\n sourceFile,\n sourcePath,\n checker,\n diagnostics,\n validateExpressions,\n inputs,\n securityExpressions,\n );\n }\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n securityExpressions.sort((a, b) => a.slotId.localeCompare(b.slotId));\n const sortedFeeds = Array.from(feeds.entries())\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([, feed]) => feed);\n return Object.freeze({\n intervals: Object.freeze(Array.from(intervals).sort()),\n feeds: Object.freeze(sortedFeeds),\n securityExpressions: Object.freeze(securityExpressions.slice()),\n });\n}\n\n/**\n * Walk a script's AST and collect every static `interval` argument to\n * `request.security({ interval: ... })` and `request.lowerTf(...)`. Dynamic\n * arguments emit `request-security-interval-not-literal` (for `request.security`)\n * or `request-lower-tf-interval-not-literal` (for `request.lowerTf`) and are\n * excluded. Thin delegate over {@link extractRequestAnalysis} kept for callers\n * that only need the interval list.\n *\n * @since 0.4\n * @example\n * // const intervals = extractRequestedIntervals(sf, checker, inputs, diagnostics);\n * // intervals === [\"1D\", \"5m\"];\n * const fn: typeof extractRequestedIntervals = extractRequestedIntervals;\n * void fn;\n */\nexport function extractRequestedIntervals(\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n sourcePath: string = sourceFile.fileName,\n): ReadonlyArray<string> {\n return extractRequestAnalysis(sourceFile, checker, inputs, diagnostics, sourcePath).intervals;\n}\n\n/**\n * Detect and record a `request.security` expression callsite — a second\n * argument that is an arrow or function expression. Mints the descriptor's\n * `slotId` via `callsiteIdFor` (lockstep with the injector), reads the literal\n * `interval`, the literal `symbol` (string literal or `input.symbol` default —\n * an `input.enum`/dynamic symbol can't anchor a single expression clock, so it\n * is omitted, mirroring how an `input.enum` interval can't anchor one), and the\n * callback's single parameter name, and — when `validate` — runs the capture\n * check. A callsite whose interval is not a compile-time literal already emitted\n * `request-security-interval-not-literal` via `readRequestInterval`; it is\n * skipped here (no descriptor).\n */\nfunction readSecurityExpression(\n call: ts.CallExpression,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n checker: ts.TypeChecker,\n diagnostics: CompileDiagnostic[],\n validate: boolean,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n out: SecurityExpressionDescriptor[],\n): void {\n const callback = call.arguments[1];\n if (\n callback === undefined ||\n !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))\n ) {\n return;\n }\n if (validate) {\n validateSecurityExpr(callback, checker, diagnostics, sourcePath);\n }\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return;\n const interval = readLiteralInterval(opts);\n if (interval === null) return;\n const symbol = readLiteralSymbol(opts, inputs);\n const firstParam = callback.parameters[0];\n const paramName =\n firstParam !== undefined && ts.isIdentifier(firstParam.name) ? firstParam.name.text : \"\";\n out.push(\n Object.freeze({\n slotId: callsiteIdFor(sourceFile, call, sourcePath),\n ...(symbol === undefined ? {} : { symbol }),\n interval,\n paramName,\n }),\n );\n}\n\n/**\n * Read the literal `interval` string off a `request.security` opts object, or\n * `null` when it is absent or non-literal. Only string-literal intervals key an\n * expression unit; an `input.enum` interval expands to multiple intervals for\n * the requested-interval list but cannot anchor a single expression clock, so it\n * is treated as non-literal here.\n */\nfunction readLiteralInterval(opts: ts.ObjectLiteralExpression): string | null {\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return null;\n const initializer = intervalProperty.initializer;\n return ts.isStringLiteral(initializer) ? initializer.text : null;\n}\n\n/**\n * Read the literal `symbol` off a `request.security` opts object for the\n * expression-descriptor anchor: a string literal or an `input.symbol` default\n * resolves to a concrete symbol; an empty literal, an `input.enum`/dynamic\n * symbol, or an absent property resolves to `undefined` (the chart symbol —\n * an enum/dynamic symbol can't anchor a single expression clock). Never pushes\n * a diagnostic; `readRequestInterval` already reported any dynamic symbol.\n */\nfunction readLiteralSymbol(\n opts: ts.ObjectLiteralExpression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): string | undefined {\n const resolved = resolveOptString(opts, \"symbol\", inputs);\n if (resolved.kind === \"literal\" || resolved.kind === \"input-default\") {\n return resolved.value === \"\" ? undefined : resolved.value;\n }\n return undefined;\n}\n\n/**\n * Resolution of an opts string property read three ways (mirroring `interval`,\n * plus the `input.symbol`-default path symbols need): a string literal, the\n * options of an `inputs.<enum>` access, the default of an `inputs.<name>`\n * `input.symbol` access, an absent property, or a genuinely-dynamic expression.\n */\ntype ResolvedOptString =\n | Readonly<{ kind: \"literal\"; value: string }>\n | Readonly<{ kind: \"enum\"; values: ReadonlyArray<string> }>\n | Readonly<{ kind: \"input-default\"; value: string }>\n | Readonly<{ kind: \"absent\" }>\n | Readonly<{ kind: \"dynamic\"; node: ts.Expression }>;\n\nfunction resolveOptString(\n opts: ts.ObjectLiteralExpression,\n propName: string,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): ResolvedOptString {\n const property = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((p) => ts.isIdentifier(p.name) && p.name.text === propName);\n if (property === undefined) return { kind: \"absent\" };\n\n const initializer = property.initializer;\n if (ts.isStringLiteral(initializer)) return { kind: \"literal\", value: initializer.text };\n\n const enumOptions = getInputsEnumOptions(initializer, inputs);\n if (enumOptions !== null) return { kind: \"enum\", values: enumOptions };\n\n const symbolDefault = getInputSymbolDefault(initializer, inputs);\n if (symbolDefault !== null) return { kind: \"input-default\", value: symbolDefault };\n\n return { kind: \"dynamic\", node: initializer };\n}\n\nfunction readRequestInterval(\n call: ts.CallExpression,\n calleeName: \"request.security\" | \"request.lowerTf\",\n sourceFile: ts.SourceFile,\n sourcePath: string,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n diagnostics: CompileDiagnostic[],\n intervals: Set<string>,\n feeds: Map<string, RequestedFeed>,\n): void {\n const opts = call.arguments[0];\n if (opts === undefined || !ts.isObjectLiteralExpression(opts)) return;\n const intervalProperty = opts.properties\n .filter(ts.isPropertyAssignment)\n .find((property) => ts.isIdentifier(property.name) && property.name.text === \"interval\");\n if (intervalProperty === undefined) return;\n\n const resolvedIntervals = resolveIntervals(intervalProperty.initializer, inputs);\n if (resolvedIntervals === null) {\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code:\n calleeName === \"request.lowerTf\"\n ? \"request-lower-tf-interval-not-literal\"\n : \"request-security-interval-not-literal\",\n message: `${calleeName}({ interval }) must be a string literal or input.enum value`,\n file: sourcePath,\n node: intervalProperty.initializer,\n sourceFile,\n }),\n );\n }\n\n // `request.lowerTf` has no symbol dimension: it only ever feeds intervals\n // (the chart-symbol HTF projection), never `feeds`. Preserve its existing\n // interval-only behavior exactly.\n if (calleeName === \"request.lowerTf\") {\n for (const interval of resolvedIntervals ?? []) intervals.add(interval);\n return;\n }\n\n const resolvedSymbols = resolveSymbols(opts, inputs, sourceFile, sourcePath, diagnostics);\n for (const symbol of resolvedSymbols) {\n for (const interval of resolvedIntervals ?? []) {\n // A symbol-omitted (chart-symbol) feed keeps its interval in the\n // main-symbol projection; a present-symbol feed does not.\n if (symbol === undefined) intervals.add(interval);\n feeds.set(feedKey(symbol, interval), {\n ...(symbol === undefined ? {} : { symbol }),\n interval,\n });\n }\n }\n}\n\n/**\n * Resolve a `request.*` `interval` initializer to its concrete interval list —\n * a single-element list for a string literal, all options for an `inputs.<enum>`\n * access — or `null` for a genuinely-dynamic interval (the caller pushes the\n * appropriate diagnostic). `interval` never uses the `input.symbol`-default path:\n * `input.interval` is the main-chart interval, not a feed interval.\n */\nfunction resolveIntervals(\n initializer: ts.Expression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): ReadonlyArray<string> | null {\n if (ts.isStringLiteral(initializer)) return [initializer.text];\n return getInputsEnumOptions(initializer, inputs);\n}\n\n/**\n * Resolve a `request.security` opts object's `symbol` axis to the list of\n * requested symbols (`undefined` ⇒ the chart's own symbol): `[undefined]` when\n * absent or an empty literal, `[value]` for a string literal or `input.symbol`\n * default, all options for an `inputs.<enum>` access, or `[]` (excluded, after\n * pushing `request-security-symbol-not-literal`) for a dynamic symbol.\n */\nfunction resolveSymbols(\n opts: ts.ObjectLiteralExpression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n sourceFile: ts.SourceFile,\n sourcePath: string,\n diagnostics: CompileDiagnostic[],\n): ReadonlyArray<string | undefined> {\n const resolved = resolveOptString(opts, \"symbol\", inputs);\n switch (resolved.kind) {\n case \"absent\":\n return [undefined];\n case \"literal\":\n // An empty-literal symbol collapses to the chart symbol, matching\n // `feedKey`'s empty-collapse.\n return [resolved.value === \"\" ? undefined : resolved.value];\n case \"input-default\":\n return [resolved.value];\n case \"enum\":\n return resolved.values;\n default:\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code: \"request-security-symbol-not-literal\",\n message:\n \"request.security({ symbol }) must be a string literal, an input.symbol default, or an input.enum value\",\n file: sourcePath,\n node: resolved.node,\n sourceFile,\n }),\n );\n return [];\n }\n}\n\n/**\n * Resolve an `inputs.<name>` access whose descriptor is an `input.symbol` to its\n * `defaultValue` string, or `null` when the access is not an `inputs.<name>`\n * property access, the descriptor is missing / not a `symbol` kind, or its\n * `defaultValue` is not a string.\n */\nfunction getInputSymbolDefault(\n expr: ts.Expression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): string | null {\n if (\n !ts.isPropertyAccessExpression(expr) ||\n !ts.isIdentifier(expr.expression) ||\n expr.expression.text !== \"inputs\"\n ) {\n return null;\n }\n const descriptor = inputs[expr.name.text];\n if (descriptor === undefined || descriptor.kind !== \"symbol\") return null;\n const defaultValue = descriptor.defaultValue;\n return typeof defaultValue === \"string\" ? defaultValue : null;\n}\n\nfunction getInputsEnumOptions(\n expr: ts.Expression,\n inputs: Readonly<Record<string, ExtractedDescriptor>>,\n): ReadonlyArray<string> | null {\n if (\n !ts.isPropertyAccessExpression(expr) ||\n !ts.isIdentifier(expr.expression) ||\n expr.expression.text !== \"inputs\"\n ) {\n return null;\n }\n const descriptor = inputs[expr.name.text];\n if (descriptor === undefined || descriptor.kind !== \"enum\") return null;\n const options = descriptor.options;\n if (!Array.isArray(options)) return null;\n const strings: string[] = [];\n for (const option of options) {\n if (typeof option !== \"string\") return null;\n strings.push(option);\n }\n return Object.freeze(strings);\n}\n"]}
@@ -2,6 +2,7 @@ export { runStructuralChecks } from "./structuralChecks.js";
2
2
  export type { StructuralBindingInfo, StructuralCheckResult } from "./structuralChecks.js";
3
3
  export { runForbiddenConstructs } from "./forbiddenConstructs.js";
4
4
  export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
5
+ export { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from "./stateArrayCapacity.js";
5
6
  export { extractCapabilities } from "./extractCapabilities.js";
6
7
  export { extractMaxLookback } from "./extractMaxLookback.js";
7
8
  export type { ExtractMaxLookbackResult } from "./extractMaxLookback.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACR,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,GAClB,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACR,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,GAClB,MAAM,6BAA6B,CAAC"}
@@ -3,6 +3,7 @@
3
3
  export { runStructuralChecks } from "./structuralChecks.js";
4
4
  export { runForbiddenConstructs } from "./forbiddenConstructs.js";
5
5
  export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
6
+ export { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from "./stateArrayCapacity.js";
6
7
  export { extractCapabilities } from "./extractCapabilities.js";
7
8
  export { extractMaxLookback } from "./extractMaxLookback.js";
8
9
  export { extractInputs } from "./extractInputs.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nexport { runStructuralChecks } from \"./structuralChecks.js\";\nexport type { StructuralBindingInfo, StructuralCheckResult } from \"./structuralChecks.js\";\nexport { runForbiddenConstructs } from \"./forbiddenConstructs.js\";\nexport { runStatefulCallInLoop } from \"./statefulCallInLoop.js\";\nexport { extractCapabilities } from \"./extractCapabilities.js\";\nexport { extractMaxLookback } from \"./extractMaxLookback.js\";\nexport type { ExtractMaxLookbackResult } from \"./extractMaxLookback.js\";\nexport { extractInputs } from \"./extractInputs.js\";\nexport type { ExtractedDescriptor, ExtractInputsResult } from \"./extractInputs.js\";\nexport { extractRequestAnalysis, extractRequestedIntervals } from \"./extractRequestedIntervals.js\";\nexport type { RequestAnalysis } from \"./extractRequestedIntervals.js\";\nexport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\nexport { validateLowerTfIntervals } from \"./validateLowerTfIntervals.js\";\nexport { extractRequiresIntervals } from \"./extractRequiresIntervals.js\";\nexport { extractAlertConditions } from \"./extractAlertConditions.js\";\nexport type { ExtractAlertConditionsResult } from \"./extractAlertConditions.js\";\nexport { extractDependencyGraph } from \"./extractDependencyGraph.js\";\nexport type {\n DepConsumesEntry,\n DepGraph,\n DrawnScript,\n PrivateDep,\n ProducerRef,\n ProducerSnapshot,\n ResolveProducer,\n} from \"./extractDependencyGraph.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nexport { runStructuralChecks } from \"./structuralChecks.js\";\nexport type { StructuralBindingInfo, StructuralCheckResult } from \"./structuralChecks.js\";\nexport { runForbiddenConstructs } from \"./forbiddenConstructs.js\";\nexport { runStatefulCallInLoop } from \"./statefulCallInLoop.js\";\nexport { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from \"./stateArrayCapacity.js\";\nexport { extractCapabilities } from \"./extractCapabilities.js\";\nexport { extractMaxLookback } from \"./extractMaxLookback.js\";\nexport type { ExtractMaxLookbackResult } from \"./extractMaxLookback.js\";\nexport { extractInputs } from \"./extractInputs.js\";\nexport type { ExtractedDescriptor, ExtractInputsResult } from \"./extractInputs.js\";\nexport { extractRequestAnalysis, extractRequestedIntervals } from \"./extractRequestedIntervals.js\";\nexport type { RequestAnalysis } from \"./extractRequestedIntervals.js\";\nexport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\nexport { validateLowerTfIntervals } from \"./validateLowerTfIntervals.js\";\nexport { extractRequiresIntervals } from \"./extractRequiresIntervals.js\";\nexport { extractAlertConditions } from \"./extractAlertConditions.js\";\nexport type { ExtractAlertConditionsResult } from \"./extractAlertConditions.js\";\nexport { extractDependencyGraph } from \"./extractDependencyGraph.js\";\nexport type {\n DepConsumesEntry,\n DepGraph,\n DrawnScript,\n PrivateDep,\n ProducerRef,\n ProducerSnapshot,\n ResolveProducer,\n} from \"./extractDependencyGraph.js\";\n"]}
@@ -0,0 +1,58 @@
1
+ import ts from "typescript";
2
+ import { type CompileDiagnostic } from "../diagnostics.js";
3
+ /**
4
+ * The largest `capacity` a `state.array<T>(capacity)` allocation may request.
5
+ * The collection is bounded-execution-safe only because its size is fixed at
6
+ * compile time: a hard ceiling caps memory, caps the JSON snapshot size, and —
7
+ * crucially — caps the per-tick two-ring `Float64Array` copy the runtime does
8
+ * on every tick rollback. `100_000` is generous (the dominant rolling-window /
9
+ * event-log cases are ≤ a few hundred) while still keeping the snapshot and
10
+ * the per-tick copy bounded.
11
+ *
12
+ * @since 1.3
13
+ * @stable
14
+ * @example
15
+ * // state.array<number>(20) → 20 <= MAX_STATE_ARRAY_CAPACITY (OK)
16
+ * // state.array<number>(1_000_000) → exceeds the cap (error)
17
+ * const cap: number = MAX_STATE_ARRAY_CAPACITY;
18
+ * void cap;
19
+ */
20
+ export declare const MAX_STATE_ARRAY_CAPACITY = 100000;
21
+ /**
22
+ * Walk the source file and flag every `state.array<T>(capacity)` allocation
23
+ * whose `capacity` argument is not a compile-time-resolvable positive integer
24
+ * within `MAX_STATE_ARRAY_CAPACITY`. This pins the bounded-execution +
25
+ * bounded-snapshot invariant at the compiler boundary: a non-literal capacity
26
+ * would make the backing ring's size — and therefore its snapshot size and
27
+ * per-tick rollback cost — non-deterministic.
28
+ *
29
+ * Capacity resolution reuses `resolveIndexUpperBound` + `collectConstNumberEnv`
30
+ * (the same machinery that sizes a series index), so a bare numeric literal,
31
+ * a parenthesised / unary-`±` literal, and a `const` numeric-literal binding
32
+ * (`const K = 20; state.array(K)`) are all accepted; a `let`, an input, or any
33
+ * runtime expression resolves to `null` and is rejected.
34
+ *
35
+ * Two error codes:
36
+ * - `state-array-capacity-not-literal` — the capacity is missing or does not
37
+ * resolve to a compile-time number.
38
+ * - `state-array-capacity-exceeds-max` — the capacity resolves but is `<= 0`,
39
+ * non-integer, or `> MAX_STATE_ARRAY_CAPACITY`.
40
+ *
41
+ * The walk runs on the **original** AST (positions match the user's source,
42
+ * and — running pre-injection — the capacity is `node.arguments[0]`, before
43
+ * the slot-id literal is injected as the leading argument). The element-access
44
+ * form `state["array"](cap)` is rejected upstream as
45
+ * `stateful-call-element-access` and never matches `"state.array"` here, so it
46
+ * is not double-reported. A `state.array(...)` inside a loop additionally
47
+ * errors `stateful-call-inside-loop`; both passes are independent.
48
+ *
49
+ * @since 1.3
50
+ * @example
51
+ * // const diagnostics = runStateArrayCapacity(
52
+ * // sourceFile, checker, "demo.chart.ts",
53
+ * // );
54
+ * const fn: typeof runStateArrayCapacity = runStateArrayCapacity;
55
+ * void fn;
56
+ */
57
+ export declare function runStateArrayCapacity(sourceFile: ts.SourceFile, checker: ts.TypeChecker, sourcePath: string): ReadonlyArray<CompileDiagnostic>;
58
+ //# sourceMappingURL=stateArrayCapacity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateArrayCapacity.d.ts","sourceRoot":"","sources":["../../src/analysis/stateArrayCapacity.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAI7E;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,qBAAqB,CACjC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,UAAU,EAAE,MAAM,GACnB,aAAa,CAAC,iBAAiB,CAAC,CAwDlC"}
@@ -0,0 +1,108 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ import { createDiagnostic } from "../diagnostics.js";
5
+ import { resolveCalleeName } from "../transformers/resolveCallee.js";
6
+ import { collectConstNumberEnv, resolveIndexUpperBound } from "./resolveIndexBound.js";
7
+ /**
8
+ * The largest `capacity` a `state.array<T>(capacity)` allocation may request.
9
+ * The collection is bounded-execution-safe only because its size is fixed at
10
+ * compile time: a hard ceiling caps memory, caps the JSON snapshot size, and —
11
+ * crucially — caps the per-tick two-ring `Float64Array` copy the runtime does
12
+ * on every tick rollback. `100_000` is generous (the dominant rolling-window /
13
+ * event-log cases are ≤ a few hundred) while still keeping the snapshot and
14
+ * the per-tick copy bounded.
15
+ *
16
+ * @since 1.3
17
+ * @stable
18
+ * @example
19
+ * // state.array<number>(20) → 20 <= MAX_STATE_ARRAY_CAPACITY (OK)
20
+ * // state.array<number>(1_000_000) → exceeds the cap (error)
21
+ * const cap: number = MAX_STATE_ARRAY_CAPACITY;
22
+ * void cap;
23
+ */
24
+ export const MAX_STATE_ARRAY_CAPACITY = 100_000;
25
+ /**
26
+ * Walk the source file and flag every `state.array<T>(capacity)` allocation
27
+ * whose `capacity` argument is not a compile-time-resolvable positive integer
28
+ * within `MAX_STATE_ARRAY_CAPACITY`. This pins the bounded-execution +
29
+ * bounded-snapshot invariant at the compiler boundary: a non-literal capacity
30
+ * would make the backing ring's size — and therefore its snapshot size and
31
+ * per-tick rollback cost — non-deterministic.
32
+ *
33
+ * Capacity resolution reuses `resolveIndexUpperBound` + `collectConstNumberEnv`
34
+ * (the same machinery that sizes a series index), so a bare numeric literal,
35
+ * a parenthesised / unary-`±` literal, and a `const` numeric-literal binding
36
+ * (`const K = 20; state.array(K)`) are all accepted; a `let`, an input, or any
37
+ * runtime expression resolves to `null` and is rejected.
38
+ *
39
+ * Two error codes:
40
+ * - `state-array-capacity-not-literal` — the capacity is missing or does not
41
+ * resolve to a compile-time number.
42
+ * - `state-array-capacity-exceeds-max` — the capacity resolves but is `<= 0`,
43
+ * non-integer, or `> MAX_STATE_ARRAY_CAPACITY`.
44
+ *
45
+ * The walk runs on the **original** AST (positions match the user's source,
46
+ * and — running pre-injection — the capacity is `node.arguments[0]`, before
47
+ * the slot-id literal is injected as the leading argument). The element-access
48
+ * form `state["array"](cap)` is rejected upstream as
49
+ * `stateful-call-element-access` and never matches `"state.array"` here, so it
50
+ * is not double-reported. A `state.array(...)` inside a loop additionally
51
+ * errors `stateful-call-inside-loop`; both passes are independent.
52
+ *
53
+ * @since 1.3
54
+ * @example
55
+ * // const diagnostics = runStateArrayCapacity(
56
+ * // sourceFile, checker, "demo.chart.ts",
57
+ * // );
58
+ * const fn: typeof runStateArrayCapacity = runStateArrayCapacity;
59
+ * void fn;
60
+ */
61
+ export function runStateArrayCapacity(sourceFile, checker, sourcePath) {
62
+ const diagnostics = [];
63
+ const visit = (node) => {
64
+ if (ts.isCallExpression(node) && resolveCalleeName(node, checker) === "state.array") {
65
+ const capacity = node.arguments[0];
66
+ if (capacity === undefined) {
67
+ diagnostics.push(createDiagnostic({
68
+ severity: "error",
69
+ code: "state-array-capacity-not-literal",
70
+ message: "`state.array` requires a numeric-literal capacity (a `const` numeric binding is accepted).",
71
+ file: sourcePath,
72
+ node,
73
+ sourceFile,
74
+ }));
75
+ }
76
+ else {
77
+ const constEnv = collectConstNumberEnv(capacity, sourceFile);
78
+ const bound = resolveIndexUpperBound(capacity, node, { constEnv, checker });
79
+ if (bound === null) {
80
+ diagnostics.push(createDiagnostic({
81
+ severity: "error",
82
+ code: "state-array-capacity-not-literal",
83
+ message: "`state.array` capacity must be a numeric literal (a `const` numeric binding is accepted), not a runtime value.",
84
+ file: sourcePath,
85
+ node: capacity,
86
+ sourceFile,
87
+ }));
88
+ }
89
+ else if (bound <= 0 ||
90
+ !Number.isInteger(bound) ||
91
+ bound > MAX_STATE_ARRAY_CAPACITY) {
92
+ diagnostics.push(createDiagnostic({
93
+ severity: "error",
94
+ code: "state-array-capacity-exceeds-max",
95
+ message: `\`state.array\` capacity must be a positive integer in 1..${MAX_STATE_ARRAY_CAPACITY}; got ${bound}.`,
96
+ file: sourcePath,
97
+ node: capacity,
98
+ sourceFile,
99
+ }));
100
+ }
101
+ }
102
+ }
103
+ ts.forEachChild(node, visit);
104
+ };
105
+ ts.forEachChild(sourceFile, visit);
106
+ return Object.freeze(diagnostics.slice());
107
+ }
108
+ //# sourceMappingURL=stateArrayCapacity.js.map