@plitzi/sdk-shared 0.33.2 → 0.34.1

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 (204) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/actions/cron.d.ts +34 -0
  3. package/dist/actions/cron.mjs +72 -0
  4. package/dist/actions/index.d.ts +6 -0
  5. package/dist/actions/index.mjs +5 -0
  6. package/dist/actions/triggerParams.d.ts +41 -0
  7. package/dist/actions/triggerParams.mjs +40 -0
  8. package/dist/actions/triggers.d.ts +29 -0
  9. package/dist/actions/triggers.mjs +4 -0
  10. package/dist/actions/validateDocument.d.ts +23 -0
  11. package/dist/actions/validateDocument.mjs +267 -0
  12. package/dist/auth/failureChannel.d.ts +9 -4
  13. package/dist/auth/failureChannel.mjs +3 -2
  14. package/dist/authoring/builder.d.ts +51 -0
  15. package/dist/authoring/builder.mjs +29 -0
  16. package/dist/authoring/declare.d.ts +64 -0
  17. package/dist/authoring/declare.mjs +4 -0
  18. package/dist/authoring/elementCallbacks.d.ts +13 -0
  19. package/dist/authoring/elementCallbacks.mjs +71 -0
  20. package/dist/authoring/index.d.ts +21 -0
  21. package/dist/authoring/index.mjs +6 -0
  22. package/dist/authoring/paramSpec.d.ts +51 -0
  23. package/dist/authoring/paramSpec.mjs +40 -0
  24. package/dist/authoring/spaceCallbacks.d.ts +10 -0
  25. package/dist/authoring/spaceCallbacks.mjs +51 -0
  26. package/dist/builder/contexts/BuilderContext.d.ts +2 -4
  27. package/dist/dataSource/getBindingsDetails.mjs +1 -1
  28. package/dist/dataSource/helpers/getSourceName.d.ts +1 -3
  29. package/dist/dataSource/helpers/getSourceName.mjs +1 -1
  30. package/dist/dataSource/index.d.ts +2 -0
  31. package/dist/dataSource/index.mjs +6 -5
  32. package/dist/dataSource/resolveVariables.d.ts +22 -0
  33. package/dist/dataSource/resolveVariables.mjs +16 -0
  34. package/dist/dataSource/utility/index.mjs +15 -13
  35. package/dist/dataSource/utility/not.d.ts +11 -0
  36. package/dist/dataSource/utility/not.mjs +15 -0
  37. package/dist/devTools/debugCookie.d.ts +16 -0
  38. package/dist/devTools/debugCookie.mjs +7 -0
  39. package/dist/devTools/index.d.ts +6 -2
  40. package/dist/devTools/index.mjs +4 -2
  41. package/dist/devTools/interactionLog.d.ts +14 -0
  42. package/dist/devTools/interactionLog.mjs +4 -0
  43. package/dist/helpers/index.d.ts +2 -0
  44. package/dist/helpers/index.mjs +11 -9
  45. package/dist/helpers/reducerOrigin.d.ts +15 -0
  46. package/dist/helpers/reducerOrigin.mjs +4 -0
  47. package/dist/helpers/ruleEvaluator.d.ts +14 -0
  48. package/dist/helpers/ruleEvaluator.mjs +5 -0
  49. package/dist/helpers/twigWrapper/AST.d.ts +12 -1
  50. package/dist/helpers/twigWrapper/AST.mjs +0 -0
  51. package/dist/helpers/twigWrapper/Evaluator/Evaluator.mjs +7 -0
  52. package/dist/helpers/twigWrapper/Parser/Cursor.d.ts +7 -0
  53. package/dist/helpers/twigWrapper/Parser/Cursor.mjs +5 -0
  54. package/dist/helpers/twigWrapper/Parser/ExpressionParser.mjs +46 -8
  55. package/dist/helpers/twigWrapper/Parser/types.mjs +0 -0
  56. package/dist/helpers/twigWrapper/charClass.d.ts +7 -0
  57. package/dist/helpers/twigWrapper/charClass.mjs +16 -12
  58. package/dist/helpers/twigWrapper/tokens/tokenPatterns.mjs +2 -2
  59. package/dist/helpers/utils.d.ts +0 -2
  60. package/dist/helpers/utils.mjs +16 -19
  61. package/dist/hooks/index.d.ts +2 -1
  62. package/dist/hooks/index.mjs +4 -3
  63. package/dist/hooks/useFontHead.d.ts +3 -0
  64. package/dist/hooks/useFontHead.mjs +23 -0
  65. package/dist/hooks/usePlitziServiceContext.d.ts +3 -2
  66. package/dist/index.mjs +61 -45
  67. package/dist/navigation/index.d.ts +1 -1
  68. package/dist/navigation/index.mjs +2 -2
  69. package/dist/navigation/routes.d.ts +8 -0
  70. package/dist/navigation/routes.mjs +39 -39
  71. package/dist/network/graphql/builder/Mutations/Segment/SegmentAddMutation.mjs +0 -1
  72. package/dist/network/graphql/builder/Mutations/Segment/SegmentAddTemplateMutation.mjs +0 -1
  73. package/dist/network/graphql/builder/Mutations/Segment/SegmentUpdateMutation.mjs +0 -1
  74. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentAddElementMutation.mjs +0 -1
  75. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentCloneElementMutation.mjs +0 -1
  76. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentMoveElementMutation.mjs +0 -1
  77. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRemoveElementMutation.mjs +0 -1
  78. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRenameElementMutation.d.ts +3 -0
  79. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentRenameElementMutation.mjs +9 -0
  80. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentUpdateElementMutation.mjs +0 -1
  81. package/dist/network/graphql/builder/Mutations/Segment/space/elements/SegmentUpdateElementsMutation.mjs +0 -1
  82. package/dist/network/graphql/builder/Mutations/Space/SpaceAddElementMutation.mjs +0 -1
  83. package/dist/network/graphql/builder/Mutations/Space/SpaceAddTemplateMutation.mjs +0 -1
  84. package/dist/network/graphql/builder/Mutations/Space/SpaceCloneElementMutation.mjs +0 -1
  85. package/dist/network/graphql/builder/Mutations/Space/SpaceMoveElementMutation.mjs +0 -1
  86. package/dist/network/graphql/builder/Mutations/Space/SpaceRemoveElementMutation.mjs +0 -1
  87. package/dist/network/graphql/builder/Mutations/Space/SpaceRenameElementMutation.d.ts +5 -0
  88. package/dist/network/graphql/builder/Mutations/Space/SpaceRenameElementMutation.mjs +9 -0
  89. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateElementMutation.mjs +0 -1
  90. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateElementsMutation.mjs +0 -1
  91. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateMutation.mjs +0 -1
  92. package/dist/network/graphql/builder/Mutations/Space/SpaceUpdateSchemaMutation.mjs +0 -1
  93. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceAddActionMutation.d.ts +8 -0
  94. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceAddActionMutation.mjs +16 -0
  95. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRemoveActionMutation.d.ts +4 -0
  96. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRemoveActionMutation.mjs +16 -0
  97. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRunActionMutation.d.ts +4 -0
  98. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceRunActionMutation.mjs +14 -0
  99. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceUpdateActionMutation.d.ts +4 -0
  100. package/dist/network/graphql/builder/Mutations/Space/actions/SpaceUpdateActionMutation.mjs +16 -0
  101. package/dist/network/graphql/builder/Mutations/Space/pages/SpaceAddPageMutation.mjs +2 -3
  102. package/dist/network/graphql/builder/Mutations/Space/pages/SpaceUpdatePageMutation.mjs +0 -1
  103. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleAddFontMutation.d.ts +2 -0
  104. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleAddFontMutation.mjs +11 -0
  105. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleRemoveFontMutation.d.ts +2 -0
  106. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleRemoveFontMutation.mjs +11 -0
  107. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleUpdateFontMutation.d.ts +2 -0
  108. package/dist/network/graphql/builder/Mutations/Style/fonts/StyleUpdateFontMutation.mjs +11 -0
  109. package/dist/network/graphql/builder/Mutations/index.d.ts +24 -0
  110. package/dist/network/graphql/builder/Mutations/index.mjs +150 -132
  111. package/dist/network/graphql/builder/Queries/InitQuery.mjs +1 -2
  112. package/dist/network/graphql/builder/Queries/Segment/SegmentQuery.mjs +0 -1
  113. package/dist/network/graphql/builder/Queries/Segment/SegmentsQuery.mjs +0 -1
  114. package/dist/network/graphql/builder/Queries/Space/SpaceActionEventsQuery.d.ts +9 -0
  115. package/dist/network/graphql/builder/Queries/Space/SpaceActionEventsQuery.mjs +30 -0
  116. package/dist/network/graphql/builder/Queries/Space/SpaceActionTasksQuery.d.ts +6 -0
  117. package/dist/network/graphql/builder/Queries/Space/SpaceActionTasksQuery.mjs +16 -0
  118. package/dist/network/graphql/builder/Queries/Space/SpaceActionsQuery.d.ts +9 -0
  119. package/dist/network/graphql/builder/Queries/Space/SpaceActionsQuery.mjs +25 -0
  120. package/dist/network/graphql/builder/Queries/Space/SpaceCheckActionQuery.d.ts +6 -0
  121. package/dist/network/graphql/builder/Queries/Space/SpaceCheckActionQuery.mjs +17 -0
  122. package/dist/network/graphql/builder/Queries/Space/SpaceQuotaQuery.d.ts +30 -0
  123. package/dist/network/graphql/builder/Queries/Space/SpaceQuotaQuery.mjs +40 -0
  124. package/dist/network/graphql/builder/Queries/index.d.ts +16 -0
  125. package/dist/network/graphql/builder/Queries/index.mjs +24 -14
  126. package/dist/network/graphql/sdk/Queries/InitQuery.d.ts +4 -0
  127. package/dist/network/graphql/sdk/Queries/InitQuery.mjs +5 -2
  128. package/dist/network/graphql/sdk/Queries/Segment/SegmentQuery.mjs +0 -1
  129. package/dist/network/graphql/sdk/Queries/Segment/SegmentsQuery.mjs +0 -1
  130. package/dist/network/spaceEvents.d.ts +20 -1
  131. package/dist/network/spaceEvents.mjs +89 -74
  132. package/dist/schema/index.d.ts +1 -0
  133. package/dist/schema/index.mjs +2 -1
  134. package/dist/schema/serverElements.d.ts +16 -0
  135. package/dist/schema/serverElements.mjs +18 -0
  136. package/dist/server/actions/useActionsSync.d.ts +10 -0
  137. package/dist/server/actions/useActionsSync.mjs +7 -0
  138. package/dist/server/index.d.ts +2 -1
  139. package/dist/server/index.mjs +5 -4
  140. package/dist/server/rsc/refreshRsc.d.ts +11 -1
  141. package/dist/server/rsc/refreshRsc.mjs +30 -12
  142. package/dist/server/rsc/useRscSync.mjs +20 -13
  143. package/dist/state/runtimeStatePersist.mjs +4 -3
  144. package/dist/store/actionRuns/actionRunsRecorder.d.ts +36 -0
  145. package/dist/store/actionRuns/actionRunsRecorder.mjs +39 -0
  146. package/dist/store/actionRuns/actionRunsStore.d.ts +16 -0
  147. package/dist/store/actionRuns/actionRunsStore.mjs +5 -0
  148. package/dist/store/actionRuns/index.d.ts +6 -0
  149. package/dist/store/actionRuns/index.mjs +4 -0
  150. package/dist/store/actionRuns/useActionRuns.d.ts +13 -0
  151. package/dist/store/actionRuns/useActionRuns.mjs +10 -0
  152. package/dist/store/index.d.ts +46 -45
  153. package/dist/store/index.mjs +6 -3
  154. package/dist/store/renderSettings.d.ts +3 -0
  155. package/dist/store/renderSettings.mjs +17 -5
  156. package/dist/store/tracing/tracingCollector.d.ts +1 -1
  157. package/dist/store/tracing/tracingCollector.mjs +37 -35
  158. package/dist/store/tracing/tracingStore.d.ts +13 -0
  159. package/dist/store/tracing/tracingStore.mjs +3 -3
  160. package/dist/style/fontAssets.d.ts +12 -0
  161. package/dist/style/fontAssets.mjs +18 -0
  162. package/dist/style/fontValidation.d.ts +8 -0
  163. package/dist/style/fontValidation.mjs +107 -0
  164. package/dist/style/fonts.d.ts +74 -0
  165. package/dist/style/fonts.mjs +198 -0
  166. package/dist/style/fontsFromCss.d.ts +27 -0
  167. package/dist/style/fontsFromCss.mjs +43 -0
  168. package/dist/style/index.d.ts +4 -0
  169. package/dist/style/index.mjs +5 -1
  170. package/dist/style/styleConstants.d.ts +2 -2
  171. package/dist/style/styleConstants.mjs +1 -0
  172. package/dist/theme/ThemeProvider.d.ts +36 -6
  173. package/dist/theme/ThemeProvider.mjs +49 -35
  174. package/dist/theme/ThemeScope.d.ts +16 -0
  175. package/dist/theme/ThemeScope.mjs +6 -0
  176. package/dist/theme/index.d.ts +7 -1
  177. package/dist/theme/index.mjs +6 -2
  178. package/dist/theme/themeCookie.d.ts +30 -0
  179. package/dist/theme/themeCookie.mjs +24 -0
  180. package/dist/theme/themeStore.d.ts +40 -0
  181. package/dist/theme/themeStore.mjs +17 -0
  182. package/dist/theme/useTheme.d.ts +29 -0
  183. package/dist/theme/useTheme.mjs +20 -0
  184. package/dist/types/ActionTypes.d.ts +396 -0
  185. package/dist/types/ActionTypes.mjs +0 -0
  186. package/dist/types/AuthTypes.d.ts +23 -2
  187. package/dist/types/BuilderTypes.d.ts +0 -12
  188. package/dist/types/CommonTypes.d.ts +13 -0
  189. package/dist/types/DevToolsTypes.d.ts +57 -14
  190. package/dist/types/EventBridgeTypes.d.ts +1 -1
  191. package/dist/types/InteractionTypes.d.ts +23 -3
  192. package/dist/types/PluginTypes.d.ts +0 -3
  193. package/dist/types/SchemaTypes.d.ts +29 -4
  194. package/dist/types/ServerTypes.d.ts +390 -46
  195. package/dist/types/SpaceTypes.d.ts +16 -0
  196. package/dist/types/StoreTypes.d.ts +181 -3
  197. package/dist/types/StyleTypes.d.ts +83 -4
  198. package/dist/types/TemplateTypes.d.ts +20 -0
  199. package/dist/types/TemplateTypes.mjs +0 -0
  200. package/dist/types/ThemeTypes.d.ts +28 -1
  201. package/dist/types/index.d.ts +2 -0
  202. package/eslint.config.mjs +2 -0
  203. package/package.json +244 -29
  204. package/vite.config.ts +27 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # @plitzi/sdk-shared
2
2
 
3
+ ## 0.34.1
4
+
5
+ ### Patch Changes
6
+
7
+ - v0.34.1
8
+
9
+ ## 0.34.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 5aceda0: `authorSpace` returns test handles, and every element carries its id in the DOM.
14
+
15
+ An end-to-end suite had nothing stable to address a rendered element by: a class is a styling decision that
16
+ changes with the design, a text match breaks when the copy is edited, and an `nth-child` chain is invalidated by
17
+ inserting a section above. What does not move is the element's id — which is also its name, chosen by whoever
18
+ authored it and unique across the document.
19
+
20
+ ```ts
21
+ const { schema, style, handles } = authorSpace(spec);
22
+ const el = locate(page, handles);
23
+
24
+ await expect(el('hero-title')).toBeVisible();
25
+ await page.goto(handles.page('pricing').path);
26
+ ```
27
+
28
+ Elements now render `data-plitzi-el="<id>"`. It ships by default — the ids are already in the page, since the
29
+ schema the browser hydrates from carries every one of them — and a deployment turns it off with
30
+ `render.testAttributes: false`.
31
+
32
+ Each handle reports whether the AUTHOR wrote the id or authoring derived a positional `<type>-<n>`, which is
33
+ what makes one generic assertion possible for any space: everything a space names must be on screen. A name that
34
+ does not exist throws at author time with a suggestion, rather than resolving to an empty locator at test time.
35
+
36
+ - v0.34.0
37
+ - 5aceda0: Draft previews you can iterate against, and a `DraftStore` contract that says so.
38
+
39
+ A preview token was one-shot: spent by the render that used it. That is right for a capture and wrong for a
40
+ person — reloading showed the saved space again, so "look at the change, adjust it, look again" meant minting a
41
+ new token for every look. `POST /__preview` now takes `mode: 'session'`, which mints a token that stays
42
+ resolvable until it expires (`preview.sessionTtlMs`, 15 minutes by default) or until `POST /__preview/end` ends
43
+ it. The token is remembered in an `HttpOnly` cookie on the first render, so the draft follows a navigation —
44
+ the page after a link carries no query parameter.
45
+
46
+ A draft render, either mode, is never cached, never metered and answers `Cache-Control: no-store` plus
47
+ `X-Robots-Tag: noindex`. Data refreshes (`/_rsc`) made from inside a session are excluded from metering and
48
+ caching too — without that, an open preview tab would be billed as live traffic.
49
+
50
+ **Breaking, for anyone who implements `DraftStore`** (a shared store for a multi-replica deployment). The
51
+ default in-memory store is unaffected; a custom one needs three changes:
52
+
53
+ ```ts
54
+ // before
55
+ put(token, data, ttlMs)
56
+ take(token): OfflineDataRaw | undefined
57
+
58
+ // after
59
+ put(token, data, { ttlMs, reusable }) // `reusable` is a session; absent is one-shot
60
+ take(token): { data, reusable } | undefined // consume unless reusable — and say which it was
61
+ drop(token) // end a session before its TTL
62
+ ```
63
+
64
+ `take` reports which kind it resolved because the render that resolves a session is the one that has to remember
65
+ it for the rest of the visit, and only the store knows whether the token survived the read.
66
+
3
67
  ## 0.33.2
4
68
 
5
69
  ### Patch Changes
@@ -0,0 +1,34 @@
1
+ export type CronExpression = Set<number>[];
2
+ /** Parses an expression, or undefined when it is not one this understands — which the validator reports rather
3
+ * than letting a silently-never-firing schedule sit in a document. */
4
+ export declare const parseCron: (expression: string) => CronExpression | undefined;
5
+ /** The five numbers a cron expression is matched against — a wall clock, in whatever zone it was read in. */
6
+ type WallClock = {
7
+ minute: number;
8
+ hour: number;
9
+ day: number;
10
+ month: number;
11
+ weekday: number;
12
+ };
13
+ /**
14
+ * The same instant, as the clock on a wall in `timeZone` reads it.
15
+ *
16
+ * Through `Intl` rather than an offset table, because an offset is not a constant: "9am in Santiago" is a
17
+ * different instant in January and in July, and a schedule that means the working day has to follow the change.
18
+ * `hourCycle: 'h23'` matters — the other cycles render midnight as 24, which cron has no hour for.
19
+ *
20
+ * An unknown zone answers `undefined` rather than falling back to UTC. Falling back is what this whole change is
21
+ * about: a schedule that fires at the wrong hour and says nothing.
22
+ */
23
+ export declare const zonedClock: (at: Date, timeZone: string) => WallClock | undefined;
24
+ /**
25
+ * Whether an expression fires at this minute — in `timeZone` when one is named, in UTC when none is.
26
+ *
27
+ * Day-of-month and day-of-week are OR'd when both are restricted, which is the rule every cron implementation
28
+ * follows and the one that surprises people who expect AND — `0 0 1 * 1` fires on the first of the month AND on
29
+ * every Monday.
30
+ */
31
+ export declare const cronMatches: (expression: string, at: Date, timeZone?: string) => boolean;
32
+ /** Whether `Intl` knows this zone. The validator asks so an author hears about a typo in the editor. */
33
+ export declare const isKnownTimeZone: (timeZone: string) => boolean;
34
+ export {};
@@ -0,0 +1,72 @@
1
+ //#region src/actions/cron.ts
2
+ var e = [
3
+ [0, 59],
4
+ [0, 23],
5
+ [1, 31],
6
+ [1, 12],
7
+ [0, 6]
8
+ ], t = (e, [t, n]) => {
9
+ let r = /* @__PURE__ */ new Set();
10
+ for (let i of e.split(",")) {
11
+ let [e, a] = i.split("/"), o = a === void 0 ? 1 : Number.parseInt(a, 10);
12
+ if (!Number.isFinite(o) || o < 1) return;
13
+ let s = t, c = n;
14
+ if (e !== void 0 && e !== "*" && e !== "") {
15
+ let [r, i] = e.split("-");
16
+ if (s = Number.parseInt(r ?? "", 10), c = i === void 0 ? s : Number.parseInt(i, 10), !Number.isFinite(s) || !Number.isFinite(c) || s < t || c > n || s > c) return;
17
+ }
18
+ for (let e = s; e <= c; e += o) r.add(e);
19
+ }
20
+ return r;
21
+ }, n = (n) => {
22
+ let r = n.trim().split(/\s+/);
23
+ if (r.length !== 5) return;
24
+ let i = r.map((n, r) => t(n, e[r]));
25
+ return i.every((e) => e !== void 0) ? i : void 0;
26
+ }, r = [
27
+ "Sun",
28
+ "Mon",
29
+ "Tue",
30
+ "Wed",
31
+ "Thu",
32
+ "Fri",
33
+ "Sat"
34
+ ], i = (e) => ({
35
+ minute: e.getUTCMinutes(),
36
+ hour: e.getUTCHours(),
37
+ day: e.getUTCDate(),
38
+ month: e.getUTCMonth() + 1,
39
+ weekday: e.getUTCDay()
40
+ }), a = (e, t) => {
41
+ try {
42
+ let n = new Intl.DateTimeFormat("en-US", {
43
+ timeZone: t,
44
+ hourCycle: "h23",
45
+ month: "numeric",
46
+ day: "numeric",
47
+ hour: "numeric",
48
+ minute: "numeric",
49
+ weekday: "short"
50
+ }).formatToParts(e), i = (e) => n.find((t) => t.type === e)?.value ?? "", a = r.indexOf(i("weekday")), o = {
51
+ minute: Number(i("minute")),
52
+ hour: Number(i("hour")),
53
+ day: Number(i("day")),
54
+ month: Number(i("month")),
55
+ weekday: a
56
+ };
57
+ return a >= 0 && Object.values(o).every(Number.isFinite) ? o : void 0;
58
+ } catch {
59
+ return;
60
+ }
61
+ }, o = (e, t, r) => {
62
+ let o = n(e);
63
+ if (!o) return !1;
64
+ let s = r ? a(t, r) : i(t);
65
+ if (!s) return !1;
66
+ let [c, l, u, d, f] = o;
67
+ if (!c.has(s.minute) || !l.has(s.hour) || !d.has(s.month)) return !1;
68
+ let p = u.size !== 31, m = f.size !== 7, h = u.has(s.day), g = f.has(s.weekday);
69
+ return p && m ? h || g : h && g;
70
+ }, s = (e) => a(/* @__PURE__ */ new Date(), e) !== void 0;
71
+ //#endregion
72
+ export { o as cronMatches, s as isKnownTimeZone, n as parseCron, a as zonedClock };
@@ -0,0 +1,6 @@
1
+ export { cronMatches, isKnownTimeZone, parseCron, zonedClock } from './cron';
2
+ export { triggerAccess, triggerCacheMs, triggerHasStaleVerify, triggerInput, triggerVerify } from './triggerParams';
3
+ export { actionName, actionTriggers, isActionEnabled } from './triggers';
4
+ export { validateActionDocument } from './validateDocument';
5
+ export type { CronExpression } from './cron';
6
+ export type { ActionDocumentIssue, ActionDocumentReport } from './validateDocument';
@@ -0,0 +1,5 @@
1
+ import { cronMatches as e, isKnownTimeZone as t, parseCron as n, zonedClock as r } from "./cron.mjs";
2
+ import { triggerAccess as i, triggerCacheMs as a, triggerHasStaleVerify as o, triggerInput as s, triggerVerify as c } from "./triggerParams.mjs";
3
+ import { actionName as l, actionTriggers as u, isActionEnabled as d } from "./triggers.mjs";
4
+ import { validateActionDocument as f } from "./validateDocument.mjs";
5
+ export { l as actionName, u as actionTriggers, e as cronMatches, d as isActionEnabled, t as isKnownTimeZone, n as parseCron, i as triggerAccess, a as triggerCacheMs, o as triggerHasStaleVerify, s as triggerInput, c as triggerVerify, f as validateActionDocument, r as zonedClock };
@@ -0,0 +1,41 @@
1
+ import { ActionAccess, ActionField, ActionTriggerParams, ActionWebhookVerification } from '../types';
2
+ /**
3
+ * Who may start a run this way.
4
+ *
5
+ * `undefined` is a trigger that states no rule, which is refused rather than guessed at — every guess is either a
6
+ * lock-out or a hole. A `schedule` has no caller and states none, and the runner does not ask.
7
+ */
8
+ export declare const triggerAccess: (params: ActionTriggerParams) => ActionAccess | undefined;
9
+ /** What a caller may send through this way in. An unparseable map reads as no contract at all, which drops every key. */
10
+ export declare const triggerInput: (params: ActionTriggerParams) => Record<string, ActionField>;
11
+ /** How an inbound webhook proves itself, or nothing — which is an endpoint anyone who learns the URL can start. */
12
+ /**
13
+ * How this webhook proves who is calling it — assembled from the trigger's own fields, not parsed out of a blob.
14
+ *
15
+ * **Naming the credential is what turns it on.** Everything else has a working default, so there is no state
16
+ * between "unsigned" and "verified" for an author to get stuck in: it was a JSON object with an empty
17
+ * `credential` in it, which the editor offered by default and the validator then refused, so picking the webhook
18
+ * trigger produced an error before anybody had typed anything.
19
+ */
20
+ export declare const triggerVerify: (params: ActionTriggerParams) => ActionWebhookVerification | undefined;
21
+ /**
22
+ * Whether this trigger carries a signature check that USED TO VERIFY something and no longer does.
23
+ *
24
+ * A guard, not a compatibility layer: nothing here interprets the old shape, and the only question it answers is
25
+ * whether ignoring it would fail OPEN. A stored `verify: '{…}'` that named a credential was a protected endpoint;
26
+ * read as "unsigned" it becomes a public one with nothing in the document changing, which is the one degradation
27
+ * that must never happen by omission — so it is refused until somebody moves it across.
28
+ *
29
+ * A leftover blob that named NO credential verified nothing to begin with. It is not a downgrade, it is a field
30
+ * the editor used to offer filled in with a default and never completed — and treating it as an error would be an
31
+ * error about something the editor no longer shows, on a webhook that was always unsigned. That one is simply
32
+ * unsigned, and warned about like any other.
33
+ */
34
+ export declare const triggerHasStaleVerify: (params: ActionTriggerParams) => boolean;
35
+ /**
36
+ * How long a `render` answer may be reused, in milliseconds.
37
+ *
38
+ * Read through here rather than parsed at the call site so the validator and the runtime agree on what an
39
+ * unparseable value means: nothing, which is the same as not caching — never an accidental eternity.
40
+ */
41
+ export declare const triggerCacheMs: (params: ActionTriggerParams) => number;
@@ -0,0 +1,40 @@
1
+ //#region src/actions/triggerParams.ts
2
+ var e = (e) => {
3
+ if (typeof e == "string" && e.trim() !== "") try {
4
+ return JSON.parse(e);
5
+ } catch {
6
+ return;
7
+ }
8
+ }, t = (e) => typeof e == "object" && !!e && !Array.isArray(e), n = (e) => {
9
+ let { access: t } = e;
10
+ if (t === "public" || t === "session") return { mode: t };
11
+ if (t === "role") return {
12
+ mode: "role",
13
+ permissions: (e.permissions ?? "").split(",").map((e) => e.trim()).filter(Boolean)
14
+ };
15
+ }, r = (n) => {
16
+ let r = e(n.input);
17
+ return t(r) ? r : {};
18
+ }, i = (e) => {
19
+ let t = e.signatureCredential?.trim();
20
+ if (!t) return;
21
+ let n = e.signatureSecretField?.trim(), r = e.signatureTimestampHeader?.trim(), i = Number.parseFloat(e.signatureToleranceSeconds ?? "");
22
+ return {
23
+ type: "hmac",
24
+ header: e.signatureHeader?.trim() || "x-signature",
25
+ algorithm: e.signatureAlgorithm?.trim() === "sha1" ? "sha1" : "sha256",
26
+ credential: t,
27
+ ...n ? { secretField: n } : {},
28
+ ...r ? { timestampHeader: r } : {},
29
+ ...Number.isFinite(i) && i > 0 ? { toleranceSeconds: Math.round(i) } : {}
30
+ };
31
+ }, a = (n) => {
32
+ if (n.signatureCredential?.trim()) return !1;
33
+ let r = e(n.verify);
34
+ return t(r) && typeof r.credential == "string" && r.credential.trim() !== "";
35
+ }, o = (e) => {
36
+ let t = Number.parseFloat(e.cacheSeconds ?? "");
37
+ return Number.isFinite(t) && t > 0 ? Math.round(t * 1e3) : 0;
38
+ };
39
+ //#endregion
40
+ export { n as triggerAccess, o as triggerCacheMs, a as triggerHasStaleVerify, r as triggerInput, i as triggerVerify };
@@ -0,0 +1,29 @@
1
+ import { ActionDocument, ElementInteraction } from '../types';
2
+ /**
3
+ * The ways into an action, read off the flow.
4
+ *
5
+ * A way in is a trigger STEP, so this is the only place that has to know it — the builder's list, the MCP summary
6
+ * and the schedule sweep all ask the same question of the same document and cannot answer it differently.
7
+ */
8
+ export declare const actionTriggers: (document: ActionDocument) => ElementInteraction[];
9
+ /**
10
+ * Whether this action can run at all: whether ANY way into it is switched on.
11
+ *
12
+ * DERIVED, never stored. The switch belongs to the trigger — one per way in — and an action-level copy of it was a
13
+ * second answer to the same question: the builder computed it from the steps, the store overwrote it from its own
14
+ * column, and the two could disagree with nothing to say which won. A store that keeps a column for it (to index
15
+ * the schedule sweep, say) writes what this returns, and reads it back as a cache rather than as a decision.
16
+ */
17
+ export declare const isActionEnabled: (document: ActionDocument) => boolean;
18
+ /**
19
+ * What to call this action, read off the flow that starts it.
20
+ *
21
+ * The trigger step is already named — the editor puts an editable title on every node — so a field beside the flow
22
+ * asked the author to name the same thing twice, and left two names with no rule for which one anybody meant. The
23
+ * FIRST trigger wins because it is the one the list and the picker show; an action with several ways in is still
24
+ * one action, and naming it after the way most callers reach it is the honest summary.
25
+ *
26
+ * Falls back to whatever the document already carries, so a document written before this — or by a caller that
27
+ * names it directly, like the authoring API — keeps the name it was given.
28
+ */
29
+ export declare const actionName: (document: ActionDocument) => string;
@@ -0,0 +1,4 @@
1
+ //#region src/actions/triggers.ts
2
+ var e = (e) => Object.values(e.nodes).filter((e) => e.type === "trigger"), t = (t) => e(t).some((e) => e.enabled), n = (t) => e(t).find((e) => e.title.trim() !== "")?.title.trim() ?? t.name;
3
+ //#endregion
4
+ export { n as actionName, e as actionTriggers, t as isActionEnabled };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The one server-action validator.
3
+ *
4
+ * Three places decide whether a document is fit to store — the builder panel, the MCP write ops and the GraphQL
5
+ * mutation that persists it — and a document is only ever wrong in one way, so the rules live here instead of
6
+ * being restated (and drifting) at each entry point. The input is `unknown` because the server runs it last, on
7
+ * whatever JSON actually arrived: it validates a document, it does not assume one.
8
+ *
9
+ * Errors are what the runner cannot recover from at request time; warnings are what will run but probably does
10
+ * not do what the author meant.
11
+ */
12
+ export type ActionDocumentIssue = {
13
+ /** Dotted path into the document, e.g. `nodes.send.action`. */
14
+ path: string;
15
+ message: string;
16
+ hint?: string;
17
+ };
18
+ export type ActionDocumentReport = {
19
+ valid: boolean;
20
+ errors: ActionDocumentIssue[];
21
+ warnings: ActionDocumentIssue[];
22
+ };
23
+ export declare const validateActionDocument: (document: unknown) => ActionDocumentReport;
@@ -0,0 +1,267 @@
1
+ import { isKnownTimeZone as e, parseCron as t } from "./cron.mjs";
2
+ import { triggerAccess as n, triggerHasStaleVerify as r, triggerVerify as i } from "./triggerParams.mjs";
3
+ //#region src/actions/validateDocument.ts
4
+ var a = [
5
+ "text",
6
+ "number",
7
+ "boolean",
8
+ "date",
9
+ "json",
10
+ "file"
11
+ ], o = [
12
+ "public",
13
+ "session",
14
+ "role"
15
+ ], s = [
16
+ "call",
17
+ "webhook",
18
+ "schedule",
19
+ "render",
20
+ "custom"
21
+ ], c = ["sha256", "sha1"], l = /* @__PURE__ */ new Set([
22
+ "input",
23
+ "user",
24
+ "spaceId",
25
+ "environment",
26
+ "trigger",
27
+ "runId"
28
+ ]), u = /^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/, d = (e) => typeof e == "object" && !!e && !Array.isArray(e), f = (e) => typeof e == "string" && e.trim() !== "", p = (e) => {
29
+ try {
30
+ let t = JSON.parse(e);
31
+ return d(t) ? t : void 0;
32
+ } catch {
33
+ return;
34
+ }
35
+ }, m = (e, t, n) => {
36
+ if (e !== void 0) {
37
+ if (!d(e)) {
38
+ n.push({
39
+ path: t,
40
+ message: "must be an object of field definitions"
41
+ });
42
+ return;
43
+ }
44
+ Object.entries(e).forEach(([e, r]) => {
45
+ if (!d(r) || !f(r.type)) {
46
+ n.push({
47
+ path: `${t}.${e}`,
48
+ message: "must declare a type"
49
+ });
50
+ return;
51
+ }
52
+ a.includes(r.type) || n.push({
53
+ path: `${t}.${e}.type`,
54
+ message: `unknown field type "${r.type}"`,
55
+ hint: `one of ${a.join(", ")}`
56
+ });
57
+ });
58
+ }
59
+ }, h = (a, l, u, h) => {
60
+ let g = `nodes.${a}`, _ = l.action;
61
+ if (!f(_) || !s.includes(_)) {
62
+ u.push({
63
+ path: `${g}.action`,
64
+ message: `"${String(_)}" is not a way an action can be started`,
65
+ hint: `one of ${s.join(", ")}`
66
+ });
67
+ return;
68
+ }
69
+ let v = d(l.params) ? l.params : {};
70
+ if (f(v.input)) {
71
+ let e = p(v.input);
72
+ e === void 0 ? u.push({
73
+ path: `${g}.params.input`,
74
+ message: "is not valid JSON"
75
+ }) : m(e, `${g}.params.input`, u);
76
+ }
77
+ if (_ !== "schedule") {
78
+ if (!f(v.access) || !o.includes(v.access)) u.push({
79
+ path: `${g}.params.access`,
80
+ message: "must say who may start a run this way",
81
+ hint: `one of ${o.join(", ")}`
82
+ });
83
+ else if (v.access === "role") {
84
+ let e = n(v);
85
+ e?.mode === "role" && e.permissions.length === 0 && u.push({
86
+ path: `${g}.params.permissions`,
87
+ message: "role access must name at least one permission"
88
+ });
89
+ }
90
+ }
91
+ if (_ === "schedule" && (f(v.cron) ? t(v.cron) || u.push({
92
+ path: `${g}.params.cron`,
93
+ message: `"${v.cron}" is not an expression this server can read, so it would never fire`,
94
+ hint: "five fields — minute hour day-of-month month day-of-week — with *, lists, ranges and steps"
95
+ }) : u.push({
96
+ path: `${g}.params.cron`,
97
+ message: "a scheduled trigger needs a cron expression"
98
+ }), f(v.timezone) && !e(v.timezone) && u.push({
99
+ path: `${g}.params.timezone`,
100
+ message: `"${v.timezone}" is not a time zone this server knows, so the schedule would never fire`,
101
+ hint: "an IANA name like America/Santiago or Europe/Madrid — leave it empty for UTC"
102
+ })), f(v.cacheSeconds)) {
103
+ let e = Number.parseFloat(v.cacheSeconds);
104
+ !Number.isFinite(e) || e < 0 ? u.push({
105
+ path: `${g}.params.cacheSeconds`,
106
+ message: "must be a number of seconds, or left empty"
107
+ }) : _ !== "render" && h.push({
108
+ path: `${g}.params.cacheSeconds`,
109
+ message: `a "${_}" trigger is never answered from a cache`,
110
+ hint: "reuse only applies to a render, where the same page is built for one visitor after another"
111
+ });
112
+ }
113
+ _ === "custom" && !f(v.name) && u.push({
114
+ path: `${g}.params.name`,
115
+ message: "a custom trigger needs a name to be mounted under"
116
+ }), _ === "webhook" && (r(v) && u.push({
117
+ path: `${g}.params.signatureCredential`,
118
+ message: "this signature check is in a format nothing reads any more, so the webhook is unverified",
119
+ hint: "name the credential holding the signing secret in \"Signing secret\", and its header beside it"
120
+ }), i(v) || h.push({
121
+ path: `${g}.params.signatureCredential`,
122
+ message: "this webhook accepts unsigned requests",
123
+ hint: "name the credential holding the signing secret, and only the sender you expect can start a run"
124
+ }), f(v.signatureAlgorithm) && !c.includes(v.signatureAlgorithm) && h.push({
125
+ path: `${g}.params.signatureAlgorithm`,
126
+ message: `"${v.signatureAlgorithm}" is not an algorithm this server signs with, so sha256 is used`,
127
+ hint: `one of ${c.join(", ")}`
128
+ }), f(v.signatureToleranceSeconds) && !f(v.signatureTimestampHeader) && h.push({
129
+ path: `${g}.params.signatureToleranceSeconds`,
130
+ message: "a tolerance with no timestamp header expires nothing",
131
+ hint: "name the header the sender puts the signing time in, or drop the tolerance"
132
+ })), _ === "call" && v.access === "public" && h.push({
133
+ path: `${g}.params.access`,
134
+ message: "anyone can run this action, including signed-out visitors",
135
+ hint: "use session access unless the page needs it before anyone signs in"
136
+ });
137
+ }, g = (e, t, n, r) => {
138
+ if (!d(e)) {
139
+ n.push({
140
+ path: "nodes",
141
+ message: "must be an object of steps"
142
+ });
143
+ return;
144
+ }
145
+ let i = Object.entries(e), a = i.filter(([, e]) => d(e) && e.type === "trigger");
146
+ a.length === 0 && n.push({
147
+ path: "nodes",
148
+ message: "this action has no way in",
149
+ hint: "add a trigger step — call, webhook, schedule, render or custom"
150
+ });
151
+ let o = /* @__PURE__ */ new Map();
152
+ a.forEach(([e, t]) => {
153
+ f(t.action) && o.set(t.action, [...o.get(t.action) ?? [], e]);
154
+ }), o.forEach((e, t) => {
155
+ e.length > 1 && n.push({
156
+ path: `nodes.${e[1]}`,
157
+ message: `this action has ${e.length} "${t}" triggers`,
158
+ hint: "one step per way in — a second flow starts from a different kind of trigger"
159
+ });
160
+ }), i.forEach(([t, i]) => {
161
+ let a = `nodes.${t}`;
162
+ if (!d(i)) {
163
+ n.push({
164
+ path: a,
165
+ message: "must be an object"
166
+ });
167
+ return;
168
+ }
169
+ if (i.type === "trigger") {
170
+ h(t, i, n, r);
171
+ return;
172
+ }
173
+ if (i.type !== "task") {
174
+ n.push({
175
+ path: `${a}.type`,
176
+ message: `"${String(i.type)}" steps run in the browser and cannot run on the server`,
177
+ hint: "a server action is made of task steps"
178
+ });
179
+ return;
180
+ }
181
+ if (!f(i.action) || !u.test(i.action)) {
182
+ n.push({
183
+ path: `${a}.action`,
184
+ message: "must name a task as <namespace>.<action>",
185
+ hint: "for example flow.output or http.request"
186
+ });
187
+ return;
188
+ }
189
+ f(i.afterNode) && !Object.hasOwn(e, i.afterNode) && n.push({
190
+ path: `${a}.afterNode`,
191
+ message: `points at "${i.afterNode}", which is not a step here`
192
+ });
193
+ }), i.forEach(([e]) => {
194
+ l.has(e) && n.push({
195
+ path: `nodes.${e}`,
196
+ message: `"${e}" is what the run itself publishes in the flow scope, so a step may not take the name`,
197
+ hint: `rename the step — every later step reads {{ ${e} }} expecting the run's own value`
198
+ });
199
+ });
200
+ let s = /* @__PURE__ */ new Set(), c = /* @__PURE__ */ new Set();
201
+ a.forEach(([t]) => {
202
+ let n = /* @__PURE__ */ new Set(), r = t;
203
+ for (; r;) {
204
+ if (n.has(r)) {
205
+ c.add(r);
206
+ break;
207
+ }
208
+ n.add(r), s.add(r);
209
+ let t = e[r];
210
+ r = f(t?.afterNode) ? t.afterNode : "";
211
+ }
212
+ }), c.forEach((e) => {
213
+ n.push({
214
+ path: `nodes.${e}`,
215
+ message: "this step is reached again by its own chain, so the flow never ends",
216
+ hint: "break the `afterNode` chain that comes back to it"
217
+ });
218
+ }), i.forEach(([e]) => {
219
+ s.has(e) || r.push({
220
+ path: `nodes.${e}`,
221
+ message: "no trigger reaches this step, so it never runs",
222
+ hint: "chain it with `afterNode` from a trigger, or delete it"
223
+ });
224
+ });
225
+ let p = i.filter(([, e]) => d(e) && e.action === "flow.output");
226
+ for (let [t, n] of p) f(n.afterNode) && Object.hasOwn(e, n.afterNode) && r.push({
227
+ path: `nodes.${t}.afterNode`,
228
+ message: "the output step is not the last one, so the steps after it run for nothing",
229
+ hint: "move flow.output to the end of the flow"
230
+ });
231
+ p.length === 0 && d(t) && Object.keys(t).length > 0 && r.push({
232
+ path: "nodes",
233
+ message: "this action is expected to answer values but no step names any",
234
+ hint: "add a flow.output step at the end naming what the caller should get"
235
+ });
236
+ }, _ = /\{\{\s*credential\./, v = (e, t) => {
237
+ Object.entries(e).forEach(([e, n]) => {
238
+ if (!d(n) || !d(n.params)) return;
239
+ let r = f(n.params.credential) ? n.params.credential : "";
240
+ Object.entries(n.params).forEach(([n, i]) => {
241
+ typeof i == "string" && _.test(i) && r === "" && t.push({
242
+ path: `nodes.${e}.params.${n}`,
243
+ message: "this step reads a credential it did not ask for, so the token resolves to nothing",
244
+ hint: "set the step’s `credential` to the identifier it should use"
245
+ });
246
+ });
247
+ });
248
+ }, y = (e) => {
249
+ let t = [], n = [];
250
+ return d(e) ? (f(e.name) || t.push({
251
+ path: "name",
252
+ message: "must have a name"
253
+ }), m(e.output, "output", t), g(e.nodes, e.output, t, n), v(d(e.nodes) ? e.nodes : {}, n), {
254
+ valid: t.length === 0,
255
+ errors: t,
256
+ warnings: n
257
+ }) : {
258
+ valid: !1,
259
+ errors: [{
260
+ path: "",
261
+ message: "the document must be an object"
262
+ }],
263
+ warnings: n
264
+ };
265
+ };
266
+ //#endregion
267
+ export { y as validateActionDocument };
@@ -16,10 +16,15 @@ export declare const onAuthFailure: (listener: AuthFailureListener) => (() => vo
16
16
  * What an HTTP refusal means for the session. Stated once, because the provider and the network layers must not
17
17
  * disagree about which refusals are renewable.
18
18
  *
19
- * A backend that names the reason (Plitzi's API answers `reason` on every 401) is believed. One that says nothing
20
- * gets the benefit of the doubt — an unexplained 401 is treated as renewable, so a stale access token costs one
21
- * refused renewal instead of signing out a session that was still good. Nothing below 401 or above 499 is an answer
22
- * about the session at all: a 5xx means the backend failed, not that the caller is a stranger.
19
+ * Only a 401 is about the session. A backend that names the reason (Plitzi's API answers `reason` on every 401) is
20
+ * believed; one that says nothing gets the benefit of the doubt — an unexplained 401 is treated as renewable, so a
21
+ * stale access token costs one refused renewal instead of signing out a session that was still good.
22
+ *
23
+ * A 403 is never about the session, whatever it carries. It is "you may not do THIS": a permission the account does
24
+ * not hold, or a CSRF check that failed — and the kernel's CSRF refusal names reasons (`missing`, `expired`) that are
25
+ * spelled like session ones. It used to be read as `inactive`, so any screen that asked for something its visitor may
26
+ * not have signed that visitor out on the spot, with nothing on the page to say why. An account that really was
27
+ * deactivated is refused with a 401 naming `inactive`, which is still believed.
23
28
  */
24
29
  export declare const authFailureFromResponse: (status: number, body?: unknown) => AuthFailureReason | undefined;
25
30
  /**
@@ -6,13 +6,14 @@ var e = /* @__PURE__ */ new Set(), t = (t) => {
6
6
  "expired",
7
7
  "revoked",
8
8
  "inactive",
9
+ "unverified",
9
10
  "malformed",
10
11
  "outdated",
11
12
  "network"
12
13
  ], i = (e, t) => {
13
- if (e < 400 || e >= 500 || e !== 401 && e !== 403) return;
14
+ if (e !== 401) return;
14
15
  let n = t?.reason;
15
- return typeof n == "string" && r.includes(n) ? n : e === 403 ? "inactive" : "expired";
16
+ return typeof n == "string" && r.includes(n) ? n : "expired";
16
17
  }, a = (e) => /([^.]+\.[^.]+)$/.exec(e)?.[1] ?? e, o = (e, t) => {
17
18
  if (!e || !t) return !1;
18
19
  try {