@invinite-org/chartlang-runtime 1.2.0 → 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 (114) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/buildComputeContext.d.ts.map +1 -1
  3. package/dist/buildComputeContext.js +6 -1
  4. package/dist/buildComputeContext.js.map +1 -1
  5. package/dist/createScriptRunner.d.ts.map +1 -1
  6. package/dist/createScriptRunner.js +37 -7
  7. package/dist/createScriptRunner.js.map +1 -1
  8. package/dist/dep/DepRunner.d.ts +7 -0
  9. package/dist/dep/DepRunner.d.ts.map +1 -1
  10. package/dist/dep/DepRunner.js +3 -0
  11. package/dist/dep/DepRunner.js.map +1 -1
  12. package/dist/emit/barcolor.d.ts +44 -0
  13. package/dist/emit/barcolor.d.ts.map +1 -0
  14. package/dist/emit/barcolor.js +40 -0
  15. package/dist/emit/barcolor.js.map +1 -0
  16. package/dist/emit/bgcolor.d.ts +44 -0
  17. package/dist/emit/bgcolor.d.ts.map +1 -0
  18. package/dist/emit/bgcolor.js +45 -0
  19. package/dist/emit/bgcolor.js.map +1 -0
  20. package/dist/emit/index.d.ts +2 -0
  21. package/dist/emit/index.d.ts.map +1 -1
  22. package/dist/emit/index.js +2 -0
  23. package/dist/emit/index.js.map +1 -1
  24. package/dist/emit/plot.d.ts +23 -1
  25. package/dist/emit/plot.d.ts.map +1 -1
  26. package/dist/emit/plot.js +32 -1
  27. package/dist/emit/plot.js.map +1 -1
  28. package/dist/execution/dispose.d.ts.map +1 -1
  29. package/dist/execution/dispose.js +3 -1
  30. package/dist/execution/dispose.js.map +1 -1
  31. package/dist/execution/runComputeStep.d.ts.map +1 -1
  32. package/dist/execution/runComputeStep.js +3 -1
  33. package/dist/execution/runComputeStep.js.map +1 -1
  34. package/dist/inputs/resolveInputs.js +1 -0
  35. package/dist/inputs/resolveInputs.js.map +1 -1
  36. package/dist/persistentStateStore.runtime.d.ts.map +1 -1
  37. package/dist/persistentStateStore.runtime.js +10 -5
  38. package/dist/persistentStateStore.runtime.js.map +1 -1
  39. package/dist/primitives.d.ts +1 -1
  40. package/dist/primitives.d.ts.map +1 -1
  41. package/dist/primitives.js +7 -1
  42. package/dist/primitives.js.map +1 -1
  43. package/dist/request/lowerTf.d.ts.map +1 -1
  44. package/dist/request/lowerTf.js +6 -0
  45. package/dist/request/lowerTf.js.map +1 -1
  46. package/dist/request/requestNamespace.d.ts.map +1 -1
  47. package/dist/request/requestNamespace.js +17 -3
  48. package/dist/request/requestNamespace.js.map +1 -1
  49. package/dist/request/security.d.ts +23 -6
  50. package/dist/request/security.d.ts.map +1 -1
  51. package/dist/request/security.js +64 -29
  52. package/dist/request/security.js.map +1 -1
  53. package/dist/request/securityExprRunner.d.ts +12 -8
  54. package/dist/request/securityExprRunner.d.ts.map +1 -1
  55. package/dist/request/securityExprRunner.js +32 -14
  56. package/dist/request/securityExprRunner.js.map +1 -1
  57. package/dist/ringBuffer.d.ts +19 -0
  58. package/dist/ringBuffer.d.ts.map +1 -1
  59. package/dist/ringBuffer.js +23 -0
  60. package/dist/ringBuffer.js.map +1 -1
  61. package/dist/runtimeContext.d.ts +49 -12
  62. package/dist/runtimeContext.d.ts.map +1 -1
  63. package/dist/runtimeContext.js.map +1 -1
  64. package/dist/state/arrayPersistence.d.ts +48 -0
  65. package/dist/state/arrayPersistence.d.ts.map +1 -0
  66. package/dist/state/arrayPersistence.js +88 -0
  67. package/dist/state/arrayPersistence.js.map +1 -0
  68. package/dist/state/arrayStateSlot.d.ts +78 -0
  69. package/dist/state/arrayStateSlot.d.ts.map +1 -0
  70. package/dist/state/arrayStateSlot.js +116 -0
  71. package/dist/state/arrayStateSlot.js.map +1 -0
  72. package/dist/state/index.d.ts +3 -1
  73. package/dist/state/index.d.ts.map +1 -1
  74. package/dist/state/index.js +3 -1
  75. package/dist/state/index.js.map +1 -1
  76. package/dist/state/lifecycle.d.ts +28 -0
  77. package/dist/state/lifecycle.d.ts.map +1 -1
  78. package/dist/state/lifecycle.js +36 -0
  79. package/dist/state/lifecycle.js.map +1 -1
  80. package/dist/state/stateNamespace.d.ts.map +1 -1
  81. package/dist/state/stateNamespace.js +27 -0
  82. package/dist/state/stateNamespace.js.map +1 -1
  83. package/dist/ta/sessionVolumeProfile.d.ts.map +1 -1
  84. package/dist/ta/sessionVolumeProfile.js +1 -17
  85. package/dist/ta/sessionVolumeProfile.js.map +1 -1
  86. package/dist/time-accessors/civil.d.ts +73 -0
  87. package/dist/time-accessors/civil.d.ts.map +1 -0
  88. package/dist/time-accessors/civil.js +105 -0
  89. package/dist/time-accessors/civil.js.map +1 -0
  90. package/dist/time-accessors/index.d.ts +8 -0
  91. package/dist/time-accessors/index.d.ts.map +1 -0
  92. package/dist/time-accessors/index.js +9 -0
  93. package/dist/time-accessors/index.js.map +1 -0
  94. package/dist/time-accessors/sessionAccessors.d.ts +50 -0
  95. package/dist/time-accessors/sessionAccessors.d.ts.map +1 -0
  96. package/dist/time-accessors/sessionAccessors.js +79 -0
  97. package/dist/time-accessors/sessionAccessors.js.map +1 -0
  98. package/dist/time-accessors/sessionWindow.d.ts +17 -0
  99. package/dist/time-accessors/sessionWindow.d.ts.map +1 -0
  100. package/dist/time-accessors/sessionWindow.js +41 -0
  101. package/dist/time-accessors/sessionWindow.js.map +1 -0
  102. package/dist/time-accessors/timeAccessors.d.ts +54 -0
  103. package/dist/time-accessors/timeAccessors.d.ts.map +1 -0
  104. package/dist/time-accessors/timeAccessors.js +132 -0
  105. package/dist/time-accessors/timeAccessors.js.map +1 -0
  106. package/dist/time-accessors/tzDiagnostic.d.ts +17 -0
  107. package/dist/time-accessors/tzDiagnostic.d.ts.map +1 -0
  108. package/dist/time-accessors/tzDiagnostic.js +34 -0
  109. package/dist/time-accessors/tzDiagnostic.js.map +1 -0
  110. package/dist/time-accessors/tzOffset.d.ts +31 -0
  111. package/dist/time-accessors/tzOffset.d.ts.map +1 -0
  112. package/dist/time-accessors/tzOffset.js +67 -0
  113. package/dist/time-accessors/tzOffset.js.map +1 -0
  114. package/package.json +3 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionAccessors.js","sourceRoot":"","sources":["../../src/time-accessors/sessionAccessors.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAK/D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,sBAAsB,CAClC,YAA0B,EAC1B,gBAAsC;IAEtC,OAAO,MAAM,CAAC,MAAM,CAAmB;QACnC,MAAM,CAAC,CAAO,EAAE,IAAY,EAAE,EAAW;YACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YACtC,MAAM,MAAM,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAElC,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YAC7C,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YACrE,IAAI,cAAc;gBAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAE/C,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YAC5C,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;YACjC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;YAC5C,IAAI,UAAU,IAAI,YAAY,EAAE,CAAC;gBAC7B,kDAAkD;gBAClD,OAAO,WAAW,IAAI,YAAY,IAAI,WAAW,GAAG,UAAU,CAAC;YACnE,CAAC;YACD,OAAO,WAAW,IAAI,YAAY,IAAI,WAAW,GAAG,UAAU,CAAC;QACnE,CAAC;KACJ,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAmB;IACrD,OAAO,sBAAsB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7F,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 { SessionNamespace, Time } from \"@invinite-org/chartlang-core\";\n\nimport type { RuntimeContext } from \"../runtimeContext.js\";\nimport { splitEpoch } from \"./civil.js\";\nimport { parseSessionWindowMinutes } from \"./sessionWindow.js\";\nimport { resolveTz } from \"./timeAccessors.js\";\nimport { buildTzDstReporter } from \"./tzDiagnostic.js\";\nimport { resolveOffsetMinutes } from \"./tzOffset.js\";\n\n/**\n * Build a frozen `session` namespace whose `isOpen` tests half-open membership\n * of an epoch's local minute-of-day against a parsed `\"HH:MM-HH:MM\"` window —\n * pure integer epoch math (Howard Hinnant `civil_from_days` via\n * {@link splitEpoch}), **no `Date`, no `Intl`** — so the result is\n * byte-reproducible across hosts. The factory closes over the mount's default\n * timezone and a `tz-dst-unsupported` reporter; the `isOpen` body is stateless.\n *\n * Membership is half-open `[start, end)` (the `end` minute is OUT, matching\n * `ta.sessionVolumeProfile` and Pine `time()`); a window with `end <= start`\n * (e.g. `\"2200-0400\"`) is treated as a midnight wrap `[start, 1440) ∪ [0, end)`.\n * A non-finite `t` or a malformed `spec` yields `false`; the accessor never\n * throws. A DST-bearing IANA zone resolves to UTC and invokes\n * `onDstUnsupported(tz)` (once-per-tz dedup lives in the caller) — fired only\n * once the call is otherwise well-formed (finite `t`, parseable `spec`).\n *\n * Unlike `ta.sessionVolumeProfile`, which defaults its window to\n * `syminfo.session`, `isOpen` takes the `spec` as an explicit argument (often\n * from `input.session`) and never reads `syminfo.session` itself.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { createSessionNamespace } from \"@invinite-org/chartlang-runtime\";\n * // const session = createSessionNamespace(() => \"UTC\", () => {});\n * // session.isOpen(0, \"0000-1200\"); // true (00:00 UTC is in [00:00, 12:00))\n */\nexport function createSessionNamespace(\n getDefaultTz: () => string,\n onDstUnsupported: (tz: string) => void,\n): SessionNamespace {\n return Object.freeze<SessionNamespace>({\n isOpen(t: Time, spec: string, tz?: string): boolean {\n if (!Number.isFinite(t)) return false;\n const parsed = parseSessionWindowMinutes(spec);\n if (parsed === null) return false;\n\n const resolved = resolveTz(tz, getDefaultTz);\n const { offsetMin, dstUnsupported } = resolveOffsetMinutes(resolved);\n if (dstUnsupported) onDstUnsupported(resolved);\n\n const { hh, mm } = splitEpoch(t, offsetMin);\n const minuteOfDay = hh * 60 + mm;\n const { startMinutes, endMinutes } = parsed;\n if (endMinutes <= startMinutes) {\n // Midnight-wrap window: [start, 1440) ∪ [0, end).\n return minuteOfDay >= startMinutes || minuteOfDay < endMinutes;\n }\n return minuteOfDay >= startMinutes && minuteOfDay < endMinutes;\n },\n });\n}\n\n/**\n * Install-time builder: bind {@link createSessionNamespace} to a mount's\n * {@link RuntimeContext}. The default timezone resolves from the live\n * `syminfo.timezone` view; the `tz-dst-unsupported` diagnostic dedupes on the\n * SAME `ctx.diagnosedTzKeys` set the `time.*` accessors use, so a script using\n * both `time.*` and `session.isOpen` on one DST zone warns once total.\n * `buildComputeContext` calls this per bar (like the `state` / `request` /\n * `runtime` namespaces, NOT a module constant like `ta`): the returned namespace\n * is a pure view bound to the mount's `RuntimeContext`, so it is cheap to rebuild\n * and is not relied upon to be identity-stable across bars.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { buildSessionNamespace } from \"@invinite-org/chartlang-runtime\";\n * // const session = buildSessionNamespace(state.runtimeContext);\n * // void session.isOpen;\n */\nexport function buildSessionNamespace(ctx: RuntimeContext): SessionNamespace {\n return createSessionNamespace(() => ctx.views.syminfo.timezone, buildTzDstReporter(ctx));\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Parse a daily session window `"HH:MM-HH:MM"` / `"HHMM-HHMM"` into start/end
3
+ * minute-of-day (`0..1439`). Returns `null` for any malformed spec or an
4
+ * out-of-range hour (`>23`) / minute (`>59`). A window whose `end <= start`
5
+ * is left as-is — the caller decides whether to treat it as a midnight wrap.
6
+ *
7
+ * @since 1.5
8
+ * @stable
9
+ * @example
10
+ * // import { parseSessionWindowMinutes } from "@invinite-org/chartlang-runtime";
11
+ * // parseSessionWindowMinutes("0930-1600"); // { startMinutes: 570, endMinutes: 960 }
12
+ */
13
+ export declare function parseSessionWindowMinutes(spec: string): {
14
+ startMinutes: number;
15
+ endMinutes: number;
16
+ } | null;
17
+ //# sourceMappingURL=sessionWindow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionWindow.d.ts","sourceRoot":"","sources":["../../src/time-accessors/sessionWindow.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACrC,IAAI,EAAE,MAAM,GACb;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAarD"}
@@ -0,0 +1,41 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ //
4
+ // The single source of the chartlang session-window grammar. Lifted out of
5
+ // `ta/sessionVolumeProfile.ts` so `ta.sessionVolumeProfile` and
6
+ // `session.isOpen` parse `"HH:MM-HH:MM"` windows through ONE regex — never a
7
+ // forked copy.
8
+ // Two `HH[:MM]` clock fields separated by `-` (optional surrounding whitespace).
9
+ // The minutes are optional (`"0930"` / `"930"` parse), and a `:` separator is
10
+ // also optional (`"09:30"` and `"0930"` are equivalent).
11
+ const SESSION_WINDOW = /^(\d{1,2})(?::?(\d{2}))?\s*-\s*(\d{1,2})(?::?(\d{2}))?$/;
12
+ /**
13
+ * Parse a daily session window `"HH:MM-HH:MM"` / `"HHMM-HHMM"` into start/end
14
+ * minute-of-day (`0..1439`). Returns `null` for any malformed spec or an
15
+ * out-of-range hour (`>23`) / minute (`>59`). A window whose `end <= start`
16
+ * is left as-is — the caller decides whether to treat it as a midnight wrap.
17
+ *
18
+ * @since 1.5
19
+ * @stable
20
+ * @example
21
+ * // import { parseSessionWindowMinutes } from "@invinite-org/chartlang-runtime";
22
+ * // parseSessionWindowMinutes("0930-1600"); // { startMinutes: 570, endMinutes: 960 }
23
+ */
24
+ export function parseSessionWindowMinutes(spec) {
25
+ const match = SESSION_WINDOW.exec(spec.trim());
26
+ if (match === null)
27
+ return null;
28
+ const startHour = Number(match[1]);
29
+ const startMinute = match[2] === undefined ? 0 : Number(match[2]);
30
+ const endHour = Number(match[3]);
31
+ const endMinute = match[4] === undefined ? 0 : Number(match[4]);
32
+ if (startHour < 0 || startHour > 23 || startMinute < 0 || startMinute > 59)
33
+ return null;
34
+ if (endHour < 0 || endHour > 23 || endMinute < 0 || endMinute > 59)
35
+ return null;
36
+ return {
37
+ startMinutes: startHour * 60 + startMinute,
38
+ endMinutes: endHour * 60 + endMinute,
39
+ };
40
+ }
41
+ //# sourceMappingURL=sessionWindow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionWindow.js","sourceRoot":"","sources":["../../src/time-accessors/sessionWindow.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,EAAE;AACF,2EAA2E;AAC3E,gEAAgE;AAChE,6EAA6E;AAC7E,eAAe;AAEf,iFAAiF;AACjF,8EAA8E;AAC9E,yDAAyD;AACzD,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CACrC,IAAY;IAEZ,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,EAAE,IAAI,WAAW,GAAG,CAAC,IAAI,WAAW,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IACxF,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,EAAE,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAChF,OAAO;QACH,YAAY,EAAE,SAAS,GAAG,EAAE,GAAG,WAAW;QAC1C,UAAU,EAAE,OAAO,GAAG,EAAE,GAAG,SAAS;KACvC,CAAC;AACN,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//\n// The single source of the chartlang session-window grammar. Lifted out of\n// `ta/sessionVolumeProfile.ts` so `ta.sessionVolumeProfile` and\n// `session.isOpen` parse `\"HH:MM-HH:MM\"` windows through ONE regex — never a\n// forked copy.\n\n// Two `HH[:MM]` clock fields separated by `-` (optional surrounding whitespace).\n// The minutes are optional (`\"0930\"` / `\"930\"` parse), and a `:` separator is\n// also optional (`\"09:30\"` and `\"0930\"` are equivalent).\nconst SESSION_WINDOW = /^(\\d{1,2})(?::?(\\d{2}))?\\s*-\\s*(\\d{1,2})(?::?(\\d{2}))?$/;\n\n/**\n * Parse a daily session window `\"HH:MM-HH:MM\"` / `\"HHMM-HHMM\"` into start/end\n * minute-of-day (`0..1439`). Returns `null` for any malformed spec or an\n * out-of-range hour (`>23`) / minute (`>59`). A window whose `end <= start`\n * is left as-is — the caller decides whether to treat it as a midnight wrap.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { parseSessionWindowMinutes } from \"@invinite-org/chartlang-runtime\";\n * // parseSessionWindowMinutes(\"0930-1600\"); // { startMinutes: 570, endMinutes: 960 }\n */\nexport function parseSessionWindowMinutes(\n spec: string,\n): { startMinutes: number; endMinutes: number } | null {\n const match = SESSION_WINDOW.exec(spec.trim());\n if (match === null) return null;\n const startHour = Number(match[1]);\n const startMinute = match[2] === undefined ? 0 : Number(match[2]);\n const endHour = Number(match[3]);\n const endMinute = match[4] === undefined ? 0 : Number(match[4]);\n if (startHour < 0 || startHour > 23 || startMinute < 0 || startMinute > 59) return null;\n if (endHour < 0 || endHour > 23 || endMinute < 0 || endMinute > 59) return null;\n return {\n startMinutes: startHour * 60 + startMinute,\n endMinutes: endHour * 60 + endMinute,\n };\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import type { TimeNamespace } from "@invinite-org/chartlang-core";
2
+ import type { RuntimeContext } from "../runtimeContext.js";
3
+ /**
4
+ * Resolve a tz argument, falling back to the mount default then `"UTC"`.
5
+ * Shared by the `time.*` and `session.*` accessor factories so both honour the
6
+ * identical explicit → `syminfo.timezone` → `"UTC"` precedence.
7
+ *
8
+ * @since 1.5
9
+ * @stable
10
+ * @example
11
+ * // import { resolveTz } from "@invinite-org/chartlang-runtime";
12
+ * // resolveTz(undefined, () => ""); // "UTC"
13
+ */
14
+ export declare function resolveTz(tz: string | undefined, getDefaultTz: () => string): string;
15
+ /**
16
+ * Build a frozen `time` namespace whose accessors do pure integer epoch math
17
+ * (Howard Hinnant `civil_from_days`) — **no `Date`, no `Intl`** — so output is
18
+ * byte-reproducible across hosts. The factory closes over the mount's default
19
+ * timezone, the live bar interval (for {@link TimeNamespace.timeClose}), and a
20
+ * `tz-dst-unsupported` reporter; the accessor bodies themselves are stateless.
21
+ *
22
+ * v1 honours UTC + fixed-offset zones only. A DST-bearing IANA zone resolves to
23
+ * UTC and invokes `onDstUnsupported(tz)` (once-per-tz dedup lives in the
24
+ * caller). Non-finite / out-of-range inputs yield `NaN`; the accessors never
25
+ * throw.
26
+ *
27
+ * @since 1.5
28
+ * @stable
29
+ * @example
30
+ * // import { createTimeNamespace } from "@invinite-org/chartlang-runtime";
31
+ * // const time = createTimeNamespace(() => "UTC", () => 60_000, () => {});
32
+ * // time.year(0); // 1970
33
+ */
34
+ export declare function createTimeNamespace(getDefaultTz: () => string, getIntervalMs: () => number, onDstUnsupported: (tz: string) => void): TimeNamespace;
35
+ /**
36
+ * Install-time builder: bind {@link createTimeNamespace} to a mount's
37
+ * {@link RuntimeContext}. The default timezone resolves from the live
38
+ * `syminfo.timezone` view, the bar interval from `timeframe.inSeconds`, and the
39
+ * `tz-dst-unsupported` diagnostic dedupes once per distinct tz on
40
+ * `ctx.diagnosedTzKeys`. `buildComputeContext` calls this per bar (like the
41
+ * `state` / `request` / `runtime` namespaces, NOT a module constant like `ta`):
42
+ * the returned namespace is a pure view bound to the mount's `RuntimeContext`,
43
+ * so it is cheap to rebuild and is not relied upon to be identity-stable across
44
+ * bars (the script receives a fresh `ctx` each bar).
45
+ *
46
+ * @since 1.5
47
+ * @stable
48
+ * @example
49
+ * // import { buildTimeNamespace } from "@invinite-org/chartlang-runtime";
50
+ * // const time = buildTimeNamespace(state.runtimeContext);
51
+ * // void time.year;
52
+ */
53
+ export declare function buildTimeNamespace(ctx: RuntimeContext): TimeNamespace;
54
+ //# sourceMappingURL=timeAccessors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeAccessors.d.ts","sourceRoot":"","sources":["../../src/time-accessors/timeAccessors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,MAAM,GAAG,MAAM,CAIpF;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAC/B,YAAY,EAAE,MAAM,MAAM,EAC1B,aAAa,EAAE,MAAM,MAAM,EAC3B,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GACvC,aAAa,CAuEf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,aAAa,CAMrE"}
@@ -0,0 +1,132 @@
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 { daysFromCivil, splitEpoch } from "./civil.js";
4
+ import { buildTzDstReporter } from "./tzDiagnostic.js";
5
+ import { resolveOffsetMinutes } from "./tzOffset.js";
6
+ /**
7
+ * Resolve a tz argument, falling back to the mount default then `"UTC"`.
8
+ * Shared by the `time.*` and `session.*` accessor factories so both honour the
9
+ * identical explicit → `syminfo.timezone` → `"UTC"` precedence.
10
+ *
11
+ * @since 1.5
12
+ * @stable
13
+ * @example
14
+ * // import { resolveTz } from "@invinite-org/chartlang-runtime";
15
+ * // resolveTz(undefined, () => ""); // "UTC"
16
+ */
17
+ export function resolveTz(tz, getDefaultTz) {
18
+ if (tz !== undefined && tz !== "")
19
+ return tz;
20
+ const fallback = getDefaultTz();
21
+ return fallback === "" ? "UTC" : fallback;
22
+ }
23
+ function isInt(value) {
24
+ return Number.isInteger(value);
25
+ }
26
+ /**
27
+ * Build a frozen `time` namespace whose accessors do pure integer epoch math
28
+ * (Howard Hinnant `civil_from_days`) — **no `Date`, no `Intl`** — so output is
29
+ * byte-reproducible across hosts. The factory closes over the mount's default
30
+ * timezone, the live bar interval (for {@link TimeNamespace.timeClose}), and a
31
+ * `tz-dst-unsupported` reporter; the accessor bodies themselves are stateless.
32
+ *
33
+ * v1 honours UTC + fixed-offset zones only. A DST-bearing IANA zone resolves to
34
+ * UTC and invokes `onDstUnsupported(tz)` (once-per-tz dedup lives in the
35
+ * caller). Non-finite / out-of-range inputs yield `NaN`; the accessors never
36
+ * throw.
37
+ *
38
+ * @since 1.5
39
+ * @stable
40
+ * @example
41
+ * // import { createTimeNamespace } from "@invinite-org/chartlang-runtime";
42
+ * // const time = createTimeNamespace(() => "UTC", () => 60_000, () => {});
43
+ * // time.year(0); // 1970
44
+ */
45
+ export function createTimeNamespace(getDefaultTz, getIntervalMs, onDstUnsupported) {
46
+ function offsetFor(tz) {
47
+ const resolved = resolveTz(tz, getDefaultTz);
48
+ const { offsetMin, dstUnsupported } = resolveOffsetMinutes(resolved);
49
+ if (dstUnsupported)
50
+ onDstUnsupported(resolved);
51
+ return offsetMin;
52
+ }
53
+ function field(t, tz, key) {
54
+ const offsetMin = offsetFor(tz);
55
+ if (!Number.isFinite(t))
56
+ return Number.NaN;
57
+ return splitEpoch(t, offsetMin)[key];
58
+ }
59
+ return Object.freeze({
60
+ year: (t, tz) => field(t, tz, "y"),
61
+ month: (t, tz) => field(t, tz, "m"),
62
+ dayofmonth: (t, tz) => field(t, tz, "d"),
63
+ hour: (t, tz) => field(t, tz, "hh"),
64
+ minute: (t, tz) => field(t, tz, "mm"),
65
+ second: (t, tz) => field(t, tz, "ss"),
66
+ dayofweek: (t, tz) => {
67
+ const offsetMin = offsetFor(tz);
68
+ if (!Number.isFinite(t))
69
+ return Number.NaN;
70
+ // Pine convention: 1=Sunday .. 7=Saturday (splitEpoch dow is 0=Sun).
71
+ return splitEpoch(t, offsetMin).dow + 1;
72
+ },
73
+ timestamp: (year, month, day, hour, minute, second, tz) => {
74
+ const offsetMin = offsetFor(tz);
75
+ const hh = hour ?? 0;
76
+ const mm = minute ?? 0;
77
+ const ss = second ?? 0;
78
+ if (!isInt(year) ||
79
+ !isInt(month) ||
80
+ !isInt(day) ||
81
+ !isInt(hh) ||
82
+ !isInt(mm) ||
83
+ !isInt(ss) ||
84
+ month < 1 ||
85
+ month > 12 ||
86
+ day < 1 ||
87
+ day > 31 ||
88
+ hh < 0 ||
89
+ hh > 23 ||
90
+ mm < 0 ||
91
+ mm > 59 ||
92
+ ss < 0 ||
93
+ ss > 59) {
94
+ return Number.NaN;
95
+ }
96
+ return (daysFromCivil(year, month, day) * 86_400_000 +
97
+ (hh * 3600 + mm * 60 + ss) * 1000 -
98
+ offsetMin * 60_000);
99
+ },
100
+ timeClose: (t, tz) => {
101
+ // `tz` is accepted for surface symmetry; the close instant is
102
+ // tz-invariant (start + interval). A DST tz still flags for
103
+ // consistency with the other accessors.
104
+ offsetFor(tz);
105
+ if (!Number.isFinite(t))
106
+ return Number.NaN;
107
+ return t + getIntervalMs();
108
+ },
109
+ });
110
+ }
111
+ /**
112
+ * Install-time builder: bind {@link createTimeNamespace} to a mount's
113
+ * {@link RuntimeContext}. The default timezone resolves from the live
114
+ * `syminfo.timezone` view, the bar interval from `timeframe.inSeconds`, and the
115
+ * `tz-dst-unsupported` diagnostic dedupes once per distinct tz on
116
+ * `ctx.diagnosedTzKeys`. `buildComputeContext` calls this per bar (like the
117
+ * `state` / `request` / `runtime` namespaces, NOT a module constant like `ta`):
118
+ * the returned namespace is a pure view bound to the mount's `RuntimeContext`,
119
+ * so it is cheap to rebuild and is not relied upon to be identity-stable across
120
+ * bars (the script receives a fresh `ctx` each bar).
121
+ *
122
+ * @since 1.5
123
+ * @stable
124
+ * @example
125
+ * // import { buildTimeNamespace } from "@invinite-org/chartlang-runtime";
126
+ * // const time = buildTimeNamespace(state.runtimeContext);
127
+ * // void time.year;
128
+ */
129
+ export function buildTimeNamespace(ctx) {
130
+ return createTimeNamespace(() => ctx.views.syminfo.timezone, () => ctx.views.timeframe.inSeconds * 1000, buildTzDstReporter(ctx));
131
+ }
132
+ //# sourceMappingURL=timeAccessors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeAccessors.js","sourceRoot":"","sources":["../../src/time-accessors/timeAccessors.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 { Time, TimeNamespace } from \"@invinite-org/chartlang-core\";\n\nimport type { RuntimeContext } from \"../runtimeContext.js\";\nimport { daysFromCivil, splitEpoch } from \"./civil.js\";\nimport { buildTzDstReporter } from \"./tzDiagnostic.js\";\nimport { resolveOffsetMinutes } from \"./tzOffset.js\";\n\n/**\n * Resolve a tz argument, falling back to the mount default then `\"UTC\"`.\n * Shared by the `time.*` and `session.*` accessor factories so both honour the\n * identical explicit → `syminfo.timezone` → `\"UTC\"` precedence.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { resolveTz } from \"@invinite-org/chartlang-runtime\";\n * // resolveTz(undefined, () => \"\"); // \"UTC\"\n */\nexport function resolveTz(tz: string | undefined, getDefaultTz: () => string): string {\n if (tz !== undefined && tz !== \"\") return tz;\n const fallback = getDefaultTz();\n return fallback === \"\" ? \"UTC\" : fallback;\n}\n\nfunction isInt(value: number): boolean {\n return Number.isInteger(value);\n}\n\n/**\n * Build a frozen `time` namespace whose accessors do pure integer epoch math\n * (Howard Hinnant `civil_from_days`) — **no `Date`, no `Intl`** — so output is\n * byte-reproducible across hosts. The factory closes over the mount's default\n * timezone, the live bar interval (for {@link TimeNamespace.timeClose}), and a\n * `tz-dst-unsupported` reporter; the accessor bodies themselves are stateless.\n *\n * v1 honours UTC + fixed-offset zones only. A DST-bearing IANA zone resolves to\n * UTC and invokes `onDstUnsupported(tz)` (once-per-tz dedup lives in the\n * caller). Non-finite / out-of-range inputs yield `NaN`; the accessors never\n * throw.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { createTimeNamespace } from \"@invinite-org/chartlang-runtime\";\n * // const time = createTimeNamespace(() => \"UTC\", () => 60_000, () => {});\n * // time.year(0); // 1970\n */\nexport function createTimeNamespace(\n getDefaultTz: () => string,\n getIntervalMs: () => number,\n onDstUnsupported: (tz: string) => void,\n): TimeNamespace {\n function offsetFor(tz: string | undefined): number {\n const resolved = resolveTz(tz, getDefaultTz);\n const { offsetMin, dstUnsupported } = resolveOffsetMinutes(resolved);\n if (dstUnsupported) onDstUnsupported(resolved);\n return offsetMin;\n }\n\n function field<K extends \"y\" | \"m\" | \"d\" | \"hh\" | \"mm\" | \"ss\">(\n t: Time,\n tz: string | undefined,\n key: K,\n ): number {\n const offsetMin = offsetFor(tz);\n if (!Number.isFinite(t)) return Number.NaN;\n return splitEpoch(t, offsetMin)[key];\n }\n\n return Object.freeze<TimeNamespace>({\n year: (t, tz) => field(t, tz, \"y\"),\n month: (t, tz) => field(t, tz, \"m\"),\n dayofmonth: (t, tz) => field(t, tz, \"d\"),\n hour: (t, tz) => field(t, tz, \"hh\"),\n minute: (t, tz) => field(t, tz, \"mm\"),\n second: (t, tz) => field(t, tz, \"ss\"),\n dayofweek: (t, tz) => {\n const offsetMin = offsetFor(tz);\n if (!Number.isFinite(t)) return Number.NaN;\n // Pine convention: 1=Sunday .. 7=Saturday (splitEpoch dow is 0=Sun).\n return splitEpoch(t, offsetMin).dow + 1;\n },\n timestamp: (year, month, day, hour, minute, second, tz) => {\n const offsetMin = offsetFor(tz);\n const hh = hour ?? 0;\n const mm = minute ?? 0;\n const ss = second ?? 0;\n if (\n !isInt(year) ||\n !isInt(month) ||\n !isInt(day) ||\n !isInt(hh) ||\n !isInt(mm) ||\n !isInt(ss) ||\n month < 1 ||\n month > 12 ||\n day < 1 ||\n day > 31 ||\n hh < 0 ||\n hh > 23 ||\n mm < 0 ||\n mm > 59 ||\n ss < 0 ||\n ss > 59\n ) {\n return Number.NaN;\n }\n return (\n daysFromCivil(year, month, day) * 86_400_000 +\n (hh * 3600 + mm * 60 + ss) * 1000 -\n offsetMin * 60_000\n );\n },\n timeClose: (t, tz) => {\n // `tz` is accepted for surface symmetry; the close instant is\n // tz-invariant (start + interval). A DST tz still flags for\n // consistency with the other accessors.\n offsetFor(tz);\n if (!Number.isFinite(t)) return Number.NaN;\n return t + getIntervalMs();\n },\n });\n}\n\n/**\n * Install-time builder: bind {@link createTimeNamespace} to a mount's\n * {@link RuntimeContext}. The default timezone resolves from the live\n * `syminfo.timezone` view, the bar interval from `timeframe.inSeconds`, and the\n * `tz-dst-unsupported` diagnostic dedupes once per distinct tz on\n * `ctx.diagnosedTzKeys`. `buildComputeContext` calls this per bar (like the\n * `state` / `request` / `runtime` namespaces, NOT a module constant like `ta`):\n * the returned namespace is a pure view bound to the mount's `RuntimeContext`,\n * so it is cheap to rebuild and is not relied upon to be identity-stable across\n * bars (the script receives a fresh `ctx` each bar).\n *\n * @since 1.5\n * @stable\n * @example\n * // import { buildTimeNamespace } from \"@invinite-org/chartlang-runtime\";\n * // const time = buildTimeNamespace(state.runtimeContext);\n * // void time.year;\n */\nexport function buildTimeNamespace(ctx: RuntimeContext): TimeNamespace {\n return createTimeNamespace(\n () => ctx.views.syminfo.timezone,\n () => ctx.views.timeframe.inSeconds * 1000,\n buildTzDstReporter(ctx),\n );\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { RuntimeContext } from "../runtimeContext.js";
2
+ /**
3
+ * Build the `onDstUnsupported(tz)` reporter the `time.*` and `session.*`
4
+ * accessor factories share. A DST-bearing IANA zone the UTC-first runtime
5
+ * cannot honour warns at most once per distinct tz per mount, deduped on
6
+ * `ctx.diagnosedTzKeys` — so a script using BOTH `time.*` and `session.isOpen`
7
+ * on one DST zone fires `tz-dst-unsupported` once total.
8
+ *
9
+ * @since 1.5
10
+ * @stable
11
+ * @example
12
+ * // import { buildTzDstReporter } from "@invinite-org/chartlang-runtime";
13
+ * // const report = buildTzDstReporter(ctx);
14
+ * // report("America/New_York");
15
+ */
16
+ export declare function buildTzDstReporter(ctx: RuntimeContext): (tz: string) => void;
17
+ //# sourceMappingURL=tzDiagnostic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tzDiagnostic.d.ts","sourceRoot":"","sources":["../../src/time-accessors/tzDiagnostic.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAc5E"}
@@ -0,0 +1,34 @@
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 { pushDiagnostic } from "../emit/index.js";
4
+ /**
5
+ * Build the `onDstUnsupported(tz)` reporter the `time.*` and `session.*`
6
+ * accessor factories share. A DST-bearing IANA zone the UTC-first runtime
7
+ * cannot honour warns at most once per distinct tz per mount, deduped on
8
+ * `ctx.diagnosedTzKeys` — so a script using BOTH `time.*` and `session.isOpen`
9
+ * on one DST zone fires `tz-dst-unsupported` once total.
10
+ *
11
+ * @since 1.5
12
+ * @stable
13
+ * @example
14
+ * // import { buildTzDstReporter } from "@invinite-org/chartlang-runtime";
15
+ * // const report = buildTzDstReporter(ctx);
16
+ * // report("America/New_York");
17
+ */
18
+ export function buildTzDstReporter(ctx) {
19
+ return (tz) => {
20
+ const key = `tz-dst-unsupported|${tz}`;
21
+ if (ctx.diagnosedTzKeys.has(key))
22
+ return;
23
+ ctx.diagnosedTzKeys.add(key);
24
+ pushDiagnostic(ctx.emissions, {
25
+ kind: "diagnostic",
26
+ severity: "warning",
27
+ code: "tz-dst-unsupported",
28
+ message: `Timezone \`${tz}\` needs DST data unavailable in this build; calendar fields used UTC.`,
29
+ slotId: null,
30
+ bar: ctx.barIndex(),
31
+ });
32
+ };
33
+ }
34
+ //# sourceMappingURL=tzDiagnostic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tzDiagnostic.js","sourceRoot":"","sources":["../../src/time-accessors/tzDiagnostic.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IAClD,OAAO,CAAC,EAAE,EAAE,EAAE;QACV,MAAM,GAAG,GAAG,sBAAsB,EAAE,EAAE,CAAC;QACvC,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QACzC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE;YAC1B,IAAI,EAAE,YAAY;YAClB,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,cAAc,EAAE,wEAAwE;YACjG,MAAM,EAAE,IAAI;YACZ,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;SACtB,CAAC,CAAC;IACP,CAAC,CAAC;AACN,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 { pushDiagnostic } from \"../emit/index.js\";\nimport type { RuntimeContext } from \"../runtimeContext.js\";\n\n/**\n * Build the `onDstUnsupported(tz)` reporter the `time.*` and `session.*`\n * accessor factories share. A DST-bearing IANA zone the UTC-first runtime\n * cannot honour warns at most once per distinct tz per mount, deduped on\n * `ctx.diagnosedTzKeys` — so a script using BOTH `time.*` and `session.isOpen`\n * on one DST zone fires `tz-dst-unsupported` once total.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { buildTzDstReporter } from \"@invinite-org/chartlang-runtime\";\n * // const report = buildTzDstReporter(ctx);\n * // report(\"America/New_York\");\n */\nexport function buildTzDstReporter(ctx: RuntimeContext): (tz: string) => void {\n return (tz) => {\n const key = `tz-dst-unsupported|${tz}`;\n if (ctx.diagnosedTzKeys.has(key)) return;\n ctx.diagnosedTzKeys.add(key);\n pushDiagnostic(ctx.emissions, {\n kind: \"diagnostic\",\n severity: \"warning\",\n code: \"tz-dst-unsupported\",\n message: `Timezone \\`${tz}\\` needs DST data unavailable in this build; calendar fields used UTC.`,\n slotId: null,\n bar: ctx.barIndex(),\n });\n };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The resolution of a timezone string to a fixed UTC offset in minutes.
3
+ * `dstUnsupported` is `true` when `tz` named a DST-bearing IANA region the
4
+ * UTC-first runtime cannot honour — callers then use the `0` offset AND raise
5
+ * a one-time `tz-dst-unsupported` diagnostic.
6
+ *
7
+ * @since 1.5
8
+ * @stable
9
+ * @example
10
+ * // import type { ResolvedOffset } from "@invinite-org/chartlang-runtime";
11
+ * // const o: ResolvedOffset = { offsetMin: 0, dstUnsupported: false };
12
+ */
13
+ export type ResolvedOffset = {
14
+ readonly offsetMin: number;
15
+ readonly dstUnsupported: boolean;
16
+ };
17
+ /**
18
+ * Resolve a timezone string to a fixed UTC offset. UTC aliases and explicit
19
+ * fixed offsets (`±HH:MM`, `UTC±H`, `Etc/GMT±H`) parse to integer minutes; any
20
+ * other (region) name returns the zero offset flagged `dstUnsupported`. Pure —
21
+ * never calls `Intl`, so the result is byte-reproducible across hosts.
22
+ *
23
+ * @since 1.5
24
+ * @stable
25
+ * @example
26
+ * // import { resolveOffsetMinutes } from "@invinite-org/chartlang-runtime";
27
+ * // resolveOffsetMinutes("+05:30"); // { offsetMin: 330, dstUnsupported: false }
28
+ * // resolveOffsetMinutes("America/New_York"); // { offsetMin: 0, dstUnsupported: true }
29
+ */
30
+ export declare function resolveOffsetMinutes(tz: string): ResolvedOffset;
31
+ //# sourceMappingURL=tzOffset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tzOffset.d.ts","sourceRoot":"","sources":["../../src/time-accessors/tzOffset.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC;AAuB9F;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,CAwB/D"}
@@ -0,0 +1,67 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ //
4
+ // The single source of the chartlang UTC-first determinism policy: only UTC
5
+ // and explicit fixed offsets resolve to a numeric minute shift. A DST-bearing
6
+ // IANA region resolves to UTC + a `dstUnsupported` flag — NEVER an `Intl`
7
+ // lookup (whose output varies by host ICU/tz-data version).
8
+ /** UTC-equivalent zone names that resolve to a zero offset. */
9
+ const UTC_ALIASES = new Set(["", "UTC", "ETC/UTC", "GMT", "Z"]);
10
+ const UTC = { offsetMin: 0, dstUnsupported: false };
11
+ const DST_UNSUPPORTED = { offsetMin: 0, dstUnsupported: true };
12
+ // `+HH:MM` / `-HH:MM` / `+HHMM` / `+HH` — a leading sign then 1-2 hour digits
13
+ // and optional 2 minute digits.
14
+ const SIGNED_OFFSET = /^([+-])(\d{1,2})(?::?(\d{2}))?$/;
15
+ // `UTC±H[:MM]` / `GMT±H[:MM]` — the offset has the same sign as the number.
16
+ const UTC_PREFIXED = /^(?:UTC|GMT)([+-]\d{1,2}(?::?\d{2})?)$/;
17
+ // `Etc/GMT±H` — POSIX-style, the sign is INVERTED (`Etc/GMT-5` is UTC+5).
18
+ const ETC_GMT = /^ETC\/GMT([+-])(\d{1,2})$/;
19
+ function parseSignedOffset(value) {
20
+ const match = SIGNED_OFFSET.exec(value);
21
+ if (match === null)
22
+ return null;
23
+ const hours = Number(match[2]);
24
+ const minutes = match[3] === undefined ? 0 : Number(match[3]);
25
+ if (hours > 23 || minutes > 59)
26
+ return null;
27
+ const magnitude = hours * 60 + minutes;
28
+ return match[1] === "-" ? -magnitude : magnitude;
29
+ }
30
+ /**
31
+ * Resolve a timezone string to a fixed UTC offset. UTC aliases and explicit
32
+ * fixed offsets (`±HH:MM`, `UTC±H`, `Etc/GMT±H`) parse to integer minutes; any
33
+ * other (region) name returns the zero offset flagged `dstUnsupported`. Pure —
34
+ * never calls `Intl`, so the result is byte-reproducible across hosts.
35
+ *
36
+ * @since 1.5
37
+ * @stable
38
+ * @example
39
+ * // import { resolveOffsetMinutes } from "@invinite-org/chartlang-runtime";
40
+ * // resolveOffsetMinutes("+05:30"); // { offsetMin: 330, dstUnsupported: false }
41
+ * // resolveOffsetMinutes("America/New_York"); // { offsetMin: 0, dstUnsupported: true }
42
+ */
43
+ export function resolveOffsetMinutes(tz) {
44
+ const normalized = tz.trim().toUpperCase();
45
+ if (UTC_ALIASES.has(normalized))
46
+ return UTC;
47
+ const signed = parseSignedOffset(normalized);
48
+ if (signed !== null)
49
+ return { offsetMin: signed, dstUnsupported: false };
50
+ const utcPrefixed = UTC_PREFIXED.exec(normalized);
51
+ if (utcPrefixed !== null) {
52
+ const minutes = parseSignedOffset(utcPrefixed[1]);
53
+ if (minutes !== null)
54
+ return { offsetMin: minutes, dstUnsupported: false };
55
+ }
56
+ const etcGmt = ETC_GMT.exec(normalized);
57
+ if (etcGmt !== null) {
58
+ const hours = Number(etcGmt[2]);
59
+ if (hours <= 23) {
60
+ // POSIX inverts the sign: `Etc/GMT-5` is UTC+5.
61
+ const magnitude = hours * 60;
62
+ return { offsetMin: etcGmt[1] === "-" ? magnitude : -magnitude, dstUnsupported: false };
63
+ }
64
+ }
65
+ return DST_UNSUPPORTED;
66
+ }
67
+ //# sourceMappingURL=tzOffset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tzOffset.js","sourceRoot":"","sources":["../../src/time-accessors/tzOffset.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//\n// The single source of the chartlang UTC-first determinism policy: only UTC\n// and explicit fixed offsets resolve to a numeric minute shift. A DST-bearing\n// IANA region resolves to UTC + a `dstUnsupported` flag — NEVER an `Intl`\n// lookup (whose output varies by host ICU/tz-data version).\n\n/** UTC-equivalent zone names that resolve to a zero offset. */\nconst UTC_ALIASES = new Set([\"\", \"UTC\", \"ETC/UTC\", \"GMT\", \"Z\"]);\n\n/**\n * The resolution of a timezone string to a fixed UTC offset in minutes.\n * `dstUnsupported` is `true` when `tz` named a DST-bearing IANA region the\n * UTC-first runtime cannot honour — callers then use the `0` offset AND raise\n * a one-time `tz-dst-unsupported` diagnostic.\n *\n * @since 1.5\n * @stable\n * @example\n * // import type { ResolvedOffset } from \"@invinite-org/chartlang-runtime\";\n * // const o: ResolvedOffset = { offsetMin: 0, dstUnsupported: false };\n */\nexport type ResolvedOffset = { readonly offsetMin: number; readonly dstUnsupported: boolean };\n\nconst UTC: ResolvedOffset = { offsetMin: 0, dstUnsupported: false };\nconst DST_UNSUPPORTED: ResolvedOffset = { offsetMin: 0, dstUnsupported: true };\n\n// `+HH:MM` / `-HH:MM` / `+HHMM` / `+HH` — a leading sign then 1-2 hour digits\n// and optional 2 minute digits.\nconst SIGNED_OFFSET = /^([+-])(\\d{1,2})(?::?(\\d{2}))?$/;\n// `UTC±H[:MM]` / `GMT±H[:MM]` — the offset has the same sign as the number.\nconst UTC_PREFIXED = /^(?:UTC|GMT)([+-]\\d{1,2}(?::?\\d{2})?)$/;\n// `Etc/GMT±H` — POSIX-style, the sign is INVERTED (`Etc/GMT-5` is UTC+5).\nconst ETC_GMT = /^ETC\\/GMT([+-])(\\d{1,2})$/;\n\nfunction parseSignedOffset(value: string): number | null {\n const match = SIGNED_OFFSET.exec(value);\n if (match === null) return null;\n const hours = Number(match[2]);\n const minutes = match[3] === undefined ? 0 : Number(match[3]);\n if (hours > 23 || minutes > 59) return null;\n const magnitude = hours * 60 + minutes;\n return match[1] === \"-\" ? -magnitude : magnitude;\n}\n\n/**\n * Resolve a timezone string to a fixed UTC offset. UTC aliases and explicit\n * fixed offsets (`±HH:MM`, `UTC±H`, `Etc/GMT±H`) parse to integer minutes; any\n * other (region) name returns the zero offset flagged `dstUnsupported`. Pure —\n * never calls `Intl`, so the result is byte-reproducible across hosts.\n *\n * @since 1.5\n * @stable\n * @example\n * // import { resolveOffsetMinutes } from \"@invinite-org/chartlang-runtime\";\n * // resolveOffsetMinutes(\"+05:30\"); // { offsetMin: 330, dstUnsupported: false }\n * // resolveOffsetMinutes(\"America/New_York\"); // { offsetMin: 0, dstUnsupported: true }\n */\nexport function resolveOffsetMinutes(tz: string): ResolvedOffset {\n const normalized = tz.trim().toUpperCase();\n if (UTC_ALIASES.has(normalized)) return UTC;\n\n const signed = parseSignedOffset(normalized);\n if (signed !== null) return { offsetMin: signed, dstUnsupported: false };\n\n const utcPrefixed = UTC_PREFIXED.exec(normalized);\n if (utcPrefixed !== null) {\n const minutes = parseSignedOffset(utcPrefixed[1]);\n if (minutes !== null) return { offsetMin: minutes, dstUnsupported: false };\n }\n\n const etcGmt = ETC_GMT.exec(normalized);\n if (etcGmt !== null) {\n const hours = Number(etcGmt[2]);\n if (hours <= 23) {\n // POSIX inverts the sign: `Etc/GMT-5` is UTC+5.\n const magnitude = hours * 60;\n return { offsetMin: etcGmt[1] === \"-\" ? magnitude : -magnitude, dstUnsupported: false };\n }\n }\n\n return DST_UNSUPPORTED;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invinite-org/chartlang-runtime",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Execution engine, Series ring buffers, ta.* math primitives",
@@ -23,8 +23,8 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "dependencies": {
26
- "@invinite-org/chartlang-adapter-kit": "^1.3.0",
27
- "@invinite-org/chartlang-core": "^1.2.0"
26
+ "@invinite-org/chartlang-adapter-kit": "^1.6.0",
27
+ "@invinite-org/chartlang-core": "^1.3.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^3.20.0"