@plitzi/sdk-shared 0.37.0 → 0.37.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/dist/actions/cron.d.ts +21 -12
  3. package/dist/actions/cron.mjs +48 -17
  4. package/dist/actions/index.d.ts +1 -1
  5. package/dist/actions/index.mjs +7 -7
  6. package/dist/actions/triggers.d.ts +4 -0
  7. package/dist/actions/triggers.mjs +4 -1
  8. package/dist/authoring/declare.d.ts +21 -0
  9. package/dist/dataSource/getBindingsDetails.mjs +21 -20
  10. package/dist/dataSource/utility/checkboxParams.d.ts +11 -0
  11. package/dist/dataSource/utility/checkboxParams.mjs +12 -0
  12. package/dist/helpers/index.mjs +13 -12
  13. package/dist/helpers/twigWrapper/Evaluator/Evaluator.mjs +21 -6
  14. package/dist/helpers/twigWrapper/Parser/ExpressionParser.mjs +1 -1
  15. package/dist/helpers/twigWrapper/filters/filters.mjs +10 -5
  16. package/dist/helpers/twigWrapper/index.d.ts +1 -0
  17. package/dist/helpers/twigWrapper/index.mjs +4 -3
  18. package/dist/helpers/twigWrapper/tokens/hasTemplateSyntax.d.ts +9 -0
  19. package/dist/helpers/twigWrapper/tokens/hasTemplateSyntax.mjs +4 -0
  20. package/dist/hooks/usePlitziServiceContext.d.ts +1 -1
  21. package/dist/index.mjs +64 -63
  22. package/dist/navigation/routes.mjs +27 -27
  23. package/dist/schema/serverElements.d.ts +5 -0
  24. package/dist/schema/serverElements.mjs +15 -14
  25. package/dist/state/index.mjs +2 -2
  26. package/dist/state/runtimeStatePersist.d.ts +17 -0
  27. package/dist/state/runtimeStatePersist.mjs +62 -9
  28. package/dist/store/index.d.ts +33 -33
  29. package/dist/theme/ThemeProvider.d.ts +1 -1
  30. package/dist/types/ActionTypes.d.ts +189 -0
  31. package/dist/types/ServerTypes.d.ts +52 -1
  32. package/dist/types/StoreTypes.d.ts +1 -1
  33. package/package.json +20 -12
@@ -38,5 +38,5 @@ export type ThemeProviderProps = {
38
38
  * updating the moment the visitor changes their system setting, which is how a panel ends up dark inside a page
39
39
  * that is still light.
40
40
  */
41
- declare const ThemeProvider: ({ defaultTheme, theme, scope, cookieName, children }: ThemeProviderProps) => import("react").JSX.Element;
41
+ declare const ThemeProvider: ({ defaultTheme, theme, scope, cookieName, children }: ThemeProviderProps) => import('react').JSX.Element;
42
42
  export default ThemeProvider;
@@ -495,3 +495,192 @@ export type ActionStreamFrame = {
495
495
  event: 'error';
496
496
  data: ActionCallError;
497
497
  };
498
+ /**
499
+ * Where a job is, as the only durable answer to "did it happen".
500
+ *
501
+ * `pending` is waiting to be taken, `running` is held by a worker under a lease, and the other four are terminal —
502
+ * except that an operator may put a `failed`, `dead` or `cancelled` one back to `pending`. `dead` is a job that
503
+ * used up every attempt it was given: it stays, visibly, because the alternative is work that quietly evaporated.
504
+ */
505
+ export type ActionJobStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'dead' | 'cancelled';
506
+ /**
507
+ * One go at a job, kept forever on the job itself.
508
+ *
509
+ * `lost` is the attempt nobody reported: a worker took the job, its lease ran out, and another worker found it
510
+ * abandoned. It is the record of a replica that died mid-run, which is otherwise the one failure that leaves no
511
+ * trace anywhere — and the reason this list exists rather than a single `error` field.
512
+ */
513
+ export type ActionJobAttempt = {
514
+ attempt: number;
515
+ status: 'succeeded' | 'failed' | 'cancelled' | 'lost';
516
+ /** Epoch ms, from the QUEUE's clock — never a worker's own. */
517
+ startedAt: number;
518
+ endedAt: number;
519
+ /** Which worker held it, so a bad replica is identifiable from the history alone. */
520
+ workerId: string;
521
+ /** The run this attempt became, when it got far enough to be one. Joins to the run history. */
522
+ runId?: string;
523
+ error?: string;
524
+ };
525
+ /** What starting a job needs. Everything else is the queue's to fill in. */
526
+ export type ActionJobInput = {
527
+ /**
528
+ * The job's identity, and the whole of its exactly-once guarantee.
529
+ *
530
+ * A schedule derives it from the fire it belongs to — `schedule:<space>:<action>:<instant>` — so two replicas
531
+ * reaching the same fire write the same id and the second one is a no-op. Enqueue is idempotent by this, which
532
+ * is what lets a producer crash between enqueuing and recording that it did.
533
+ */
534
+ id: string;
535
+ spaceId: number;
536
+ actionId: string;
537
+ environment: Environment;
538
+ trigger: ActionTriggerType;
539
+ input: Record<string, unknown>;
540
+ /** The instant this job was due, as the schedule stored it. Epoch ms. */
541
+ dueAt: number;
542
+ maxAttempts: number;
543
+ /** Fires that went by unclaimed before this one. Shown to an operator; never acted on. */
544
+ missed?: number;
545
+ };
546
+ /** One unit of work the queue is holding, as an operator sees it. */
547
+ export type ActionJob = ActionJobInput & {
548
+ status: ActionJobStatus;
549
+ /** Epoch ms before which no worker may take it — a retry's backoff, or the moment it was enqueued. */
550
+ runAt: number;
551
+ attempts: number;
552
+ /** While `running`: the instant the holder's claim lapses and another worker may take it over. */
553
+ leaseUntil?: number;
554
+ workerId?: string;
555
+ runId?: string;
556
+ /** Set by an operator who wants it stopped; the worker holding it reads this and aborts. */
557
+ cancelRequested?: boolean;
558
+ createdAt: number;
559
+ updatedAt: number;
560
+ /** Why the last attempt ended badly. */
561
+ error?: string;
562
+ history: ActionJobAttempt[];
563
+ };
564
+ /**
565
+ * How a worker reports what became of a claim.
566
+ *
567
+ * `delayMs` rather than an instant, and no timestamps: every moment the queue records is read from the QUEUE's
568
+ * clock, because a cluster whose nodes sit in different countries agrees about durations and not about now.
569
+ */
570
+ export type ActionJobSettlement = {
571
+ jobId: string;
572
+ workerId: string;
573
+ status: Extract<ActionJobStatus, 'succeeded' | 'failed' | 'dead' | 'cancelled' | 'pending'>;
574
+ /** For a retry: how long from now before it may be claimed again. */
575
+ delayMs?: number;
576
+ runId?: string;
577
+ error?: string;
578
+ /** The attempt to append to the history. Omitted only when the claim is being handed back untouched. */
579
+ attempt?: Omit<ActionJobAttempt, 'attempt' | 'startedAt' | 'endedAt'>;
580
+ };
581
+ export type ActionJobQuery = {
582
+ spaceIds: number[];
583
+ actionId?: string;
584
+ status?: ActionJobStatus;
585
+ limit: number;
586
+ offset: number;
587
+ };
588
+ /**
589
+ * A recurring intent, as a row rather than as a timer.
590
+ *
591
+ * `nextRunAt` is the point of the whole design: the instant of the next fire is DECIDED ONCE and written down, so
592
+ * every replica afterwards reads the same number instead of asking its own clock whether the moment has come. Two
593
+ * nodes whose clocks differ by a minute then produce the same fires, in the same order, exactly once each — the
594
+ * disagreement moves the WHEN by their skew and can never move the WHETHER.
595
+ */
596
+ export type ActionSchedule = {
597
+ spaceId: number;
598
+ actionId: string;
599
+ cron: string;
600
+ timezone?: string;
601
+ environment: Environment;
602
+ enabled: boolean;
603
+ /** Epoch ms of the next fire this schedule owes. The queue's clock decides when that has arrived. */
604
+ nextRunAt: number;
605
+ /** The last fire actually enqueued. */
606
+ lastFireAt?: number;
607
+ /** Fires that went by while nothing was running to take them. Cumulative, and shown to an operator. */
608
+ missed: number;
609
+ maxAttempts: number;
610
+ updatedAt: number;
611
+ };
612
+ /** A schedule as it is derived from an action document — everything the queue does not own itself. */
613
+ export type ActionScheduleInput = Omit<ActionSchedule, 'missed' | 'updatedAt' | 'lastFireAt'>;
614
+ /**
615
+ * Where a deployment keeps its jobs and schedules.
616
+ *
617
+ * The same kind of seam as {@link ActionKvAdapter} and for the same reason — the server never learns where a
618
+ * deployment stores anything — but with one rule this one cannot be written without, and it is written here so no
619
+ * deployment has to rediscover it:
620
+ *
621
+ * **Every instant comes from the store.** `now` is the cluster's clock, and every comparison the queue makes
622
+ * internally — is this schedule due, has this lease lapsed, may this retry be claimed — is made against it, never
623
+ * against the caller's. That is why `settle` takes a `delayMs` and not a deadline, and why `claim` takes a
624
+ * `leaseMs` and not a `leaseUntil`: durations survive a cluster spread across time zones and machines whose clocks
625
+ * drift, and absolute instants minted by a caller do not.
626
+ *
627
+ * **`enqueue` is idempotent by `id`,** and answers whether it was the one that created the job. That, and nothing
628
+ * else, is what makes a fire happen exactly once when several replicas are racing to produce it.
629
+ *
630
+ * **`claim` is atomic, and it also reaps.** A job whose lease has lapsed is a job whose worker died, and it is
631
+ * taken by whoever claims next — the same operation, because a separate reaper is a second thing to deploy, to
632
+ * schedule and to forget.
633
+ */
634
+ export type ActionJobQueue = {
635
+ /** The cluster's clock. The only clock anything in the scheduling path is allowed to read. */
636
+ now: () => Promise<Date>;
637
+ /** True when this call created the job; false when that id was already there. Never throws on a duplicate. */
638
+ enqueue: (job: ActionJobInput) => Promise<boolean>;
639
+ /**
640
+ * Takes up to `limit` jobs that are due, and any whose lease has lapsed — atomically, one worker per job.
641
+ *
642
+ * A reclaimed job arrives with its previous holder's attempt already written to the history as `lost`.
643
+ */
644
+ claim: (params: {
645
+ workerId: string;
646
+ leaseMs: number;
647
+ limit: number;
648
+ }) => Promise<ActionJob[]>;
649
+ /** Extends the lease on jobs this worker still holds, and answers which of them an operator asked to cancel. */
650
+ heartbeat: (params: {
651
+ jobIds: string[];
652
+ workerId: string;
653
+ leaseMs: number;
654
+ }) => Promise<string[]>;
655
+ settle: (settlement: ActionJobSettlement) => Promise<void>;
656
+ /** Due schedules, oldest fire first. */
657
+ dueSchedules: (limit: number) => Promise<ActionSchedule[]>;
658
+ /**
659
+ * Moves a schedule forward, but only if it is still where the caller last saw it.
660
+ *
661
+ * The compare-and-set is what stops two replicas from advancing the same schedule twice. Answers false when
662
+ * somebody else got there first, which is not an error — it is the other replica having done the work.
663
+ */
664
+ advanceSchedule: (params: {
665
+ spaceId: number;
666
+ actionId: string;
667
+ from: number;
668
+ to: number;
669
+ missed: number;
670
+ }) => Promise<boolean>;
671
+ /** Replaces what this space has scheduled. An action no longer listed stops having a schedule at all. */
672
+ putSchedules: (params: {
673
+ spaceId: number;
674
+ schedules: ActionScheduleInput[];
675
+ }) => Promise<void>;
676
+ listJobs: (query: ActionJobQuery) => Promise<{
677
+ jobs: ActionJob[];
678
+ total: number;
679
+ }>;
680
+ getJob: (spaceIds: number[], jobId: string) => Promise<ActionJob | undefined>;
681
+ listSchedules: (spaceIds: number[]) => Promise<ActionSchedule[]>;
682
+ /** An operator putting a finished job back in the queue. Answers false when it is not in a state to go back. */
683
+ requeue: (spaceIds: number[], jobId: string) => Promise<boolean>;
684
+ /** An operator stopping a job: dropped if it is waiting, aborted at its next step if it is running. */
685
+ cancel: (spaceIds: number[], jobId: string) => Promise<boolean>;
686
+ };
@@ -1,4 +1,4 @@
1
- import { ActionEmailConfig, ActionEntry, ActionLimits, ActionRejectReason, ActionRunSummary, ActionRejectRecord, ActionRunRecord, ActionRunStatus, ActionTaskDescriptor, ActionTriggerType } from './ActionTypes';
1
+ import { ActionEmailConfig, ActionEntry, ActionJobQueue, ActionLimits, ActionRejectReason, ActionRunSummary, ActionRejectRecord, ActionRunRecord, ActionRunStatus, ActionTaskDescriptor, ActionTriggerType } from './ActionTypes';
2
2
  import { Environment } from './CommonTypes';
3
3
  import { ConnectorEntry } from './ConnectorTypes';
4
4
  import { Schema } from './SchemaTypes';
@@ -465,6 +465,8 @@ export type SpaceRevision = {
465
465
  export type ActionLookupsConfig = {
466
466
  getAction: (spaceId: number, actionId: string, at?: SpaceRevision) => Promise<unknown>;
467
467
  listActions?: (spaceId: number, at?: SpaceRevision) => Promise<unknown[]>;
468
+ /** Which spaces have something scheduled — the periodic reconcile's safety net. See `ActionLookups`. */
469
+ listScheduledSpaces?: () => Promise<number[]>;
468
470
  getCredential?: (spaceId: number, identifier: string) => Promise<Record<string, string> | undefined>;
469
471
  getConnector?: (spaceId: number, connectorId: string, at?: SpaceRevision) => Promise<unknown>;
470
472
  };
@@ -554,6 +556,55 @@ export type SSRActionConfig = {
554
556
  increment: (key: string, amount: number) => Promise<number>;
555
557
  expire: (key: string, ttlSeconds: number) => Promise<void>;
556
558
  };
559
+ /**
560
+ * Scheduled runs: the durable queue they wait in, how many this replica runs at once, and whether it produces.
561
+ *
562
+ * On wherever `lookups.listActions` is answerable — a `schedule` trigger the editor offers is a trigger the
563
+ * server honours, which before this it only did if the deployment wrote a timer and a distributed lock of its
564
+ * own. With no `queue` it runs over this process's memory: right for one replica, and wrong for two, because
565
+ * each then keeps its own schedules and the nightly email goes out once per replica.
566
+ *
567
+ * `false` turns it off for a replica that must only serve pages.
568
+ */
569
+ jobs?: SSRActionJobsConfig | false;
570
+ };
571
+ /**
572
+ * How this deployment runs what a clock starts.
573
+ *
574
+ * Every default here is a safe one for a single machine; the two a cluster has to think about are `queue` — which
575
+ * must be shared, or the replicas do not see each other's work — and `workers`, which is how fast a backlog
576
+ * drains. Nothing in this config is a clock: the queue's own is the only one the scheduling path reads, so
577
+ * replicas in different countries agree about what has run without agreeing about what time it is.
578
+ */
579
+ export type SSRActionJobsConfig = {
580
+ /** Shaped as `ActionJobQueue`. Omitted → this process's memory. */
581
+ queue?: ActionJobQueue;
582
+ /** Jobs this replica runs at once. Default 4; `0` produces without consuming. */
583
+ workers?: number;
584
+ /** Whether this replica sweeps due schedules into the queue. Default true. */
585
+ produce?: boolean;
586
+ /** The spaces this server schedules for, when it serves a known few — the self-hosted shape. */
587
+ spaces?: number[];
588
+ /** How often a free worker looks for a job. Default 1s. */
589
+ pollMs?: number;
590
+ /** How often this replica looks for due schedules. Default 15s. */
591
+ schedulePollMs?: number;
592
+ /** How long a claim is held before another replica may take the job over — the failover window. Default 30s. */
593
+ leaseMs?: number;
594
+ /** Attempts a job gets before it is left for an operator as `dead`. Default 3. */
595
+ maxAttempts?: number;
596
+ /** First retry delay, doubling per attempt, up to `maxMs`. Defaults to 30s and 15 minutes. */
597
+ backoff?: {
598
+ baseMs?: number;
599
+ maxMs?: number;
600
+ };
601
+ /** Which environment scheduled runs execute in. Default `main`. */
602
+ environment?: Environment;
603
+ /** How often schedules are re-derived from documents. Default 15 minutes. */
604
+ reconcileMs?: number;
605
+ /** Names this replica in the job history. Defaults to the pid and a random suffix. */
606
+ workerId?: string;
607
+ onError?: (error: unknown) => void;
557
608
  };
558
609
  /**
559
610
  * How the server reaches a space's connectors: the manifest that describes one, and the credential it authenticates
@@ -102,7 +102,7 @@ export type RenderSettings = {
102
102
  *
103
103
  * Not the visitor's business and not the page's either — it is a fact about the account behind the site, which is
104
104
  * why it rides the render surface rather than the schema: nothing an author writes can set it, and nothing in the
105
- * space's settings can turn it off. `branding` is forced on by the same state; this is what says WHY.
105
+ * space's settings can turn it off.
106
106
  */
107
107
  overQuota?: boolean;
108
108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-shared",
3
- "version": "0.37.0",
3
+ "version": "0.37.2",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist",
@@ -186,6 +186,10 @@
186
186
  "types": "./dist/dataSource/utility/capitalize.d.ts",
187
187
  "import": "./dist/dataSource/utility/capitalize.mjs"
188
188
  },
189
+ "./dataSource/utility/checkboxParams": {
190
+ "types": "./dist/dataSource/utility/checkboxParams.d.ts",
191
+ "import": "./dist/dataSource/utility/checkboxParams.mjs"
192
+ },
189
193
  "./dataSource/utility/dateConverter": {
190
194
  "types": "./dist/dataSource/utility/dateConverter.d.ts",
191
195
  "import": "./dist/dataSource/utility/dateConverter.mjs"
@@ -412,6 +416,10 @@
412
416
  "types": "./dist/helpers/twigWrapper/templateRoots.d.ts",
413
417
  "import": "./dist/helpers/twigWrapper/templateRoots.mjs"
414
418
  },
419
+ "./helpers/twigWrapper/tokens/hasTemplateSyntax": {
420
+ "types": "./dist/helpers/twigWrapper/tokens/hasTemplateSyntax.d.ts",
421
+ "import": "./dist/helpers/twigWrapper/tokens/hasTemplateSyntax.mjs"
422
+ },
415
423
  "./helpers/twigWrapper/tokens/hasValidToken": {
416
424
  "types": "./dist/helpers/twigWrapper/tokens/hasValidToken.d.ts",
417
425
  "import": "./dist/helpers/twigWrapper/tokens/hasValidToken.mjs"
@@ -1285,7 +1293,7 @@
1285
1293
  "dependencies": {
1286
1294
  "@modelcontextprotocol/sdk": "^1.30.0",
1287
1295
  "@plitzi/nexus": "^1.2.2",
1288
- "@plitzi/plitzi-ui": "^1.6.21",
1296
+ "@plitzi/plitzi-ui": "^1.6.22",
1289
1297
  "date-fns": "^4.4.0",
1290
1298
  "date-fns-tz": "^3.2.0",
1291
1299
  "graphql-tag": "^2.12.7",
@@ -1297,12 +1305,12 @@
1297
1305
  "react-dom": "^19"
1298
1306
  },
1299
1307
  "devDependencies": {
1300
- "@babel/cli": "^8.0.5",
1301
- "@babel/core": "^8.0.5",
1302
- "@babel/eslint-parser": "^8.0.5",
1308
+ "@babel/cli": "^8.0.6",
1309
+ "@babel/core": "^8.0.6",
1310
+ "@babel/eslint-parser": "^7.29.9",
1303
1311
  "@babel/plugin-syntax-flow": "^8.0.1",
1304
1312
  "@babel/plugin-transform-react-jsx": "^8.0.1",
1305
- "@babel/preset-env": "^8.0.5",
1313
+ "@babel/preset-env": "^8.0.6",
1306
1314
  "@eslint/js": "^10.0.1",
1307
1315
  "@tailwindcss/forms": "^0.5.11",
1308
1316
  "@testing-library/dom": "^10.4.2",
@@ -1317,12 +1325,12 @@
1317
1325
  "eslint": "^9.39.5",
1318
1326
  "eslint-config-airbnb": "^19.0.4",
1319
1327
  "eslint-config-prettier": "^10.1.8",
1320
- "eslint-config-turbo": "^2.10.13",
1328
+ "eslint-config-turbo": "^2.11.3",
1321
1329
  "eslint-import-resolver-alias": "^1.1.2",
1322
1330
  "eslint-plugin-cypress": "^7.0.2",
1323
1331
  "eslint-plugin-flowtype": "8.0.3",
1324
1332
  "eslint-plugin-import": "^2.32.0",
1325
- "eslint-plugin-jsdoc": "^64.5.2",
1333
+ "eslint-plugin-jsdoc": "^64.5.4",
1326
1334
  "eslint-plugin-jsx-a11y": "^6.10.2",
1327
1335
  "eslint-plugin-prettier": "^5.5.6",
1328
1336
  "eslint-plugin-react": "^7.37.5",
@@ -1330,17 +1338,17 @@
1330
1338
  "eslint-plugin-react-refresh": "^0.5.7",
1331
1339
  "eslint-plugin-storybook": "^10.6.0",
1332
1340
  "globals": "^17.12.0",
1333
- "jsdom": "^30.1.0",
1341
+ "jsdom": "^30.1.1",
1334
1342
  "postcss": "^8.5.28",
1335
- "prettier": "^3.9.8",
1343
+ "prettier": "^3.9.9",
1336
1344
  "react": "^19.3.0",
1337
1345
  "react-dom": "^19.3.0",
1338
1346
  "tailwindcss": "^4.3.3",
1339
1347
  "terser": "^5.51.2",
1340
1348
  "typescript": "^6.0.3",
1341
- "typescript-eslint": "^8.70.0",
1349
+ "typescript-eslint": "^8.70.1",
1342
1350
  "vite": "^8.3.0",
1343
- "vite-plugin-dts": "^5.1.0",
1351
+ "vite-plugin-dts": "^5.1.1",
1344
1352
  "vite-plugin-react": "^4.0.1",
1345
1353
  "vitest": "^5.0.1",
1346
1354
  "vitest-fetch-mock": "^0.4.5"