@greatwait/contracts 0.1.0-alpha.3 → 0.1.0-alpha.4

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 (299) hide show
  1. package/README.md +11 -9
  2. package/dist/authority.d.ts +33 -27
  3. package/dist/authority.js +12 -2
  4. package/dist/billing.d.ts +1001 -0
  5. package/dist/billing.js +684 -0
  6. package/dist/callback-safety.d.ts +2 -2
  7. package/dist/callback-safety.js +2 -2
  8. package/dist/compatibility.d.ts +3 -3
  9. package/dist/compatibility.js +1 -1
  10. package/dist/data-export.d.ts +67 -0
  11. package/dist/data-export.js +182 -0
  12. package/dist/endpoints.d.ts +181 -240
  13. package/dist/endpoints.js +77 -81
  14. package/dist/events.d.ts +17 -17
  15. package/dist/events.js +1 -1
  16. package/dist/first-value-journey.d.ts +49 -55
  17. package/dist/first-value-journey.js +6 -13
  18. package/dist/forecast.d.ts +71 -71
  19. package/dist/forecast.js +1 -1
  20. package/dist/identifiers.d.ts +20 -20
  21. package/dist/identifiers.js +1 -1
  22. package/dist/identity.d.ts +41 -37
  23. package/dist/identity.js +5 -4
  24. package/dist/index.d.ts +10 -5
  25. package/dist/index.js +10 -5
  26. package/dist/mcp.d.ts +21 -19
  27. package/dist/mcp.js +1 -1
  28. package/dist/naming.d.ts +45 -0
  29. package/dist/naming.js +159 -0
  30. package/dist/oauth.d.ts +186 -172
  31. package/dist/oauth.js +1 -1
  32. package/dist/operation-definitions.d.ts +9 -0
  33. package/dist/operation-definitions.js +7 -0
  34. package/dist/operations.d.ts +4162 -2171
  35. package/dist/operations.js +2679 -908
  36. package/dist/organization-lifecycle.d.ts +34 -0
  37. package/dist/organization-lifecycle.js +122 -0
  38. package/dist/partition.d.ts +26 -0
  39. package/dist/policy.d.ts +4 -4
  40. package/dist/policy.js +9 -5
  41. package/dist/problems.d.ts +32 -26
  42. package/dist/problems.js +6 -2
  43. package/dist/protocol.d.ts +204 -75
  44. package/dist/protocol.js +108 -36
  45. package/dist/scheduling-records.d.ts +343 -285
  46. package/dist/scheduling-records.js +1 -1
  47. package/dist/scheduling.d.ts +670 -360
  48. package/dist/scheduling.js +382 -56
  49. package/dist/secrets.d.ts +119 -105
  50. package/dist/secrets.js +1 -1
  51. package/dist/signatures.d.ts +74 -74
  52. package/dist/signatures.js +1 -1
  53. package/dist/tenancy.d.ts +142 -68
  54. package/dist/tenancy.js +70 -17
  55. package/dist/test-sink.d.ts +127 -127
  56. package/dist/test-sink.js +1 -1
  57. package/dist/time.d.ts +4 -4
  58. package/dist/time.js +1 -1
  59. package/dist/timing.d.ts +65 -0
  60. package/dist/timing.js +82 -0
  61. package/dist/usage.d.ts +103 -47
  62. package/dist/usage.js +197 -31
  63. package/dist/workspace.d.ts +34 -34
  64. package/dist/workspace.js +1 -1
  65. package/dist/zod-values.d.ts +3 -0
  66. package/dist/zod-values.js +2 -0
  67. package/dist/zod.d.ts +2 -0
  68. package/dist/zod.js +2 -0
  69. package/generated/v1/examples/attempts.list.response.json +4 -0
  70. package/generated/v1/examples/attempts.read.response.json +4 -0
  71. package/generated/v1/examples/billing.autotopup.request.json +5 -0
  72. package/generated/v1/examples/billing.autotopup.response.json +120 -0
  73. package/generated/v1/examples/billing.autotopuppreview.request.json +19 -0
  74. package/generated/v1/examples/billing.autotopuppreview.response.json +45 -0
  75. package/generated/v1/examples/billing.budgets.request.json +13 -0
  76. package/generated/v1/examples/billing.budgets.response.json +22 -0
  77. package/generated/v1/examples/billing.ceiling.request.json +8 -0
  78. package/generated/v1/examples/billing.ceiling.response.json +120 -0
  79. package/generated/v1/examples/billing.change.request.json +5 -0
  80. package/generated/v1/examples/billing.change.response.json +12 -0
  81. package/generated/v1/examples/billing.funding.response.json +120 -0
  82. package/generated/v1/examples/billing.fundingquote.request.json +7 -0
  83. package/generated/v1/examples/billing.fundingquote.response.json +13 -0
  84. package/generated/v1/examples/billing.ledger.response.json +23 -0
  85. package/generated/v1/examples/billing.notices.response.json +41 -0
  86. package/generated/v1/examples/billing.notificationsettings.response.json +17 -0
  87. package/generated/v1/examples/billing.notificationsettingsupdate.request.json +9 -0
  88. package/generated/v1/examples/billing.notificationsettingsupdate.response.json +17 -0
  89. package/generated/v1/examples/billing.portal.response.json +3 -0
  90. package/generated/v1/examples/billing.projects.response.json +12 -0
  91. package/generated/v1/examples/billing.purchase.request.json +5 -0
  92. package/generated/v1/examples/billing.purchase.response.json +120 -0
  93. package/generated/v1/examples/billing.quote.request.json +4 -0
  94. package/generated/v1/examples/billing.quote.response.json +19 -0
  95. package/generated/v1/examples/billing.reconcile.response.json +73 -0
  96. package/generated/v1/examples/billing.refundrequest.request.json +10 -0
  97. package/generated/v1/examples/billing.refundrequest.response.json +11 -0
  98. package/generated/v1/examples/billing.refundresolution.request.json +4 -0
  99. package/generated/v1/examples/billing.refundresolution.response.json +120 -0
  100. package/generated/v1/examples/billing.summary.response.json +73 -0
  101. package/generated/v1/examples/endpoints.acknowledgeSigningKeyRotation.response.json +18 -0
  102. package/generated/v1/examples/endpoints.create.response.json +1 -1
  103. package/generated/v1/examples/endpoints.disable.response.json +0 -1
  104. package/generated/v1/examples/{endpoints.verify.response.json → endpoints.enable.response.json} +4 -5
  105. package/generated/v1/examples/endpoints.list.response.json +1 -2
  106. package/generated/v1/examples/endpoints.read.response.json +2 -2
  107. package/generated/v1/examples/endpoints.revealSigningSecret.response.json +7 -0
  108. package/generated/v1/examples/endpoints.rotateSigningKey.response.json +1 -2
  109. package/generated/v1/examples/endpoints.update.response.json +1 -1
  110. package/generated/v1/examples/executions.list.response.json +20 -0
  111. package/generated/v1/examples/exports.cancel.response.json +11 -0
  112. package/generated/v1/examples/exports.create.request.json +1 -0
  113. package/generated/v1/examples/exports.create.response.json +8 -0
  114. package/generated/v1/examples/exports.part.response.json +5 -0
  115. package/generated/v1/examples/exports.read.response.json +8 -0
  116. package/generated/v1/examples/occurrences.read.response.json +4 -0
  117. package/generated/v1/examples/organizations.close.request.json +5 -0
  118. package/generated/v1/examples/organizations.close.response.json +10 -0
  119. package/generated/v1/examples/organizations.lifecycle.response.json +10 -0
  120. package/generated/v1/examples/organizations.restore.request.json +3 -0
  121. package/generated/v1/examples/organizations.restore.response.json +10 -0
  122. package/generated/v1/examples/projects.archive.request.json +3 -0
  123. package/generated/v1/examples/projects.archive.response.json +14 -0
  124. package/generated/v1/examples/projects.create.response.json +10 -1
  125. package/generated/v1/examples/projects.delete.response.json +9 -0
  126. package/generated/v1/examples/projects.list.response.json +12 -2
  127. package/generated/v1/examples/projects.rename.request.json +3 -0
  128. package/generated/v1/examples/projects.rename.response.json +14 -0
  129. package/generated/v1/examples/schedules.archive.response.json +3 -0
  130. package/generated/v1/examples/schedules.cancel.response.json +3 -0
  131. package/generated/v1/examples/schedules.create.request.json +4 -0
  132. package/generated/v1/examples/schedules.create.response.json +3 -0
  133. package/generated/v1/examples/schedules.list.response.json +3 -0
  134. package/generated/v1/examples/schedules.pause.response.json +3 -0
  135. package/generated/v1/examples/schedules.read.response.json +6 -0
  136. package/generated/v1/examples/schedules.replace.request.json +4 -0
  137. package/generated/v1/examples/schedules.replace.response.json +3 -0
  138. package/generated/v1/examples/schedules.resume.response.json +3 -0
  139. package/generated/v1/examples/timing.summary.response.json +35 -0
  140. package/generated/v1/examples/usage.read.response.json +1 -0
  141. package/generated/v1/first-value-journey.json +19 -10
  142. package/generated/v1/manifest.json +936 -23
  143. package/generated/v1/openapi.json +29983 -5784
  144. package/generated/v1/policy-matrix.json +278 -47
  145. package/generated/v1/schemas/attempts.list.response.json +24 -1
  146. package/generated/v1/schemas/attempts.read.response.json +24 -1
  147. package/generated/v1/schemas/billing.autotopup.request-headers.json +13 -0
  148. package/generated/v1/schemas/billing.autotopup.request.json +22 -0
  149. package/generated/v1/schemas/billing.autotopup.response-headers.json +10 -0
  150. package/generated/v1/schemas/billing.autotopup.response.json +552 -0
  151. package/generated/v1/schemas/billing.autotopuppreview.request.json +77 -0
  152. package/generated/v1/schemas/billing.autotopuppreview.response.json +277 -0
  153. package/generated/v1/schemas/billing.budgets.request-headers.json +13 -0
  154. package/generated/v1/schemas/billing.budgets.request.json +66 -0
  155. package/generated/v1/schemas/billing.budgets.response-headers.json +10 -0
  156. package/generated/v1/schemas/billing.budgets.response.json +92 -0
  157. package/generated/v1/schemas/billing.ceiling.request-headers.json +13 -0
  158. package/generated/v1/schemas/billing.ceiling.request.json +40 -0
  159. package/generated/v1/schemas/billing.ceiling.response-headers.json +10 -0
  160. package/generated/v1/schemas/billing.ceiling.response.json +552 -0
  161. package/generated/v1/schemas/billing.change.request-headers.json +13 -0
  162. package/generated/v1/schemas/billing.change.request.json +22 -0
  163. package/generated/v1/schemas/billing.change.response.json +119 -0
  164. package/generated/v1/schemas/billing.funding.response.json +552 -0
  165. package/generated/v1/schemas/billing.fundingquote.request.json +28 -0
  166. package/generated/v1/schemas/billing.fundingquote.response.json +61 -0
  167. package/generated/v1/schemas/billing.ledger.query.json +16 -0
  168. package/generated/v1/schemas/billing.ledger.response.json +125 -0
  169. package/generated/v1/schemas/billing.notices.query.json +16 -0
  170. package/generated/v1/schemas/billing.notices.response.json +278 -0
  171. package/generated/v1/schemas/billing.notificationsettings.response.json +70 -0
  172. package/generated/v1/schemas/billing.notificationsettingsupdate.request-headers.json +13 -0
  173. package/generated/v1/schemas/billing.notificationsettingsupdate.request.json +36 -0
  174. package/generated/v1/schemas/billing.notificationsettingsupdate.response-headers.json +10 -0
  175. package/generated/v1/schemas/billing.notificationsettingsupdate.response.json +70 -0
  176. package/generated/v1/schemas/billing.portal.response.json +11 -0
  177. package/generated/v1/schemas/billing.projects.response.json +119 -0
  178. package/generated/v1/schemas/billing.purchase.request-headers.json +13 -0
  179. package/generated/v1/schemas/billing.purchase.request.json +22 -0
  180. package/generated/v1/schemas/billing.purchase.response-headers.json +10 -0
  181. package/generated/v1/schemas/billing.purchase.response.json +552 -0
  182. package/generated/v1/schemas/billing.quote.request.json +20 -0
  183. package/generated/v1/schemas/billing.quote.response.json +86 -0
  184. package/generated/v1/schemas/billing.reconcile.response.json +468 -0
  185. package/generated/v1/schemas/billing.refundrequest.request-headers.json +13 -0
  186. package/generated/v1/schemas/billing.refundrequest.request.json +42 -0
  187. package/generated/v1/schemas/billing.refundrequest.response-headers.json +10 -0
  188. package/generated/v1/schemas/billing.refundrequest.response.json +45 -0
  189. package/generated/v1/schemas/billing.refundresolution.request-headers.json +13 -0
  190. package/generated/v1/schemas/billing.refundresolution.request.json +61 -0
  191. package/generated/v1/schemas/billing.refundresolution.response-headers.json +10 -0
  192. package/generated/v1/schemas/billing.refundresolution.response.json +552 -0
  193. package/generated/v1/schemas/billing.summary.response.json +468 -0
  194. package/generated/v1/schemas/context.read.response.json +2 -0
  195. package/generated/v1/schemas/credentials.connection.response.json +2 -0
  196. package/generated/v1/schemas/credentials.create.request.json +2 -0
  197. package/generated/v1/schemas/credentials.create.response.json +2 -0
  198. package/generated/v1/schemas/credentials.list.response.json +2 -0
  199. package/generated/v1/schemas/credentials.revoke.response.json +2 -0
  200. package/generated/v1/schemas/credentials.rotate.response.json +4 -0
  201. package/generated/v1/schemas/{endpoints.verify.request-headers.json → endpoints.acknowledgeSigningKeyRotation.request-headers.json} +2 -1
  202. package/generated/v1/schemas/{endpoints.verify.response.json → endpoints.acknowledgeSigningKeyRotation.response.json} +8 -21
  203. package/generated/v1/schemas/endpoints.create.request.json +5 -0
  204. package/generated/v1/schemas/endpoints.create.response.json +8 -21
  205. package/generated/v1/schemas/endpoints.disable.response.json +8 -21
  206. package/generated/v1/schemas/endpoints.enable.response-headers.json +11 -0
  207. package/generated/v1/schemas/endpoints.enable.response.json +87 -0
  208. package/generated/v1/schemas/endpoints.list.response.json +8 -21
  209. package/generated/v1/schemas/endpoints.read.response.json +8 -21
  210. package/generated/v1/schemas/endpoints.revealSigningSecret.response.json +43 -0
  211. package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +8 -21
  212. package/generated/v1/schemas/endpoints.update.request.json +5 -0
  213. package/generated/v1/schemas/endpoints.update.response.json +8 -21
  214. package/generated/v1/schemas/executions.list.query.json +46 -0
  215. package/generated/v1/schemas/executions.list.response.json +116 -0
  216. package/generated/v1/schemas/exports.cancel.response.json +48 -0
  217. package/generated/v1/schemas/exports.create.request.json +6 -0
  218. package/generated/v1/schemas/exports.create.response.json +38 -0
  219. package/generated/v1/schemas/exports.part.query.json +11 -0
  220. package/generated/v1/schemas/exports.part.response.json +22 -0
  221. package/generated/v1/schemas/exports.read.response.json +38 -0
  222. package/generated/v1/schemas/grants.list.response.json +2 -0
  223. package/generated/v1/schemas/grants.revoke.response.json +2 -0
  224. package/generated/v1/schemas/occurrences.list.query.json +5 -0
  225. package/generated/v1/schemas/occurrences.list.response.json +23 -0
  226. package/generated/v1/schemas/occurrences.read.response.json +23 -0
  227. package/generated/v1/schemas/occurrences.redeliver.response.json +24 -1
  228. package/generated/v1/schemas/organizations.close.request.json +20 -0
  229. package/generated/v1/schemas/organizations.close.response.json +68 -0
  230. package/generated/v1/schemas/organizations.lifecycle.response.json +68 -0
  231. package/generated/v1/schemas/organizations.restore.request.json +13 -0
  232. package/generated/v1/schemas/organizations.restore.response.json +68 -0
  233. package/generated/v1/schemas/projects.archive.request-headers.json +11 -0
  234. package/generated/v1/schemas/projects.archive.request.json +10 -0
  235. package/generated/v1/schemas/projects.archive.response-headers.json +11 -0
  236. package/generated/v1/schemas/projects.archive.response.json +79 -0
  237. package/generated/v1/schemas/projects.create.response.json +55 -1
  238. package/generated/v1/schemas/projects.delete.request-headers.json +11 -0
  239. package/generated/v1/schemas/projects.delete.response.json +37 -0
  240. package/generated/v1/schemas/projects.list.response.json +61 -2
  241. package/generated/v1/schemas/projects.rename.request-headers.json +11 -0
  242. package/generated/v1/schemas/projects.rename.request.json +12 -0
  243. package/generated/v1/schemas/projects.rename.response-headers.json +11 -0
  244. package/generated/v1/schemas/projects.rename.response.json +79 -0
  245. package/generated/v1/schemas/schedules.archive.response.json +90 -2
  246. package/generated/v1/schemas/schedules.cancel.response.json +90 -2
  247. package/generated/v1/schemas/schedules.create.request.json +24 -3
  248. package/generated/v1/schemas/schedules.create.response.json +90 -2
  249. package/generated/v1/schemas/schedules.list.query.json +13 -0
  250. package/generated/v1/schemas/schedules.list.response.json +67 -2
  251. package/generated/v1/schemas/schedules.pause.response.json +90 -2
  252. package/generated/v1/schemas/schedules.read.response.json +90 -2
  253. package/generated/v1/schemas/schedules.replace.request.json +24 -3
  254. package/generated/v1/schemas/schedules.replace.response.json +90 -2
  255. package/generated/v1/schemas/schedules.resume.response.json +90 -2
  256. package/generated/v1/schemas/timing.summary.response.json +191 -0
  257. package/generated/v1/schemas/usage.read.query.json +7 -0
  258. package/generated/v1/schemas/usage.read.response.json +36 -1
  259. package/generated/v1/snippets/billing.autotopup.json +6 -0
  260. package/generated/v1/snippets/billing.autotopuppreview.json +6 -0
  261. package/generated/v1/snippets/billing.budgets.json +6 -0
  262. package/generated/v1/snippets/billing.ceiling.json +6 -0
  263. package/generated/v1/snippets/billing.change.json +6 -0
  264. package/generated/v1/snippets/billing.funding.json +6 -0
  265. package/generated/v1/snippets/billing.fundingquote.json +6 -0
  266. package/generated/v1/snippets/billing.ledger.json +6 -0
  267. package/generated/v1/snippets/billing.notices.json +6 -0
  268. package/generated/v1/snippets/billing.notificationsettings.json +6 -0
  269. package/generated/v1/snippets/billing.notificationsettingsupdate.json +6 -0
  270. package/generated/v1/snippets/billing.portal.json +6 -0
  271. package/generated/v1/snippets/billing.projects.json +6 -0
  272. package/generated/v1/snippets/billing.purchase.json +6 -0
  273. package/generated/v1/snippets/billing.quote.json +6 -0
  274. package/generated/v1/snippets/billing.reconcile.json +6 -0
  275. package/generated/v1/snippets/billing.refundrequest.json +6 -0
  276. package/generated/v1/snippets/billing.refundresolution.json +6 -0
  277. package/generated/v1/snippets/billing.summary.json +6 -0
  278. package/generated/v1/snippets/endpoints.acknowledgeSigningKeyRotation.json +6 -0
  279. package/generated/v1/snippets/{endpoints.verify.json → endpoints.enable.json} +3 -3
  280. package/generated/v1/snippets/endpoints.revealSigningSecret.json +6 -0
  281. package/generated/v1/snippets/executions.list.json +6 -0
  282. package/generated/v1/snippets/exports.cancel.json +6 -0
  283. package/generated/v1/snippets/exports.create.json +6 -0
  284. package/generated/v1/snippets/exports.part.json +6 -0
  285. package/generated/v1/snippets/exports.read.json +6 -0
  286. package/generated/v1/snippets/organizations.close.json +6 -0
  287. package/generated/v1/snippets/organizations.lifecycle.json +6 -0
  288. package/generated/v1/snippets/organizations.restore.json +6 -0
  289. package/generated/v1/snippets/projects.archive.json +6 -0
  290. package/generated/v1/snippets/projects.delete.json +6 -0
  291. package/generated/v1/snippets/projects.rename.json +6 -0
  292. package/generated/v1/snippets/schedules.create.json +3 -3
  293. package/generated/v1/snippets/schedules.replace.json +3 -3
  294. package/generated/v1/snippets/timing.summary.json +6 -0
  295. package/package.json +1 -6
  296. package/dist/callback-ownership.d.ts +0 -58
  297. package/dist/callback-ownership.js +0 -71
  298. package/generated/v1/callback-endpoint-verification.json +0 -59
  299. /package/generated/v1/schemas/{endpoints.verify.response-headers.json → endpoints.acknowledgeSigningKeyRotation.response-headers.json} +0 -0
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { z } from "./zod.js";
2
2
  import type { Authority } from "./authority.js";
3
3
  import type { FieldError } from "./problems.js";
4
4
  /**
@@ -15,11 +15,74 @@ export declare const PAGE_SIZE: {
15
15
  readonly max: 100;
16
16
  readonly min: 1;
17
17
  };
18
- export declare const pageRequestSchema: z.ZodObject<{
19
- cursor: z.ZodOptional<z.ZodString>;
20
- limit: z.ZodDefault<z.ZodNumber>;
21
- }, z.core.$strip>;
18
+ export declare const pageRequestSchema: import("zod").ZodObject<{
19
+ cursor: import("zod").ZodOptional<import("zod").ZodString>;
20
+ limit: import("zod").ZodDefault<import("zod").ZodNumber>;
21
+ }, import("zod/v4/core").$strip>;
22
22
  export type PageRequest = z.infer<typeof pageRequestSchema>;
23
+ /**
24
+ * A schedules listing: the page, and optionally one endpoint to keep to.
25
+ *
26
+ * The way back from a destination to everything pointed at it. Somebody who
27
+ * disabled an endpoint by mistake, or is about to move one, has to be able to
28
+ * see the schedules that deliver to it without reading the whole project.
29
+ */
30
+ /**
31
+ * How a schedules listing may be ordered.
32
+ *
33
+ * `created` is the order this listing has always had and keeps by default, so a
34
+ * client that never heard of this parameter sees nothing change. `due` is what
35
+ * somebody looking at a list of schedules wants (FXO-2283) — what fires next,
36
+ * first — and it is a different index rather than a sort of the page, because
37
+ * sorting a page would order a hundred rows the cursor had already chosen by
38
+ * creation instant.
39
+ */
40
+ export declare const SCHEDULE_ORDERS: readonly ["created", "due"];
41
+ export type ScheduleOrder = (typeof SCHEDULE_ORDERS)[number];
42
+ /**
43
+ * A schedules listing: the page, optionally one endpoint, and how to order it.
44
+ *
45
+ * The way back from a destination to everything pointed at it. Somebody who
46
+ * disabled an endpoint by mistake, or is about to move one, has to be able to
47
+ * see the schedules that deliver to it without reading the whole project.
48
+ *
49
+ * `dueBefore` filters on the earliest unsettled occurrence. It is served by the
50
+ * same index `order=due` walks, so filtering and ordering cost the same probe;
51
+ * a schedule with nothing on the books matches no `dueBefore`, because it has
52
+ * no instant to compare and inventing one either way would be a guess.
53
+ */
54
+ export declare const scheduleListRequestSchema: import("zod").ZodObject<{
55
+ cursor: import("zod").ZodOptional<import("zod").ZodString>;
56
+ limit: import("zod").ZodDefault<import("zod").ZodNumber>;
57
+ dueBefore: import("zod").ZodOptional<import("zod").ZodString>;
58
+ endpoint: import("zod").ZodOptional<import("zod").ZodString>;
59
+ order: import("zod").ZodDefault<import("zod").ZodEnum<{
60
+ created: "created";
61
+ due: "due";
62
+ }>>;
63
+ }, import("zod/v4/core").$strip>;
64
+ export type ScheduleListRequest = z.infer<typeof scheduleListRequestSchema>;
65
+ /**
66
+ * Which end of a history a page walks from.
67
+ *
68
+ * `oldest` is the order every listing has always had and keeps by default; a
69
+ * client that never heard of this parameter sees nothing change. `newest` is
70
+ * what a person looking at a schedule wants (FXO-2259): what happened most
71
+ * recently, first — and on a schedule firing every five minutes for thirty
72
+ * days that is eight thousand rows away from the other end.
73
+ */
74
+ export declare const HISTORY_ORDERS: readonly ["oldest", "newest"];
75
+ export type HistoryOrder = (typeof HISTORY_ORDERS)[number];
76
+ /** A page request over a history, which may be walked from either end. */
77
+ export declare const historyPageRequestSchema: import("zod").ZodObject<{
78
+ cursor: import("zod").ZodOptional<import("zod").ZodString>;
79
+ limit: import("zod").ZodDefault<import("zod").ZodNumber>;
80
+ order: import("zod").ZodDefault<import("zod").ZodEnum<{
81
+ oldest: "oldest";
82
+ newest: "newest";
83
+ }>>;
84
+ }, import("zod/v4/core").$strip>;
85
+ export type HistoryPageRequest = z.infer<typeof historyPageRequestSchema>;
23
86
  /**
24
87
  * A page of results.
25
88
  *
@@ -29,13 +92,13 @@ export type PageRequest = z.infer<typeof pageRequestSchema>;
29
92
  * stale data as fact. `authoritative` means the answer came from the owning
30
93
  * partition; `projected` means it may lag and carries the age.
31
94
  */
32
- export declare const freshnessSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
33
- kind: z.ZodLiteral<"authoritative">;
34
- }, z.core.$strip>, z.ZodObject<{
35
- asOf: z.ZodString;
36
- kind: z.ZodLiteral<"projected">;
37
- lagSeconds: z.ZodNumber;
38
- }, z.core.$strip>], "kind">;
95
+ export declare const freshnessSchema: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
96
+ kind: import("zod").ZodLiteral<"authoritative">;
97
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
98
+ asOf: import("zod").ZodString;
99
+ kind: import("zod").ZodLiteral<"projected">;
100
+ lagSeconds: import("zod").ZodNumber;
101
+ }, import("zod/v4/core").$strip>], "kind">;
39
102
  export type Freshness = z.infer<typeof freshnessSchema>;
40
103
  /**
41
104
  * The `projected` arm alone, for a response that is never authoritative.
@@ -47,37 +110,74 @@ export type Freshness = z.infer<typeof freshnessSchema>;
47
110
  * taken, were unreachable without narrowing that no consumer could justify.
48
111
  * Naming the narrower shape moves the claim into the type.
49
112
  */
50
- export declare const projectedFreshnessSchema: z.ZodObject<{
51
- asOf: z.ZodString;
52
- kind: z.ZodLiteral<"projected">;
53
- lagSeconds: z.ZodNumber;
54
- }, z.core.$strip>;
113
+ export declare const projectedFreshnessSchema: import("zod").ZodObject<{
114
+ asOf: import("zod").ZodString;
115
+ kind: import("zod").ZodLiteral<"projected">;
116
+ lagSeconds: import("zod").ZodNumber;
117
+ }, import("zod/v4/core").$strip>;
55
118
  export type ProjectedFreshness = z.infer<typeof projectedFreshnessSchema>;
56
- export declare function pageSchema<TItem extends z.ZodTypeAny>(item: TItem): z.ZodObject<{
57
- data: z.ZodArray<TItem>;
58
- freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
59
- kind: z.ZodLiteral<"authoritative">;
60
- }, z.core.$strip>, z.ZodObject<{
61
- asOf: z.ZodString;
62
- kind: z.ZodLiteral<"projected">;
63
- lagSeconds: z.ZodNumber;
64
- }, z.core.$strip>], "kind">;
65
- nextCursor: z.ZodOptional<z.ZodString>;
66
- }, z.core.$strip>;
119
+ export declare function pageSchema<TItem extends z.ZodTypeAny>(item: TItem): import("zod").ZodObject<{
120
+ data: import("zod").ZodArray<TItem>;
121
+ freshness: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
122
+ kind: import("zod").ZodLiteral<"authoritative">;
123
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
124
+ asOf: import("zod").ZodString;
125
+ kind: import("zod").ZodLiteral<"projected">;
126
+ lagSeconds: import("zod").ZodNumber;
127
+ }, import("zod/v4/core").$strip>], "kind">;
128
+ nextCursor: import("zod").ZodOptional<import("zod").ZodString>;
129
+ }, import("zod/v4/core").$strip>;
67
130
  /**
68
131
  * Where a page stopped.
69
132
  *
70
- * Every collection in this product is ordered by the instant a row was created
71
- * and then by its identifier, so one position type serves all of them. The
72
- * identifier is the tiebreak rather than decoration: two credentials issued in
73
- * the same millisecond are ordinary, and an order that does not break the tie
74
- * can show one of them twice and the other never.
133
+ * Every collection in this product is ordered by one timestamp and then by its
134
+ * identifier, so one position type serves all of them. Most use creation time;
135
+ * schedules ordered by next due use that due instant, and the project execution
136
+ * index uses the occurrence's planned instant. The identifier is the tiebreak
137
+ * rather than decoration: two occurrences planned for the same millisecond are
138
+ * ordinary, and an order that does not break the tie can show one of them twice
139
+ * and the other never.
75
140
  */
76
141
  export interface PagePosition {
77
- /** The creation instant of the last item on the page just served. */
142
+ /**
143
+ * The ordering instant of the last item served. The historical property name
144
+ * remains for wire and adapter compatibility; see the type comment above.
145
+ */
78
146
  readonly createdAtMs: number;
79
147
  /** That item's identifier. */
80
148
  readonly id: string;
149
+ /**
150
+ * The order the page was walked in, for a listing that has more than one
151
+ * (FXO-2259, FXO-2283). A position is only a position in the order that
152
+ * issued it: presented under another, it is refused rather than resumed from
153
+ * the middle of the book in the wrong direction. Absent on the listings that
154
+ * have one order, and on cursors issued before this field.
155
+ *
156
+ * **Two vocabularies, deliberately, because they are two different
157
+ * questions.** A history is walked from an end — `oldest` or `newest` — and a
158
+ * schedules listing is ordered by a *field*: `created` or `due`. Collapsing
159
+ * them into one enum would let a history cursor and a schedules cursor
160
+ * compare equal, which is the comparison this field exists to make impossible.
161
+ * A cursor carries whichever its listing issued, and the two listings never
162
+ * read each other's.
163
+ */
164
+ readonly order?: "created" | "due" | "newest" | "oldest";
165
+ }
166
+ /**
167
+ * The collection and filters under which a cursor was issued.
168
+ *
169
+ * A position is meaningful only inside the exact listing that produced it.
170
+ * Authority already keeps it inside one tenant; this binding keeps a cursor
171
+ * from one collection, or from one filtered view of a collection, from being
172
+ * replayed into another and silently resuming at an unrelated position.
173
+ *
174
+ * Parameters are a record rather than an encoded string so normalization has
175
+ * one home here. Their names are sorted before encoding; callers may build the
176
+ * object in any order and still mint the same binding.
177
+ */
178
+ export interface CursorBinding {
179
+ readonly operation: string;
180
+ readonly parameters?: Readonly<Record<string, string>>;
81
181
  }
82
182
  /**
83
183
  * The cursor a client is handed for the next page.
@@ -88,7 +188,7 @@ export interface PagePosition {
88
188
  * carries authority, and it carries none — every listing is scoped by the
89
189
  * principal before a cursor is even looked at.
90
190
  */
91
- export declare function encodeCursor(authority: Authority, position: PagePosition): string;
191
+ export declare function encodeCursor(authority: Authority, position: PagePosition, binding?: CursorBinding): string;
92
192
  /**
93
193
  * The position a cursor names, or nothing.
94
194
  *
@@ -98,7 +198,7 @@ export declare function encodeCursor(authority: Authority, position: PagePositio
98
198
  * their cursor is wrong tells them what a valid one looks like, and the caller
99
199
  * never constructs one anyway.
100
200
  */
101
- export declare function decodeCursor(authority: Authority, cursor: string): PagePosition | undefined;
201
+ export declare function decodeCursor(authority: Authority, cursor: string, binding?: CursorBinding): PagePosition | undefined;
102
202
  export type PageRequestParse = {
103
203
  readonly ok: true;
104
204
  readonly page: PageRequest;
@@ -122,13 +222,31 @@ export type PageRequestParse = {
122
222
  * is the direction compatibility has to hold in.
123
223
  */
124
224
  export declare function parsePageRequest(query: URLSearchParams): PageRequestParse;
225
+ export type ScheduleListRequestParse = {
226
+ readonly ok: true;
227
+ readonly page: ScheduleListRequest;
228
+ } | {
229
+ readonly ok: false;
230
+ readonly errors: readonly FieldError[];
231
+ };
232
+ /** A schedules listing request: the page, and the endpoint filter if given. */
233
+ export declare function parseScheduleListRequest(query: URLSearchParams): ScheduleListRequestParse;
234
+ export type HistoryPageRequestParse = {
235
+ readonly ok: true;
236
+ readonly page: HistoryPageRequest;
237
+ } | {
238
+ readonly ok: false;
239
+ readonly errors: readonly FieldError[];
240
+ };
241
+ /** A history page request: the page, and which end it walks from. */
242
+ export declare function parseHistoryPageRequest(query: URLSearchParams): HistoryPageRequestParse;
125
243
  /**
126
244
  * Every mutable resource carries a monotonically increasing `version`, surfaced
127
245
  * as a strong ETag. A conditional write that loses the race is refused with
128
246
  * `version_conflict` rather than silently overwriting — a cancel racing a
129
247
  * replace must not resolve by luck.
130
248
  */
131
- export declare const versionSchema: z.ZodNumber;
249
+ export declare const versionSchema: import("zod").ZodNumber;
132
250
  export type Version = z.infer<typeof versionSchema>;
133
251
  export declare function toETag(version: Version): string;
134
252
  /** Parses a strong ETag, returning undefined for anything malformed or weak. */
@@ -143,7 +261,7 @@ export declare function fromETag(etag: string): Version | undefined;
143
261
  * refinement is the extra strictness the OpenAPI preamble already discloses
144
262
  * that a described schema does not capture.
145
263
  */
146
- export declare const etagSchema: z.ZodString;
264
+ export declare const etagSchema: import("zod").ZodString;
147
265
  /**
148
266
  * Idempotency keys make an unsafe write safe to retry.
149
267
  *
@@ -154,7 +272,7 @@ export declare const etagSchema: z.ZodString;
154
272
  * silent second write — that distinction is the whole point, and the legacy
155
273
  * scheduler had no equivalent.
156
274
  */
157
- export declare const idempotencyKeySchema: z.ZodString;
275
+ export declare const idempotencyKeySchema: import("zod").ZodString;
158
276
  export type IdempotencyKey = z.infer<typeof idempotencyKeySchema>;
159
277
  /**
160
278
  * The scope a key was used in. Carried on the receipt rather than assumed,
@@ -163,24 +281,24 @@ export type IdempotencyKey = z.infer<typeof idempotencyKeySchema>;
163
281
  * send the same key. The scope is part of the record so that mistake cannot be
164
282
  * made silently.
165
283
  */
166
- export declare const idempotencyScopeSchema: z.ZodObject<{
167
- environment: z.ZodString;
168
- operation: z.ZodString;
169
- organization: z.ZodString;
170
- project: z.ZodString;
171
- }, z.core.$strip>;
284
+ export declare const idempotencyScopeSchema: import("zod").ZodObject<{
285
+ environment: import("zod").ZodString;
286
+ operation: import("zod").ZodString;
287
+ organization: import("zod").ZodString;
288
+ project: import("zod").ZodString;
289
+ }, import("zod/v4/core").$strip>;
172
290
  export type IdempotencyScope = z.infer<typeof idempotencyScopeSchema>;
173
- export declare const idempotencyReceiptSchema: z.ZodObject<{
174
- requestFingerprint: z.ZodString;
175
- replayed: z.ZodBoolean;
176
- scope: z.ZodObject<{
177
- environment: z.ZodString;
178
- operation: z.ZodString;
179
- organization: z.ZodString;
180
- project: z.ZodString;
181
- }, z.core.$strip>;
182
- storedAt: z.ZodString;
183
- }, z.core.$strip>;
291
+ export declare const idempotencyReceiptSchema: import("zod").ZodObject<{
292
+ requestFingerprint: import("zod").ZodString;
293
+ replayed: import("zod").ZodBoolean;
294
+ scope: import("zod").ZodObject<{
295
+ environment: import("zod").ZodString;
296
+ operation: import("zod").ZodString;
297
+ organization: import("zod").ZodString;
298
+ project: import("zod").ZodString;
299
+ }, import("zod/v4/core").$strip>;
300
+ storedAt: import("zod").ZodString;
301
+ }, import("zod/v4/core").$strip>;
184
302
  export type IdempotencyReceipt = z.infer<typeof idempotencyReceiptSchema>;
185
303
  /**
186
304
  * The canonical form of a request, as the receipt's `requestFingerprint`
@@ -256,9 +374,9 @@ export declare const CONCURRENCY_HEADERS: {
256
374
  readonly ifMatch: "If-Match";
257
375
  readonly replayed: "Idempotency-Replayed";
258
376
  };
259
- export declare const idempotencyRequestHeaderSchema: z.ZodObject<{
260
- "Idempotency-Key": z.ZodOptional<z.ZodString>;
261
- }, z.core.$strip>;
377
+ export declare const idempotencyRequestHeaderSchema: import("zod").ZodObject<{
378
+ "Idempotency-Key": import("zod").ZodOptional<import("zod").ZodString>;
379
+ }, import("zod/v4/core").$strip>;
262
380
  /**
263
381
  * A write for which idempotency is mandatory, rather than an optional
264
382
  * protection a caller may opt into.
@@ -268,21 +386,32 @@ export declare const idempotencyRequestHeaderSchema: z.ZodObject<{
268
386
  * `idempotencyRequestHeaderSchema` lets schedule creation retain its published
269
387
  * optional behavior without weakening the redelivery promise.
270
388
  */
271
- export declare const requiredIdempotencyRequestHeaderSchema: z.ZodObject<{
272
- "Idempotency-Key": z.ZodString;
273
- }, z.core.$strip>;
274
- export declare const conditionalRequestHeaderSchema: z.ZodObject<{
275
- "If-Match": z.ZodOptional<z.ZodString>;
276
- }, z.core.$strip>;
389
+ export declare const requiredIdempotencyRequestHeaderSchema: import("zod").ZodObject<{
390
+ "Idempotency-Key": import("zod").ZodString;
391
+ }, import("zod/v4/core").$strip>;
392
+ export declare const conditionalRequestHeaderSchema: import("zod").ZodObject<{
393
+ "If-Match": import("zod").ZodOptional<import("zod").ZodString>;
394
+ }, import("zod/v4/core").$strip>;
395
+ /**
396
+ * A conditional write that refuses an unversioned request.
397
+ *
398
+ * Project lifecycle changes use this stricter shape because rename, archive,
399
+ * restore and permanent deletion are management actions over one shared row:
400
+ * silently accepting a missing precondition would let a stale General page
401
+ * overwrite a newer decision.
402
+ */
403
+ export declare const requiredConditionalRequestHeaderSchema: import("zod").ZodObject<{
404
+ "If-Match": import("zod").ZodString;
405
+ }, import("zod/v4/core").$strip>;
277
406
  /**
278
407
  * Required rather than optional, deliberately: an operation that declares this
279
408
  * promises the header on every success, and a client that has to test for its
280
409
  * presence before making a conditional write will write the untested branch
281
410
  * wrong.
282
411
  */
283
- export declare const versionResponseHeaderSchema: z.ZodObject<{
284
- ETag: z.ZodString;
285
- }, z.core.$strip>;
412
+ export declare const versionResponseHeaderSchema: import("zod").ZodObject<{
413
+ ETag: import("zod").ZodString;
414
+ }, import("zod/v4/core").$strip>;
286
415
  /**
287
416
  * What a replayed write answers with, on top of the version.
288
417
  *
@@ -298,11 +427,11 @@ export declare const versionResponseHeaderSchema: z.ZodObject<{
298
427
  * header is the ordinary case, and a client that has to read a header to learn
299
428
  * nothing happened is a client that will not read it.
300
429
  */
301
- export declare const replayedResponseHeaderSchema: z.ZodObject<{
302
- ETag: z.ZodString;
303
- "Idempotency-Replayed": z.ZodOptional<z.ZodLiteral<"true">>;
304
- }, z.core.$strip>;
430
+ export declare const replayedResponseHeaderSchema: import("zod").ZodObject<{
431
+ ETag: import("zod").ZodString;
432
+ "Idempotency-Replayed": import("zod").ZodOptional<import("zod").ZodLiteral<"true">>;
433
+ }, import("zod/v4/core").$strip>;
305
434
  /** Replay marker for an idempotent create whose result has no ETag. */
306
- export declare const idempotencyReplayResponseHeaderSchema: z.ZodObject<{
307
- "Idempotency-Replayed": z.ZodOptional<z.ZodLiteral<"true">>;
308
- }, z.core.$strip>;
435
+ export declare const idempotencyReplayResponseHeaderSchema: import("zod").ZodObject<{
436
+ "Idempotency-Replayed": import("zod").ZodOptional<import("zod").ZodLiteral<"true">>;
437
+ }, import("zod/v4/core").$strip>;
package/dist/protocol.js CHANGED
@@ -1,5 +1,5 @@
1
- import { z } from "zod";
2
- import { environmentIdSchema, organizationIdSchema, projectIdSchema, } from "./identifiers.js";
1
+ import { z } from "./zod.js";
2
+ import { endpointIdSchema, environmentIdSchema, organizationIdSchema, projectIdSchema, } from "./identifiers.js";
3
3
  import { timestampSchema } from "./time.js";
4
4
  /**
5
5
  * Request and collection mechanics: paging, optimistic concurrency, and
@@ -23,6 +23,55 @@ export const pageRequestSchema = z.object({
23
23
  .max(PAGE_SIZE.max)
24
24
  .default(PAGE_SIZE.default),
25
25
  });
26
+ /**
27
+ * A schedules listing: the page, and optionally one endpoint to keep to.
28
+ *
29
+ * The way back from a destination to everything pointed at it. Somebody who
30
+ * disabled an endpoint by mistake, or is about to move one, has to be able to
31
+ * see the schedules that deliver to it without reading the whole project.
32
+ */
33
+ /**
34
+ * How a schedules listing may be ordered.
35
+ *
36
+ * `created` is the order this listing has always had and keeps by default, so a
37
+ * client that never heard of this parameter sees nothing change. `due` is what
38
+ * somebody looking at a list of schedules wants (FXO-2283) — what fires next,
39
+ * first — and it is a different index rather than a sort of the page, because
40
+ * sorting a page would order a hundred rows the cursor had already chosen by
41
+ * creation instant.
42
+ */
43
+ export const SCHEDULE_ORDERS = ["created", "due"];
44
+ /**
45
+ * A schedules listing: the page, optionally one endpoint, and how to order it.
46
+ *
47
+ * The way back from a destination to everything pointed at it. Somebody who
48
+ * disabled an endpoint by mistake, or is about to move one, has to be able to
49
+ * see the schedules that deliver to it without reading the whole project.
50
+ *
51
+ * `dueBefore` filters on the earliest unsettled occurrence. It is served by the
52
+ * same index `order=due` walks, so filtering and ordering cost the same probe;
53
+ * a schedule with nothing on the books matches no `dueBefore`, because it has
54
+ * no instant to compare and inventing one either way would be a guess.
55
+ */
56
+ export const scheduleListRequestSchema = pageRequestSchema.extend({
57
+ dueBefore: timestampSchema.optional(),
58
+ endpoint: endpointIdSchema.optional(),
59
+ order: z.enum(SCHEDULE_ORDERS).default("created"),
60
+ });
61
+ /**
62
+ * Which end of a history a page walks from.
63
+ *
64
+ * `oldest` is the order every listing has always had and keeps by default; a
65
+ * client that never heard of this parameter sees nothing change. `newest` is
66
+ * what a person looking at a schedule wants (FXO-2259): what happened most
67
+ * recently, first — and on a schedule firing every five minutes for thirty
68
+ * days that is eight thousand rows away from the other end.
69
+ */
70
+ export const HISTORY_ORDERS = ["oldest", "newest"];
71
+ /** A page request over a history, which may be walked from either end. */
72
+ export const historyPageRequestSchema = pageRequestSchema.extend({
73
+ order: z.enum(HISTORY_ORDERS).default("oldest"),
74
+ });
26
75
  /**
27
76
  * A page of results.
28
77
  *
@@ -63,6 +112,12 @@ export function pageSchema(item) {
63
112
  nextCursor: z.string().min(1).optional(),
64
113
  });
65
114
  }
115
+ function normalizedCursorBinding(binding) {
116
+ return JSON.stringify({
117
+ operation: binding.operation,
118
+ parameters: Object.entries(binding.parameters ?? {}).sort(([left], [right]) => left.localeCompare(right)),
119
+ });
120
+ }
66
121
  /**
67
122
  * A 32-bit FNV-1a hash of the authority tuple, hex encoded.
68
123
  *
@@ -136,11 +191,13 @@ function base64UrlDecode(value) {
136
191
  * carries authority, and it carries none — every listing is scoped by the
137
192
  * principal before a cursor is even looked at.
138
193
  */
139
- export function encodeCursor(authority, position) {
194
+ export function encodeCursor(authority, position, binding) {
140
195
  return base64UrlEncode(JSON.stringify({
141
196
  a: position.createdAtMs,
197
+ ...(binding === undefined ? {} : { b: normalizedCursorBinding(binding) }),
142
198
  i: position.id,
143
199
  k: authorityFingerprint(authority),
200
+ ...(position.order === undefined ? {} : { o: position.order }),
144
201
  }));
145
202
  }
146
203
  /**
@@ -152,7 +209,7 @@ export function encodeCursor(authority, position) {
152
209
  * their cursor is wrong tells them what a valid one looks like, and the caller
153
210
  * never constructs one anyway.
154
211
  */
155
- export function decodeCursor(authority, cursor) {
212
+ export function decodeCursor(authority, cursor, binding) {
156
213
  const decoded = base64UrlDecode(cursor);
157
214
  if (decoded === undefined) {
158
215
  return undefined;
@@ -167,47 +224,40 @@ export function decodeCursor(authority, cursor) {
167
224
  const shape = z
168
225
  .object({
169
226
  a: z.number().int().nonnegative(),
227
+ b: z.string().min(1).optional(),
170
228
  i: z.string().min(1),
171
229
  k: z.string(),
230
+ o: z.enum(["created", "due", "newest", "oldest"]).optional(),
172
231
  })
173
232
  .safeParse(parsed);
174
- if (!shape.success || shape.data.k !== authorityFingerprint(authority)) {
233
+ const expectedBinding = binding === undefined ? undefined : normalizedCursorBinding(binding);
234
+ if (!shape.success ||
235
+ shape.data.k !== authorityFingerprint(authority) ||
236
+ shape.data.b !== expectedBinding) {
175
237
  return undefined;
176
238
  }
177
- return { createdAtMs: shape.data.a, id: shape.data.i };
239
+ return {
240
+ createdAtMs: shape.data.a,
241
+ id: shape.data.i,
242
+ ...(shape.data.o === undefined ? {} : { order: shape.data.o }),
243
+ };
178
244
  }
179
245
  /** Query values arrive as strings; only a whole number may become one. */
180
246
  function asNumberIfInteger(value) {
181
247
  return /^-?\d+$/u.test(value) ? Number(value) : value;
182
248
  }
183
- /**
184
- * The page parameters, and which of them are numeric — derived from
185
- * `pageRequestSchema` rather than written out again.
186
- *
187
- * Every projection reads the parameter list out of this same schema's JSON
188
- * Schema. A hand-written list here agrees with it exactly until somebody adds a
189
- * field: the document would publish it and the generated client would send it,
190
- * while the service silently ignored it. That is the drift the operation
191
- * registry exists to prevent, one line below the schema it prevents it for.
192
- *
193
- * Numeric-ness has to be derived too rather than assumed from the name. Coercing
194
- * every all-digit value would turn a cursor that happened to be digits into a
195
- * number, and a cursor is opaque precisely so its encoding can change.
196
- *
197
- * **Only numeric coercion is derived.** A future page parameter that is neither
198
- * a string nor a number — a boolean, say — would arrive here as the text
199
- * `"true"` and be refused by its own schema. That is the safe direction and it
200
- * is still a trap, so whoever adds one adds its coercion beside this list.
201
- *
202
- * Computed once: `toJSONSchema` is not cheap enough to run per request.
203
- */
204
- const PAGE_PARAMETERS = Object.entries(z.toJSONSchema(pageRequestSchema, {
205
- io: "input",
206
- unrepresentable: "any",
207
- }).properties ?? {}).map(([name, schema]) => [
208
- name,
209
- schema.type === "integer" || schema.type === "number",
210
- ]);
249
+ const PARAMETERS = new Map();
250
+ function parametersOf(schema) {
251
+ const cached = PARAMETERS.get(schema);
252
+ if (cached !== undefined)
253
+ return cached;
254
+ const derived = Object.entries(z.toJSONSchema(schema, {
255
+ io: "input",
256
+ unrepresentable: "any",
257
+ }).properties ?? {}).map(([name, field]) => [name, field.type === "integer" || field.type === "number"]);
258
+ PARAMETERS.set(schema, derived);
259
+ return derived;
260
+ }
211
261
  /**
212
262
  * A page request, read from a URL's query string.
213
263
  *
@@ -224,9 +274,20 @@ const PAGE_PARAMETERS = Object.entries(z.toJSONSchema(pageRequestSchema, {
224
274
  * is the direction compatibility has to hold in.
225
275
  */
226
276
  export function parsePageRequest(query) {
277
+ return parseListing(query, pageRequestSchema);
278
+ }
279
+ /** A schedules listing request: the page, and the endpoint filter if given. */
280
+ export function parseScheduleListRequest(query) {
281
+ return parseListing(query, scheduleListRequestSchema);
282
+ }
283
+ /** A history page request: the page, and which end it walks from. */
284
+ export function parseHistoryPageRequest(query) {
285
+ return parseListing(query, historyPageRequestSchema);
286
+ }
287
+ function parseListing(query, schema) {
227
288
  const errors = [];
228
289
  const raw = {};
229
- for (const [field, numeric] of PAGE_PARAMETERS) {
290
+ for (const [field, numeric] of parametersOf(schema)) {
230
291
  const values = query.getAll(field);
231
292
  if (values.length > 1) {
232
293
  errors.push({
@@ -244,7 +305,7 @@ export function parsePageRequest(query) {
244
305
  if (errors.length > 0) {
245
306
  return { errors, ok: false };
246
307
  }
247
- const parsed = pageRequestSchema.safeParse(raw);
308
+ const parsed = schema.safeParse(raw);
248
309
  if (parsed.success) {
249
310
  return { ok: true, page: parsed.data };
250
311
  }
@@ -481,6 +542,17 @@ export const requiredIdempotencyRequestHeaderSchema = z.object({
481
542
  export const conditionalRequestHeaderSchema = z.object({
482
543
  [CONCURRENCY_HEADERS.ifMatch]: etagSchema.optional(),
483
544
  });
545
+ /**
546
+ * A conditional write that refuses an unversioned request.
547
+ *
548
+ * Project lifecycle changes use this stricter shape because rename, archive,
549
+ * restore and permanent deletion are management actions over one shared row:
550
+ * silently accepting a missing precondition would let a stale General page
551
+ * overwrite a newer decision.
552
+ */
553
+ export const requiredConditionalRequestHeaderSchema = z.object({
554
+ [CONCURRENCY_HEADERS.ifMatch]: etagSchema,
555
+ });
484
556
  /**
485
557
  * Required rather than optional, deliberately: an operation that declares this
486
558
  * promises the header on every success, and a client that has to test for its