@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 +431 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/jobs-DrMLTUFm.mjs +2 -0
- package/dist/jobs-DrMLTUFm.mjs.map +1 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/sync-state-table-CM6eL5W9.mjs +2 -0
- package/dist/sync-state-table-CM6eL5W9.mjs.map +1 -0
- package/dist/watchdog-BVf1F4sR.mjs +2 -0
- package/dist/watchdog-BVf1F4sR.mjs.map +1 -0
- package/package.json +7 -7
- package/dist/jobs-D2perJoW.mjs +0 -2
- package/dist/jobs-D2perJoW.mjs.map +0 -1
- package/dist/sync-state-table-CYl_DVX9.mjs +0 -2
- package/dist/sync-state-table-CYl_DVX9.mjs.map +0 -1
- package/dist/watchdog-CNsiJMUR.mjs +0 -2
- package/dist/watchdog-CNsiJMUR.mjs.map +0 -1
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
|
-
/**
|
|
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
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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":";;;;;;;;;;;;AASA;;cAAa,uBAAA;;AAAuB;AAWpC;;;;AAAiC;AAOjC;;cAPa,oBAAA;;AAOa;AAC1B;;;cADa,aAAA;AAAA,cACA,iBAAA;
|
|
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":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as e,
|
|
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"}
|
package/dist/plugin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;
|
|
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{
|
|
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
|