@murumets-ee/yhikas-sync 0.92.0 → 0.94.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 (57) hide show
  1. package/README.md +108 -0
  2. package/dist/client-J_w85GUr.mjs +2 -0
  3. package/dist/client-J_w85GUr.mjs.map +1 -0
  4. package/dist/config-DM3zAAhm.d.mts +244 -0
  5. package/dist/config-DM3zAAhm.d.mts.map +1 -0
  6. package/dist/constants-Bmm3P17G.mjs +2 -0
  7. package/dist/constants-Bmm3P17G.mjs.map +1 -0
  8. package/dist/en-BOWwWf-X.mjs +2 -0
  9. package/dist/en-BOWwWf-X.mjs.map +1 -0
  10. package/dist/et-CGDUOj83.mjs +2 -0
  11. package/dist/et-CGDUOj83.mjs.map +1 -0
  12. package/dist/i18n.d.mts +18 -0
  13. package/dist/i18n.d.mts.map +1 -0
  14. package/dist/i18n.mjs +2 -0
  15. package/dist/i18n.mjs.map +1 -0
  16. package/dist/index.d.mts +360 -900
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +2 -1
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/jobs-Di4Z8go9.mjs +2 -0
  21. package/dist/jobs-Di4Z8go9.mjs.map +1 -0
  22. package/dist/plugin.d.mts +6 -4
  23. package/dist/plugin.d.mts.map +1 -1
  24. package/dist/plugin.mjs +1 -1
  25. package/dist/plugin.mjs.map +1 -1
  26. package/dist/rolldown-runtime-DK3Fl9T5.mjs +1 -0
  27. package/dist/room-page-registry-BFbyGTC7.mjs +2 -0
  28. package/dist/room-page-registry-BFbyGTC7.mjs.map +1 -0
  29. package/dist/room-page-registry-CnqXH_Ls.d.mts +974 -0
  30. package/dist/room-page-registry-CnqXH_Ls.d.mts.map +1 -0
  31. package/dist/room-pages-adapters-DxFWkFFF.mjs +2 -0
  32. package/dist/room-pages-adapters-DxFWkFFF.mjs.map +1 -0
  33. package/dist/room-pages-table-Vd9BqTSl.mjs +2 -0
  34. package/dist/room-pages-table-Vd9BqTSl.mjs.map +1 -0
  35. package/dist/room-type-DtMaQbza.mjs +2 -0
  36. package/dist/room-type-DtMaQbza.mjs.map +1 -0
  37. package/dist/ru-uzvamDhj.mjs +2 -0
  38. package/dist/ru-uzvamDhj.mjs.map +1 -0
  39. package/dist/sync-state-table-CZjRypK0.mjs +2 -0
  40. package/dist/sync-state-table-CZjRypK0.mjs.map +1 -0
  41. package/dist/widgets-client.d.mts +27 -0
  42. package/dist/widgets-client.d.mts.map +1 -0
  43. package/dist/widgets-client.mjs +3 -0
  44. package/dist/widgets-client.mjs.map +1 -0
  45. package/dist/widgets.d.mts +73 -0
  46. package/dist/widgets.d.mts.map +1 -0
  47. package/dist/widgets.mjs +2 -0
  48. package/dist/widgets.mjs.map +1 -0
  49. package/package.json +41 -7
  50. package/dist/config-CHVLiXZf.d.mts +0 -77
  51. package/dist/config-CHVLiXZf.d.mts.map +0 -1
  52. package/dist/jobs-QeVJDpW9.mjs +0 -2
  53. package/dist/jobs-QeVJDpW9.mjs.map +0 -1
  54. package/dist/sync-state-table-BrcoCu-Z.mjs +0 -2
  55. package/dist/sync-state-table-BrcoCu-Z.mjs.map +0 -1
  56. package/dist/watchdog-CcOXpzE3.mjs +0 -2
  57. package/dist/watchdog-CcOXpzE3.mjs.map +0 -1
@@ -0,0 +1,244 @@
1
+ //#region src/room-offers.d.ts
2
+ /**
3
+ * Which synced room types are room OFFERS — rooms a visitor can rent — and
4
+ * which are units yhikas-admin merely prices.
5
+ *
6
+ * Moved here from the yhikas site's `lib/room-prices.ts` (block-library PR21),
7
+ * because two things in this package now ask the question: the room-page
8
+ * drafts (`roomPages`, one page per offered room, never one per unit) and the
9
+ * `yhikasRoomFacts()` picker. THE one list — a site that needs a different
10
+ * rule passes `roomPages.offered`, rather than keeping a second copy.
11
+ *
12
+ * Pure module: no imports, no I/O. Reached from the jiti-loaded plugin entry.
13
+ */
14
+ /**
15
+ * Names of the units yhikas-admin prices by area — ateliers and practice
16
+ * rooms. They are room TYPES upstream but not room OFFERS: the rooms list
17
+ * leaves them out and they never set a "from" price. Upstream has no column
18
+ * that says which is which, so the name is the only signal; both locales are
19
+ * listed because a row may be read in either.
20
+ */
21
+ declare const AREA_PRICED_UNIT_NAMES: ReadonlySet<string>;
22
+ /** The shape {@link isRoomOffer} and `roomPages.offered` are asked about. */
23
+ interface RoomOfferCandidate {
24
+ readonly code: string;
25
+ readonly name: string;
26
+ }
27
+ /**
28
+ * True for a room type a visitor can rent as a room — not an area-priced unit.
29
+ * The default for `roomPages.offered` and for `yhikasRoomFacts({ offered })`.
30
+ */
31
+ declare function isRoomOffer(room: RoomOfferCandidate): boolean;
32
+ /** The predicate a caller passes to decide what is offered. */
33
+ type RoomOfferPredicate = (room: RoomOfferCandidate) => boolean;
34
+ //#endregion
35
+ //#region src/room-type-view.d.ts
36
+ /**
37
+ * A synced room type as the room-page drafts and the room facts read it — a
38
+ * narrowed, typed view of one `yhikas_room_type` row.
39
+ *
40
+ * Pure module: no imports at runtime, so the jiti-loaded config can name the
41
+ * type and the tests can build one without a database.
42
+ */
43
+ /**
44
+ * One room type, in ONE locale (`name` is that locale's). What
45
+ * `roomPages.draft(room, locale)` receives.
46
+ *
47
+ * Money and area values are the DECIMAL STRINGS the sync stores (see
48
+ * `entities/room-type.ts` — a price never goes through a binary float on its
49
+ * way in). `null` is data: upstream had no value, and nothing invents one.
50
+ */
51
+ interface RoomTypeForPage {
52
+ /** The `yhikas_room_type` row id. */
53
+ readonly id: string;
54
+ /** The business key — `2ne-1koht`, `L12`. */
55
+ readonly code: string;
56
+ /** The name in the locale this view was read in. */
57
+ readonly name: string;
58
+ readonly status: string | null;
59
+ readonly capacity: number | null;
60
+ readonly placesOccupied: number | null;
61
+ readonly totalArea: string | null;
62
+ readonly livingArea: string | null;
63
+ readonly commonArea: string | null;
64
+ readonly monthlyRent: string | null;
65
+ readonly discountedMonthlyRent: string | null;
66
+ readonly dailyRent: string | null;
67
+ /** Declared by the sync (`EUR`), not data upstream. */
68
+ readonly currency: string;
69
+ }
70
+ /**
71
+ * Narrow one stored row. `undefined` when it carries no id or no code — a row
72
+ * nothing can be keyed on is not a room this package can act for.
73
+ */
74
+ declare function roomTypeForPageFrom(row: Readonly<Record<string, unknown>>): RoomTypeForPage | undefined;
75
+ /** Published — the only state in which a room type is still offered by yhikas-admin. */
76
+ declare function isPublishedRoomType(room: Pick<RoomTypeForPage, 'status'>): boolean;
77
+ //#endregion
78
+ //#region src/config.d.ts
79
+ /**
80
+ * One block of a room page's draft tree, structurally — the stored shape a
81
+ * `field.blockTree()` holds (`{ id, type, props, traits, children }`). Loose
82
+ * in `props` on purpose: a block's props are its own fields, which only the
83
+ * site's block definitions know. The tree is validated where every tree is,
84
+ * by the page entity's write path.
85
+ */
86
+ interface RoomPageBlock {
87
+ readonly id: string;
88
+ readonly type: string;
89
+ readonly props: Readonly<Record<string, unknown>>;
90
+ readonly traits?: Readonly<Record<string, unknown>>;
91
+ readonly children?: readonly RoomPageBlock[];
92
+ }
93
+ /** A new room page's title and block tree, in one locale. */
94
+ interface RoomPageDraft {
95
+ readonly title: string;
96
+ readonly layout: readonly RoomPageBlock[];
97
+ }
98
+ /**
99
+ * Every offered room is an ordinary CMS page (block-library D010), a child of
100
+ * the room list page, edited on the canvas; its prices are a live facts block
101
+ * bound to `yhikasRoomFacts()`.
102
+ *
103
+ * Setting this does three things:
104
+ *
105
+ * 1. The room type entity declares NO frontend path — a room is not routable
106
+ * from its synced row; its page owns `/<parentSlug>/<code>`. (Legal
107
+ * documents keep following `detailPages`, which no longer affects room
108
+ * types.)
109
+ * 2. After every successful room-types sync, a DRAFT page is created for each
110
+ * published, offered room type that has none — once, ever (see
111
+ * `yhikas_room_pages`): never published, never overwritten, never
112
+ * re-created after an editor empties or deletes it.
113
+ * 3. The admin dashboard shows the room pages waiting for text and photos,
114
+ * and the published room pages whose room yhikas-admin has retired.
115
+ */
116
+ interface YhikasRoomPagesConfig {
117
+ /** The page entity. Default `'page'`. */
118
+ readonly entity?: string;
119
+ /** The room list page's slug, in the default locale — room pages are its children. `'toad'` on yhikas. */
120
+ readonly parentSlug: string;
121
+ /**
122
+ * Which room types get a page. Default {@link isRoomOffer}: every room type
123
+ * except the area-priced units (ateliers, practice rooms). Asked with the
124
+ * default-locale name.
125
+ */
126
+ readonly offered?: RoomOfferPredicate;
127
+ /** The new DRAFT page's title and block tree, per locale. Called once per locale, when the page is created. */
128
+ readonly draft: (room: RoomTypeForPage, locale: string) => RoomPageDraft;
129
+ /**
130
+ * yhikas-admin's PUBLIC base URL (`https:`), for the "change it in
131
+ * yhikas-admin" link beside a room's facts. Not `baseUrl` — that is the
132
+ * Docker-internal origin the sync pulls from, which no browser can reach.
133
+ */
134
+ readonly adminUrl?: string;
135
+ }
136
+ /** {@link YhikasRoomPagesConfig}, validated and defaulted. */
137
+ interface ResolvedRoomPagesConfig {
138
+ readonly entity: string;
139
+ readonly parentSlug: string;
140
+ readonly offered: RoomOfferPredicate;
141
+ readonly draft: (room: RoomTypeForPage, locale: string) => RoomPageDraft;
142
+ readonly adminUrl: string | undefined;
143
+ }
144
+ interface YhikasSyncConfig {
145
+ /**
146
+ * Origin of the yhikas-admin deployment, e.g. `http://yhikas-admin:3000`
147
+ * over the Docker-internal network. Falls back to `YHIKAS_ADMIN_BASE_URL`.
148
+ */
149
+ baseUrl?: string;
150
+ /** Cron/interval expression for the pull. Default `@every 6h` (D017). */
151
+ schedule?: string;
152
+ /** Cron/interval expression for the staleness watchdog. Default `@every 1h`. */
153
+ watchdogSchedule?: string;
154
+ /** Alert when the newest successful sync is older than this. Default 24h. */
155
+ staleAfterMs?: number;
156
+ /** Per-request deadline. The queue has NO per-job timeout (R012 §3). Default 15s. */
157
+ requestTimeoutMs?: number;
158
+ /** Refuse a snapshot larger than this rather than truncating it (D016). */
159
+ maxRowsPerResource?: number;
160
+ /** Refuse a run retiring more than this fraction of published rows. Default 0.5. */
161
+ maxRetireFraction?: number;
162
+ /** The fraction rule only applies once at least this many rows are published. Default 3. */
163
+ minRowsForRetireFloor?: number;
164
+ /**
165
+ * Explicit opt-out. An unconfigured sync is a BROKEN sync, and a broken sync
166
+ * must be visibly broken (S7) — so an absent credential fails the job loudly
167
+ * rather than quietly skipping registration. Set this to `false` when the
168
+ * install genuinely has no yhikas-admin to talk to.
169
+ */
170
+ enabled?: boolean;
171
+ /**
172
+ * Who draws a room's and a legal document's detail page.
173
+ *
174
+ * The default is the toolkit's: each entity's stored detail template
175
+ * (`drawnBy: 'template'`, block-library D009), which also narrows the public
176
+ * content API to what the template draws. A site that draws those pages with
177
+ * hand-built routes says so here, WITH a reason — the opt-out is reported at
178
+ * start-up (`handBuiltDetailPages`), so it is a recorded decision rather
179
+ * than a default (block-library PR19).
180
+ *
181
+ * @example
182
+ * yhikasSync({ detailPages: { drawnBy: 'site', reason: 'Hand-built Afišš room and document pages (owner, 2026-10-02)' } })
183
+ */
184
+ detailPages?: {
185
+ readonly drawnBy: 'template';
186
+ } | {
187
+ readonly drawnBy: 'site';
188
+ readonly reason: string;
189
+ };
190
+ /**
191
+ * Rooms as ordinary CMS pages (block-library PR21, D010). With this set,
192
+ * `detailPages` applies to legal documents only. See
193
+ * {@link YhikasRoomPagesConfig}.
194
+ *
195
+ * @example
196
+ * yhikasSync({
197
+ * roomPages: {
198
+ * parentSlug: 'toad',
199
+ * adminUrl: 'https://admin.yhikas.ee',
200
+ * draft: (room, locale) => ({ title: room.name, layout: roomPageTree(room, locale) }),
201
+ * },
202
+ * })
203
+ */
204
+ roomPages?: YhikasRoomPagesConfig;
205
+ }
206
+ interface ResolvedYhikasSyncConfig {
207
+ readonly baseUrl: string | undefined;
208
+ readonly schedule: string;
209
+ readonly watchdogSchedule: string;
210
+ readonly staleAfterMs: number;
211
+ readonly requestTimeoutMs: number;
212
+ readonly maxRowsPerResource: number;
213
+ readonly maxRetireFraction: number;
214
+ readonly minRowsForRetireFloor: number;
215
+ readonly enabled: boolean;
216
+ readonly detailPages: {
217
+ readonly drawnBy: 'template';
218
+ } | {
219
+ readonly drawnBy: 'site';
220
+ readonly reason: string;
221
+ };
222
+ /** `undefined` when the site does not keep its rooms as pages. */
223
+ readonly roomPages: ResolvedRoomPagesConfig | undefined;
224
+ }
225
+ /** Thrown at plugin construction for a structurally invalid config. */
226
+ declare class YhikasSyncConfigError extends Error {
227
+ constructor(message: string);
228
+ }
229
+ /**
230
+ * Resolve config + environment into the frozen object the plugin declares.
231
+ *
232
+ * Throws only on a STRUCTURALLY invalid config — a nonsensical number. It never
233
+ * throws for a missing credential or a missing base URL: `lumi.config.ts` is
234
+ * loaded at build time by the CLI, and throwing there breaks every `lumi`
235
+ * command on a machine that simply has no secrets (see
236
+ * `gotcha_lumi_config_throw_at_build_time`). A missing credential surfaces at
237
+ * job-run time, where it becomes an alert.
238
+ */
239
+ declare function resolveYhikasSyncConfig(config?: YhikasSyncConfig, env?: NodeJS.ProcessEnv): ResolvedYhikasSyncConfig;
240
+ /** A page slug — the grammar the page entity's slugs follow. */
241
+ declare const PAGE_SLUG_PATTERN: RegExp;
242
+ //#endregion
243
+ export { RoomPageDraft as a, YhikasSyncConfigError as c, isPublishedRoomType as d, roomTypeForPageFrom as f, isRoomOffer as g, RoomOfferPredicate as h, RoomPageBlock as i, resolveYhikasSyncConfig as l, RoomOfferCandidate as m, ResolvedRoomPagesConfig as n, YhikasRoomPagesConfig as o, AREA_PRICED_UNIT_NAMES as p, ResolvedYhikasSyncConfig as r, YhikasSyncConfig as s, PAGE_SLUG_PATTERN as t, RoomTypeForPage as u };
244
+ //# sourceMappingURL=config-DM3zAAhm.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-DM3zAAhm.d.mts","names":[],"sources":["../src/room-offers.ts","../src/room-type-view.ts","../src/config.ts"],"mappings":";;AAoBA;;;;AAAgD;AAQhD;;;;AAEe;AAOf;;;;AAAoD;AAKpD;;;cAtBa,sBAAA,EAAwB,WAAW;AAsBU;AAAA,UAdzC,kBAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAI;AAAA;;;;;iBAOC,WAAA,CAAY,IAAwB,EAAlB,kBAAkB;;KAKxC,kBAAA,IAAsB,IAAwB,EAAlB,kBAAkB;;;;AAtB1D;;;;AAAgD;AAQhD;;;;AAEe;AAOf;;;;UCrBiB,eAAA;ED0BL;EAAA,SCxBD,EAAA;;WAEA,IAAA;EDsB+C;EAAA,SCpB/C,IAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA;EAAA,SACA,cAAA;EAAA,SACA,SAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;EAAA,SACA,qBAAA;EAAA,SACA,SAAA;EARA;EAAA,SAUA,QAAA;AAAA;;;;;iBAWK,mBAAA,CACd,GAAA,EAAK,QAAA,CAAS,MAAA,qBACb,eAAA;;iBAsBa,mBAAA,CAAoB,IAAA,EAAM,IAAI,CAAC,eAAA;;;;;;AApD/C;;;;UCwBiB,aAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA,EAAO,QAAA,CAAS,MAAA;EAAA,SAChB,MAAA,GAAS,QAAA,CAAS,MAAA;EAAA,SAClB,QAAA,YAAoB,aAAA;AAAA;;UAId,aAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA,WAAiB,aAAa;AAAA;;;ADlBtB;AAWnB;;;;;;;;;;;;;AAEkB;AAsBlB;UCIiB,qBAAA;;WAEN,MAAA;EDN+B;EAAA,SCQ/B,UAAA;EDRyB;;AAAqC;;;EAArC,SCczB,OAAA,GAAU,kBAAA;EA1CJ;EAAA,SA4CN,KAAA,GAAQ,IAAA,EAAM,eAAA,EAAiB,MAAA,aAAmB,aAAA;;;;;;WAMlD,QAAA;AAAA;;UAIM,uBAAA;EAAA,SACN,MAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA,EAAS,kBAAA;EAAA,SACT,KAAA,GAAQ,IAAA,EAAM,eAAA,EAAiB,MAAA,aAAmB,aAAA;EAAA,SAClD,QAAA;AAAA;AAAA,UAGM,gBAAA;EAzDN;;;AAAiC;EA8D1C,OAAA;EA1D4B;EA4D5B,QAAA;EA1DuC;EA4DvC,gBAAA;EA5DS;EA8DT,YAAA;EA9DuC;EAgEvC,gBAAA;EA3Ce;EA6Cf,kBAAA;;EAEA,iBAAA;EAnCuB;EAqCvB,qBAAA;EArCwE;;;;;;EA4CxE,OAAA;EA5CuB;;;;;;AAMN;AAInB;;;;;;EAgDE,WAAA;IAAA,SACe,OAAA;EAAA;IAAA,SACA,OAAA;IAAA,SAA0B,MAAA;EAAA;EA9ChC;;;;;;;AACQ;AAGnB;;;;;;EAyDE,SAAA,GAAY,qBAAqB;AAAA;AAAA,UAGlB,wBAAA;EAAA,SACN,OAAA;EAAA,SACA,QAAA;EAAA,SACA,gBAAA;EAAA,SACA,YAAA;EAAA,SACA,gBAAA;EAAA,SACA,kBAAA;EAAA,SACA,iBAAA;EAAA,SACA,qBAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;IAAA,SACM,OAAA;EAAA;IAAA,SACA,OAAA;IAAA,SAA0B,MAAA;EAAA;EAEE;EAAA,SAAlC,SAAA,EAAW,uBAAuB;AAAA;;cAIhC,qBAAA,SAA8B,KAAK;cAClC,OAAA;AAAA;;;;;;;;;;;iBAoCE,uBAAA,CACd,MAAA,GAAQ,gBAAA,EACR,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,wBAAA;AAxCH;AAAA,cAmGa,iBAAA,EAAiB,MAAiB"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./rolldown-runtime-DK3Fl9T5.mjs";var t=e({API_KEY_ENV_VAR:()=>w,BASE_URL_ENV_VAR:()=>T,DECLARED_CURRENCY:()=>`EUR`,DECLARED_VAT_TREATMENT:()=>`net`,DEFAULT_MAX_RETIRE_FRACTION:()=>y,DEFAULT_MAX_ROWS_PER_RESOURCE:()=>500,DEFAULT_MIN_ROWS_FOR_RETIRE_FLOOR:()=>3,DEFAULT_REQUEST_TIMEOUT_MS:()=>v,DEFAULT_STALE_AFTER_MS:()=>_,DEFAULT_SYNC_SCHEDULE:()=>h,DEFAULT_WATCHDOG_SCHEDULE:()=>g,EN_LOCALE:()=>`en`,ET_LOCALE:()=>`et`,LEGAL_DOCUMENTS_PATH:()=>x,LEGAL_DOCUMENTS_RESOURCE:()=>s,MAX_SITE_NOTICES:()=>25,ROOM_TYPES_PATH:()=>b,ROOM_TYPES_RESOURCE:()=>o,SITE_INFO_KEY:()=>u,SITE_INFO_PATH:()=>S,SITE_INFO_RESOURCE:()=>c,SYNC_JOB_NAME:()=>i,SYNC_RESOURCES:()=>l,WATCHDOG_JOB_NAME:()=>a,YHIKAS_SYNC_ACTOR_ID:()=>r,YHIKAS_SYNC_PLUGIN_NAME:()=>n});const n=`@murumets-ee/yhikas-sync`,r=`yhikas-sync`,i=`yhikas-sync:pull`,a=`yhikas-sync:staleness-watchdog`,o=`room_types`,s=`legal_documents`,c=`site_info`,l=[o,s,c],u=`default`,d=`et`,f=`en`,p=`EUR`,m=`net`,h=`@every 6h`,g=`@every 1h`,_=1440*60*1e3,v=15e3,y=.5,b=`/api/public/room-types`,x=`/api/public/legal-documents`,S=`/api/public/site-info`,C=25,w=`YHIKAS_SYNC_API_KEY`,T=`YHIKAS_ADMIN_BASE_URL`;export{r as C,a as S,t as T,u as _,y as a,i as b,h as c,d,x as f,o as g,b as h,m as i,g as l,C as m,T as n,v as o,s as p,p as r,_ as s,w as t,f as u,S as v,n as w,l as x,c as y};
2
+ //# sourceMappingURL=constants-Bmm3P17G.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants-Bmm3P17G.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Names, identifiers and stated numbers for the yhikas-admin sync.\n *\n * Pure module — no imports, no side effects. Both the jiti-loaded plugin entry\n * and the worker-side job modules read from here, so nothing in this file may\n * reach a `server-only` module.\n */\n\n/** Plugin name, as it appears in `app.plugins` and `Plugin.requires`. */\nexport const YHIKAS_SYNC_PLUGIN_NAME = '@murumets-ee/yhikas-sync'\n\n/**\n * The synthetic actor every sync write is attributed to.\n *\n * Not `'cli'`. `auditable()` records this in `created_by`/`updated_by`, so a\n * human reading the audit log can tell a sync write from an operator running a\n * `lumi` command — and the machine-written guard (see `entities/guard.ts`) can\n * refuse every other writer by name. Fits `varchar(255)`; user ids in this\n * codebase are varchar, never uuid.\n */\nexport const YHIKAS_SYNC_ACTOR_ID = 'yhikas-sync'\n\n/**\n * Job names. `JOB_NAME_RE` in `@murumets-ee/queue` is\n * `/^[a-zA-Z0-9:_-]{1,128}$/` — colons are the `plugin:action` separator and\n * dots are rejected outright.\n */\nexport const SYNC_JOB_NAME = 'yhikas-sync:pull'\nexport const WATCHDOG_JOB_NAME = 'yhikas-sync:staleness-watchdog'\n\n/**\n * The three resources in scope.\n *\n * `site_info` was deliberately absent until 2026-08-11 (D027). PR 05 left it\n * out on a MEASUREMENT — `site_info`/`site_notice` appear only in\n * yhikas-admin's `src/db/schema.ts` and its public route, with no admin UI, no\n * server action, no seed and no internal consumer — from which the plan\n * concluded that no rows existed and a sync would pull nothing.\n *\n * Measured against production on 2026-08-11 (R028), that premise had expired:\n * the route answers 200 with populated reception hours and two live notices.\n * The rows were written outside the app. Hardcoding them in the rebuilt site\n * would therefore have frozen live data, not stood in for an empty upstream.\n */\nexport const ROOM_TYPES_RESOURCE = 'room_types'\nexport const LEGAL_DOCUMENTS_RESOURCE = 'legal_documents'\nexport const SITE_INFO_RESOURCE = 'site_info'\n\nexport const SYNC_RESOURCES = [\n ROOM_TYPES_RESOURCE,\n LEGAL_DOCUMENTS_RESOURCE,\n SITE_INFO_RESOURCE,\n] as const\nexport type SyncResource = (typeof SYNC_RESOURCES)[number]\n\n/**\n * The business key of the one `yhikas_site_info` row.\n *\n * Upstream `site_info` is a singleton and its notices carry no id or code of\n * their own — only `{text, isActive, order}` — so there is no natural key to\n * diff N rows on. Modelling it as ONE row with a constant key keeps the whole\n * package on a single identity story (`code`, `type`, and now `key`), and\n * keeps the notices ordered without inventing an identity that upstream would\n * not preserve across an edit.\n */\nexport const SITE_INFO_KEY = 'default'\n\n/**\n * Locale codes. Upstream `MultilingualText` is `{ en, et }` and the lumi codes\n * are identical, so there is no mapping to configure — inventing one would be\n * a seam for a second customer this package does not have (N1).\n */\nexport const ET_LOCALE = 'et'\nexport const EN_LOCALE = 'en'\n\n/**\n * Currency and VAT treatment, DECLARED here because they are not data upstream\n * (R009 §6 / R013): `room_type` has no currency column and no tax column, and\n * VAT is computed at invoice time against Merit article codes, never stored.\n * Stating them makes a mis-entered source value render as a visibly wrong\n * number against a declared unit rather than as a plausible one.\n *\n * The third semantic — the period — stays encoded in the field NAME\n * (`monthlyRent` / `dailyRent`), because one row carries two different periods\n * and a single `period` column could only be wrong about one of them.\n */\nexport const DECLARED_CURRENCY = 'EUR'\n/** `net` = the amount excludes VAT. */\nexport const DECLARED_VAT_TREATMENT = 'net'\n\n/**\n * D017. Four successes a day against a fee schedule revised at most annually,\n * and an alert only after four consecutive failures.\n *\n * The interval form rather than a cron expression is deliberate: the queue's\n * cron parser is UTC-only with no timezone field and no DST awareness (R012\n * §4), so a wall-clock schedule would silently shift twice a year.\n */\nexport const DEFAULT_SYNC_SCHEDULE = '@every 6h'\nexport const DEFAULT_WATCHDOG_SCHEDULE = '@every 1h'\nexport const DEFAULT_STALE_AFTER_MS = 24 * 60 * 60 * 1000\n\n/** The upstream limiter allows 60/min; a run makes three calls. A 15s deadline is generous. */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 15_000\n\n/**\n * Hard ceiling on snapshot size. A snapshot ABOVE this refuses the run rather\n * than processing a prefix — truncating would turn the dropped tail into\n * apparent absences and therefore into mass retirement (D016).\n * ~25 room types today; 500 leaves three orders of magnitude of headroom.\n */\nexport const DEFAULT_MAX_ROWS_PER_RESOURCE = 500\n\n/** D016's sanity floor: refuse a run retiring more than half of at least this many published rows. */\nexport const DEFAULT_MAX_RETIRE_FRACTION = 0.5\nexport const DEFAULT_MIN_ROWS_FOR_RETIRE_FLOOR = 3\n\n/** Upstream paths. GET-only, by construction — there is no write path to name. */\nexport const ROOM_TYPES_PATH = '/api/public/room-types'\nexport const LEGAL_DOCUMENTS_PATH = '/api/public/legal-documents'\nexport const SITE_INFO_PATH = '/api/public/site-info'\n\n/**\n * Hard ceiling on ticker items. A ticker is a marquee: past a couple of dozen\n * entries nobody reads the tail, and an unbounded array from upstream would\n * ride into a JSONB column and out onto every page of the site.\n */\nexport const MAX_SITE_NOTICES = 25\n\n/**\n * Env var carrying the sync's OWN bearer credential — deliberately not the\n * site's `PUBLIC_SITE_API_KEY`. The upstream limiter buckets on\n * `sha256(key).slice(0,16)` rather than on the caller IP (there is no\n * `x-forwarded-for` over the Docker-internal path), so sharing a key means\n * sharing one 60/min bucket, and revoking one credential would blind both.\n */\nexport const API_KEY_ENV_VAR = 'YHIKAS_SYNC_API_KEY'\nexport const BASE_URL_ENV_VAR = 'YHIKAS_ADMIN_BASE_URL'\n"],"mappings":"uvBASA,MAAa,EAA0B,2BAW1B,EAAuB,cAOvB,EAAgB,mBAChB,EAAoB,iCAgBpB,EAAsB,aACtB,EAA2B,kBAC3B,EAAqB,YAErB,EAAiB,CAC5B,EACA,EACA,CACF,EAaa,EAAgB,UAOhB,EAAY,KACZ,EAAY,KAaZ,EAAoB,MAEpB,EAAyB,MAUzB,EAAwB,YACxB,EAA4B,YAC5B,EAAyB,KAAU,GAAK,IAGxC,EAA6B,KAW7B,EAA8B,GAI9B,EAAkB,yBAClB,EAAuB,8BACvB,EAAiB,wBAOjB,EAAmB,GASnB,EAAkB,sBAClB,EAAmB"}
@@ -0,0 +1,2 @@
1
+ var e={YhikasSync:{facts:{sourceLabel:`Room facts`},provenance:{recordRoomType:`room type`,cadenceEvery6Hours:`every 6 hours`,cadenceEvery12Hours:`every 12 hours`,cadenceHourly:`every hour`,cadenceDaily:`every day`},roomPages:{title:`Room pages`,waiting:`{count, plural, one {# new room waits for text and photos} other {# new rooms wait for text and photos}}`,open:`Open`,retired:`Room {code} no longer exists in yhikas-admin — unpublish?`,unpublish:`Unpublish`,unpublishing:`Unpublishing…`,unpublishFailed:`Not unpublished: {failures}. Nothing is refreshed until every language is down — try again.`,retiredAsk:`Room {code} no longer exists in yhikas-admin, but its page is still published. Someone who may publish pages should unpublish it.`,refused:`Room {code} could not get a page: {reason}`,refusedRoomTypePath:`Room {code} could not get a page: its address is still held by the room type's old URL ({reason}). Remove the yhikas_room_type rows from the path index (toolkit_paths); the next sync then creates the page.`}}};export{e as default};
2
+ //# sourceMappingURL=en-BOWwWf-X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en-BOWwWf-X.mjs","names":[],"sources":["../src/messages/en.json"],"sourcesContent":[""],"mappings":""}
@@ -0,0 +1,2 @@
1
+ var e={YhikasSync:{facts:{sourceLabel:`Toa andmed`},provenance:{recordRoomType:`toatüüp`,cadenceEvery6Hours:`iga 6 tunni järel`,cadenceEvery12Hours:`iga 12 tunni järel`,cadenceHourly:`iga tund`,cadenceDaily:`iga päev`},roomPages:{title:`Toalehed`,waiting:`{count, plural, one {# uus tuba ootab teksti ja fotosid} other {# uut tuba ootab teksti ja fotosid}}`,open:`Ava`,retired:`Tuba {code} ei ole yhikas-adminis enam olemas — kas võtta leht maha?`,unpublish:`Võta maha`,unpublishing:`Võtan maha…`,unpublishFailed:`Maha ei võetud: {failures}. Midagi ei värskendata enne, kui kõik keeled on maas — proovi uuesti.`,retiredAsk:`Tuba {code} ei ole yhikas-adminis enam olemas, aga selle leht on endiselt avaldatud. Keegi, kellel on õigus lehti avaldada, peaks selle maha võtma.`,refused:`Tuba {code} ei saanud lehte: {reason}`,refusedRoomTypePath:`Tuba {code} ei saanud lehte: selle aadressi hoiab endiselt toatüübi vana URL ({reason}). Eemalda yhikas_room_type read aadressiindeksist (toolkit_paths); järgmine sünkroonimine loob lehe.`}}};export{e as default};
2
+ //# sourceMappingURL=et-CGDUOj83.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"et-CGDUOj83.mjs","names":[],"sources":["../src/messages/et.json"],"sourcesContent":[""],"mappings":""}
@@ -0,0 +1,18 @@
1
+ //#region src/i18n.d.ts
2
+ /**
3
+ * Load `@murumets-ee/yhikas-sync` messages for a given locale — the room
4
+ * pages dashboard widget's words and the room facts source's label and
5
+ * provenance, all under `YhikasSync.*`.
6
+ *
7
+ * `./i18n` is its own tsdown entry, never the package root: this module is
8
+ * loaded by every `lumi` command at config time (via `plugin.ts`'s
9
+ * `shared.messages`), and the loader is ultimately called from next-intl's
10
+ * `getRequestConfig`, so both ends stay clean of `server-only`. The JSON stays
11
+ * behind a dynamic import inside the loader, so declaring the slot costs a
12
+ * function reference and reads no file. Shape copied from
13
+ * `@murumets-ee/queue-ui/i18n`.
14
+ */
15
+ declare function getYhikasSyncMessages(locale: string): Promise<Record<string, unknown>>;
16
+ //#endregion
17
+ export { getYhikasSyncMessages };
18
+ //# sourceMappingURL=i18n.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.mts","names":[],"sources":["../src/i18n.ts"],"mappings":";;AAaA;;;;;;;;AAA2E;;;;iBAArD,qBAAA,CAAsB,MAAA,WAAiB,OAAO,CAAC,MAAA"}
package/dist/i18n.mjs ADDED
@@ -0,0 +1,2 @@
1
+ async function e(e){switch(e){case`et`:return(await import(`./et-CGDUOj83.mjs`)).default;case`ru`:return(await import(`./ru-uzvamDhj.mjs`)).default;default:return(await import(`./en-BOWwWf-X.mjs`)).default}}export{e as getYhikasSyncMessages};
2
+ //# sourceMappingURL=i18n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.mjs","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["/**\n * Load `@murumets-ee/yhikas-sync` messages for a given locale — the room\n * pages dashboard widget's words and the room facts source's label and\n * provenance, all under `YhikasSync.*`.\n *\n * `./i18n` is its own tsdown entry, never the package root: this module is\n * loaded by every `lumi` command at config time (via `plugin.ts`'s\n * `shared.messages`), and the loader is ultimately called from next-intl's\n * `getRequestConfig`, so both ends stay clean of `server-only`. The JSON stays\n * behind a dynamic import inside the loader, so declaring the slot costs a\n * function reference and reads no file. Shape copied from\n * `@murumets-ee/queue-ui/i18n`.\n */\nexport async function getYhikasSyncMessages(locale: string): Promise<Record<string, unknown>> {\n switch (locale) {\n case 'et':\n return (await import('./messages/et.json')).default\n case 'ru':\n return (await import('./messages/ru.json')).default\n default:\n return (await import('./messages/en.json')).default\n }\n}\n"],"mappings":"AAaA,eAAsB,EAAsB,EAAkD,CAC5F,OAAQ,EAAR,CACE,IAAK,KACH,OAAQ,MAAM,OAAO,qBAAA,CAAuB,QAC9C,IAAK,KACH,OAAQ,MAAM,OAAO,qBAAA,CAAuB,QAC9C,QACE,OAAQ,MAAM,OAAO,qBAAA,CAAuB,OAChD,CACF"}