@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.
- package/LICENSE +22 -0
- package/README.md +68 -0
- package/dist/kerusi/index.d.ts +19 -0
- package/dist/kerusi/index.d.ts.map +1 -0
- package/dist/kerusi/index.js +19 -0
- package/dist/kerusi/index.js.map +1 -0
- package/dist/kerusi/kerusi-layout-mode.d.ts +58 -0
- package/dist/kerusi/kerusi-layout-mode.d.ts.map +1 -0
- package/dist/kerusi/kerusi-layout-mode.js +117 -0
- package/dist/kerusi/kerusi-layout-mode.js.map +1 -0
- package/dist/kerusi/kerusi-locale.d.ts +43 -0
- package/dist/kerusi/kerusi-locale.d.ts.map +1 -0
- package/dist/kerusi/kerusi-locale.js +111 -0
- package/dist/kerusi/kerusi-locale.js.map +1 -0
- package/dist/kerusi/kerusi-map.model.d.ts +258 -0
- package/dist/kerusi/kerusi-map.model.d.ts.map +1 -0
- package/dist/kerusi/kerusi-map.model.js +11 -0
- package/dist/kerusi/kerusi-map.model.js.map +1 -0
- package/dist/kerusi/kerusi-price.d.ts +22 -0
- package/dist/kerusi/kerusi-price.d.ts.map +1 -0
- package/dist/kerusi/kerusi-price.js +48 -0
- package/dist/kerusi/kerusi-price.js.map +1 -0
- package/dist/kerusi/kerusi-rows.d.ts +25 -0
- package/dist/kerusi/kerusi-rows.d.ts.map +1 -0
- package/dist/kerusi/kerusi-rows.js +36 -0
- package/dist/kerusi/kerusi-rows.js.map +1 -0
- package/dist/kerusi/kerusi-state.d.ts +85 -0
- package/dist/kerusi/kerusi-state.d.ts.map +1 -0
- package/dist/kerusi/kerusi-state.js +117 -0
- package/dist/kerusi/kerusi-state.js.map +1 -0
- package/dist/kerusi/kerusi-state.model.d.ts +79 -0
- package/dist/kerusi/kerusi-state.model.d.ts.map +1 -0
- package/dist/kerusi/kerusi-state.model.js +7 -0
- package/dist/kerusi/kerusi-state.model.js.map +1 -0
- package/dist/kerusi/kerusi-validator.d.ts +96 -0
- package/dist/kerusi/kerusi-validator.d.ts.map +1 -0
- package/dist/kerusi/kerusi-validator.js +525 -0
- package/dist/kerusi/kerusi-validator.js.map +1 -0
- package/dist/kerusi/kerusi-violation.d.ts +36 -0
- package/dist/kerusi/kerusi-violation.d.ts.map +1 -0
- package/dist/kerusi/kerusi-violation.js +13 -0
- package/dist/kerusi/kerusi-violation.js.map +1 -0
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +13 -0
- package/dist/public-api.js.map +1 -0
- package/dist/render/build-render-model.d.ts +36 -0
- package/dist/render/build-render-model.d.ts.map +1 -0
- package/dist/render/build-render-model.js +258 -0
- package/dist/render/build-render-model.js.map +1 -0
- package/dist/render/element-shapes.d.ts +34 -0
- package/dist/render/element-shapes.d.ts.map +1 -0
- package/dist/render/element-shapes.js +41 -0
- package/dist/render/element-shapes.js.map +1 -0
- package/dist/render/index.d.ts +14 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +14 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/navigation-order.d.ts +49 -0
- package/dist/render/navigation-order.d.ts.map +1 -0
- package/dist/render/navigation-order.js +109 -0
- package/dist/render/navigation-order.js.map +1 -0
- package/dist/render/render-model.d.ts +151 -0
- package/dist/render/render-model.d.ts.map +1 -0
- package/dist/render/render-model.js +2 -0
- package/dist/render/render-model.js.map +1 -0
- package/dist/render/seat-shapes.d.ts +72 -0
- package/dist/render/seat-shapes.d.ts.map +1 -0
- package/dist/render/seat-shapes.js +161 -0
- package/dist/render/seat-shapes.js.map +1 -0
- package/dist/render/section-layout.d.ts +66 -0
- package/dist/render/section-layout.d.ts.map +1 -0
- package/dist/render/section-layout.js +173 -0
- package/dist/render/section-layout.js.map +1 -0
- package/dist/view/index.d.ts +12 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +12 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/kerusi-seatmap-colors.d.ts +176 -0
- package/dist/view/kerusi-seatmap-colors.d.ts.map +1 -0
- package/dist/view/kerusi-seatmap-colors.js +215 -0
- package/dist/view/kerusi-seatmap-colors.js.map +1 -0
- package/dist/view/rtl.d.ts +17 -0
- package/dist/view/rtl.d.ts.map +1 -0
- package/dist/view/rtl.js +29 -0
- package/dist/view/rtl.js.map +1 -0
- package/dist/view/seat-aria.d.ts +44 -0
- package/dist/view/seat-aria.d.ts.map +1 -0
- package/dist/view/seat-aria.js +103 -0
- package/dist/view/seat-aria.js.map +1 -0
- package/dist/view/selection.d.ts +84 -0
- package/dist/view/selection.d.ts.map +1 -0
- package/dist/view/selection.js +98 -0
- package/dist/view/selection.js.map +1 -0
- 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":"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"}
|
|
@@ -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":"AA2CA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,uBAAuB,GAAmB,CAAC,KAAK,EAAE,EAAE;IAC/D,MAAM,GAAG,GAAI,KAAgD,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;IAChF,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAiB,EACjB,KAAuB,EACvB,OAA+D,EAAE;IAEjE,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACnE,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,OAAO;YACL,KAAK,EAAE,IAAI;YACX,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,oBAAoB,KAAK,CAAC,SAAS,wCAAwC;SACpF,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QACrC,OAAO;YACL,KAAK,EAAE,IAAI;YACX,OAAO,EAAE,OAAO;YAChB,MAAM,EACJ,oBAAoB,KAAK,CAAC,SAAS,iCAAiC;gBACpE,IAAI,IAAI,CAAC,SAAS,sBAAsB;SAC3C,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAgB;QACzB,GAAG,IAAI;QACP,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE;KAC3C,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,uBAAuB,CAAC;IAC9D,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QACxF,OAAO;YACL,KAAK;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EACJ,kBAAkB,GAAG,YAAY,IAAI,CAAC,YAAY,IAAI;gBACtD,GAAG,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,yCAAyC;gBACvE,qBAAqB;SACxB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACvC,CAAC;AAED,gFAAgF;AAChF,SAAS,aAAa,CAAC,IAAiB,EAAE,KAAuB;IAC/D,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;YACvC,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,0BAA0B,KAAK,CAAC,SAAS,gBAAgB,IAAI,CAAC,SAAS,IAAI,CAAC;IAClF,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK;YAC/B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,sBAAsB,KAAK,CAAC,KAAK,gBAAgB,IAAI,CAAC,KAAK,IAAI,CAAC;IACtE,CAAC;IACD,qEAAqE;IACrE,qDAAqD;IACrD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAkB,EAAE,MAAqB,IAAI,IAAI,EAAE;IAC7E,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACxE,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,KAAK,GAA+B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACjE,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,KAAK,EAAE,CAAC;YAC9F,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/C,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,KAAkB,EAClB,MAAqB,IAAI,IAAI,EAAE;IAE/B,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACxE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;SACrC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;SACzD,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrB,MAAM;QACN,SAAS,EAAE,CAAC,CAAC,WAAY;QACzB,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,WAAY,CAAC,GAAG,KAAK;KAChD,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;AACnD,CAAC"}
|