@invinite-org/chartlang-runtime 1.1.1 → 1.3.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 (294) hide show
  1. package/CHANGELOG.md +307 -0
  2. package/dist/barPoint.d.ts +20 -0
  3. package/dist/barPoint.d.ts.map +1 -0
  4. package/dist/barPoint.js +72 -0
  5. package/dist/barPoint.js.map +1 -0
  6. package/dist/bufferSnapshot.d.ts +102 -0
  7. package/dist/bufferSnapshot.d.ts.map +1 -0
  8. package/dist/bufferSnapshot.js +119 -0
  9. package/dist/bufferSnapshot.js.map +1 -0
  10. package/dist/buildComputeContext.d.ts.map +1 -1
  11. package/dist/buildComputeContext.js +6 -1
  12. package/dist/buildComputeContext.js.map +1 -1
  13. package/dist/createScriptRunner.d.ts +6 -3
  14. package/dist/createScriptRunner.d.ts.map +1 -1
  15. package/dist/createScriptRunner.js +65 -11
  16. package/dist/createScriptRunner.js.map +1 -1
  17. package/dist/dep/DepRunner.d.ts +7 -0
  18. package/dist/dep/DepRunner.d.ts.map +1 -1
  19. package/dist/dep/DepRunner.js +4 -0
  20. package/dist/dep/DepRunner.js.map +1 -1
  21. package/dist/emit/barcolor.d.ts +44 -0
  22. package/dist/emit/barcolor.d.ts.map +1 -0
  23. package/dist/emit/barcolor.js +40 -0
  24. package/dist/emit/barcolor.js.map +1 -0
  25. package/dist/emit/bgcolor.d.ts +44 -0
  26. package/dist/emit/bgcolor.d.ts.map +1 -0
  27. package/dist/emit/bgcolor.js +45 -0
  28. package/dist/emit/bgcolor.js.map +1 -0
  29. package/dist/emit/draw/boxes/fillBetween.d.ts +45 -0
  30. package/dist/emit/draw/boxes/fillBetween.d.ts.map +1 -0
  31. package/dist/emit/draw/boxes/fillBetween.js +36 -0
  32. package/dist/emit/draw/boxes/fillBetween.js.map +1 -0
  33. package/dist/emit/draw/handle.d.ts +9 -0
  34. package/dist/emit/draw/handle.d.ts.map +1 -1
  35. package/dist/emit/draw/handle.js +65 -10
  36. package/dist/emit/draw/handle.js.map +1 -1
  37. package/dist/emit/draw/namespace.d.ts +4 -3
  38. package/dist/emit/draw/namespace.d.ts.map +1 -1
  39. package/dist/emit/draw/namespace.js +6 -3
  40. package/dist/emit/draw/namespace.js.map +1 -1
  41. package/dist/emit/index.d.ts +2 -0
  42. package/dist/emit/index.d.ts.map +1 -1
  43. package/dist/emit/index.js +2 -0
  44. package/dist/emit/index.js.map +1 -1
  45. package/dist/emit/plot.d.ts +30 -1
  46. package/dist/emit/plot.d.ts.map +1 -1
  47. package/dist/emit/plot.js +45 -1
  48. package/dist/emit/plot.js.map +1 -1
  49. package/dist/execution/dispose.d.ts.map +1 -1
  50. package/dist/execution/dispose.js +18 -0
  51. package/dist/execution/dispose.js.map +1 -1
  52. package/dist/execution/runComputeStep.d.ts.map +1 -1
  53. package/dist/execution/runComputeStep.js +12 -1
  54. package/dist/execution/runComputeStep.js.map +1 -1
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/inputs/resolveInputs.js +1 -0
  60. package/dist/inputs/resolveInputs.js.map +1 -1
  61. package/dist/persistentStateStore.runtime.d.ts.map +1 -1
  62. package/dist/persistentStateStore.runtime.js +26 -7
  63. package/dist/persistentStateStore.runtime.js.map +1 -1
  64. package/dist/primitives.d.ts +1 -1
  65. package/dist/primitives.d.ts.map +1 -1
  66. package/dist/primitives.js +7 -1
  67. package/dist/primitives.js.map +1 -1
  68. package/dist/request/index.d.ts +2 -1
  69. package/dist/request/index.d.ts.map +1 -1
  70. package/dist/request/index.js +2 -1
  71. package/dist/request/index.js.map +1 -1
  72. package/dist/request/lowerTf.d.ts.map +1 -1
  73. package/dist/request/lowerTf.js +6 -0
  74. package/dist/request/lowerTf.js.map +1 -1
  75. package/dist/request/requestNamespace.d.ts.map +1 -1
  76. package/dist/request/requestNamespace.js +30 -3
  77. package/dist/request/requestNamespace.js.map +1 -1
  78. package/dist/request/security.d.ts +40 -4
  79. package/dist/request/security.d.ts.map +1 -1
  80. package/dist/request/security.js +114 -40
  81. package/dist/request/security.js.map +1 -1
  82. package/dist/request/securityExprRunner.d.ts +137 -0
  83. package/dist/request/securityExprRunner.d.ts.map +1 -0
  84. package/dist/request/securityExprRunner.js +253 -0
  85. package/dist/request/securityExprRunner.js.map +1 -0
  86. package/dist/request/streamBars.d.ts +14 -1
  87. package/dist/request/streamBars.d.ts.map +1 -1
  88. package/dist/request/streamBars.js +39 -1
  89. package/dist/request/streamBars.js.map +1 -1
  90. package/dist/ringBuffer.d.ts +19 -0
  91. package/dist/ringBuffer.d.ts.map +1 -1
  92. package/dist/ringBuffer.js +23 -0
  93. package/dist/ringBuffer.js.map +1 -1
  94. package/dist/runtimeContext.d.ts +90 -5
  95. package/dist/runtimeContext.d.ts.map +1 -1
  96. package/dist/runtimeContext.js.map +1 -1
  97. package/dist/seriesView.d.ts +42 -17
  98. package/dist/seriesView.d.ts.map +1 -1
  99. package/dist/seriesView.js +65 -42
  100. package/dist/seriesView.js.map +1 -1
  101. package/dist/state/arrayPersistence.d.ts +48 -0
  102. package/dist/state/arrayPersistence.d.ts.map +1 -0
  103. package/dist/state/arrayPersistence.js +88 -0
  104. package/dist/state/arrayPersistence.js.map +1 -0
  105. package/dist/state/arrayStateSlot.d.ts +78 -0
  106. package/dist/state/arrayStateSlot.d.ts.map +1 -0
  107. package/dist/state/arrayStateSlot.js +116 -0
  108. package/dist/state/arrayStateSlot.js.map +1 -0
  109. package/dist/state/index.d.ts +4 -1
  110. package/dist/state/index.d.ts.map +1 -1
  111. package/dist/state/index.js +4 -1
  112. package/dist/state/index.js.map +1 -1
  113. package/dist/state/lifecycle.d.ts +68 -0
  114. package/dist/state/lifecycle.d.ts.map +1 -1
  115. package/dist/state/lifecycle.js +89 -0
  116. package/dist/state/lifecycle.js.map +1 -1
  117. package/dist/state/seriesPersistence.d.ts +48 -0
  118. package/dist/state/seriesPersistence.d.ts.map +1 -0
  119. package/dist/state/seriesPersistence.js +87 -0
  120. package/dist/state/seriesPersistence.js.map +1 -0
  121. package/dist/state/seriesSlot.d.ts +105 -0
  122. package/dist/state/seriesSlot.d.ts.map +1 -0
  123. package/dist/state/seriesSlot.js +123 -0
  124. package/dist/state/seriesSlot.js.map +1 -0
  125. package/dist/state/stateNamespace.d.ts.map +1 -1
  126. package/dist/state/stateNamespace.js +55 -0
  127. package/dist/state/stateNamespace.js.map +1 -1
  128. package/dist/streamState.d.ts +25 -19
  129. package/dist/streamState.d.ts.map +1 -1
  130. package/dist/streamState.js +40 -66
  131. package/dist/streamState.js.map +1 -1
  132. package/dist/ta/adx.d.ts +3 -2
  133. package/dist/ta/adx.d.ts.map +1 -1
  134. package/dist/ta/adx.js +3 -2
  135. package/dist/ta/adx.js.map +1 -1
  136. package/dist/ta/alma.d.ts +6 -4
  137. package/dist/ta/alma.d.ts.map +1 -1
  138. package/dist/ta/alma.js +19 -6
  139. package/dist/ta/alma.js.map +1 -1
  140. package/dist/ta/atr.d.ts +3 -2
  141. package/dist/ta/atr.d.ts.map +1 -1
  142. package/dist/ta/atr.js +3 -2
  143. package/dist/ta/atr.js.map +1 -1
  144. package/dist/ta/bb.d.ts +3 -2
  145. package/dist/ta/bb.d.ts.map +1 -1
  146. package/dist/ta/bb.js +3 -2
  147. package/dist/ta/bb.js.map +1 -1
  148. package/dist/ta/chaikinOsc.d.ts +3 -2
  149. package/dist/ta/chaikinOsc.d.ts.map +1 -1
  150. package/dist/ta/chaikinOsc.js +3 -2
  151. package/dist/ta/chaikinOsc.js.map +1 -1
  152. package/dist/ta/crossover.d.ts +3 -2
  153. package/dist/ta/crossover.d.ts.map +1 -1
  154. package/dist/ta/crossover.js +3 -2
  155. package/dist/ta/crossover.js.map +1 -1
  156. package/dist/ta/crossunder.d.ts +3 -2
  157. package/dist/ta/crossunder.d.ts.map +1 -1
  158. package/dist/ta/crossunder.js +3 -2
  159. package/dist/ta/crossunder.js.map +1 -1
  160. package/dist/ta/dmi.d.ts +4 -3
  161. package/dist/ta/dmi.d.ts.map +1 -1
  162. package/dist/ta/dmi.js +4 -3
  163. package/dist/ta/dmi.js.map +1 -1
  164. package/dist/ta/ema.d.ts +3 -2
  165. package/dist/ta/ema.d.ts.map +1 -1
  166. package/dist/ta/ema.js +3 -2
  167. package/dist/ta/ema.js.map +1 -1
  168. package/dist/ta/eom.d.ts +3 -1
  169. package/dist/ta/eom.d.ts.map +1 -1
  170. package/dist/ta/eom.js +3 -1
  171. package/dist/ta/eom.js.map +1 -1
  172. package/dist/ta/highestbars.d.ts +25 -0
  173. package/dist/ta/highestbars.d.ts.map +1 -0
  174. package/dist/ta/highestbars.js +106 -0
  175. package/dist/ta/highestbars.js.map +1 -0
  176. package/dist/ta/historicalVolatility.d.ts +3 -2
  177. package/dist/ta/historicalVolatility.d.ts.map +1 -1
  178. package/dist/ta/historicalVolatility.js +3 -2
  179. package/dist/ta/historicalVolatility.js.map +1 -1
  180. package/dist/ta/ichimoku.d.ts +3 -1
  181. package/dist/ta/ichimoku.d.ts.map +1 -1
  182. package/dist/ta/ichimoku.js +3 -1
  183. package/dist/ta/ichimoku.js.map +1 -1
  184. package/dist/ta/lowestbars.d.ts +25 -0
  185. package/dist/ta/lowestbars.d.ts.map +1 -0
  186. package/dist/ta/lowestbars.js +102 -0
  187. package/dist/ta/lowestbars.js.map +1 -0
  188. package/dist/ta/macd.d.ts +3 -2
  189. package/dist/ta/macd.d.ts.map +1 -1
  190. package/dist/ta/macd.js +3 -2
  191. package/dist/ta/macd.js.map +1 -1
  192. package/dist/ta/massIndex.d.ts +3 -2
  193. package/dist/ta/massIndex.d.ts.map +1 -1
  194. package/dist/ta/massIndex.js +3 -2
  195. package/dist/ta/massIndex.js.map +1 -1
  196. package/dist/ta/mfi.d.ts +3 -1
  197. package/dist/ta/mfi.d.ts.map +1 -1
  198. package/dist/ta/mfi.js +3 -1
  199. package/dist/ta/mfi.js.map +1 -1
  200. package/dist/ta/netVolume.d.ts +3 -1
  201. package/dist/ta/netVolume.d.ts.map +1 -1
  202. package/dist/ta/netVolume.js +3 -1
  203. package/dist/ta/netVolume.js.map +1 -1
  204. package/dist/ta/nvi.d.ts +3 -1
  205. package/dist/ta/nvi.d.ts.map +1 -1
  206. package/dist/ta/nvi.js +3 -1
  207. package/dist/ta/nvi.js.map +1 -1
  208. package/dist/ta/persistence.d.ts.map +1 -1
  209. package/dist/ta/persistence.js +1 -40
  210. package/dist/ta/persistence.js.map +1 -1
  211. package/dist/ta/ppo.d.ts +3 -2
  212. package/dist/ta/ppo.d.ts.map +1 -1
  213. package/dist/ta/ppo.js +3 -2
  214. package/dist/ta/ppo.js.map +1 -1
  215. package/dist/ta/pvi.d.ts +3 -1
  216. package/dist/ta/pvi.d.ts.map +1 -1
  217. package/dist/ta/pvi.js +3 -1
  218. package/dist/ta/pvi.js.map +1 -1
  219. package/dist/ta/pvo.d.ts +3 -1
  220. package/dist/ta/pvo.d.ts.map +1 -1
  221. package/dist/ta/pvo.js +3 -1
  222. package/dist/ta/pvo.js.map +1 -1
  223. package/dist/ta/pvt.d.ts +3 -1
  224. package/dist/ta/pvt.d.ts.map +1 -1
  225. package/dist/ta/pvt.js +3 -1
  226. package/dist/ta/pvt.js.map +1 -1
  227. package/dist/ta/registry.d.ts +7 -1
  228. package/dist/ta/registry.d.ts.map +1 -1
  229. package/dist/ta/registry.js +4 -0
  230. package/dist/ta/registry.js.map +1 -1
  231. package/dist/ta/rsi.d.ts +3 -2
  232. package/dist/ta/rsi.d.ts.map +1 -1
  233. package/dist/ta/rsi.js +3 -2
  234. package/dist/ta/rsi.js.map +1 -1
  235. package/dist/ta/rvi.d.ts +3 -2
  236. package/dist/ta/rvi.d.ts.map +1 -1
  237. package/dist/ta/rvi.js +3 -2
  238. package/dist/ta/rvi.js.map +1 -1
  239. package/dist/ta/sessionVolumeProfile.d.ts.map +1 -1
  240. package/dist/ta/sessionVolumeProfile.js +1 -17
  241. package/dist/ta/sessionVolumeProfile.js.map +1 -1
  242. package/dist/ta/sma.d.ts +6 -3
  243. package/dist/ta/sma.d.ts.map +1 -1
  244. package/dist/ta/sma.js +6 -3
  245. package/dist/ta/sma.js.map +1 -1
  246. package/dist/ta/stdev.d.ts +3 -2
  247. package/dist/ta/stdev.d.ts.map +1 -1
  248. package/dist/ta/stdev.js +3 -2
  249. package/dist/ta/stdev.js.map +1 -1
  250. package/dist/ta/trendStrengthIndex.d.ts +3 -2
  251. package/dist/ta/trendStrengthIndex.d.ts.map +1 -1
  252. package/dist/ta/trendStrengthIndex.js +3 -2
  253. package/dist/ta/trendStrengthIndex.js.map +1 -1
  254. package/dist/ta/trix.d.ts +4 -3
  255. package/dist/ta/trix.d.ts.map +1 -1
  256. package/dist/ta/trix.js +4 -3
  257. package/dist/ta/trix.js.map +1 -1
  258. package/dist/ta/vortex.d.ts +3 -2
  259. package/dist/ta/vortex.d.ts.map +1 -1
  260. package/dist/ta/vortex.js +3 -2
  261. package/dist/ta/vortex.js.map +1 -1
  262. package/dist/time-accessors/civil.d.ts +73 -0
  263. package/dist/time-accessors/civil.d.ts.map +1 -0
  264. package/dist/time-accessors/civil.js +105 -0
  265. package/dist/time-accessors/civil.js.map +1 -0
  266. package/dist/time-accessors/index.d.ts +8 -0
  267. package/dist/time-accessors/index.d.ts.map +1 -0
  268. package/dist/time-accessors/index.js +9 -0
  269. package/dist/time-accessors/index.js.map +1 -0
  270. package/dist/time-accessors/sessionAccessors.d.ts +50 -0
  271. package/dist/time-accessors/sessionAccessors.d.ts.map +1 -0
  272. package/dist/time-accessors/sessionAccessors.js +79 -0
  273. package/dist/time-accessors/sessionAccessors.js.map +1 -0
  274. package/dist/time-accessors/sessionWindow.d.ts +17 -0
  275. package/dist/time-accessors/sessionWindow.d.ts.map +1 -0
  276. package/dist/time-accessors/sessionWindow.js +41 -0
  277. package/dist/time-accessors/sessionWindow.js.map +1 -0
  278. package/dist/time-accessors/timeAccessors.d.ts +54 -0
  279. package/dist/time-accessors/timeAccessors.d.ts.map +1 -0
  280. package/dist/time-accessors/timeAccessors.js +132 -0
  281. package/dist/time-accessors/timeAccessors.js.map +1 -0
  282. package/dist/time-accessors/tzDiagnostic.d.ts +17 -0
  283. package/dist/time-accessors/tzDiagnostic.d.ts.map +1 -0
  284. package/dist/time-accessors/tzDiagnostic.js +34 -0
  285. package/dist/time-accessors/tzDiagnostic.js.map +1 -0
  286. package/dist/time-accessors/tzOffset.d.ts +31 -0
  287. package/dist/time-accessors/tzOffset.d.ts.map +1 -0
  288. package/dist/time-accessors/tzOffset.js +67 -0
  289. package/dist/time-accessors/tzOffset.js.map +1 -0
  290. package/package.json +3 -3
  291. package/dist/ta/lib/applyOffset.d.ts +0 -19
  292. package/dist/ta/lib/applyOffset.d.ts.map +0 -1
  293. package/dist/ta/lib/applyOffset.js +0 -38
  294. package/dist/ta/lib/applyOffset.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,312 @@
1
1
  # @invinite-org/chartlang-runtime
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e620ba8: Add `bgcolor(color, opts?)` and `barcolor(color, opts?)` — Pine-ergonomic
8
+ top-level aliases for the `bg-color` / `bar-color` plot styles. One call
9
+ (`bgcolor(close > open ? "#16a34a" : "#dc2626", { transp: 80 })`) replaces
10
+ the verbose `plot(NaN, { style: { kind: "bg-color", … } })`. Surfaced in the
11
+ generated primitive reference and taught in the chartlang-coding skill.
12
+
13
+ Deliverable 2 (per-bar dynamic color): `PlotEmission` gains an optional
14
+ `colorValue: Color | null` channel; the runtime resolves the `bgcolor` /
15
+ `barcolor` per-bar color into it (omitted on the static `plot` path → wire
16
+ byte-identical, every pinned `plot-hash` untouched), validates it
17
+ (non-empty color string or `null`), and dedups it last-write-wins per
18
+ `(slotId, bar)` like `value`. Adapters prefer `colorValue` over the static
19
+ `style.color` at render time — this precedence is now the normative
20
+ adapter-kit contract (`PlotEmission.colorValue` JSDoc) and is implemented in
21
+ the canvas2d reference renderer (`null` ⇒ paint-nothing gap; omitted ⇒ static
22
+ fallback). The Pine converter emits the real per-bar dynamic color
23
+ (`bgcolor(close > open ? "#16a34a" : "#dc2626")`) instead of a static
24
+ `plot(NaN, …)`, so `bgcolor`/`barcolor` round-trip with per-bar semantics
25
+ intact.
26
+
27
+ - 08cba38: Add `time.*` calendar accessors (`time.year/month/dayofmonth/dayofweek/hour/
28
+ minute/second/timestamp`), a `time.timeClose(t, tz?)` bar-close accessor
29
+ (Pine's `time_close()` = bar start + interval), a `session.isOpen(t, spec, tz?)`
30
+ helper, and an `input.session` kind. Calendar fields are derived from a `Time`
31
+ epoch via the host (authors stay sandboxed — `Date`/`Intl` remain banned). v1
32
+ is UTC + fixed-offset only; exchange-tz/DST is a scoped follow-up. The Pine
33
+ converter lowers `dayofweek` / `time()` / `time_close()` / `input.session`.
34
+ - 1efb49c: Add multi-symbol support to `request.security`. `request.security({ symbol,
35
+ interval })` now reads a **different instrument** (not just a higher
36
+ timeframe), e.g. `request.security({ symbol: "AMEX:SPY", interval: "1D" })`.
37
+ `symbol` is optional (defaults to the chart symbol) and must be a compile-time
38
+ literal (`input.symbol` / `input.enum` resolved). A new `multiSymbol` adapter
39
+ capability gates non-chart-symbol requests: a different-symbol request against
40
+ an adapter declaring `multiSymbol: false` degrades to an all-NaN
41
+ bar/series with a single deduped `multi-symbol-not-supported` diagnostic,
42
+ mirroring `multi-timeframe-not-supported` (the symbol gate precedes the
43
+ timeframe gate, so a both-different request emits only the symbol diagnostic).
44
+ The Pine converter now lowers `request.security("OTHER", tf, expr)`, and the
45
+ `chartlang scaffold-adapter` template advertises `multiSymbol`.
46
+ - 1efb49c: Add `state.array<T>(capacity)` — a persistent, bounded FIFO collection. Push
47
+ many values across bars (`a.push(v)`) into a fixed-capacity ring and read
48
+ them back by element (`a.get(0)` = newest, `a.last()`, `a.size`,
49
+ `a.capacity`, `a.clear()`). Bounded literal capacity keeps it
50
+ serialization-clean. The Pine converter lowers a bounded numeric
51
+ `var array<…>` Camp B ring to it.
52
+
53
+ The compiler guards the capacity: it must be a compile-time numeric literal
54
+ (a `const` numeric binding is accepted) that is a positive integer within
55
+ `MAX_STATE_ARRAY_CAPACITY` (100_000). A non-literal capacity errors
56
+ `state-array-capacity-not-literal`; an out-of-range / non-integer literal
57
+ errors `state-array-capacity-exceeds-max`.
58
+
59
+ ### Patch Changes
60
+
61
+ - Updated dependencies [189493a]
62
+ - Updated dependencies [8bc628e]
63
+ - Updated dependencies [ab8b218]
64
+ - Updated dependencies [8bc628e]
65
+ - Updated dependencies [ab8b218]
66
+ - Updated dependencies [189493a]
67
+ - Updated dependencies [e620ba8]
68
+ - Updated dependencies [08cba38]
69
+ - Updated dependencies [1efb49c]
70
+ - Updated dependencies [1efb49c]
71
+ - @invinite-org/chartlang-adapter-kit@1.6.0
72
+ - @invinite-org/chartlang-core@1.3.0
73
+
74
+ ## 1.2.0
75
+
76
+ ### Minor Changes
77
+
78
+ - 850ae21: Add `bar.point(offset, price)` — index authoring sugar for anchoring drawings
79
+ by bar offset instead of an absolute timestamp.
80
+
81
+ `bar.point` resolves the offset to the existing time-based `WorldPoint`
82
+ (`{ time, price }`) at compute time, so it composes directly with every
83
+ `draw.*` anchor argument and introduces no new wire format or anchor union:
84
+
85
+ - `bar.point(0, price)` — the current bar.
86
+ - `bar.point(-n, price)` — `n` bars back, using the real historical timestamp
87
+ from the runtime's time ring buffer (`NaN` time past retained history; never
88
+ throws).
89
+ - `bar.point(n, price)` — a future bar, with the time extrapolated from the
90
+ median recent bar spacing (falling back to the parsed bar interval when
91
+ fewer than two bars are retained).
92
+
93
+ The compiler's max-lookback analysis now counts a negative integer-literal
94
+ `bar.point(-n, …)` offset toward `maxLookback` exactly like a `series[n]`
95
+ lookback, so the runtime sizes the time buffer deeply enough; positive (future)
96
+ offsets and dynamic offsets contribute no extra depth. The recogniser peels
97
+ parentheses, so the converter's emitted form `bar.point(-(n), …)` is sized
98
+ identically to a hand-written `bar.point(-n, …)` (without it, a converted
99
+ historical tracking line sized its buffer to 0 and resolved to a NaN anchor).
100
+
101
+ The Pine v6 converter now lowers `bar_index` drawing anchors to
102
+ `bar.point(<signed offset>, <price>)` and drops the dead `__BAR_INTERVAL_MS`
103
+ sentinel and its `bar.time ± (N * __BAR_INTERVAL_MS)` arithmetic — future
104
+ anchors resolve at runtime instead of needing a host-supplied bar interval.
105
+
106
+ - ca19e20: Bidirectional plot `offset` — negative offsets shift a plotted series left.
107
+
108
+ `offset` becomes a presentation-only **display shift** in bars with the
109
+ fixed sign convention `+n` = right (future), `−n` = left (past); the
110
+ numeric series value is unshifted. This replaces the old value-read model
111
+ (where a positive offset made `series.current` read the value N bars ago
112
+ and a negative offset resolved to `NaN`). The `*Opts` `offset` JSDoc (and
113
+ ALMA's `barShift`) now describe both directions and drop the old
114
+ "negative ⇒ NaN" wording (`AlmaOpts.offset`, the Gaussian-centre
115
+ position, is unchanged).
116
+
117
+ `PlotEmission` gains an optional presentation field `xShift?: number`
118
+ (signed integer bars; omitted/`0` ≡ no shift, so a no-shift emission is
119
+ byte-identical to today). `validateEmission` rejects a non-integer
120
+ `xShift`. The compiler no longer counts `offset` toward `maxLookback`
121
+ (the value is no longer read from a deeper slot). The runtime threads the
122
+ declared offset onto the emission as `xShift` (reading a
123
+ `WeakMap<Series, number>` offset tag set by `makeShiftedSeriesView`; ALMA
124
+ tags `opts.barShift`) and stops the old value-read shift so
125
+ `series.current` is unshifted; the reference adapter renders it by
126
+ projecting `xShift` onto the x-axis (extending the viewport for
127
+ future-shifted points).
128
+
129
+ The Pine converter now maps `plot(<ta.* call>, offset=N)` onto the
130
+ emitted `ta.*` call's `offset` opt (signed, both directions); a plot
131
+ whose value is not a direct `ta.*` call drops the offset and emits the
132
+ new `plot-offset-needs-ta-call` warning, and a plot-level offset
133
+ replacing the ta call's own `offset=` emits `plot-offset-overrides-ta-offset`.
134
+
135
+ The conformance harness's `plot-field` assertion gains an `xShift` field,
136
+ and a new scenario pins both shift directions plus the unshifted value
137
+ series.
138
+
139
+ - 6235ad7: Make the compute bar's OHLCV + derived fields directly indexable as a series.
140
+
141
+ `bar.close`, `bar.open`, `bar.high`, `bar.low`, `bar.volume`, and the derived
142
+ `bar.hl2` / `bar.hlc3` / `bar.ohlc4` / `bar.hlcc4` are now `PriceSeries` /
143
+ `VolumeSeries` (`number & Series<number>`) on the bar passed to `compute`
144
+ (`ComputeContext.bar`, typed as the new `BarSeries`). Each field is **both** a
145
+ scalar — `bar.close * 2`, `plot(bar.close)`, `ta.ema(bar.close, 20)` keep
146
+ working unchanged — **and** an indexable series, so a script can read prior
147
+ bars directly:
148
+
149
+ ```ts
150
+ const sma5 =
151
+ (bar.close[0] + bar.close[1] + bar.close[2] + bar.close[3] + bar.close[4]) /
152
+ 5;
153
+ ```
154
+
155
+ This removes the `ta.ema(bar.close, 1)` identity-trick that scripts previously
156
+ needed to "republish" a scalar price as an indexable `Series`.
157
+
158
+ The adapter-supplied candle type `Bar` (and `request.lowerTf` intrabar bars) is
159
+ unchanged — it stays scalar OHLCV; only the streaming `compute` bar gains the
160
+ series shape. `request.security`'s higher-timeframe bar remains the separate
161
+ `SecurityBar`.
162
+
163
+ Migration note: because the field is now an object, `Number.isFinite(bar.close)`
164
+ is always `false` (it does not coerce) and `bar.close === 42` is `false` (object
165
+ vs number). Use `bar.close.current` or `+bar.close` in those raw-number
166
+ contexts. `bar.point(0, bar.close)` continues to work — the runtime coerces the
167
+ anchor price to a scalar.
168
+
169
+ - 3bf391a: Add the `draw.fillBetween(edgeA, edgeB, opts?)` drawing primitive — a
170
+ native filled ribbon between two edges (the closed polygon `edgeA`
171
+ forward then `edgeB` reversed). It is the chartlang equivalent of Pine's
172
+ `linefill.new(line1, line2, color)` / `fill(plot1, plot2)`. The
173
+ pine-converter now lowers static two-line `linefill.new` to it instead of
174
+ approximating with `draw.rotatedRectangle`, retiring the
175
+ `linefill-rotatedrect-approximated` diagnostic.
176
+ - 8086003: Add an optional presentation-only `z` (render-order / z-index) option to
177
+ `plot()` and every `draw.*` primitive. Default `0`; higher renders on
178
+ top, ties fall back to the existing group + declaration order. Finite
179
+ numbers only. Affects stacking only — values, alerts, and `state.*` are
180
+ unchanged.
181
+
182
+ Adapter kit: `PlotEmission` and `DrawingEmission` gain the matching
183
+ presentation-only `z?: number` wire field, validated by
184
+ `validateEmission` as a finite number (NaN / ±Infinity rejected;
185
+ fractional and negative allowed). Omitted/`0` stays byte-identical to a
186
+ pre-feature emission, so existing goldens and conformance hashes are
187
+ untouched.
188
+
189
+ Runtime: `plotImpl` reads `opts.z`, and the drawing-emit path
190
+ (`createDrawingHandle`) lifts `z` out of `state.style` — into a shallow
191
+ clone with `z` removed, where the per-kind `draw.*` impls fold the opts
192
+ bag — and threads it onto the top-level `PlotEmission.z` /
193
+ `DrawingEmission.z` with the same omit-when-`0` conditional spread used
194
+ for `xShift`. `z` is persisted **beside** the drawing slot's `state`
195
+ (never inside `DrawingState`), so an `update` retains the last value. A
196
+ no-`z` plot or drawing emits no `z` key — byte-identical to the
197
+ pre-feature baseline. `draw.table` / `draw.group` do not carry `z` in
198
+ v1.
199
+
200
+ Pine converter: `explicit_plot_zorder` is now a recognized no-op instead
201
+ of an unmapped warning. chartlang already layers marks by declaration
202
+ order within their group (the normative ordering contract), which is
203
+ exactly what Pine's `explicit_plot_zorder=true` makes authoritative — so
204
+ the flag is satisfied by default and needs no chartlang option.
205
+ `mapDeclarationArgs` no longer raises `indicator-arg-not-mapped` for it;
206
+ instead it emits a single `explicit-plot-zorder-default` info note
207
+ (covering both `explicit_plot_zorder=true` and the Pine-default
208
+ `=false`). The converter still never _emits_ a numeric `z` — Pine has no
209
+ per-element z source construct. Other unmapped `indicator(...)` args
210
+ (`timeframe`, etc.) keep warning.
211
+
212
+ Compiler: the ambient `@invinite-org/chartlang-core` `.d.ts` shim gains a
213
+ `ZOrdered { z?: number }` mixin intersected into `PlotOpts` and every
214
+ `draw.*` option type (mirroring core's `drawingStyle.ts`), so a compiled
215
+ script's `plot(value, { z })` **and** `draw.*(…, { z })` type-check (the
216
+ shim stays in lockstep with core).
217
+
218
+ Conformance: a new `z-order` scenario pins the plot `z` →
219
+ `PlotEmission.z` wire contract — a `plot(value, { z: -1 })` emits
220
+ `z: -1`, a no-`z` plot omits the field (omit-when-`0` byte-identity), and
221
+ a value-hash proves `z` never transforms the series. The `plot-field`
222
+ assertion's `field` union widens to also accept `"z"`.
223
+
224
+ - 073f41b: Add the higher-timeframe expression/callback overload to `request.security`.
225
+ Alongside the existing data form `request.security({ interval })` →
226
+ `SecurityBar`, scripts can now write `request.security({ interval }, (bar) =>
227
+ …)` → `Series<number>`, where the callback runs on the **higher-timeframe
228
+ clock** — `request.security({ interval: "1W" }, (bar) => ta.ema(bar.close, 20))`
229
+ is a true weekly EMA(20) (20 weekly bars), not 20 main bars of a weekly-stepped
230
+ series. The result is aligned no-lookahead down to the main timeline.
231
+
232
+ - **core** — the `SecurityExpr` callback type (re-exported from the package
233
+ root), the second `security` overload, and the shared `statefulPrimitives`
234
+ entry annotated as covering both arities.
235
+ - **compiler** — records one `SecurityExpressionDescriptor { slotId, interval,
236
+ paramName }` per expression callsite in `manifest.securityExpressions`
237
+ (sorted by `slotId`, omitted for the data-only form), and validates each
238
+ callback against the allowed subset — its `bar` parameter and body locals,
239
+ the ambient `ta` / `inputs`, safe `Math.*` globals, and literals — rejecting
240
+ any captured outer binding with the new
241
+ `request-security-expr-captures-local` diagnostic.
242
+ - **runtime** — mounts one `SecurityExprRunner` per manifest entry: the
243
+ callback is captured lazily on the first main compute, driven once per HTF bar
244
+ close through a dedicated fold `StreamState` so `ta.*` accumulate on the HTF
245
+ clock, and one sampled value per HTF bar feeds a per-slot output buffer that
246
+ `request.security(opts, expr)` returns aligned no-lookahead to the main
247
+ timeline. Capability / interval / stream fallbacks return an all-NaN series
248
+ with a deduped diagnostic.
249
+ - **host-worker / host-quickjs** — boot the expression form unchanged; the
250
+ `__manifest` sidecar already carries `securityExpressions`.
251
+ - **pine-converter** — Pine's `request.security(sym, "D", ta.ema(close, 9))`
252
+ now lowers to the chartlang callback form
253
+ `request.security({ interval: "1d" }, (bar) => ta.ema(bar.close, 9))` (a bare
254
+ OHLCV third arg keeps lowering to the data form).
255
+ - **conformance** — new scenarios prove the weekly expression value differs
256
+ from a same-length main-timeframe EMA, plus the `multiTimeframe: false` NaN
257
+ fallback.
258
+
259
+ - 5a9c24d: Add `state.series(init)` — a writable, indexable user series. Store an
260
+ arbitrary value each bar (`s.value = expr`) and read its history N bars
261
+ back (`s[1]`). Number-coercible (`+s`, `s.current`) and usable as a `ta.*`
262
+ source. The Pine converter lowers a history-indexed `var` to it.
263
+ - 08c536c: Add the `ta.highestbars` / `ta.lowestbars` primitives plus the cross-package
264
+ wiring that makes them usable as drawing anchors and Pine-converter targets.
265
+
266
+ - **core / runtime:** `ta.highestbars(source, length, opts?)` and
267
+ `ta.lowestbars(source, length, opts?)` return the bar OFFSET (≤ 0) to the
268
+ highest / lowest `source` value over the trailing `length` bars (window
269
+ INCLUDES the current bar). `0` → current bar is the extreme; `-k` → the
270
+ extreme occurred `k` bars ago. Ties resolve to the most recent bar; NaN
271
+ inputs are skipped; warmup is `length − 1` bars; tick-mode replays the
272
+ in-progress head as the offset-0 candidate. Registered in
273
+ `STATEFUL_PRIMITIVES` (now 174 entries) and `TA_REGISTRY` (now 96 entries).
274
+ - **compiler:** a literal-length `ta.highestbars` / `ta.lowestbars` call
275
+ contributes `length − 1` toward `maxLookback`, so the runtime sizes the time
276
+ ring buffer deep enough for a `bar.point(<that offset>, …)` anchor to resolve.
277
+ A non-literal length contributes 0.
278
+ - **pine-converter:** `ta.highestbars` / `ta.lowestbars` now map to the real
279
+ chartlang primitives (previously lossy passthroughs to `ta.highest` /
280
+ `ta.lowest`). **Behavior change:** a DYNAMIC `bar_index + <non-literal>`
281
+ drawing-x anchor no longer raises the hard `requires-bar-interval` error —
282
+ the offset is resolved by `bar.point` at runtime sign-agnostically (a
283
+ negative runtime offset, e.g. what `ta.highestbars` returns, resolves to the
284
+ historical timestamp via the time buffer). Only the literal `bar_index + N`
285
+ future case still requires a bar interval.
286
+ - **conformance:** new `TA_HIGHEST_LOWEST_BARS_SCENARIO` export pins both
287
+ primitives end-to-end through the compiler + runtime over the bundled
288
+ `goldenBars.json` fixture, and is added to `ALL_SCENARIOS`.
289
+
290
+ ### Patch Changes
291
+
292
+ - 850ae21: Promote every remaining `@experimental` symbol to `@stable`. The entire
293
+ `pine-converter` public surface, the three `pineConverterRoundTrip*` conformance
294
+ scenarios, and `runtime/barPoint.ts` now carry the stable maturity marker.
295
+ Annotation-only — no behavior, API, or output changes; goldens and conformance
296
+ reports are byte-identical. The hand-authored `docs/converter/index.md`
297
+ stability line is updated to match.
298
+ - Updated dependencies [850ae21]
299
+ - Updated dependencies [ca19e20]
300
+ - Updated dependencies [6235ad7]
301
+ - Updated dependencies [3bf391a]
302
+ - Updated dependencies [8086003]
303
+ - Updated dependencies [850ae21]
304
+ - Updated dependencies [073f41b]
305
+ - Updated dependencies [5a9c24d]
306
+ - Updated dependencies [08c536c]
307
+ - @invinite-org/chartlang-core@1.2.0
308
+ - @invinite-org/chartlang-adapter-kit@1.3.0
309
+
3
310
  ## 1.1.1
4
311
 
5
312
  ### Patch Changes
@@ -0,0 +1,20 @@
1
+ import type { Price, WorldPoint } from "@invinite-org/chartlang-core";
2
+ import type { Float64RingBuffer } from "./ringBuffer.js";
3
+ /**
4
+ * Resolve a `bar.point(offset, price)` call to the time-based
5
+ * {@link WorldPoint} the rest of the drawing pipeline already speaks.
6
+ *
7
+ * `offset === 0` reads the live `bar.time`; `offset < 0` reads the real
8
+ * historical timestamp `|offset|` bars back from the time ring buffer
9
+ * (`NaN` past retention); `offset > 0` extrapolates `lastTime + offset *
10
+ * spacing`, where `spacing` is the median retained-bar delta and falls back
11
+ * to the parsed bar interval when fewer than two bars are retained. `price`
12
+ * passes through unchanged. Never throws.
13
+ *
14
+ * @since 0.9
15
+ * @stable
16
+ * @example
17
+ * // const wp = resolveBarPoint(stream.ohlcv.time, "1D", currentTime, -10, 42);
18
+ */
19
+ export declare function resolveBarPoint(time: Float64RingBuffer, interval: string, currentTime: number, offset: number, price: Price): WorldPoint;
20
+ //# sourceMappingURL=barPoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barPoint.d.ts","sourceRoot":"","sources":["../src/barPoint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAoCzD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC3B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,GACb,UAAU,CAaZ"}
@@ -0,0 +1,72 @@
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 { intervalToSeconds } from "@invinite-org/chartlang-core";
4
+ /**
5
+ * Parse a bar-interval string to its spacing in milliseconds, or `NaN` when
6
+ * the string is not a parseable interval. `intervalToSeconds` throws on an
7
+ * unparseable descriptor; this wrapper swallows that so `bar.point` stays a
8
+ * non-throwing, graceful-degradation helper.
9
+ */
10
+ function intervalSpacingMs(interval) {
11
+ try {
12
+ return intervalToSeconds({ value: interval, label: interval, group: "" }) * 1000;
13
+ }
14
+ catch {
15
+ return Number.NaN;
16
+ }
17
+ }
18
+ /**
19
+ * The median delta between the most recent retained bar times, used to
20
+ * extrapolate future-bar timestamps. Walks the newest `min(length - 1, cap)`
21
+ * deltas (cap keeps the scan O(1) on long histories) and returns the median;
22
+ * `NaN` when fewer than two bars are retained or every delta is non-finite.
23
+ */
24
+ function medianSpacingMs(time) {
25
+ const pairs = Math.min(time.length - 1, 100);
26
+ if (pairs < 1)
27
+ return Number.NaN;
28
+ const deltas = [];
29
+ for (let i = 0; i < pairs; i += 1) {
30
+ const delta = time.at(i) - time.at(i + 1);
31
+ if (Number.isFinite(delta))
32
+ deltas.push(delta);
33
+ }
34
+ if (deltas.length === 0)
35
+ return Number.NaN;
36
+ deltas.sort((a, b) => a - b);
37
+ const mid = deltas.length >> 1;
38
+ return deltas.length % 2 === 1 ? deltas[mid] : (deltas[mid - 1] + deltas[mid]) / 2;
39
+ }
40
+ /**
41
+ * Resolve a `bar.point(offset, price)` call to the time-based
42
+ * {@link WorldPoint} the rest of the drawing pipeline already speaks.
43
+ *
44
+ * `offset === 0` reads the live `bar.time`; `offset < 0` reads the real
45
+ * historical timestamp `|offset|` bars back from the time ring buffer
46
+ * (`NaN` past retention); `offset > 0` extrapolates `lastTime + offset *
47
+ * spacing`, where `spacing` is the median retained-bar delta and falls back
48
+ * to the parsed bar interval when fewer than two bars are retained. `price`
49
+ * passes through unchanged. Never throws.
50
+ *
51
+ * @since 0.9
52
+ * @stable
53
+ * @example
54
+ * // const wp = resolveBarPoint(stream.ohlcv.time, "1D", currentTime, -10, 42);
55
+ */
56
+ export function resolveBarPoint(time, interval, currentTime, offset, price) {
57
+ // `price` is a `Price` (number) by contract, but a script may pass a bar
58
+ // price field (`bar.point(0, bar.close)`) which is now a number-coercible
59
+ // series view. Coerce to the scalar so the persisted `WorldPoint.price`
60
+ // is always a number, never the view object. `Number(NaN)` stays NaN.
61
+ const p = Number(price);
62
+ if (offset === 0)
63
+ return { time: currentTime, price: p };
64
+ if (offset < 0)
65
+ return { time: time.at(-offset), price: p };
66
+ const spacing = (() => {
67
+ const median = medianSpacingMs(time);
68
+ return Number.isFinite(median) ? median : intervalSpacingMs(interval);
69
+ })();
70
+ return { time: currentTime + offset * spacing, price: p };
71
+ }
72
+ //# sourceMappingURL=barPoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barPoint.js","sourceRoot":"","sources":["../src/barPoint.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAG/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAIjE;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,QAAgB;IACvC,IAAI,CAAC;QACD,OAAO,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,MAAM,CAAC,GAAG,CAAC;IACtB,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAuB;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACjC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC3B,IAAuB,EACvB,QAAgB,EAChB,WAAmB,EACnB,MAAc,EACd,KAAY;IAEZ,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACzD,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC5D,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC1E,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAC9D,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\nimport type { Price, WorldPoint } from \"@invinite-org/chartlang-core\";\nimport { intervalToSeconds } from \"@invinite-org/chartlang-core\";\n\nimport type { Float64RingBuffer } from \"./ringBuffer.js\";\n\n/**\n * Parse a bar-interval string to its spacing in milliseconds, or `NaN` when\n * the string is not a parseable interval. `intervalToSeconds` throws on an\n * unparseable descriptor; this wrapper swallows that so `bar.point` stays a\n * non-throwing, graceful-degradation helper.\n */\nfunction intervalSpacingMs(interval: string): number {\n try {\n return intervalToSeconds({ value: interval, label: interval, group: \"\" }) * 1000;\n } catch {\n return Number.NaN;\n }\n}\n\n/**\n * The median delta between the most recent retained bar times, used to\n * extrapolate future-bar timestamps. Walks the newest `min(length - 1, cap)`\n * deltas (cap keeps the scan O(1) on long histories) and returns the median;\n * `NaN` when fewer than two bars are retained or every delta is non-finite.\n */\nfunction medianSpacingMs(time: Float64RingBuffer): number {\n const pairs = Math.min(time.length - 1, 100);\n if (pairs < 1) return Number.NaN;\n const deltas: number[] = [];\n for (let i = 0; i < pairs; i += 1) {\n const delta = time.at(i) - time.at(i + 1);\n if (Number.isFinite(delta)) deltas.push(delta);\n }\n if (deltas.length === 0) return Number.NaN;\n deltas.sort((a, b) => a - b);\n const mid = deltas.length >> 1;\n return deltas.length % 2 === 1 ? deltas[mid] : (deltas[mid - 1] + deltas[mid]) / 2;\n}\n\n/**\n * Resolve a `bar.point(offset, price)` call to the time-based\n * {@link WorldPoint} the rest of the drawing pipeline already speaks.\n *\n * `offset === 0` reads the live `bar.time`; `offset < 0` reads the real\n * historical timestamp `|offset|` bars back from the time ring buffer\n * (`NaN` past retention); `offset > 0` extrapolates `lastTime + offset *\n * spacing`, where `spacing` is the median retained-bar delta and falls back\n * to the parsed bar interval when fewer than two bars are retained. `price`\n * passes through unchanged. Never throws.\n *\n * @since 0.9\n * @stable\n * @example\n * // const wp = resolveBarPoint(stream.ohlcv.time, \"1D\", currentTime, -10, 42);\n */\nexport function resolveBarPoint(\n time: Float64RingBuffer,\n interval: string,\n currentTime: number,\n offset: number,\n price: Price,\n): WorldPoint {\n // `price` is a `Price` (number) by contract, but a script may pass a bar\n // price field (`bar.point(0, bar.close)`) which is now a number-coercible\n // series view. Coerce to the scalar so the persisted `WorldPoint.price`\n // is always a number, never the view object. `Number(NaN)` stays NaN.\n const p = Number(price);\n if (offset === 0) return { time: currentTime, price: p };\n if (offset < 0) return { time: time.at(-offset), price: p };\n const spacing = (() => {\n const median = medianSpacingMs(time);\n return Number.isFinite(median) ? median : intervalSpacingMs(interval);\n })();\n return { time: currentTime + offset * spacing, price: p };\n}\n"]}
@@ -0,0 +1,102 @@
1
+ import type { JsonValue } from "@invinite-org/chartlang-core";
2
+ import { Float64RingBuffer } from "./ringBuffer.js";
3
+ /**
4
+ * JSON-clean snapshot shape of a {@link Float64RingBuffer} — the head
5
+ * index, the filled count, and the raw cell values with non-finite cells
6
+ * persisted as `null` (the snapshot validator rejects `NaN`). Shared by the
7
+ * `ta.*` and `state.series` slot persistence paths.
8
+ *
9
+ * @since 0.5
10
+ * @internal
11
+ * @stable
12
+ * @example
13
+ * // const snap: BufferSnapshot = { headIndex: 0, filled: 1, values: [1] };
14
+ */
15
+ export type BufferSnapshot = Readonly<{
16
+ headIndex: number;
17
+ filled: number;
18
+ values: ReadonlyArray<number | null>;
19
+ }>;
20
+ /**
21
+ * Narrow an `unknown` to a plain (non-array) object record. The first guard
22
+ * every snapshot restore runs before reading named fields.
23
+ *
24
+ * @since 0.5
25
+ * @internal
26
+ * @stable
27
+ * @example
28
+ * isRecord({ a: 1 }); // true
29
+ */
30
+ export declare function isRecord(value: unknown): value is Readonly<Record<string, unknown>>;
31
+ /**
32
+ * Narrow an `unknown` to a finite integer (used for `headIndex` / `filled`
33
+ * snapshot fields).
34
+ *
35
+ * @since 0.5
36
+ * @internal
37
+ * @stable
38
+ * @example
39
+ * isInteger(3); // true
40
+ */
41
+ export declare function isInteger(value: unknown): value is number;
42
+ /**
43
+ * Map a runtime number to its JSON-clean form: a finite number rides
44
+ * through, any non-finite value (`NaN`, `±Infinity`) becomes `null` so the
45
+ * snapshot validator accepts it.
46
+ *
47
+ * @since 0.5
48
+ * @internal
49
+ * @stable
50
+ * @example
51
+ * finiteOrNull(Number.NaN); // null
52
+ */
53
+ export declare function finiteOrNull(value: number): number | null;
54
+ /**
55
+ * Inverse of {@link finiteOrNull} for restore: a persisted `null` rehydrates
56
+ * to `NaN`, a finite number rides through, and anything else (a string, an
57
+ * `Infinity` that slipped past serialise) fails to `null` so the caller can
58
+ * reject the whole slot.
59
+ *
60
+ * @since 0.5
61
+ * @internal
62
+ * @stable
63
+ * @example
64
+ * restoreNumber(null); // NaN
65
+ */
66
+ export declare function restoreNumber(value: unknown): number | null;
67
+ /**
68
+ * Validate that an `unknown` is a well-formed {@link BufferSnapshot}: integer
69
+ * `headIndex` + `filled`, and a `values` array whose every cell is `null` or
70
+ * a finite number.
71
+ *
72
+ * @since 0.5
73
+ * @internal
74
+ * @stable
75
+ * @example
76
+ * isBufferSnapshot({ headIndex: 0, filled: 0, values: [] }); // true
77
+ */
78
+ export declare function isBufferSnapshot(value: unknown): value is BufferSnapshot;
79
+ /**
80
+ * Serialise a {@link Float64RingBuffer} into its JSON-clean
81
+ * {@link BufferSnapshot} record.
82
+ *
83
+ * @since 0.5
84
+ * @internal
85
+ * @stable
86
+ * @example
87
+ * // const snap = serialiseBuffer(new Float64RingBuffer(8));
88
+ */
89
+ export declare function serialiseBuffer(buffer: Float64RingBuffer): JsonValue;
90
+ /**
91
+ * Rebuild a {@link Float64RingBuffer} of the given `capacity` from a
92
+ * {@link BufferSnapshot}, returning `null` when the snapshot is incompatible
93
+ * with the capacity (the underlying restore throws and is caught).
94
+ *
95
+ * @since 0.5
96
+ * @internal
97
+ * @stable
98
+ * @example
99
+ * // const buf = restoreBuffer(snap, 8); // null on capacity mismatch
100
+ */
101
+ export declare function restoreBuffer(snapshot: BufferSnapshot, capacity: number): Float64RingBuffer | null;
102
+ //# sourceMappingURL=bufferSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bufferSnapshot.d.ts","sourceRoot":"","sources":["../src/bufferSnapshot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CASxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAOpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CACzB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,MAAM,GACjB,iBAAiB,GAAG,IAAI,CAQ1B"}
@@ -0,0 +1,119 @@
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 { Float64RingBuffer } from "./ringBuffer.js";
4
+ /**
5
+ * Narrow an `unknown` to a plain (non-array) object record. The first guard
6
+ * every snapshot restore runs before reading named fields.
7
+ *
8
+ * @since 0.5
9
+ * @internal
10
+ * @stable
11
+ * @example
12
+ * isRecord({ a: 1 }); // true
13
+ */
14
+ export function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ /**
18
+ * Narrow an `unknown` to a finite integer (used for `headIndex` / `filled`
19
+ * snapshot fields).
20
+ *
21
+ * @since 0.5
22
+ * @internal
23
+ * @stable
24
+ * @example
25
+ * isInteger(3); // true
26
+ */
27
+ export function isInteger(value) {
28
+ return typeof value === "number" && Number.isInteger(value);
29
+ }
30
+ /**
31
+ * Map a runtime number to its JSON-clean form: a finite number rides
32
+ * through, any non-finite value (`NaN`, `±Infinity`) becomes `null` so the
33
+ * snapshot validator accepts it.
34
+ *
35
+ * @since 0.5
36
+ * @internal
37
+ * @stable
38
+ * @example
39
+ * finiteOrNull(Number.NaN); // null
40
+ */
41
+ export function finiteOrNull(value) {
42
+ return Number.isFinite(value) ? value : null;
43
+ }
44
+ /**
45
+ * Inverse of {@link finiteOrNull} for restore: a persisted `null` rehydrates
46
+ * to `NaN`, a finite number rides through, and anything else (a string, an
47
+ * `Infinity` that slipped past serialise) fails to `null` so the caller can
48
+ * reject the whole slot.
49
+ *
50
+ * @since 0.5
51
+ * @internal
52
+ * @stable
53
+ * @example
54
+ * restoreNumber(null); // NaN
55
+ */
56
+ export function restoreNumber(value) {
57
+ if (value === null)
58
+ return Number.NaN;
59
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
60
+ }
61
+ /**
62
+ * Validate that an `unknown` is a well-formed {@link BufferSnapshot}: integer
63
+ * `headIndex` + `filled`, and a `values` array whose every cell is `null` or
64
+ * a finite number.
65
+ *
66
+ * @since 0.5
67
+ * @internal
68
+ * @stable
69
+ * @example
70
+ * isBufferSnapshot({ headIndex: 0, filled: 0, values: [] }); // true
71
+ */
72
+ export function isBufferSnapshot(value) {
73
+ if (!isRecord(value))
74
+ return false;
75
+ if (!isInteger(value.headIndex) || !isInteger(value.filled))
76
+ return false;
77
+ return (Array.isArray(value.values) &&
78
+ value.values.every((entry) => entry === null || (typeof entry === "number" && Number.isFinite(entry))));
79
+ }
80
+ /**
81
+ * Serialise a {@link Float64RingBuffer} into its JSON-clean
82
+ * {@link BufferSnapshot} record.
83
+ *
84
+ * @since 0.5
85
+ * @internal
86
+ * @stable
87
+ * @example
88
+ * // const snap = serialiseBuffer(new Float64RingBuffer(8));
89
+ */
90
+ export function serialiseBuffer(buffer) {
91
+ const snapshot = buffer.serialiseSnapshotBuffer();
92
+ return {
93
+ headIndex: snapshot.headIndex,
94
+ filled: snapshot.filled,
95
+ values: snapshot.values,
96
+ };
97
+ }
98
+ /**
99
+ * Rebuild a {@link Float64RingBuffer} of the given `capacity` from a
100
+ * {@link BufferSnapshot}, returning `null` when the snapshot is incompatible
101
+ * with the capacity (the underlying restore throws and is caught).
102
+ *
103
+ * @since 0.5
104
+ * @internal
105
+ * @stable
106
+ * @example
107
+ * // const buf = restoreBuffer(snap, 8); // null on capacity mismatch
108
+ */
109
+ export function restoreBuffer(snapshot, capacity) {
110
+ const buffer = new Float64RingBuffer(capacity);
111
+ try {
112
+ buffer.restoreFromSnapshotBuffer(snapshot);
113
+ return buffer;
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ //# sourceMappingURL=bufferSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bufferSnapshot.js","sourceRoot":"","sources":["../src/bufferSnapshot.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAoBpD;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACtC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC3C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1E,OAAO,CACH,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QAC3B,KAAK,CAAC,MAAM,CAAC,KAAK,CACd,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CACrF,CACJ,CAAC;AACN,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB;IACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,uBAAuB,EAAE,CAAC;IAClD,OAAO;QACH,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;KAC1B,CAAC;AACN,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CACzB,QAAwB,EACxB,QAAgB;IAEhB,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,CAAC;QACD,MAAM,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,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\nimport type { JsonValue } from \"@invinite-org/chartlang-core\";\n\nimport { Float64RingBuffer } from \"./ringBuffer.js\";\n\n/**\n * JSON-clean snapshot shape of a {@link Float64RingBuffer} — the head\n * index, the filled count, and the raw cell values with non-finite cells\n * persisted as `null` (the snapshot validator rejects `NaN`). Shared by the\n * `ta.*` and `state.series` slot persistence paths.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * // const snap: BufferSnapshot = { headIndex: 0, filled: 1, values: [1] };\n */\nexport type BufferSnapshot = Readonly<{\n headIndex: number;\n filled: number;\n values: ReadonlyArray<number | null>;\n}>;\n\n/**\n * Narrow an `unknown` to a plain (non-array) object record. The first guard\n * every snapshot restore runs before reading named fields.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * isRecord({ a: 1 }); // true\n */\nexport function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Narrow an `unknown` to a finite integer (used for `headIndex` / `filled`\n * snapshot fields).\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * isInteger(3); // true\n */\nexport function isInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value);\n}\n\n/**\n * Map a runtime number to its JSON-clean form: a finite number rides\n * through, any non-finite value (`NaN`, `±Infinity`) becomes `null` so the\n * snapshot validator accepts it.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * finiteOrNull(Number.NaN); // null\n */\nexport function finiteOrNull(value: number): number | null {\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Inverse of {@link finiteOrNull} for restore: a persisted `null` rehydrates\n * to `NaN`, a finite number rides through, and anything else (a string, an\n * `Infinity` that slipped past serialise) fails to `null` so the caller can\n * reject the whole slot.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * restoreNumber(null); // NaN\n */\nexport function restoreNumber(value: unknown): number | null {\n if (value === null) return Number.NaN;\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\n/**\n * Validate that an `unknown` is a well-formed {@link BufferSnapshot}: integer\n * `headIndex` + `filled`, and a `values` array whose every cell is `null` or\n * a finite number.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * isBufferSnapshot({ headIndex: 0, filled: 0, values: [] }); // true\n */\nexport function isBufferSnapshot(value: unknown): value is BufferSnapshot {\n if (!isRecord(value)) return false;\n if (!isInteger(value.headIndex) || !isInteger(value.filled)) return false;\n return (\n Array.isArray(value.values) &&\n value.values.every(\n (entry) => entry === null || (typeof entry === \"number\" && Number.isFinite(entry)),\n )\n );\n}\n\n/**\n * Serialise a {@link Float64RingBuffer} into its JSON-clean\n * {@link BufferSnapshot} record.\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * // const snap = serialiseBuffer(new Float64RingBuffer(8));\n */\nexport function serialiseBuffer(buffer: Float64RingBuffer): JsonValue {\n const snapshot = buffer.serialiseSnapshotBuffer();\n return {\n headIndex: snapshot.headIndex,\n filled: snapshot.filled,\n values: snapshot.values,\n };\n}\n\n/**\n * Rebuild a {@link Float64RingBuffer} of the given `capacity` from a\n * {@link BufferSnapshot}, returning `null` when the snapshot is incompatible\n * with the capacity (the underlying restore throws and is caught).\n *\n * @since 0.5\n * @internal\n * @stable\n * @example\n * // const buf = restoreBuffer(snap, 8); // null on capacity mismatch\n */\nexport function restoreBuffer(\n snapshot: BufferSnapshot,\n capacity: number,\n): Float64RingBuffer | null {\n const buffer = new Float64RingBuffer(capacity);\n try {\n buffer.restoreFromSnapshotBuffer(snapshot);\n return buffer;\n } catch {\n return null;\n }\n}\n"]}