@murumets-ee/yhikas-sync 0.40.0 → 0.41.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/dist/index.d.mts CHANGED
@@ -30,11 +30,36 @@ declare const YHIKAS_SYNC_ACTOR_ID = "yhikas-sync";
30
30
  */
31
31
  declare const SYNC_JOB_NAME = "yhikas-sync:pull";
32
32
  declare const WATCHDOG_JOB_NAME = "yhikas-sync:staleness-watchdog";
33
- /** The two resources in scope. `site_info` / `site_notice` are Q1, deliberately absent. */
33
+ /**
34
+ * The three resources in scope.
35
+ *
36
+ * `site_info` was deliberately absent until 2026-08-11 (D027). PR 05 left it
37
+ * out on a MEASUREMENT — `site_info`/`site_notice` appear only in
38
+ * yhikas-admin's `src/db/schema.ts` and its public route, with no admin UI, no
39
+ * server action, no seed and no internal consumer — from which the plan
40
+ * concluded that no rows existed and a sync would pull nothing.
41
+ *
42
+ * Measured against production on 2026-08-11 (R028), that premise had expired:
43
+ * the route answers 200 with populated reception hours and two live notices.
44
+ * The rows were written outside the app. Hardcoding them in the rebuilt site
45
+ * would therefore have frozen live data, not stood in for an empty upstream.
46
+ */
34
47
  declare const ROOM_TYPES_RESOURCE = "room_types";
35
48
  declare const LEGAL_DOCUMENTS_RESOURCE = "legal_documents";
36
- declare const SYNC_RESOURCES: readonly ["room_types", "legal_documents"];
49
+ declare const SITE_INFO_RESOURCE = "site_info";
50
+ declare const SYNC_RESOURCES: readonly ["room_types", "legal_documents", "site_info"];
37
51
  type SyncResource = (typeof SYNC_RESOURCES)[number];
52
+ /**
53
+ * The business key of the one `yhikas_site_info` row.
54
+ *
55
+ * Upstream `site_info` is a singleton and its notices carry no id or code of
56
+ * their own — only `{text, isActive, order}` — so there is no natural key to
57
+ * diff N rows on. Modelling it as ONE row with a constant key keeps the whole
58
+ * package on a single identity story (`code`, `type`, and now `key`), and
59
+ * keeps the notices ordered without inventing an identity that upstream would
60
+ * not preserve across an edit.
61
+ */
62
+ declare const SITE_INFO_KEY = "default";
38
63
  /**
39
64
  * Locale codes. Upstream `MultilingualText` is `{ en, et }` and the lumi codes
40
65
  * are identical, so there is no mapping to configure — inventing one would be
@@ -56,6 +81,12 @@ declare const EN_LOCALE = "en";
56
81
  declare const DECLARED_CURRENCY = "EUR";
57
82
  /** `net` = the amount excludes VAT. */
58
83
  declare const DECLARED_VAT_TREATMENT = "net";
84
+ /**
85
+ * Hard ceiling on ticker items. A ticker is a marquee: past a couple of dozen
86
+ * entries nobody reads the tail, and an unbounded array from upstream would
87
+ * ride into a JSONB column and out onto every page of the site.
88
+ */
89
+ declare const MAX_SITE_NOTICES = 25;
59
90
  /**
60
91
  * Env var carrying the sync's OWN bearer credential — deliberately not the
61
92
  * site's `PUBLIC_SITE_API_KEY`. The upstream limiter buckets on
@@ -446,6 +477,132 @@ declare const legalDocumentsResponseSchema: z.ZodObject<{
446
477
  htmlContentEn: string;
447
478
  }[];
448
479
  }>;
480
+ /**
481
+ * One `site_notice` — a ticker item.
482
+ *
483
+ * No id, no code, no timestamp: `{text, isActive, order}` is the whole row as
484
+ * the route serves it. That absence is why `site_info` is modelled as one
485
+ * local row carrying an ordered list rather than as N diffable rows — there is
486
+ * no key an edit upstream would preserve.
487
+ *
488
+ * `isActive` is honoured HERE rather than assumed: the route returns inactive
489
+ * notices too, and a ticker that shows a retired notice is worse than one that
490
+ * shows nothing.
491
+ */
492
+ declare const siteNoticeRowSchema: z.ZodObject<{
493
+ text: z.ZodObject<{
494
+ et: z.ZodString;
495
+ en: z.ZodString;
496
+ }, "strip", z.ZodTypeAny, {
497
+ et: string;
498
+ en: string;
499
+ }, {
500
+ et: string;
501
+ en: string;
502
+ }>;
503
+ isActive: z.ZodBoolean;
504
+ order: z.ZodNumber;
505
+ }, "strip", z.ZodTypeAny, {
506
+ text: {
507
+ et: string;
508
+ en: string;
509
+ };
510
+ order: number;
511
+ isActive: boolean;
512
+ }, {
513
+ text: {
514
+ et: string;
515
+ en: string;
516
+ };
517
+ order: number;
518
+ isActive: boolean;
519
+ }>;
520
+ type SiteNoticeRow = z.infer<typeof siteNoticeRowSchema>;
521
+ /**
522
+ * `/api/public/site-info` — reception hours plus the notice ticker.
523
+ *
524
+ * 🔴 **This envelope cannot express "not configured yet".** The route answers
525
+ * `200 { success: true, receptionHours: {et:'',en:''}, notices: [] }` when no
526
+ * row exists, which is byte-identical to an operator having deliberately
527
+ * cleared everything (F015). The other two resources have no such hole —
528
+ * theirs discriminate on `success` and on array length against a local
529
+ * snapshot.
530
+ *
531
+ * The schema is therefore NOT where that is solved, and deliberately so: the
532
+ * shape is valid either way. `projectSiteInfo` refuses an all-empty payload as
533
+ * unusable (D027), which is the only place that has the standing to decide
534
+ * that a well-formed response is not authoritative.
535
+ */
536
+ declare const siteInfoResponseSchema: z.ZodObject<{
537
+ success: z.ZodLiteral<true>;
538
+ receptionHours: z.ZodObject<{
539
+ et: z.ZodString;
540
+ en: z.ZodString;
541
+ }, "strip", z.ZodTypeAny, {
542
+ et: string;
543
+ en: string;
544
+ }, {
545
+ et: string;
546
+ en: string;
547
+ }>;
548
+ notices: z.ZodArray<z.ZodObject<{
549
+ text: z.ZodObject<{
550
+ et: z.ZodString;
551
+ en: z.ZodString;
552
+ }, "strip", z.ZodTypeAny, {
553
+ et: string;
554
+ en: string;
555
+ }, {
556
+ et: string;
557
+ en: string;
558
+ }>;
559
+ isActive: z.ZodBoolean;
560
+ order: z.ZodNumber;
561
+ }, "strip", z.ZodTypeAny, {
562
+ text: {
563
+ et: string;
564
+ en: string;
565
+ };
566
+ order: number;
567
+ isActive: boolean;
568
+ }, {
569
+ text: {
570
+ et: string;
571
+ en: string;
572
+ };
573
+ order: number;
574
+ isActive: boolean;
575
+ }>, "many">;
576
+ }, "strip", z.ZodTypeAny, {
577
+ receptionHours: {
578
+ et: string;
579
+ en: string;
580
+ };
581
+ notices: {
582
+ text: {
583
+ et: string;
584
+ en: string;
585
+ };
586
+ order: number;
587
+ isActive: boolean;
588
+ }[];
589
+ success: true;
590
+ }, {
591
+ receptionHours: {
592
+ et: string;
593
+ en: string;
594
+ };
595
+ notices: {
596
+ text: {
597
+ et: string;
598
+ en: string;
599
+ };
600
+ order: number;
601
+ isActive: boolean;
602
+ }[];
603
+ success: true;
604
+ }>;
605
+ type SiteInfoResponse = z.infer<typeof siteInfoResponseSchema>;
449
606
  //#endregion
450
607
  //#region src/projection.d.ts
451
608
  /** Injected at the seam; see the module docblock for why it is not imported. */
@@ -479,6 +636,36 @@ interface ProjectionResult {
479
636
  declare function secondaryLocaleOf(defaultLocale: string): string;
480
637
  declare function projectRoomTypes(rows: readonly RoomTypeRow[], options: ProjectionOptions): ProjectionResult;
481
638
  declare function projectLegalDocuments(rows: readonly LegalDocumentRow[], options: ProjectionOptions, sanitize: HtmlSanitizer): ProjectionResult;
639
+ /**
640
+ * Reception hours + the notice ticker → the one `yhikas_site_info` row.
641
+ *
642
+ * ## 🔴 The refusal is the point of this function
643
+ *
644
+ * `/api/public/site-info` answers `200 { success: true, receptionHours:
645
+ * {et:'',en:''}, notices: [] }` when no upstream row exists. That is
646
+ * byte-identical to an operator having deliberately cleared both, and it is the
647
+ * state the endpoint is in whenever nobody has filled it in (F015). The other
648
+ * two resources have no equivalent hole — theirs discriminate on `success` and
649
+ * on array length against a local snapshot.
650
+ *
651
+ * The shipped `empty-snapshot` floor cannot cover it, and the reason is
652
+ * structural rather than an oversight: that rule is a row-COUNT test, and this
653
+ * resource always projects exactly one row. The count is 1 whether the row says
654
+ * anything or not, so the floor never fires and an all-empty payload would be
655
+ * applied as an authoritative blanking — emptying the ticker and the hours on
656
+ * every page of the site, silently, six hours after upstream hiccupped.
657
+ *
658
+ * So emptiness is restated here as a CONTENT test: if the default locale has
659
+ * neither hours nor a single active notice, the payload is refused as
660
+ * unusable. Local content is left exactly as it was and the failure arms the
661
+ * staleness watchdog, which is the same posture as an unreachable endpoint —
662
+ * because epistemically it is the same situation.
663
+ *
664
+ * Note what is NOT refused: hours with no notices, or notices with no hours.
665
+ * Both are ordinary states of a real dormitory, and refusing them would make
666
+ * the guard fire on exactly the operator action it exists to protect.
667
+ */
668
+ declare function projectSiteInfo(response: SiteInfoResponse, options: ProjectionOptions): ProjectionResult;
482
669
  //#endregion
483
670
  //#region src/apply.d.ts
484
671
  /**
@@ -529,7 +716,7 @@ declare function applyPlan(input: ApplyPlanInput): Promise<ApplyCounts>;
529
716
  //#region src/entities/guard.d.ts
530
717
  /** Thrown when a writer other than the sync tries to mutate synced content. */
531
718
  declare class YhikasSyncedContentReadOnlyError extends Error {
532
- constructor(entityName: string, action: 'create' | 'update' | 'delete' | 'bulk update', actorId: string);
719
+ constructor(entityName: string, action: 'create' | 'update' | 'delete' | 'bulk update' | 'translation write' | 'locale publish change', actorId: string);
533
720
  }
534
721
  /**
535
722
  * Refuse every create/update/delete not performed by the sync actor.
@@ -539,6 +726,132 @@ declare class YhikasSyncedContentReadOnlyError extends Error {
539
726
  */
540
727
  declare function machineWritten<N extends string>(entityName: N): Behavior<Record<never, never>, `yhikas-sync:machine-written:${N}`>;
541
728
  //#endregion
729
+ //#region src/entities/index.d.ts
730
+ /**
731
+ * The entities this plugin syncs — ONE list, consumed by the plugin's
732
+ * declaration and by the contract tests.
733
+ *
734
+ * Adding `site_info` turned twelve tests red across two files, every one of
735
+ * them because a resource or entity had been re-listed by hand somewhere. This
736
+ * export exists so the next one cannot: `plugin.ts` spreads it into
737
+ * `server.entities`, and `contract.test.ts` iterates it, so a fourth entity is
738
+ * covered by both the moment it is added here.
739
+ */
740
+ declare const SYNCED_ENTITIES: readonly [import("@murumets-ee/entity").Entity<{
741
+ id: import("@murumets-ee/entity").IdField;
742
+ } & import("@murumets-ee/entity").AuditableFields & import("@murumets-ee/entity").PublishableFields & Record<never, never> & {
743
+ code: import("@murumets-ee/entity").TextField & {
744
+ readonly required: true;
745
+ readonly unique: true;
746
+ readonly indexed: true;
747
+ readonly maxLength: 190;
748
+ };
749
+ name: import("@murumets-ee/entity").TextField & {
750
+ readonly required: true;
751
+ readonly translatable: true;
752
+ };
753
+ totalArea: import("@murumets-ee/entity").TextField & {
754
+ readonly maxLength: 32;
755
+ };
756
+ livingArea: import("@murumets-ee/entity").TextField & {
757
+ readonly maxLength: 32;
758
+ };
759
+ commonArea: import("@murumets-ee/entity").TextField & {
760
+ readonly maxLength: 32;
761
+ };
762
+ capacity: import("@murumets-ee/entity").NumberField & {
763
+ readonly integer: true;
764
+ };
765
+ placesOccupied: import("@murumets-ee/entity").NumberField & {
766
+ readonly integer: true;
767
+ };
768
+ monthlyRent: import("@murumets-ee/entity").TextField & {
769
+ readonly maxLength: 32;
770
+ };
771
+ discountedMonthlyRent: import("@murumets-ee/entity").TextField & {
772
+ readonly maxLength: 32;
773
+ };
774
+ dailyRent: import("@murumets-ee/entity").TextField & {
775
+ readonly maxLength: 32;
776
+ };
777
+ currency: import("@murumets-ee/entity").TextField & {
778
+ readonly required: true;
779
+ readonly maxLength: 3;
780
+ readonly default: "EUR";
781
+ };
782
+ vatTreatment: import("@murumets-ee/entity").SelectField & {
783
+ options: readonly ["net", "gross"];
784
+ } & {
785
+ readonly options: readonly ["net", "gross"];
786
+ readonly required: true;
787
+ readonly default: "net";
788
+ };
789
+ hasEnglish: import("@murumets-ee/entity").BooleanField & {
790
+ readonly required: true;
791
+ readonly default: false;
792
+ };
793
+ sourceHash: import("@murumets-ee/entity").TextField & {
794
+ readonly maxLength: 64;
795
+ };
796
+ }, "yhikas_room_type", [import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").AuditableFields, string>, import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").PublishableFields, "publishable">, import("@murumets-ee/entity").Behavior<Record<never, never>, "yhikas-sync:machine-written:yhikas_room_type">]>, import("@murumets-ee/entity").Entity<{
797
+ id: import("@murumets-ee/entity").IdField;
798
+ } & import("@murumets-ee/entity").AuditableFields & import("@murumets-ee/entity").PublishableFields & Record<never, never> & {
799
+ type: import("@murumets-ee/entity").TextField & {
800
+ readonly required: true;
801
+ readonly unique: true;
802
+ readonly indexed: true;
803
+ readonly maxLength: 190;
804
+ };
805
+ title: import("@murumets-ee/entity").TextField & {
806
+ readonly required: true;
807
+ readonly translatable: true;
808
+ };
809
+ sourceSlug: import("@murumets-ee/entity").TextField & {
810
+ readonly required: true;
811
+ readonly indexed: true;
812
+ readonly maxLength: 190;
813
+ };
814
+ body: import("@murumets-ee/entity").RichTextField & {
815
+ readonly required: true;
816
+ readonly translatable: true;
817
+ };
818
+ order: import("@murumets-ee/entity").NumberField & {
819
+ readonly integer: true;
820
+ readonly required: true;
821
+ readonly default: 0;
822
+ };
823
+ hasEnglish: import("@murumets-ee/entity").BooleanField & {
824
+ readonly required: true;
825
+ readonly default: false;
826
+ };
827
+ sourceHash: import("@murumets-ee/entity").TextField & {
828
+ readonly maxLength: 64;
829
+ };
830
+ }, "yhikas_legal_document", [import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").AuditableFields, string>, import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").PublishableFields, "publishable">, import("@murumets-ee/entity").Behavior<Record<never, never>, "yhikas-sync:machine-written:yhikas_legal_document">]>, import("@murumets-ee/entity").Entity<{
831
+ id: import("@murumets-ee/entity").IdField;
832
+ } & import("@murumets-ee/entity").AuditableFields & import("@murumets-ee/entity").PublishableFields & Record<never, never> & {
833
+ key: import("@murumets-ee/entity").TextField & {
834
+ readonly required: true;
835
+ readonly unique: true;
836
+ readonly indexed: true;
837
+ readonly maxLength: 32;
838
+ };
839
+ receptionHours: import("@murumets-ee/entity").TextField & {
840
+ readonly translatable: true;
841
+ readonly maxLength: 500;
842
+ };
843
+ notices: import("@murumets-ee/entity").JsonField & {
844
+ readonly translatable: true;
845
+ };
846
+ hasEnglish: import("@murumets-ee/entity").BooleanField & {
847
+ readonly default: false;
848
+ readonly required: true;
849
+ };
850
+ sourceHash: import("@murumets-ee/entity").TextField & {
851
+ readonly maxLength: 64;
852
+ };
853
+ }, "yhikas_site_info", [import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").AuditableFields, string>, import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").PublishableFields, "publishable">, import("@murumets-ee/entity").Behavior<Record<never, never>, "yhikas-sync:machine-written:yhikas_site_info">]>];
854
+ //#endregion
542
855
  //#region src/entities/legal-document.d.ts
543
856
  /**
544
857
  * `yhikas_legal_document` — the local projection of yhikas-admin's
@@ -755,6 +1068,96 @@ declare const YhikasRoomType: import("@murumets-ee/entity").Entity<{
755
1068
  };
756
1069
  }, "yhikas_room_type", [import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").AuditableFields, string>, import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").PublishableFields, "publishable">, import("@murumets-ee/entity").Behavior<Record<never, never>, "yhikas-sync:machine-written:yhikas_room_type">]>;
757
1070
  //#endregion
1071
+ //#region src/entities/site-info.d.ts
1072
+ /**
1073
+ * `yhikas_site_info` — reception hours and the notice ticker, as ONE row.
1074
+ *
1075
+ * ## Why a singleton and not N notice rows
1076
+ *
1077
+ * Upstream `site_notice` carries `{text, isActive, order}` and nothing else —
1078
+ * no id, no code, no timestamp. So there is no business key an edit upstream
1079
+ * would preserve, and a diff keyed on `order` would read "notice 2 was
1080
+ * reworded" and "notices 2 and 3 swapped places" as the same event. Modelling
1081
+ * the resource as one row carrying an ordered list keeps identity honest: the
1082
+ * whole list is the value, and it is replaced as a value.
1083
+ *
1084
+ * That also matches what the site does with it. A ticker renders the list in
1085
+ * order; nothing links to an individual notice, so nothing needs one to have a
1086
+ * URL, a version or a publish state of its own.
1087
+ *
1088
+ * ## Both halves are optional, and that is the point
1089
+ *
1090
+ * `receptionHours` and `notices` can each legitimately be empty — a dormitory
1091
+ * with no current notices is an ordinary state. What is NOT ordinary is BOTH
1092
+ * being empty, because that is exactly what `/api/public/site-info` returns
1093
+ * when no row exists at all (F015), and it is indistinguishable at the wire
1094
+ * from a deliberate clearing. `projectSiteInfo` refuses that payload rather
1095
+ * than writing it (D027) — the refusal lives there because emptiness is a
1096
+ * content judgement, not a schema one.
1097
+ *
1098
+ * ## Security
1099
+ *
1100
+ * As with the other two synced entities: `access` is advisory metadata, the
1101
+ * firewall does not read it, and `admin` bypasses it unconditionally. The real
1102
+ * controls are deny-by-default (no non-admin role is granted anything here) and
1103
+ * `machineWritten`, which since PR 12 refuses non-sync writers on the base row
1104
+ * AND on the translation path.
1105
+ */
1106
+ declare const YHIKAS_SITE_INFO_ENTITY = "yhikas_site_info";
1107
+ declare const YhikasSiteInfo: import("@murumets-ee/entity").Entity<{
1108
+ id: import("@murumets-ee/entity").IdField;
1109
+ } & import("@murumets-ee/entity").AuditableFields & import("@murumets-ee/entity").PublishableFields & Record<never, never> & {
1110
+ /**
1111
+ * The business key, constant at {@link SITE_INFO_KEY}.
1112
+ *
1113
+ * A singleton still needs a key, for the same reason the other two do: the
1114
+ * differ matches local to upstream by key, and `applyPlan` upserts on it.
1115
+ * A constant key makes the singleton a one-element case of the same
1116
+ * machinery rather than a second code path.
1117
+ */
1118
+ key: import("@murumets-ee/entity").TextField & {
1119
+ readonly required: true;
1120
+ readonly unique: true;
1121
+ readonly indexed: true;
1122
+ readonly maxLength: 32;
1123
+ };
1124
+ /**
1125
+ * Reception hours as free text — `E–R 9–17`, or whatever the office writes.
1126
+ *
1127
+ * Deliberately NOT structured. Upstream stores one multilingual string and
1128
+ * the site renders it verbatim; parsing it into open/close pairs here would
1129
+ * invent a schema neither system has, and would fail the first time someone
1130
+ * writes "suletud 24.06".
1131
+ */
1132
+ receptionHours: import("@murumets-ee/entity").TextField & {
1133
+ readonly translatable: true;
1134
+ readonly maxLength: 500;
1135
+ };
1136
+ /**
1137
+ * The ticker, as an ordered array of strings for ONE locale.
1138
+ *
1139
+ * Already filtered to `isActive` and already sorted by `order` at
1140
+ * projection time, so a renderer maps over it and nothing more. `isActive`
1141
+ * and `order` are upstream's editing affordances, not content — carrying
1142
+ * them across would oblige every consumer to re-implement the same filter
1143
+ * and sort, and one of them would eventually get it wrong.
1144
+ */
1145
+ notices: import("@murumets-ee/entity").JsonField & {
1146
+ readonly translatable: true;
1147
+ };
1148
+ /**
1149
+ * Whether the secondary locale has anything publishable. A convenience
1150
+ * flag for the admin list, never the control — per-locale publish status is.
1151
+ */
1152
+ hasEnglish: import("@murumets-ee/entity").BooleanField & {
1153
+ readonly default: false;
1154
+ readonly required: true;
1155
+ }; /** The change signal. Written LAST, so a stale hash means "redo this row". */
1156
+ sourceHash: import("@murumets-ee/entity").TextField & {
1157
+ readonly maxLength: 64;
1158
+ };
1159
+ }, "yhikas_site_info", [import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").AuditableFields, string>, import("@murumets-ee/entity").Behavior<import("@murumets-ee/entity").PublishableFields, "publishable">, import("@murumets-ee/entity").Behavior<Record<never, never>, "yhikas-sync:machine-written:yhikas_site_info">]>;
1160
+ //#endregion
758
1161
  //#region src/entity-client.d.ts
759
1162
  /** The `AdminClient` methods the adapter forwards, structurally. */
760
1163
  type AdminClientLike = ReturnType<ToolkitApp['getClient']>;
@@ -930,6 +1333,15 @@ declare class YhikasUpstreamClient {
930
1333
  fetchRoomTypes(): Promise<RoomTypeRow[]>;
931
1334
  /** `GET /api/public/legal-documents`. Active only, ordered by `order`; unpaginated. */
932
1335
  fetchLegalDocuments(): Promise<LegalDocumentRow[]>;
1336
+ /**
1337
+ * `GET /api/public/site-info`. Reception hours + notice ticker; a singleton,
1338
+ * so there is nothing to order or paginate.
1339
+ *
1340
+ * Returns the whole envelope rather than one key, because both halves are the
1341
+ * payload and neither is meaningful without the other — a caller deciding
1342
+ * whether this response is usable at all has to see both (D027).
1343
+ */
1344
+ fetchSiteInfo(): Promise<SiteInfoResponse>;
933
1345
  }
934
1346
  //#endregion
935
1347
  //#region src/run-sync.d.ts
@@ -937,6 +1349,7 @@ interface SyncRunDeps {
937
1349
  readonly upstream: YhikasUpstreamClient;
938
1350
  readonly roomTypeClient: SyncEntityClient;
939
1351
  readonly legalDocumentClient: SyncEntityClient;
1352
+ readonly siteInfoClient: SyncEntityClient;
940
1353
  readonly state: SyncStateStore;
941
1354
  readonly sanitizeHtml: HtmlSanitizer;
942
1355
  readonly logger: SyncLogger;
@@ -982,7 +1395,20 @@ declare class YhikasUpstreamError extends Error {
982
1395
  */
983
1396
  declare class YhikasSyncRefusedError extends Error {
984
1397
  readonly resource: SyncResource;
985
- readonly reason: 'empty-snapshot' | 'retire-fraction' | 'oversized-snapshot' | 'duplicate-key';
1398
+ readonly reason: 'empty-snapshot' | 'retire-fraction' | 'oversized-snapshot' | 'duplicate-key'
1399
+ /**
1400
+ * A well-formed 200 whose CONTENT is entirely empty (D027).
1401
+ *
1402
+ * Distinct from `empty-snapshot`, which is a row-COUNT test and therefore
1403
+ * cannot see this: `site_info` always projects exactly one row, so the
1404
+ * count is 1 whether or not that row says anything. `/api/public/site-info`
1405
+ * answers 200 with empty strings and no notices when no upstream row
1406
+ * exists, which is byte-identical to a deliberate clearing (F015) — so the
1407
+ * only safe reading of an all-empty payload is "not authoritative", and the
1408
+ * only safe response is to leave local content alone and let the watchdog
1409
+ * arm.
1410
+ */
1411
+ | 'empty-content';
986
1412
  constructor(resource: SyncResource, reason: YhikasSyncRefusedError['reason'], message: string);
987
1413
  }
988
1414
  //#endregion
@@ -1022,5 +1448,5 @@ declare function assessStaleness(records: readonly SyncStateRecord[], now: Date,
1022
1448
  */
1023
1449
  declare function assertNotStale(records: readonly SyncStateRecord[], now: Date, staleAfterMs: number): ResourceStaleness[];
1024
1450
  //#endregion
1025
- export { API_KEY_ENV_VAR, type AdminClientLike, type ApplyCounts, type ApplyPlanInput, BASE_URL_ENV_VAR, DECLARED_CURRENCY, DECLARED_VAT_TREATMENT, type DiffPlan, EN_LOCALE, ET_LOCALE, type HtmlSanitizer, LEGAL_DOCUMENTS_RESOURCE, type LegalDocumentRow, type LocalRow, type MultilingualText, type PlanDiffInput, type ProjectedRow, type ProjectionOptions, type ProjectionResult, ROOM_TYPES_RESOURCE, type ResolvedYhikasSyncConfig, type ResourceOutcome, type ResourceStaleness, type RoomTypeRow, SYNC_JOB_NAME, SYNC_RESOURCES, type SanityFloor, type SkippedRow, type SyncEntityClient, type SyncLogger, type SyncResource, type SyncRunDeps, type SyncRunSummary, type SyncStateRecord, type SyncStateStore, type UpstreamFailureKind, VAT_TREATMENTS, WATCHDOG_JOB_NAME, YHIKAS_LEGAL_DOCUMENT_ENTITY, YHIKAS_ROOM_TYPE_ENTITY, YHIKAS_SYNC_ACTOR_ID, YHIKAS_SYNC_PLUGIN_NAME, YhikasApplyPartialError, YhikasLegalDocument, YhikasRoomType, type YhikasSyncConfig, YhikasSyncConfigError, YhikasSyncRefusedError, YhikasSyncRunError, YhikasSyncStaleError, YhikasSyncedContentReadOnlyError, YhikasUpstreamClient, type YhikasUpstreamClientOptions, YhikasUpstreamError, applyPlan, assertNotStale, assessStaleness, createSyncStateStore, legalDocumentRowSchema, legalDocumentsResponseSchema, machineWritten, multilingualTextSchema, planDiff, projectLegalDocuments, projectRoomTypes, readLocalRows, resolveYhikasSyncConfig, roomTypeRowSchema, roomTypesResponseSchema, runYhikasSync, secondaryLocaleOf, stableHash, toSyncEntityClient, truncateError, yhikasSyncStateTable };
1451
+ export { API_KEY_ENV_VAR, type AdminClientLike, type ApplyCounts, type ApplyPlanInput, BASE_URL_ENV_VAR, DECLARED_CURRENCY, DECLARED_VAT_TREATMENT, type DiffPlan, EN_LOCALE, ET_LOCALE, type HtmlSanitizer, LEGAL_DOCUMENTS_RESOURCE, type LegalDocumentRow, type LocalRow, MAX_SITE_NOTICES, type MultilingualText, type PlanDiffInput, type ProjectedRow, type ProjectionOptions, type ProjectionResult, ROOM_TYPES_RESOURCE, type ResolvedYhikasSyncConfig, type ResourceOutcome, type ResourceStaleness, type RoomTypeRow, SITE_INFO_KEY, SITE_INFO_RESOURCE, SYNCED_ENTITIES, SYNC_JOB_NAME, SYNC_RESOURCES, type SanityFloor, type SiteInfoResponse, type SiteNoticeRow, type SkippedRow, type SyncEntityClient, type SyncLogger, type SyncResource, type SyncRunDeps, type SyncRunSummary, type SyncStateRecord, type SyncStateStore, type UpstreamFailureKind, VAT_TREATMENTS, WATCHDOG_JOB_NAME, YHIKAS_LEGAL_DOCUMENT_ENTITY, YHIKAS_ROOM_TYPE_ENTITY, YHIKAS_SITE_INFO_ENTITY, YHIKAS_SYNC_ACTOR_ID, YHIKAS_SYNC_PLUGIN_NAME, YhikasApplyPartialError, YhikasLegalDocument, YhikasRoomType, YhikasSiteInfo, type YhikasSyncConfig, YhikasSyncConfigError, YhikasSyncRefusedError, YhikasSyncRunError, YhikasSyncStaleError, YhikasSyncedContentReadOnlyError, YhikasUpstreamClient, type YhikasUpstreamClientOptions, YhikasUpstreamError, applyPlan, assertNotStale, assessStaleness, createSyncStateStore, legalDocumentRowSchema, legalDocumentsResponseSchema, machineWritten, multilingualTextSchema, planDiff, projectLegalDocuments, projectRoomTypes, projectSiteInfo, readLocalRows, resolveYhikasSyncConfig, roomTypeRowSchema, roomTypesResponseSchema, runYhikasSync, secondaryLocaleOf, siteInfoResponseSchema, siteNoticeRowSchema, stableHash, toSyncEntityClient, truncateError, yhikasSyncStateTable };
1026
1452
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/diff.ts","../src/upstream/wire.ts","../src/projection.ts","../src/apply.ts","../src/entities/guard.ts","../src/entities/legal-document.ts","../src/entities/room-type.ts","../src/entity-client.ts","../src/sync-state-table.ts","../src/sync-state.ts","../src/upstream/client.ts","../src/run-sync.ts","../src/upstream/errors.ts","../src/watchdog.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/diff.ts","../src/upstream/wire.ts","../src/projection.ts","../src/apply.ts","../src/entities/guard.ts","../src/entities/index.ts","../src/entities/legal-document.ts","../src/entities/room-type.ts","../src/entities/site-info.ts","../src/entity-client.ts","../src/sync-state-table.ts","../src/sync-state.ts","../src/upstream/client.ts","../src/run-sync.ts","../src/upstream/errors.ts","../src/watchdog.ts"],"mappings":";;;;;;;;;;;;AASA;;cAAa,uBAAA;;AAAuB;AAWpC;;;;AAAiC;AAOjC;;cAPa,oBAAA;;AAOa;AAC1B;;;cADa,aAAA;AAAA,cACA,iBAAA;AAgBb;;;;AAAgC;AAChC;;;;AAAqC;AACrC;;;;AAFA,cAAa,mBAAA;AAAA,cACA,wBAAA;AAAA,cACA,kBAAA;AAAA,cAEA,cAAA;AAAA,KAKD,YAAA,WAAuB,cAAc;AADvC;AACV;;;;AAAiD;AAYjD;;;;AAbU,cAaG,aAAA;AAOb;;;;AAAsB;AAAtB,cAAa,SAAA;AAAA,cACA,SAAA;;;AAAS;AAatB;;;;AAA8B;AAE9B;;;cAFa,iBAAA;AAEsB;AAAA,cAAtB,sBAAA;;ACxDe;AAG5B;;;cD4Fa,gBAAA;;;;;;;;cASA,eAAA;AAAA,cACA,gBAAA;;;AArHoB;AAAA,UCAhB,QAAA;EAAA,SACN,EAAA;;WAEA,GAAA;EAAA,SACA,MAAA;EDIE;;;;AAAiB;EAAjB,SCEF,UAAA;EDcqB;EAAA,SCZrB,WAAA,EAAa,IAAI;AAAA;AAAA,UAGX,QAAA;EDUJ;EAAA,SCRF,MAAA,WAAiB,CAAA;;WAEjB,MAAA;IAAA,SAA4B,KAAA,EAAO,QAAA;IAAA,SAAmB,GAAA,EAAK,CAAA;EAAA;;WAE3D,SAAA,WAAoB,QAAA;EDKA;EAAA,SCHpB,MAAA,WAAiB,QAAA;AAAA;AAAA,UAGX,WAAA;EDMP;EAAA,SCJC,OAAA;EDKC;EAAA,SCHD,iBAAA;;WAEA,kBAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,QAAA,EAAU,YAAA;EAAA,SACV,QAAA,WAAmB,CAAA;EAAA,SACnB,KAAA,WAAgB,QAAA;EAAA,SAChB,KAAA,GAAQ,GAAA,EAAK,CAAA;EAAA,SACb,MAAA,GAAS,GAAA,EAAK,CAAA;EAAA,SACd,KAAA,EAAO,WAAA;AAAA;;ADWI;AACtB;;;;AAAsB;iBCFN,UAAA,CAAW,KAAc;;;;ADeX;AAE9B;;;;AAAmC;AAuCnC;;;;AAA6B;AAS7B;iBChCgB,QAAA,GAAA,CAAY,KAAA,EAAO,aAAA,CAAc,CAAA,IAAK,QAAA,CAAS,CAAA;;;AD5E/D;;;;AAA8B;AAgB9B;;;AAhBA,cE2Ba,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;KAKvB,gBAAA,GAAmB,CAAA,CAAE,KAAK,QAAQ,sBAAA;AFdf;AAE/B;;;;AAIU;AACV;;;AAP+B,cEyBlB,iBAAA,EAAiB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAalB,WAAA,GAAc,CAAA,CAAE,KAAK,QAAQ,iBAAA;;;;;;;ADpDb;AAG5B;;cC4Da,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;;;EDxDI;;;;;;;;;AAIH;AAGpC;;;;;;;;;;;;;;;;;;;;;;;;;;KCqEY,gBAAA,GAAmB,CAAA,CAAE,KAAK,QAAQ,sBAAA;;;;;;;;;;ADtDjB;cCkEhB,uBAAA,EAAuB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAKvB,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiB5B,mBAAA,EAAmB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAMpB,aAAA,GAAgB,CAAA,CAAE,KAAK,QAAQ,mBAAA;;;;;;;;;;;;;;;;cAiC9B,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAMvB,gBAAA,GAAmB,CAAA,CAAE,KAAK,QAAQ,sBAAA;;;;KCvJlC,aAAA,IAAiB,IAAY;AAAA,UAExB,iBAAA;EHDe;EAAA,SGGrB,aAAa;AAAA;AAAA,UAGP,YAAA;EHLJ;EAAA,SGOF,GAAA;;WAEA,IAAA,EAAM,MAAA;EHToB;AACrC;;;;EADqC,SGe1B,SAAA,EAAW,MAAM;EAAA,SACjB,IAAA;AAAA;AAAA,UAGM,UAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAM;AAAA;AAAA,UAGA,gBAAA;EAAA,SACN,SAAA,WAAoB,YAAA;EAAA,SACpB,OAAA,WAAkB,UAAU;AAAA;;iBAgCvB,iBAAA,CAAkB,aAAqB;AAAA,iBAmDvC,gBAAA,CACd,IAAA,WAAe,WAAA,IACf,OAAA,EAAS,iBAAA,GACR,gBAAA;AAAA,iBAmDa,qBAAA,CACd,IAAA,WAAe,gBAAA,IACf,OAAA,EAAS,iBAAA,EACT,QAAA,EAAU,aAAA,GACT,gBAAA;;;AH5HgC;AAuCnC;;;;AAA6B;AAS7B;;;;AAA4B;AAC5B;;;;AAA6B;;;;ACrH7B;;;;;;;;iBEkSgB,eAAA,CACd,QAAA,EAAU,gBAAA,EACV,OAAA,EAAS,iBAAA,GACR,gBAAA;;;;;;AH5QkC;AACrC;;;;UIJiB,gBAAA;EACf,QAAA,CAAS,OAAA;IAAW,KAAA;EAAA,IAAkB,OAAA,CAAQ,MAAA;EAC9C,MAAA,CAAO,IAAA,EAAM,MAAA,oBAA0B,OAAA,CAAQ,MAAA;EAC/C,MAAA,CAAO,EAAA,UAAY,IAAA,EAAM,MAAA,oBAA0B,OAAA,CAAQ,MAAA;EAC3D,eAAA,CACE,EAAA,UACA,IAAA,EAAM,MAAA,mBACN,MAAA,WACC,OAAA,CAAQ,MAAA;EACX,iBAAA,CAAkB,EAAA,UAAY,MAAA,WAAiB,OAAA;AAAA;AAAA,UAGhC,UAAA;EACf,IAAA,CAAK,GAAA,EAAK,MAAA,mBAAyB,GAAA;EACnC,IAAA,CAAK,GAAA,EAAK,MAAA,mBAAyB,GAAA;EACnC,KAAA,CAAM,GAAA,EAAK,MAAA,mBAAyB,GAAA;AAAA;AAAA,UAGrB,WAAA;EACf,OAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,MAAA;AAAA;AJQF;AAAA,cIJa,uBAAA,SAAgC,KAAA;EAAA,SAClC,MAAA,EAAQ,WAAA;cACL,QAAA,EAAU,YAAA,EAAc,MAAA,EAAQ,WAAA;AAAA;AAAA,iBAqCxB,aAAA,CACpB,MAAA,EAAQ,gBAAA,EACR,QAAA,UACA,KAAA,WACC,OAAA,CAAQ,QAAA;AAAA,UAWM,cAAA;EAAA,SACN,QAAA,EAAU,YAAA;EAAA,SACV,IAAA,EAAM,QAAA,CAAS,YAAA;EAAA,SACf,MAAA,EAAQ,gBAAA;EJtCN;EAAA,SIwCF,eAAA;EAAA,SACA,MAAA,EAAQ,UAAA;AAAA;AAAA,iBAGG,SAAA,CAAU,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,WAAA;;;AJ9ClC;AAAA,cKZjB,gCAAA,SAAyC,KAAK;cAEvD,UAAA,UACA,MAAA,kGAOA,OAAA;AAAA;;ALI+B;AAuCnC;;;;iBKEgB,cAAA,kBAAA,CACd,UAAA,EAAY,CAAA,GACX,QAAA,CAAS,MAAA,+CAAqD,CAAA;;;;;;;;;AL1HjE;;;;cMMa,eAAA,0CAAe,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ANN5B;;;;AAAoC;AAWpC;;;;AAAiC;AAOjC;;;;AAA0B;AAC1B;;;;cOCa,4BAAA;AAAA,cAEA,mBAAA,gCAAmB,MAAA;;sGAyE9B,MAAA;EP5D8B;AAAA;AAChC;;;;AAAqC;;;;;;;;;;;EAQzB;;;;AAAqC;AAYjD;;;;AAA0B;;;;;KAQb;;;;KAaA;;;;;;EAEsB;;;AAAA;AAuCnC;;;;;;;;EAS4B;AAAA;AAC5B;;;;;;;2QOjCE,MAAA;;;;;;;;;AP/FF;;;;AAAoC;AAWpC;;;;AAAiC;AAOjC;;;;AAA0B;AAC1B;;;;AAA8B;AAgB9B;;;;AAAgC;AAChC;;;;cQLa,uBAAA;ARMb;AAAA,cQHa,cAAA;AAAA,cAEA,cAAA,gCAAc,MAAA;;sGAgFzB,MAAA;ER7EW;;;;AAIH;AACV;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCmC;AAAA;AAuCnC;;;;;;;;;;;;;;;;;;;;;;;;ECjGW;;;;AAEiB;AAG5B;;;;;;EAM+B;;;;;;;;;;sQOoF7B,MAAA;;;;;;;;;ARpHF;;;;AAAoC;AAWpC;;;;AAAiC;AAOjC;;;;AAA0B;AAC1B;;;;AAA8B;AAgB9B;;;;AAAgC;AAChC;;;cSPa,uBAAA;AAAA,cAEA,cAAA,gCAAc,MAAA;;sGA4DzB,MAAA;;;ATtD6B;AAE/B;;;;AAIU;;;;;;;EAagB;;;AAAA;AAO1B;;;;;;;;EACsB;AAAA;AAatB;;;;AAA8B;AAE9B;;;;;EAuCa;;;;;;;KASe;;;;sQSpC1B,MAAA;;;;KC/EU,eAAA,GAAkB,UAAU,CAAC,UAAA;AVMf;AAC1B;;;;AAA8B;AAgB9B;;AAjB0B,iBUIV,kBAAA,CACd,MAAA,EAAQ,eAAA,EACR,aAAA,WACC,gBAAgB;;;;;;;;;AVzBnB;;;;AAAoC;AAWpC;;;;AAAiC;AAOjC;;;;AAA0B;AAC1B;;;;AAA8B;AAgB9B;cWda,oBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;IXmCa;IAAA;AAO1B;;;;AAAsB;IAPI,8FAQJ;IAAA,kGAAA;IAAA;IAaT;;;;AAAiB;IAAjB,8FAEsB;IAAA;;;;;;;;IAgDtB;IAAe;;AAAA;AAC5B;;;IAD4B,8FACC;IAAA;;ICrHZ;;;;;IAAA,8FAIN;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UWVM,eAAA;EAAA,SACN,QAAA;EAAA,SACA,WAAA,EAAa,IAAA;EAAA,SACb,aAAA,EAAe,IAAA;EAAA,SACf,aAAA,EAAe,IAAA;EAAA,SACf,SAAA;AAAA;AAAA,UAGM,cAAA;EZKS;EYHxB,MAAA,CAAO,QAAA,EAAU,YAAA,EAAc,GAAA,EAAK,IAAA,GAAO,OAAA;EAC3C,aAAA,CAAc,QAAA,EAAU,YAAA,EAAc,GAAA,EAAK,IAAA,GAAO,OAAA;EAClD,aAAA,CAAc,QAAA,EAAU,YAAA,EAAc,GAAA,EAAK,IAAA,EAAM,MAAA,EAAQ,WAAA,GAAc,OAAA;EACvE,aAAA,CAAc,QAAA,EAAU,YAAA,EAAc,GAAA,EAAK,IAAA,EAAM,KAAA,WAAgB,OAAA;EACjE,OAAA,IAAW,OAAA,CAAQ,eAAA;AAAA;AAAA,KAGhB,eAAA,GAAkB,UAAU,QAAQ,oBAAA,CAAqB,UAAA;AAAA,iBAK9C,aAAA,CAAc,OAAe;AAAA,iBAI7B,oBAAA,CAAqB,MAAA,EAAQ,eAAA,GAAkB,cAAc;;;UCU5D,2BAAA;;EAEf,OAAA;EbzBwB;Ea2BxB,MAAA;Eb1B4B;Ea4B5B,SAAA;Eb5B4B;Ea8B5B,SAAA,UAAmB,KAAK;AAAA;AAAA,cAgEb,oBAAA;EAAA;cAMC,OAAA,EAAS,2BAAA;EbpFS;EasHxB,cAAA,CAAA,GAAkB,OAAA,CAAQ,WAAA;EbrHG;Ea2H7B,mBAAA,CAAA,GAAuB,OAAA,CAAQ,gBAAA;Eb3HF;AAAA;AACrC;;;;AAA+B;AAE/B;EayIQ,aAAA,CAAA,GAAiB,OAAA,CAAQ,gBAAA;AAAA;;;UCnIhB,WAAA;EAAA,SACN,QAAA,EAAU,oBAAA;EAAA,SACV,cAAA,EAAgB,gBAAA;EAAA,SAChB,mBAAA,EAAqB,gBAAA;EAAA,SACrB,cAAA,EAAgB,gBAAA;EAAA,SAChB,KAAA,EAAO,cAAA;EAAA,SACP,YAAA,EAAc,aAAA;EAAA,SACd,MAAA,EAAQ,UAAA;EAAA,SACR,aAAA;EAAA,SACA,KAAA,EAAO,WAAA;EdjBa;EAAA,ScmBpB,GAAA,SAAY,IAAA;AAAA;AAAA,UAGN,eAAA;EAAA,SACN,QAAA,EAAU,YAAA;EAAA,SACV,EAAA;EAAA,SACA,MAAA,EAAQ,WAAW;EAAA,SACnB,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,UAGM,cAAA;EAAA,SACN,QAAA,WAAmB,eAAe;EAAA,SAClC,EAAA;AAAA;;cAIE,kBAAA,SAA2B,KAAA;EAAA,SAC7B,OAAA,EAAS,cAAA;cACN,OAAA,EAAS,cAAA;AAAA;AAAA,iBAWD,aAAA,CAAc,IAAA,EAAM,WAAA,GAAc,OAAA,CAAQ,cAAA;;;KCpFpD,mBAAA,GfFwB,yRAkBpC;AAAA,ceNa,mBAAA,SAA4B,KAAA;EAAA,SAC9B,QAAA,EAAU,YAAA;EAAA,SACV,IAAA,EAAM,mBAAA;EAAA,SACN,MAAA;cAGP,QAAA,EAAU,YAAA,EACV,IAAA,EAAM,mBAAA,EACN,OAAA,UACA,OAAA;IAAY,MAAA;IAAiB,KAAA;EAAA;AAAA;;;;AfcD;AAChC;;ceCa,sBAAA,SAA+B,KAAA;EAAA,SACjC,QAAA,EAAU,YAAA;EAAA,SACV,MAAA;EfFE;;;;AAAkB;AAE/B;;;;AAIU;AACV;;EAPa;ceqBC,QAAA,EAAU,YAAA,EAAc,MAAA,EAAQ,sBAAA,YAAkC,OAAA;AAAA;;;UCrC/D,iBAAA;EAAA,SACN,QAAA,EAAU,YAAA;EhBaW;EAAA,SgBXrB,aAAA,EAAe,IAAI;EhBYO;EAAA,SgBV1B,KAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA;AAAA;;cAIE,oBAAA,SAA6B,KAAA;EAAA,SAC/B,KAAA,WAAgB,iBAAA;cACb,KAAA,WAAgB,iBAAA,IAAqB,YAAA;AAAA;;;;AhBSzC;AACV;;;;AAAiD;AAYjD;;;iBgBwBgB,eAAA,CACd,OAAA,WAAkB,eAAA,IAClB,GAAA,EAAK,IAAA,EACL,YAAA,WACC,iBAAA;AhB5BuB;AAO1B;;;;AAAsB;AAPI,iBgB4DV,cAAA,CACd,OAAA,WAAkB,eAAA,IAClB,GAAA,EAAK,IAAA,EACL,YAAA,WACC,iBAAA"}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{C as e,S as t,T as n,_ as r,a as i,b as a,c as o,d as s,f as c,g as l,h as u,i as d,l as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./watchdog-CNsiJMUR.mjs";import{C as w,S as T,T as E,a as D,b as O,c as k,d as A,f as j,g as M,h as N,i as P,l as F,m as I,n as L,o as R,p as z,r as B,s as V,t as H,u as U,v as W,w as G,x as K}from"./sync-state-table-CYl_DVX9.mjs";export{A as API_KEY_ENV_VAR,j as BASE_URL_ENV_VAR,z as DECLARED_CURRENCY,I as DECLARED_VAT_TREATMENT,N as EN_LOCALE,M as ET_LOCALE,W as LEGAL_DOCUMENTS_RESOURCE,O as ROOM_TYPES_RESOURCE,K as SYNC_JOB_NAME,T as SYNC_RESOURCES,L as VAT_TREATMENTS,w as WATCHDOG_JOB_NAME,D as YHIKAS_LEGAL_DOCUMENT_ENTITY,B as YHIKAS_ROOM_TYPE_ENTITY,G as YHIKAS_SYNC_ACTOR_ID,E as YHIKAS_SYNC_PLUGIN_NAME,e as YhikasApplyPartialError,R as YhikasLegalDocument,P as YhikasRoomType,F as YhikasSyncConfigError,S as YhikasSyncRefusedError,c as YhikasSyncRunError,v as YhikasSyncStaleError,V as YhikasSyncedContentReadOnlyError,d as YhikasUpstreamClient,t as YhikasUpstreamError,x as applyPlan,p as assertNotStale,g as assessStaleness,y as createSyncStateStore,i as legalDocumentRowSchema,m as legalDocumentsResponseSchema,k as machineWritten,_ as multilingualTextSchema,C as planDiff,u as projectLegalDocuments,l as projectRoomTypes,n as readLocalRows,U as resolveYhikasSyncConfig,o as roomTypeRowSchema,f as roomTypesResponseSchema,h as runYhikasSync,r as secondaryLocaleOf,a as stableHash,b as toSyncEntityClient,s as truncateError,H as yhikasSyncStateTable};
1
+ import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,h as f,i as p,l as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,x as T,y as E}from"./watchdog-BVf1F4sR.mjs";import{A as D,D as O,M as k,O as A,S as j,T as M,_ as N,a as P,c as F,d as I,f as L,g as R,h as z,i as B,j as V,k as H,l as U,m as W,n as G,o as K,p as q,r as J,s as Y,t as X,u as Z,v as Q,w as $,x as ee,y as te}from"./sync-state-table-CM6eL5W9.mjs";export{W as API_KEY_ENV_VAR,z as BASE_URL_ENV_VAR,R as DECLARED_CURRENCY,N as DECLARED_VAT_TREATMENT,Q as EN_LOCALE,te as ET_LOCALE,ee as LEGAL_DOCUMENTS_RESOURCE,j as MAX_SITE_NOTICES,$ as ROOM_TYPES_RESOURCE,M as SITE_INFO_KEY,O as SITE_INFO_RESOURCE,G as SYNCED_ENTITIES,A as SYNC_JOB_NAME,H as SYNC_RESOURCES,P as VAT_TREATMENTS,D as WATCHDOG_JOB_NAME,F as YHIKAS_LEGAL_DOCUMENT_ENTITY,K as YHIKAS_ROOM_TYPE_ENTITY,J as YHIKAS_SITE_INFO_ENTITY,V as YHIKAS_SYNC_ACTOR_ID,k as YHIKAS_SYNC_PLUGIN_NAME,n as YhikasApplyPartialError,U as YhikasLegalDocument,Y as YhikasRoomType,B as YhikasSiteInfo,L as YhikasSyncConfigError,w as YhikasSyncRefusedError,h as YhikasSyncRunError,x as YhikasSyncStaleError,Z as YhikasSyncedContentReadOnlyError,p as YhikasUpstreamClient,a as YhikasUpstreamError,t as applyPlan,g as assertNotStale,y as assessStaleness,d as createSyncStateStore,s as legalDocumentRowSchema,_ as legalDocumentsResponseSchema,I as machineWritten,b as multilingualTextSchema,i as planDiff,o as projectLegalDocuments,C as projectRoomTypes,E as projectSiteInfo,r as readLocalRows,q as resolveYhikasSyncConfig,l as roomTypeRowSchema,m as roomTypesResponseSchema,f as runYhikasSync,c as secondaryLocaleOf,S as siteInfoResponseSchema,u as siteNoticeRowSchema,e as stableHash,T as toSyncEntityClient,v as truncateError,X as yhikasSyncStateTable};
@@ -0,0 +1,2 @@
1
+ import{b as e,f as t,g as n,h as r,i,n as a,x as o}from"./watchdog-BVf1F4sR.mjs";import{h as s,i as c,j as l,k as u,l as d,m as f,s as p,t as m}from"./sync-state-table-CM6eL5W9.mjs";import{elevateRequestContext as h,runWithContextAsync as g}from"@murumets-ee/core";var _=class extends Error{constructor(e){super(`yhikas-sync is enabled but ${e.join(` and `)} ${e.length===1?`is`:`are`} not set. Failing loudly rather than skipping quietly: an unconfigured sync is a broken sync, and a broken sync must be visibly broken. Set the variable, or pass \`enabled: false\` to yhikasSync() if this install has no yhikas-admin.`),this.name=`YhikasSyncNotConfiguredError`}};function v(){return h({user:{id:l,groups:[`admin`]}})}function y(e){return e.logger}async function b(t){let{resolveI18nConfig:r}=await import(`@murumets-ee/content`),{defaultLocale:i}=await r(t);if(!n.includes(i))throw new x(`The app's default locale is '${i}', but yhikas-admin carries content in 'et' and 'en' only. The sync has nothing to write to the base row, so it refuses rather than publishing one language under another's name.`);return{defaultLocale:i,secondaryLocale:e(i)}}var x=class extends Error{constructor(e){super(e),this.name=`YhikasSyncLocaleError`}};function S(e,t){let n=t[f],r=[];if(e.baseUrl||r.push(s),n||r.push(f),!e.baseUrl||!n)throw new _(r);return{baseUrl:e.baseUrl,apiKey:n}}async function C(e,n,a=process.env){let{baseUrl:s,apiKey:l}=S(n,a),{sanitizeHtml:u}=await import(`@murumets-ee/blocks`);return g(v(),async()=>{let{defaultLocale:a}=await b(e);return r({upstream:new i({baseUrl:s,apiKey:l,timeoutMs:n.requestTimeoutMs}),roomTypeClient:o(e.getClient(p),a),legalDocumentClient:o(e.getClient(d),a),siteInfoClient:o(e.getClient(c),a),state:t(m.makeClient(e.db.readWrite)),sanitizeHtml:u,logger:y(e),defaultLocale:a,floor:{maxRows:n.maxRowsPerResource,maxRetireFraction:n.maxRetireFraction,minRowsForFraction:n.minRowsForRetireFloor}})})}async function w(e,n,r=new Date){let i=t(m.makeClient(e.db.readWrite));await T(e,r);let o=a(await i.readAll(),r,n.staleAfterMs);return e.logger.debug({window:n.staleAfterMs,resources:o.map(e=>({resource:e.resource,lastSuccessAt:e.lastSuccessAt}))},`yhikas-sync: staleness check passed`),o}async function T(e,n=new Date){let r=t(m.makeClient(e.db.readWrite));for(let e of u)await r.ensure(e,n)}export{C as runSyncJob,w as runWatchdogJob};
2
+ //# sourceMappingURL=jobs-DrMLTUFm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs-DrMLTUFm.mjs","names":[],"sources":["../src/jobs.ts"],"sourcesContent":["/**\n * The two job handlers, bound to a live `ToolkitApp`.\n *\n * This module is reached ONLY through a dynamic `import()` from the plugin's\n * `init` (see `plugin.ts`). It statically imports `@murumets-ee/core` and\n * `@murumets-ee/blocks`, either of which would be evaluated at\n * `lumi.config.ts`-load time under the CLI's jiti loader if the plugin entry\n * reached it statically — and `@murumets-ee/blocks`' root export evaluates\n * React's `createContext` at module scope.\n */\n\nimport { elevateRequestContext, runWithContextAsync, type ToolkitApp } from '@murumets-ee/core'\nimport type { SyncLogger } from './apply.js'\nimport type { ResolvedYhikasSyncConfig } from './config.js'\nimport {\n API_KEY_ENV_VAR,\n BASE_URL_ENV_VAR,\n EN_LOCALE,\n ET_LOCALE,\n LEGAL_DOCUMENTS_RESOURCE,\n ROOM_TYPES_RESOURCE,\n SYNC_RESOURCES,\n YHIKAS_SYNC_ACTOR_ID,\n} from './constants.js'\nimport { YhikasLegalDocument } from './entities/legal-document.js'\nimport { YhikasRoomType } from './entities/room-type.js'\nimport { YhikasSiteInfo } from './entities/site-info.js'\nimport { toSyncEntityClient } from './entity-client.js'\nimport { SUPPORTED_LOCALES, secondaryLocaleOf } from './projection.js'\nimport { runYhikasSync, type SyncRunSummary } from './run-sync.js'\nimport { createSyncStateStore } from './sync-state.js'\nimport { yhikasSyncStateTable } from './sync-state-table.js'\nimport { YhikasUpstreamClient } from './upstream/client.js'\nimport { assertNotStale, type ResourceStaleness } from './watchdog.js'\n\n/** Thrown when the sync is enabled but has no credential or no origin to talk to. */\nexport class YhikasSyncNotConfiguredError extends Error {\n constructor(missing: readonly string[]) {\n super(\n `yhikas-sync is enabled but ${missing.join(' and ')} ${missing.length === 1 ? 'is' : 'are'} ` +\n `not set. Failing loudly rather than skipping quietly: an unconfigured sync is a broken ` +\n `sync, and a broken sync must be visibly broken. Set the variable, or pass ` +\n `\\`enabled: false\\` to yhikasSync() if this install has no yhikas-admin.`,\n )\n this.name = 'YhikasSyncNotConfiguredError'\n }\n}\n\n/**\n * Every entity write runs as this identity.\n *\n * `elevateRequestContext` rather than `runAsCli`: both replace the permission\n * checker, but this one carries a NAMED actor, so `auditable()` records\n * `yhikas-sync` in `created_by`/`updated_by` instead of a generic `cli`. That\n * makes the sync's writes attributable — worth having precisely because the\n * upstream writes are not (F006) — and it is what the machine-written guard\n * recognises when it refuses every other writer.\n *\n * Built per run rather than at module scope: `elevateRequestContext` reads the\n * ambient context at CALL time, and a module-scope constant would bake in\n * whatever context happened to exist when the module first loaded.\n */\nfunction syncContext() {\n return elevateRequestContext({\n user: { id: YHIKAS_SYNC_ACTOR_ID, groups: ['admin'] },\n })\n}\n\n/** Narrow `app.logger` to the shape the sync uses, without importing a logger type. */\nfunction toSyncLogger(app: ToolkitApp): SyncLogger {\n return app.logger\n}\n\n/**\n * The app's REAL default locale, read from the content plugin rather than\n * configured here.\n *\n * A configured copy could disagree with the app's actual default, and the\n * failure would be silent in both directions: `updateForLocale` would take its\n * `isDefault` branch for what this package thought was the secondary locale\n * and overwrite the base row with the wrong language, while no translation was\n * ever written for the real secondary. `resolveI18nConfig` is the authoritative\n * answer — it merges the runtime `i18n` override over the shipped config — so\n * there is nothing to keep in sync.\n */\nasync function resolveLocales(\n app: ToolkitApp,\n): Promise<{ defaultLocale: string; secondaryLocale: string }> {\n const { resolveI18nConfig } = await import('@murumets-ee/content')\n const { defaultLocale } = await resolveI18nConfig(app)\n\n if (!SUPPORTED_LOCALES.includes(defaultLocale)) {\n throw new YhikasSyncLocaleError(\n `The app's default locale is '${defaultLocale}', but yhikas-admin carries content in ` +\n `'${ET_LOCALE}' and '${EN_LOCALE}' only. The sync has nothing to write to the base row, ` +\n `so it refuses rather than publishing one language under another's name.`,\n )\n }\n // `secondaryLocaleOf` asserts the same thing, so the two can never disagree.\n return { defaultLocale, secondaryLocale: secondaryLocaleOf(defaultLocale) }\n}\n\n/** Thrown when the app's locale configuration cannot carry the synced content. */\nexport class YhikasSyncLocaleError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'YhikasSyncLocaleError'\n }\n}\n\n/**\n * Resolve the bearer credential at RUN time, from the environment.\n *\n * Deliberately not part of the plugin's declared config: resolved config lands\n * in the framework's `PluginConfigRegistry`, a process-global any plugin can\n * read, and a secret does not belong there (mirroring `@murumets-ee/merit`'s\n * D003). Reading it here also means rotating the key needs a restart rather\n * than a rebuild.\n */\nfunction resolveCredentials(\n config: ResolvedYhikasSyncConfig,\n env: NodeJS.ProcessEnv,\n): { baseUrl: string; apiKey: string } {\n const apiKey = env[API_KEY_ENV_VAR]\n const missing: string[] = []\n if (!config.baseUrl) missing.push(BASE_URL_ENV_VAR)\n if (!apiKey) missing.push(API_KEY_ENV_VAR)\n if (!config.baseUrl || !apiKey) throw new YhikasSyncNotConfiguredError(missing)\n return { baseUrl: config.baseUrl, apiKey }\n}\n\n/** One full sync run. Throws on any resource failure, so the queue retries and alerts. */\nexport async function runSyncJob(\n app: ToolkitApp,\n config: ResolvedYhikasSyncConfig,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<SyncRunSummary> {\n const { baseUrl, apiKey } = resolveCredentials(config, env)\n const { sanitizeHtml } = await import('@murumets-ee/blocks')\n\n return runWithContextAsync(syncContext(), async () => {\n const { defaultLocale } = await resolveLocales(app)\n return runYhikasSync({\n upstream: new YhikasUpstreamClient({\n baseUrl,\n apiKey,\n timeoutMs: config.requestTimeoutMs,\n }),\n // The clients are built HERE, inside the job, not in the plugin's\n // `init`. That is load-bearing and easy to \"tidy\" into a bug:\n // `createAdminClient` reads the locale-status writer ONCE at\n // construction, and `@murumets-ee/content` installs that writer from its\n // own `init`. `Plugin.requires` guarantees content is CONFIGURED, not\n // that it initialised first — so a client constructed during init could\n // capture no writer, and `updateForLocale` would then silently assign\n // the requested status to the BASE row. Constructing at job-run time is\n // after every plugin's init, unconditionally.\n roomTypeClient: toSyncEntityClient(app.getClient(YhikasRoomType), defaultLocale),\n legalDocumentClient: toSyncEntityClient(app.getClient(YhikasLegalDocument), defaultLocale),\n siteInfoClient: toSyncEntityClient(app.getClient(YhikasSiteInfo), defaultLocale),\n state: createSyncStateStore(yhikasSyncStateTable.makeClient(app.db.readWrite)),\n sanitizeHtml,\n logger: toSyncLogger(app),\n defaultLocale,\n floor: {\n maxRows: config.maxRowsPerResource,\n maxRetireFraction: config.maxRetireFraction,\n minRowsForFraction: config.minRowsForRetireFloor,\n },\n })\n })\n}\n\n/**\n * The staleness check. Throws when any resource is past the window, which is\n * how it reaches the queue's dead-letter alerting — the detection is new, the\n * delivery is not.\n */\nexport async function runWatchdogJob(\n app: ToolkitApp,\n config: ResolvedYhikasSyncConfig,\n now: Date = new Date(),\n): Promise<ResourceStaleness[]> {\n const store = createSyncStateStore(yhikasSyncStateTable.makeClient(app.db.readWrite))\n // Seed before assessing, so a fresh install gets a `firstSeenAt` to be\n // measured from rather than reading as \"no tracking row at all\" — which is\n // correct but alarming on an app that simply has not reached its first sync\n // tick yet.\n //\n // The cost, stated: `assessStaleness`'s missing-row branch is therefore\n // unreachable from here, so a tracking row DELETED by hand while the sync is\n // broken resets the clock instead of alerting. That needs deliberate DB\n // surgery to reach, and the alternative — alerting on every fresh install's\n // first tick — trains people to ignore the one channel that has to stay\n // trustworthy.\n await ensureSyncState(app, now)\n const records = await store.readAll()\n const assessed = assertNotStale(records, now, config.staleAfterMs)\n app.logger.debug(\n {\n window: config.staleAfterMs,\n resources: assessed.map((entry) => ({\n resource: entry.resource,\n lastSuccessAt: entry.lastSuccessAt,\n })),\n },\n 'yhikas-sync: staleness check passed',\n )\n return assessed\n}\n\n/**\n * Seed a tracking row per resource, so a fresh install has a clock to be\n * measured against before its first sync tick.\n *\n * Called from the WATCHDOG, not from the plugin's `init`. Doing DB I/O during\n * `init` runs it in every process that builds a `ToolkitApp` — web included —\n * and would fail app boot outright on a database blip, or on any boot that\n * precedes `lumi migrate` creating the table, in exchange for two bookkeeping\n * rows.\n */\nexport async function ensureSyncState(app: ToolkitApp, now: Date = new Date()): Promise<void> {\n const store = createSyncStateStore(yhikasSyncStateTable.makeClient(app.db.readWrite))\n for (const resource of SYNC_RESOURCES) {\n await store.ensure(resource, now)\n }\n}\n\nexport { LEGAL_DOCUMENTS_RESOURCE, ROOM_TYPES_RESOURCE }\n"],"mappings":"yQAoCA,IAAa,EAAb,cAAkD,KAAM,CACtD,YAAY,EAA4B,CACtC,MACE,8BAA8B,EAAQ,KAAK,OAAO,EAAE,GAAG,EAAQ,SAAW,EAAI,KAAO,MAAM,0OAI7F,EACA,KAAK,KAAO,8BACd,CACF,EAgBA,SAAS,GAAc,CACrB,OAAO,EAAsB,CAC3B,KAAM,CAAE,GAAI,EAAsB,OAAQ,CAAC,OAAO,CAAE,CACtD,CAAC,CACH,CAGA,SAAS,EAAa,EAA6B,CACjD,OAAO,EAAI,MACb,CAcA,eAAe,EACb,EAC6D,CAC7D,GAAM,CAAE,qBAAsB,MAAM,OAAO,wBACrC,CAAE,iBAAkB,MAAM,EAAkB,CAAG,EAErD,GAAI,CAAC,EAAkB,SAAS,CAAa,EAC3C,MAAM,IAAI,EACR,gCAAgC,EAAc,kLAGhD,EAGF,MAAO,CAAE,gBAAe,gBAAiB,EAAkB,CAAa,CAAE,CAC5E,CAGA,IAAa,EAAb,cAA2C,KAAM,CAC/C,YAAY,EAAiB,CAC3B,MAAM,CAAO,EACb,KAAK,KAAO,uBACd,CACF,EAWA,SAAS,EACP,EACA,EACqC,CACrC,IAAM,EAAS,EAAI,GACb,EAAoB,CAAC,EAG3B,GAFK,EAAO,SAAS,EAAQ,KAAK,CAAgB,EAC7C,GAAQ,EAAQ,KAAK,CAAe,EACrC,CAAC,EAAO,SAAW,CAAC,EAAQ,MAAM,IAAI,EAA6B,CAAO,EAC9E,MAAO,CAAE,QAAS,EAAO,QAAS,QAAO,CAC3C,CAGA,eAAsB,EACpB,EACA,EACA,EAAyB,QAAQ,IACR,CACzB,GAAM,CAAE,UAAS,UAAW,EAAmB,EAAQ,CAAG,EACpD,CAAE,gBAAiB,MAAM,OAAO,uBAEtC,OAAO,EAAoB,EAAY,EAAG,SAAY,CACpD,GAAM,CAAE,iBAAkB,MAAM,EAAe,CAAG,EAClD,OAAO,EAAc,CACnB,SAAU,IAAI,EAAqB,CACjC,UACA,SACA,UAAW,EAAO,gBACpB,CAAC,EAUD,eAAgB,EAAmB,EAAI,UAAU,CAAc,EAAG,CAAa,EAC/E,oBAAqB,EAAmB,EAAI,UAAU,CAAmB,EAAG,CAAa,EACzF,eAAgB,EAAmB,EAAI,UAAU,CAAc,EAAG,CAAa,EAC/E,MAAO,EAAqB,EAAqB,WAAW,EAAI,GAAG,SAAS,CAAC,EAC7E,eACA,OAAQ,EAAa,CAAG,EACxB,gBACA,MAAO,CACL,QAAS,EAAO,mBAChB,kBAAmB,EAAO,kBAC1B,mBAAoB,EAAO,qBAC7B,CACF,CAAC,CACH,CAAC,CACH,CAOA,eAAsB,EACpB,EACA,EACA,EAAY,IAAI,KACc,CAC9B,IAAM,EAAQ,EAAqB,EAAqB,WAAW,EAAI,GAAG,SAAS,CAAC,EAYpF,MAAM,EAAgB,EAAK,CAAG,EAE9B,IAAM,EAAW,EAAe,MADV,EAAM,QAAQ,EACK,EAAK,EAAO,YAAY,EAWjE,OAVA,EAAI,OAAO,MACT,CACE,OAAQ,EAAO,aACf,UAAW,EAAS,IAAK,IAAW,CAClC,SAAU,EAAM,SAChB,cAAe,EAAM,aACvB,EAAE,CACJ,EACA,qCACF,EACO,CACT,CAYA,eAAsB,EAAgB,EAAiB,EAAY,IAAI,KAAuB,CAC5F,IAAM,EAAQ,EAAqB,EAAqB,WAAW,EAAI,GAAG,SAAS,CAAC,EACpF,IAAK,IAAM,KAAY,EACrB,MAAM,EAAM,OAAO,EAAU,CAAG,CAEpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;YAyBY,oBAAA;IACR,0BAAA,EAA4B,wBAAwB;EAAA;AAAA;;;;;;;;;;;iBAcxC,UAAA,CAAW,MAAA,GAAS,gBAAA,GAAgB,IAAA,6BAAA,MAAA;EAAA"}
1
+ {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;YAwBY,oBAAA;IACR,0BAAA,EAA4B,wBAAwB;EAAA;AAAA;;;;;;;;;;;iBAcxC,UAAA,CAAW,MAAA,GAAS,gBAAA,GAAgB,IAAA,6BAAA,MAAA;EAAA"}
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{C as e,T as t,i as n,o as r,t as i,u as a,x as o}from"./sync-state-table-CYl_DVX9.mjs";import{definePlugin as s}from"@murumets-ee/core";function c(c){let l=a(c);return s({name:t,requires:[`@murumets-ee/content`,`@murumets-ee/queue`],server:{config:l,entities:[n,r],tables:{yhikas_sync_state:i.table},init:async n=>{if(!l.enabled){n.logger.warn({plugin:t},`yhikas-sync is disabled by config — no scheduled pull, and no staleness alerting`);return}let{defineJob:r,registerJob:i}=await import(`@murumets-ee/queue/client`);i(r({name:o,description:`Pull room types and legal documents from yhikas-admin and apply a full-snapshot diff.`,schedule:l.schedule}),async()=>{let{runSyncJob:e}=await import(`./jobs-D2perJoW.mjs`);await e(n,l)}),i(r({name:e,description:`Alert when the newest successful yhikas-admin sync is older than the staleness window.`,schedule:l.watchdogSchedule}),async()=>{let{runWatchdogJob:e}=await import(`./jobs-D2perJoW.mjs`);await e(n,l)})}}})}export{c as yhikasSync};
1
+ import{A as e,M as t,O as n,n as r,p as i,t as a}from"./sync-state-table-CM6eL5W9.mjs";import{definePlugin as o}from"@murumets-ee/core";function s(s){let c=i(s);return o({name:t,requires:[`@murumets-ee/content`,`@murumets-ee/queue`],server:{config:c,entities:[...r],tables:{yhikas_sync_state:a.table},init:async r=>{if(!c.enabled){r.logger.warn({plugin:t},`yhikas-sync is disabled by config — no scheduled pull, and no staleness alerting`);return}let{defineJob:i,registerJob:a}=await import(`@murumets-ee/queue/client`);a(i({name:n,description:`Pull room types and legal documents from yhikas-admin and apply a full-snapshot diff.`,schedule:c.schedule}),async()=>{let{runSyncJob:e}=await import(`./jobs-DrMLTUFm.mjs`);await e(r,c)}),a(i({name:e,description:`Alert when the newest successful yhikas-admin sync is older than the staleness window.`,schedule:c.watchdogSchedule}),async()=>{let{runWatchdogJob:e}=await import(`./jobs-DrMLTUFm.mjs`);await e(r,c)})}}})}export{s as yhikasSync};
2
2
  //# sourceMappingURL=plugin.mjs.map