@kerusiweb/core 2.0.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 (95) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -0
  3. package/dist/kerusi/index.d.ts +19 -0
  4. package/dist/kerusi/index.d.ts.map +1 -0
  5. package/dist/kerusi/index.js +19 -0
  6. package/dist/kerusi/index.js.map +1 -0
  7. package/dist/kerusi/kerusi-layout-mode.d.ts +58 -0
  8. package/dist/kerusi/kerusi-layout-mode.d.ts.map +1 -0
  9. package/dist/kerusi/kerusi-layout-mode.js +117 -0
  10. package/dist/kerusi/kerusi-layout-mode.js.map +1 -0
  11. package/dist/kerusi/kerusi-locale.d.ts +43 -0
  12. package/dist/kerusi/kerusi-locale.d.ts.map +1 -0
  13. package/dist/kerusi/kerusi-locale.js +111 -0
  14. package/dist/kerusi/kerusi-locale.js.map +1 -0
  15. package/dist/kerusi/kerusi-map.model.d.ts +258 -0
  16. package/dist/kerusi/kerusi-map.model.d.ts.map +1 -0
  17. package/dist/kerusi/kerusi-map.model.js +11 -0
  18. package/dist/kerusi/kerusi-map.model.js.map +1 -0
  19. package/dist/kerusi/kerusi-price.d.ts +22 -0
  20. package/dist/kerusi/kerusi-price.d.ts.map +1 -0
  21. package/dist/kerusi/kerusi-price.js +48 -0
  22. package/dist/kerusi/kerusi-price.js.map +1 -0
  23. package/dist/kerusi/kerusi-rows.d.ts +25 -0
  24. package/dist/kerusi/kerusi-rows.d.ts.map +1 -0
  25. package/dist/kerusi/kerusi-rows.js +36 -0
  26. package/dist/kerusi/kerusi-rows.js.map +1 -0
  27. package/dist/kerusi/kerusi-state.d.ts +85 -0
  28. package/dist/kerusi/kerusi-state.d.ts.map +1 -0
  29. package/dist/kerusi/kerusi-state.js +117 -0
  30. package/dist/kerusi/kerusi-state.js.map +1 -0
  31. package/dist/kerusi/kerusi-state.model.d.ts +79 -0
  32. package/dist/kerusi/kerusi-state.model.d.ts.map +1 -0
  33. package/dist/kerusi/kerusi-state.model.js +7 -0
  34. package/dist/kerusi/kerusi-state.model.js.map +1 -0
  35. package/dist/kerusi/kerusi-validator.d.ts +96 -0
  36. package/dist/kerusi/kerusi-validator.d.ts.map +1 -0
  37. package/dist/kerusi/kerusi-validator.js +525 -0
  38. package/dist/kerusi/kerusi-validator.js.map +1 -0
  39. package/dist/kerusi/kerusi-violation.d.ts +36 -0
  40. package/dist/kerusi/kerusi-violation.d.ts.map +1 -0
  41. package/dist/kerusi/kerusi-violation.js +13 -0
  42. package/dist/kerusi/kerusi-violation.js.map +1 -0
  43. package/dist/public-api.d.ts +4 -0
  44. package/dist/public-api.d.ts.map +1 -0
  45. package/dist/public-api.js +13 -0
  46. package/dist/public-api.js.map +1 -0
  47. package/dist/render/build-render-model.d.ts +36 -0
  48. package/dist/render/build-render-model.d.ts.map +1 -0
  49. package/dist/render/build-render-model.js +258 -0
  50. package/dist/render/build-render-model.js.map +1 -0
  51. package/dist/render/element-shapes.d.ts +34 -0
  52. package/dist/render/element-shapes.d.ts.map +1 -0
  53. package/dist/render/element-shapes.js +41 -0
  54. package/dist/render/element-shapes.js.map +1 -0
  55. package/dist/render/index.d.ts +14 -0
  56. package/dist/render/index.d.ts.map +1 -0
  57. package/dist/render/index.js +14 -0
  58. package/dist/render/index.js.map +1 -0
  59. package/dist/render/navigation-order.d.ts +49 -0
  60. package/dist/render/navigation-order.d.ts.map +1 -0
  61. package/dist/render/navigation-order.js +109 -0
  62. package/dist/render/navigation-order.js.map +1 -0
  63. package/dist/render/render-model.d.ts +151 -0
  64. package/dist/render/render-model.d.ts.map +1 -0
  65. package/dist/render/render-model.js +2 -0
  66. package/dist/render/render-model.js.map +1 -0
  67. package/dist/render/seat-shapes.d.ts +72 -0
  68. package/dist/render/seat-shapes.d.ts.map +1 -0
  69. package/dist/render/seat-shapes.js +161 -0
  70. package/dist/render/seat-shapes.js.map +1 -0
  71. package/dist/render/section-layout.d.ts +66 -0
  72. package/dist/render/section-layout.d.ts.map +1 -0
  73. package/dist/render/section-layout.js +173 -0
  74. package/dist/render/section-layout.js.map +1 -0
  75. package/dist/view/index.d.ts +12 -0
  76. package/dist/view/index.d.ts.map +1 -0
  77. package/dist/view/index.js +12 -0
  78. package/dist/view/index.js.map +1 -0
  79. package/dist/view/kerusi-seatmap-colors.d.ts +176 -0
  80. package/dist/view/kerusi-seatmap-colors.d.ts.map +1 -0
  81. package/dist/view/kerusi-seatmap-colors.js +215 -0
  82. package/dist/view/kerusi-seatmap-colors.js.map +1 -0
  83. package/dist/view/rtl.d.ts +17 -0
  84. package/dist/view/rtl.d.ts.map +1 -0
  85. package/dist/view/rtl.js +29 -0
  86. package/dist/view/rtl.js.map +1 -0
  87. package/dist/view/seat-aria.d.ts +44 -0
  88. package/dist/view/seat-aria.d.ts.map +1 -0
  89. package/dist/view/seat-aria.js +103 -0
  90. package/dist/view/seat-aria.js.map +1 -0
  91. package/dist/view/selection.d.ts +84 -0
  92. package/dist/view/selection.d.ts.map +1 -0
  93. package/dist/view/selection.js +98 -0
  94. package/dist/view/selection.js.map +1 -0
  95. package/package.json +42 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Kerusi Seat Map document types, transcribed from the Kerusi Seat Map and
3
+ * Availability Format v1.0.0-draft, §4. Field-level comments preserve the
4
+ * normative REQUIRED/OPTIONAL constraints and defaults from the standard.
5
+ *
6
+ * A `KerusiMap` describes a physical venue/vehicle configuration (layout,
7
+ * pricing, seat types). It is static and cacheable; live availability lives
8
+ * in a separate {@link KerusiState} (see kerusi-state.model.ts).
9
+ */
10
+ /**
11
+ * A label that may be a plain string or a BCP-47 locale map, e.g.
12
+ * `{ "en": "Orchestra", "ms": "Orkestra" }`. Resolve with
13
+ * `resolveLocalizedText` (§4.1, §4.7).
14
+ */
15
+ export type LocalizedText = string | Record<string, string>;
16
+ /**
17
+ * Structured accessibility properties for a seat (§4.3.4). All members are
18
+ * OPTIONAL, per the progressive-enhancement principle (§2): a map with no
19
+ * accessibility needs to express simply omits `accessibility` on every seat.
20
+ *
21
+ * These are first-class fields, not `attributes` — an attribute tag like
22
+ * "wheelchair" is free text a consumer cannot rely on.
23
+ */
24
+ export interface Accessibility {
25
+ /** Seat or space usable by a wheelchair user. */
26
+ wheelchairAccessible?: boolean;
27
+ /** Which armrest lifts, for transfer from a mobility device. */
28
+ transferArmrest?: 'left' | 'right' | 'both' | 'fixed' | 'none';
29
+ /** Reachable via aisle-chair transfer. */
30
+ aisleChairCompatible?: boolean;
31
+ /**
32
+ * The occupant is expected to need an adjacent companion. Pair with
33
+ * `Seat.companions` (§4.6) to link the actual companion seat.
34
+ */
35
+ companionRequired?: boolean;
36
+ }
37
+ /** A monetary amount, in minor units to avoid floating-point error (§4.8). */
38
+ export interface Money {
39
+ /** REQUIRED. Minor units (e.g. cents), to avoid floating-point error. */
40
+ amount: number;
41
+ /** REQUIRED. ISO 4217 currency code, e.g. "MYR", "USD". */
42
+ currency: string;
43
+ }
44
+ /** A named price tier, referenced by seats via `Seat.priceTier` (§4.8). */
45
+ export interface PriceTier {
46
+ /** REQUIRED. */
47
+ id: string;
48
+ label?: string;
49
+ /** REQUIRED. */
50
+ price: Money;
51
+ }
52
+ /** A legend entry: the vocabulary of seat types used in a map (§4.7). */
53
+ export interface SeatType {
54
+ /** REQUIRED. e.g. "standard" | "recliner" | "wheelchair" | "business". */
55
+ id: string;
56
+ label?: LocalizedText;
57
+ /** Suggested render color, hex. Non-normative hint. */
58
+ color?: string;
59
+ defaultPriceTier?: string;
60
+ }
61
+ /**
62
+ * A row's declaration: label, ordering key, row-level metadata. NOT a container
63
+ * — seats reference a row by id via `Seat.row` rather than nesting inside it
64
+ * (§4.2).
65
+ *
66
+ * `Section.rows` as a whole is OPTIONAL, but where it is present it is the
67
+ * section's **complete, ordered row registry** rather than an annotation on the
68
+ * rows the seats happen to mention: §4.6 requires every `Seat.row` to resolve
69
+ * against it, so no seat can occupy a row it does not declare. A `RowMeta` that
70
+ * no seat references is an *empty row* — see §4.2.2 and {@link Section.rows}.
71
+ */
72
+ export interface RowMeta {
73
+ /** REQUIRED. */
74
+ id: string;
75
+ label?: string;
76
+ /**
77
+ * Ordering key among the section's rows (§4.2.1) — a key, not a position.
78
+ * Rows at 0 and 11 with nothing between them are adjacent, two rows and not
79
+ * twelve; vertical space is reserved by declaring a row, never by leaving a
80
+ * numeric hole here.
81
+ */
82
+ index?: number;
83
+ metadata?: Record<string, unknown>;
84
+ }
85
+ /**
86
+ * A human-facing label for one of a section's four addressing axes (§4.10) —
87
+ * "front of train" / "back of train" for a carriage, compass points for
88
+ * open-air seating whose orientation decides where the sun falls.
89
+ *
90
+ * Non-normative and purely informational: a validator MUST NOT reject a
91
+ * document over it, and a renderer MUST NOT read it to decide layout or screen
92
+ * placement. It says what an axis *means* in the physical world, not which edge
93
+ * of the screen it is drawn on — that stays a rendering decision (§2).
94
+ */
95
+ export interface Direction {
96
+ /** REQUIRED. Which addressing axis (§4.3) this label pair describes. */
97
+ axis: 'row' | 'col' | 'x' | 'y';
98
+ /**
99
+ * REQUIRED. The low end: ascending row order (§4.2.1) or `col` from the
100
+ * section's first, or `x`/`y` = 0.
101
+ */
102
+ low: LocalizedText;
103
+ /** REQUIRED. The high end: descending row/col order, or `x`/`y` = 100. */
104
+ high: LocalizedText;
105
+ }
106
+ /**
107
+ * A single seat within a section (§4.3). A seat MUST specify `col`, or `x` and
108
+ * `y`, or both — never neither (§4.3.1).
109
+ */
110
+ export interface Seat {
111
+ /** REQUIRED. Globally unique within the KerusiMap. */
112
+ id: string;
113
+ /** Display label, e.g. "12" or "12A". Defaults to `id`. */
114
+ label?: string;
115
+ /** References `RowMeta.id`, or a free-text row label. */
116
+ row?: string;
117
+ /** Grid column within the row. */
118
+ col?: number;
119
+ /** 0–100, percent of section width (see `Section.aspectRatio`). */
120
+ x?: number;
121
+ /** 0–100, percent of section height. */
122
+ y?: number;
123
+ /** Degrees. Lets one seat tilt independently of its neighbours. */
124
+ rotation?: number;
125
+ /** REQUIRED. References a `SeatType.id` in the map's `legend`. */
126
+ type: string;
127
+ /** References a `PriceTier.id`. */
128
+ priceTier?: string;
129
+ /** Literal override; takes precedence over `priceTier` (§4.9). */
130
+ price?: Money;
131
+ /** ids of other seats that must be booked together (couple/family bays). */
132
+ companions?: string[];
133
+ /**
134
+ * Free, non-exclusive tags: "aisle" | "window" | "extra-legroom" | ...
135
+ * Descriptive only; attributes MUST NOT independently affect price (§4.3.3).
136
+ */
137
+ attributes?: string[];
138
+ /** Structured accessibility properties (§4.3.4). */
139
+ accessibility?: Accessibility;
140
+ metadata?: Record<string, unknown>;
141
+ }
142
+ /**
143
+ * A non-bookable feature that still requires rendering: screens, stages,
144
+ * exits, lavatories, staircases, or a labelled gap (§4.4).
145
+ *
146
+ * An element is bound to its section's positioning mode exactly as its seats
147
+ * are (§4.4.1), for the same reason §4.5 binds the seats: a section whose
148
+ * elements may be addressed differently from its seats cannot be laid out
149
+ * deterministically by two independent renderers.
150
+ */
151
+ export interface Element {
152
+ /** REQUIRED. */
153
+ id: string;
154
+ /** REQUIRED. "screen" | "stage" | "exit" | "lavatory" | "gap" | "aisle" | ... */
155
+ kind: string;
156
+ label?: string;
157
+ /** References `RowMeta.id` when the section declares `rows` (§4.6). */
158
+ row?: string;
159
+ /** Grid column. MUST NOT appear in a freeform section (§4.4.1). */
160
+ col?: number;
161
+ /** Percent of section width. MUST NOT appear in a grid section (§4.4.1). */
162
+ x?: number;
163
+ /** Percent of section height. MUST NOT appear in a grid section (§4.4.1). */
164
+ y?: number;
165
+ /**
166
+ * Grid: a **column span** in cells — a positive integer, default 1. Omitting
167
+ * `col` spans the section's full column extent and ignores `width`, which is
168
+ * the usual form for a screen or a stage.
169
+ * Freeform: a percentage of the section's width (§4.4.1).
170
+ */
171
+ width?: number;
172
+ /**
173
+ * Grid: a **row span** in cells — a positive integer, default 1, counted from
174
+ * this element's `row` through the section's row order (§4.2.1). The rows it
175
+ * reaches are ordinarily empty rows declared for the purpose (§4.2.2).
176
+ * Freeform: a percentage of the section's height (§4.4.1).
177
+ */
178
+ height?: number;
179
+ rotation?: number;
180
+ metadata?: Record<string, unknown>;
181
+ }
182
+ /**
183
+ * A section holds a FLAT list of seats, not a grid of rows containing seats
184
+ * (§4.1). A seat's position is a property of the seat, which is what makes
185
+ * curves, offsets, and irregular gaps representable.
186
+ */
187
+ export interface Section {
188
+ /** REQUIRED. */
189
+ id: string;
190
+ /** String, or locale map: `{ "en": "Orchestra", "ms": "Orkestra" }`. */
191
+ label?: LocalizedText;
192
+ /** Display order among sections. */
193
+ index?: number;
194
+ /**
195
+ * A strict, validated constraint on how this section's seats are positioned
196
+ * — NOT a rendering hint (§4.5). Every seat MUST conform:
197
+ *
198
+ * - `grid` — every seat has `col`; no seat has `x` or `y`.
199
+ * - `freeform` — every seat has both `x` and `y`; no seat has `col`.
200
+ * - `mixed` — every seat has `col` AND both `x` and `y`; `x`/`y` places
201
+ * the seat and `col` gives its logical adjacency.
202
+ *
203
+ * Omitted, the mode is INFERRED from the seats; a section whose seats are
204
+ * inconsistent is invalid and MUST be rejected. A free-text `row` is allowed
205
+ * in every mode — it carries no positional information.
206
+ */
207
+ layout?: 'grid' | 'freeform' | 'mixed';
208
+ /**
209
+ * "width:height", e.g. "16:9". Meaningful only for freeform/mixed sections.
210
+ * Default: "1:1".
211
+ */
212
+ aspectRatio?: string;
213
+ /**
214
+ * The section's row registry, when present: a complete, ordered list of its
215
+ * rows, not merely an annotation on the ones its seats mention (§4.2).
216
+ * Omitted, a seat's `row` is opaque free text and the section's rows are
217
+ * exactly those its seats reference.
218
+ *
219
+ * Rows are ordered by §4.2.1 — those declaring `index` first, ascending, then
220
+ * the rest in declaration order. A row no seat references is an **empty row**
221
+ * (§4.2.2): it still occupies a slot, reserving the vertical space one row of
222
+ * seats would. That is how a grid section expresses the throw between a
223
+ * cinema screen and its front row, or a cross-aisle between two rows — the
224
+ * row-axis counterpart to a skipped column (§4.3.2).
225
+ */
226
+ rows?: RowMeta[];
227
+ /** Human-facing labels for this section's addressing axes (§4.10). */
228
+ directions?: Direction[];
229
+ /** REQUIRED. Flat list; order is not significant. */
230
+ seats: Seat[];
231
+ /** Non-bookable features: screens, stages, aisles, stairs. */
232
+ elements?: Element[];
233
+ metadata?: Record<string, unknown>;
234
+ }
235
+ /** The top-level static seat-map document (§4). */
236
+ export interface KerusiMap {
237
+ /** REQUIRED. Spec version this document conforms to. */
238
+ kerusi: '1.0';
239
+ /** REQUIRED. Unique id for this map (e.g. "cinema3-hallA"). */
240
+ id: string;
241
+ /** Human label, e.g. "Hall A". */
242
+ name?: string;
243
+ /**
244
+ * Free-text hint: "cinema" | "flight" | "theatre" | "stadium" | "bus" |
245
+ * "train" | "custom". Non-normative; informational only.
246
+ */
247
+ domain?: string;
248
+ /** BCP-47 language tag. Default: "en". */
249
+ locale?: string;
250
+ /** REQUIRED. Vocabulary of seat types used in this map. */
251
+ legend: SeatType[];
252
+ /** Named price tiers, referenced by id. */
253
+ priceTiers?: PriceTier[];
254
+ /** REQUIRED. */
255
+ sections: Section[];
256
+ metadata?: Record<string, unknown>;
257
+ }
258
+ //# sourceMappingURL=kerusi-map.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-map.model.d.ts","sourceRoot":"","sources":["../../src/kerusi/kerusi-map.model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC/D,0CAA0C;IAC1C,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,8EAA8E;AAC9E,MAAM,WAAW,KAAK;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACvB,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IAChC;;;OAGG;IACH,GAAG,EAAE,aAAa,CAAC;IACnB,0EAA0E;IAC1E,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,oDAAoD;IACpD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,OAAO;IACtB,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,sEAAsE;IACtE,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,qDAAqD;IACrD,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,MAAM,EAAE,KAAK,CAAC;IACd,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,gBAAgB;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Kerusi Seat Map document types, transcribed from the Kerusi Seat Map and
3
+ * Availability Format v1.0.0-draft, §4. Field-level comments preserve the
4
+ * normative REQUIRED/OPTIONAL constraints and defaults from the standard.
5
+ *
6
+ * A `KerusiMap` describes a physical venue/vehicle configuration (layout,
7
+ * pricing, seat types). It is static and cacheable; live availability lives
8
+ * in a separate {@link KerusiState} (see kerusi-state.model.ts).
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=kerusi-map.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-map.model.js","sourceRoot":"","sources":["../../src/kerusi/kerusi-map.model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,22 @@
1
+ import { KerusiMap, Money, Seat } from './kerusi-map.model.js';
2
+ /**
3
+ * Resolves the price of a seat following the precedence order in spec §4.9,
4
+ * stopping at the first match:
5
+ *
6
+ * 1. `Seat.price` — a literal override.
7
+ * 2. The `PriceTier` referenced by `Seat.priceTier`.
8
+ * 3. The `PriceTier` referenced by `SeatType.defaultPriceTier` (via `Seat.type`).
9
+ * 4. None of the above — the seat is unpriced. This is a valid, terminal
10
+ * state (general admission, free events).
11
+ *
12
+ * Returns `undefined` for an unpriced seat. This function does not validate
13
+ * that referenced ids resolve — use {@link validateKerusiMap} for that; here an
14
+ * unresolvable reference simply falls through to the next step.
15
+ */
16
+ export declare function resolveSeatPrice(seat: Seat, map: KerusiMap): Money | undefined;
17
+ /**
18
+ * The price of a named tier, for rendering a legend or a price key. Returns
19
+ * `undefined` when the tier does not resolve.
20
+ */
21
+ export declare function resolveTierPrice(map: KerusiMap, tierId: string | undefined): Money | undefined;
22
+ //# sourceMappingURL=kerusi-price.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-price.d.ts","sourceRoot":"","sources":["../../src/kerusi/kerusi-price.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE/D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,GAAG,KAAK,GAAG,SAAS,CAyB9E;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAE9F"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Resolves the price of a seat following the precedence order in spec §4.9,
3
+ * stopping at the first match:
4
+ *
5
+ * 1. `Seat.price` — a literal override.
6
+ * 2. The `PriceTier` referenced by `Seat.priceTier`.
7
+ * 3. The `PriceTier` referenced by `SeatType.defaultPriceTier` (via `Seat.type`).
8
+ * 4. None of the above — the seat is unpriced. This is a valid, terminal
9
+ * state (general admission, free events).
10
+ *
11
+ * Returns `undefined` for an unpriced seat. This function does not validate
12
+ * that referenced ids resolve — use {@link validateKerusiMap} for that; here an
13
+ * unresolvable reference simply falls through to the next step.
14
+ */
15
+ export function resolveSeatPrice(seat, map) {
16
+ // 1. Literal override.
17
+ if (seat.price) {
18
+ return seat.price;
19
+ }
20
+ // 2. Explicit price tier on the seat.
21
+ if (seat.priceTier) {
22
+ const tier = findPriceTier(map, seat.priceTier);
23
+ if (tier) {
24
+ return tier.price;
25
+ }
26
+ }
27
+ // 3. Default price tier of the seat's type.
28
+ const seatType = map.legend.find((t) => t.id === seat.type);
29
+ if (seatType?.defaultPriceTier) {
30
+ const tier = findPriceTier(map, seatType.defaultPriceTier);
31
+ if (tier) {
32
+ return tier.price;
33
+ }
34
+ }
35
+ // 4. Unpriced.
36
+ return undefined;
37
+ }
38
+ function findPriceTier(map, id) {
39
+ return map.priceTiers?.find((t) => t.id === id);
40
+ }
41
+ /**
42
+ * The price of a named tier, for rendering a legend or a price key. Returns
43
+ * `undefined` when the tier does not resolve.
44
+ */
45
+ export function resolveTierPrice(map, tierId) {
46
+ return tierId ? findPriceTier(map, tierId)?.price : undefined;
47
+ }
48
+ //# sourceMappingURL=kerusi-price.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-price.js","sourceRoot":"","sources":["../../src/kerusi/kerusi-price.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU,EAAE,GAAc;IACzD,uBAAuB;IACvB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,sCAAsC;IACtC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,IAAI,QAAQ,EAAE,gBAAgB,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QAC3D,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IAED,eAAe;IACf,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa,CAAC,GAAc,EAAE,EAAU;IAC/C,OAAO,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAc,EAAE,MAA0B;IACzE,OAAO,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { RowMeta, Section } from './kerusi-map.model.js';
2
+ /**
3
+ * The §4.2.1 row order, shared by the validator and the render model so both
4
+ * derive the same order from the same document — which is exactly what §4.2.1
5
+ * requires of two conformant consumers.
6
+ */
7
+ /**
8
+ * Orders a section's declared rows:
9
+ *
10
+ * 1. rows declaring `index` first, ascending;
11
+ * 2. rows with no `index` after them, in declaration order;
12
+ * 3. rows sharing an `index` in declaration order relative to one another.
13
+ *
14
+ * `index` is an ordering key, not a position: rows at 0 and 11 with nothing
15
+ * between them are adjacent. Vertical space comes from declaring a row
16
+ * (§4.2.2), never from a numeric hole here.
17
+ */
18
+ export declare function orderRowMeta(rows: readonly RowMeta[]): RowMeta[];
19
+ /**
20
+ * Each declared row's position in the §4.2.1 order, by id. Empty for a section
21
+ * that declares no `rows`, where a seat's `row` is opaque free text (§4.6) and
22
+ * there is no registry to resolve against.
23
+ */
24
+ export declare function rowOrderIndexes(section: Section): Map<string, number>;
25
+ //# sourceMappingURL=kerusi-rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-rows.d.ts","sourceRoot":"","sources":["../../src/kerusi/kerusi-rows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,CAKhE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAGrE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The §4.2.1 row order, shared by the validator and the render model so both
3
+ * derive the same order from the same document — which is exactly what §4.2.1
4
+ * requires of two conformant consumers.
5
+ */
6
+ /**
7
+ * Orders a section's declared rows:
8
+ *
9
+ * 1. rows declaring `index` first, ascending;
10
+ * 2. rows with no `index` after them, in declaration order;
11
+ * 3. rows sharing an `index` in declaration order relative to one another.
12
+ *
13
+ * `index` is an ordering key, not a position: rows at 0 and 11 with nothing
14
+ * between them are adjacent. Vertical space comes from declaring a row
15
+ * (§4.2.2), never from a numeric hole here.
16
+ */
17
+ export function orderRowMeta(rows) {
18
+ return [...rows]
19
+ .map((row, order) => ({ row, order }))
20
+ .sort((a, b) => rowOrderKey(a.row, a.order) - rowOrderKey(b.row, b.order) || a.order - b.order)
21
+ .map((entry) => entry.row);
22
+ }
23
+ /**
24
+ * Each declared row's position in the §4.2.1 order, by id. Empty for a section
25
+ * that declares no `rows`, where a seat's `row` is opaque free text (§4.6) and
26
+ * there is no registry to resolve against.
27
+ */
28
+ export function rowOrderIndexes(section) {
29
+ const ordered = orderRowMeta(section.rows ?? []);
30
+ return new Map(ordered.map((row, index) => [row.id, index]));
31
+ }
32
+ /** Sorts indexed rows ahead of unindexed ones, the latter in declaration order. */
33
+ function rowOrderKey(row, order) {
34
+ return row.index ?? Number.MAX_SAFE_INTEGER - 1_000_000 + order;
35
+ }
36
+ //# sourceMappingURL=kerusi-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-rows.js","sourceRoot":"","sources":["../../src/kerusi/kerusi-rows.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,IAAwB;IACnD,OAAO,CAAC,GAAG,IAAI,CAAC;SACb,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SAC9F,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,mFAAmF;AACnF,SAAS,WAAW,CAAC,GAAY,EAAE,KAAa;IAC9C,OAAO,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,gBAAgB,GAAG,SAAS,GAAG,KAAK,CAAC;AAClE,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { KerusiState, KerusiStateDelta } from './kerusi-state.model.js';
2
+ /**
3
+ * Live-availability lifecycle: applying deltas in order, noticing when the
4
+ * stream has lost a message, and letting a lapsed hold fall back to available.
5
+ *
6
+ * These are pure functions over an immutable {@link KerusiState}. A framework
7
+ * binding may wrap them in whatever reactive primitive it prefers — see
8
+ * `KerusiStateStore` in `@kerusiweb/angular` for the Angular signal-backed
9
+ * version — but nothing here needs one.
10
+ */
11
+ /** What happened when a delta was offered to the store. */
12
+ export type DeltaOutcome =
13
+ /** Merged into the state. */
14
+ 'applied'
15
+ /** `updatedAt` is at or before the current state's; discarded (§5.2). */
16
+ | 'stale'
17
+ /** Identical `updatedAt` to the last applied delta; discarded. */
18
+ | 'duplicate'
19
+ /**
20
+ * The transport's sequence skipped ahead: messages were lost. Applied
21
+ * best-effort, but the consumer SHOULD re-fetch a full state (§5.2).
22
+ */
23
+ | 'gap'
24
+ /** The delta names a different session or map; discarded. */
25
+ | 'scope-mismatch';
26
+ export interface DeltaApplication {
27
+ /** The state after the attempt — unchanged when the delta was discarded. */
28
+ state: KerusiState;
29
+ outcome: DeltaOutcome;
30
+ /** Human-readable explanation, for logging a discard. */
31
+ detail?: string;
32
+ }
33
+ /**
34
+ * Reads a monotonic sequence number off a delta, when the transport supplies
35
+ * one. Returns `undefined` when it does not — see {@link applyStateDeltaOrdered}
36
+ * for why that matters.
37
+ */
38
+ export type SequenceReader = (delta: KerusiStateDelta) => number | undefined;
39
+ /** Default: a numeric `metadata.seq`, the conventional place to put one. */
40
+ export declare const DEFAULT_SEQUENCE_READER: SequenceReader;
41
+ /**
42
+ * Applies a delta with the §5.2 ordering rules enforced.
43
+ *
44
+ * Three things are checked, in order:
45
+ *
46
+ * 1. **Scope.** A delta whose `sessionId`/`mapId` disagrees with the base state
47
+ * belongs to a different event and is discarded.
48
+ * 2. **Ordering.** `updatedAt` is REQUIRED to be strictly increasing per
49
+ * session/map, so a delta at or before the state's timestamp is stale.
50
+ * 3. **Gaps.** Reported only when `sequenceOf` yields a number and it skips
51
+ * ahead of the last one seen. This is a deliberate limitation:
52
+ * `updatedAt` is guaranteed strictly increasing but *not* contiguous, so
53
+ * "delta N+2 arrived after N" is indistinguishable from "N+1 never
54
+ * existed". Gap detection therefore needs a sequence the transport
55
+ * provides. A gap still applies the delta — showing slightly-wrong
56
+ * availability beats showing none while the consumer re-fetches.
57
+ */
58
+ export declare function applyStateDeltaOrdered(base: KerusiState, delta: KerusiStateDelta, opts?: {
59
+ sequenceOf?: SequenceReader;
60
+ lastSequence?: number;
61
+ }): DeltaApplication;
62
+ /**
63
+ * Reverts every seat whose hold has lapsed to `"available"` (§5.1). Returns the
64
+ * same state object when nothing changed, so a signal reading it does not fire.
65
+ *
66
+ * A `held` seat with no `holdExpires` never lapses — the spec makes the field
67
+ * optional, and an absent expiry means the hold is managed elsewhere.
68
+ */
69
+ export declare function expireHolds(state: KerusiState, now?: string | Date): KerusiState;
70
+ /** A seat currently held, with how long is left on it. */
71
+ export interface HeldSeat {
72
+ seatId: string;
73
+ expiresAt: string;
74
+ /** Milliseconds until expiry; negative once lapsed. */
75
+ msRemaining: number;
76
+ }
77
+ /**
78
+ * Every currently-held seat, soonest to expire first.
79
+ *
80
+ * A binding renders these as countdowns; the arithmetic is the same wherever
81
+ * it runs, so it lives here rather than in each store. Seats held without a
82
+ * `holdExpires` are omitted — there is no countdown to show.
83
+ */
84
+ export declare function heldSeats(state: KerusiState, now?: string | Date): readonly HeldSeat[];
85
+ //# sourceMappingURL=kerusi-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-state.d.ts","sourceRoot":"","sources":["../../src/kerusi/kerusi-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAc,MAAM,yBAAyB,CAAC;AAEpF;;;;;;;;GAQG;AAEH,2DAA2D;AAC3D,MAAM,MAAM,YAAY;AACtB,6BAA6B;AAC3B,SAAS;AACX,yEAAyE;GACvE,OAAO;AACT,kEAAkE;GAChE,WAAW;AACb;;;GAGG;GACD,KAAK;AACP,6DAA6D;GAC3D,gBAAgB,CAAC;AAErB,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,CAAC;AAE7E,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,EAAE,cAGrC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,gBAAgB,EACvB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,cAAc,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GAChE,gBAAgB,CA2ClB;AAmBD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAE,MAAM,GAAG,IAAiB,GAAG,WAAW,CAe5F;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,WAAW,EAClB,GAAG,GAAE,MAAM,GAAG,IAAiB,GAC9B,SAAS,QAAQ,EAAE,CAUrB"}
@@ -0,0 +1,117 @@
1
+ /** Default: a numeric `metadata.seq`, the conventional place to put one. */
2
+ export const DEFAULT_SEQUENCE_READER = (delta) => {
3
+ const seq = delta.metadata?.['seq'];
4
+ return typeof seq === 'number' ? seq : undefined;
5
+ };
6
+ /**
7
+ * Applies a delta with the §5.2 ordering rules enforced.
8
+ *
9
+ * Three things are checked, in order:
10
+ *
11
+ * 1. **Scope.** A delta whose `sessionId`/`mapId` disagrees with the base state
12
+ * belongs to a different event and is discarded.
13
+ * 2. **Ordering.** `updatedAt` is REQUIRED to be strictly increasing per
14
+ * session/map, so a delta at or before the state's timestamp is stale.
15
+ * 3. **Gaps.** Reported only when `sequenceOf` yields a number and it skips
16
+ * ahead of the last one seen. This is a deliberate limitation:
17
+ * `updatedAt` is guaranteed strictly increasing but *not* contiguous, so
18
+ * "delta N+2 arrived after N" is indistinguishable from "N+1 never
19
+ * existed". Gap detection therefore needs a sequence the transport
20
+ * provides. A gap still applies the delta — showing slightly-wrong
21
+ * availability beats showing none while the consumer re-fetches.
22
+ */
23
+ export function applyStateDeltaOrdered(base, delta, opts = {}) {
24
+ const scope = scopeMismatch(base, delta);
25
+ if (scope) {
26
+ return { state: base, outcome: 'scope-mismatch', detail: scope };
27
+ }
28
+ if (delta.updatedAt === base.updatedAt) {
29
+ return {
30
+ state: base,
31
+ outcome: 'duplicate',
32
+ detail: `Delta updatedAt "${delta.updatedAt}" equals the current state's; ignored.`,
33
+ };
34
+ }
35
+ if (delta.updatedAt < base.updatedAt) {
36
+ return {
37
+ state: base,
38
+ outcome: 'stale',
39
+ detail: `Delta updatedAt "${delta.updatedAt}" precedes the current state's ` +
40
+ `"${base.updatedAt}"; discarded (§5.2).`,
41
+ };
42
+ }
43
+ const state = {
44
+ ...base,
45
+ updatedAt: delta.updatedAt,
46
+ seats: { ...base.seats, ...delta.changes },
47
+ };
48
+ const sequenceOf = opts.sequenceOf ?? DEFAULT_SEQUENCE_READER;
49
+ const seq = sequenceOf(delta);
50
+ if (seq !== undefined && opts.lastSequence !== undefined && seq > opts.lastSequence + 1) {
51
+ return {
52
+ state,
53
+ outcome: 'gap',
54
+ detail: `Delta sequence ${seq} follows ${opts.lastSequence}: ` +
55
+ `${seq - opts.lastSequence - 1} message(s) were lost. Re-fetch a full ` +
56
+ 'KerusiState (§5.2).',
57
+ };
58
+ }
59
+ return { state, outcome: 'applied' };
60
+ }
61
+ /** Why the delta does not belong to this state, or `undefined` when it does. */
62
+ function scopeMismatch(base, delta) {
63
+ if (base.sessionId !== undefined && delta.sessionId !== undefined) {
64
+ return base.sessionId === delta.sessionId
65
+ ? undefined
66
+ : `Delta targets session "${delta.sessionId}", state is "${base.sessionId}".`;
67
+ }
68
+ if (base.mapId !== undefined && delta.mapId !== undefined) {
69
+ return base.mapId === delta.mapId
70
+ ? undefined
71
+ : `Delta targets map "${delta.mapId}", state is "${base.mapId}".`;
72
+ }
73
+ // One side is scoped by session and the other by map: they cannot be
74
+ // compared, so accept rather than invent a mismatch.
75
+ return undefined;
76
+ }
77
+ /**
78
+ * Reverts every seat whose hold has lapsed to `"available"` (§5.1). Returns the
79
+ * same state object when nothing changed, so a signal reading it does not fire.
80
+ *
81
+ * A `held` seat with no `holdExpires` never lapses — the spec makes the field
82
+ * optional, and an absent expiry means the hold is managed elsewhere.
83
+ */
84
+ export function expireHolds(state, now = new Date()) {
85
+ const nowMs = typeof now === 'string' ? Date.parse(now) : now.getTime();
86
+ let changed = false;
87
+ const seats = {};
88
+ for (const [seatId, status] of Object.entries(state.seats ?? {})) {
89
+ if (status.status === 'held' && status.holdExpires && Date.parse(status.holdExpires) <= nowMs) {
90
+ seats[seatId] = { status: 'available' };
91
+ changed = true;
92
+ }
93
+ else {
94
+ seats[seatId] = status;
95
+ }
96
+ }
97
+ return changed ? { ...state, seats } : state;
98
+ }
99
+ /**
100
+ * Every currently-held seat, soonest to expire first.
101
+ *
102
+ * A binding renders these as countdowns; the arithmetic is the same wherever
103
+ * it runs, so it lives here rather than in each store. Seats held without a
104
+ * `holdExpires` are omitted — there is no countdown to show.
105
+ */
106
+ export function heldSeats(state, now = new Date()) {
107
+ const nowMs = typeof now === 'string' ? Date.parse(now) : now.getTime();
108
+ return Object.entries(state.seats ?? {})
109
+ .filter(([, s]) => s.status === 'held' && !!s.holdExpires)
110
+ .map(([seatId, s]) => ({
111
+ seatId,
112
+ expiresAt: s.holdExpires,
113
+ msRemaining: Date.parse(s.holdExpires) - nowMs,
114
+ }))
115
+ .sort((a, b) => a.msRemaining - b.msRemaining);
116
+ }
117
+ //# sourceMappingURL=kerusi-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kerusi-state.js","sourceRoot":"","sources":["../../src/kerusi/kerusi-state.ts"],"names":[],"mappings":"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"}