@michaelthielemann/kestrel 4.1.3 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +26 -201
  2. package/dist/boot.d.ts +45 -0
  3. package/dist/boot.js +284 -0
  4. package/dist/cast.d.ts +3 -0
  5. package/dist/cast.js +6 -0
  6. package/dist/catalogue.d.ts +11 -0
  7. package/dist/catalogue.js +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +55 -0
  10. package/dist/context.d.ts +62 -0
  11. package/dist/context.js +61 -0
  12. package/dist/dataflow.d.ts +3 -0
  13. package/dist/dataflow.js +65 -0
  14. package/dist/defineConfig.d.ts +162 -0
  15. package/dist/defineConfig.js +42 -0
  16. package/dist/defineContract.d.ts +12 -0
  17. package/dist/defineContract.js +16 -0
  18. package/dist/defineModule.d.ts +75 -0
  19. package/dist/defineModule.js +9 -0
  20. package/dist/definePipeline.d.ts +7 -0
  21. package/dist/definePipeline.js +11 -0
  22. package/dist/errors.d.ts +26 -0
  23. package/dist/errors.js +48 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.js +14 -0
  26. package/dist/load.d.ts +7 -0
  27. package/dist/load.js +79 -0
  28. package/dist/logger.d.ts +18 -0
  29. package/dist/logger.js +36 -0
  30. package/dist/registry.d.ts +14 -0
  31. package/dist/registry.js +87 -0
  32. package/dist/result.d.ts +19 -0
  33. package/dist/result.js +19 -0
  34. package/dist/runner.d.ts +29 -0
  35. package/dist/runner.js +133 -0
  36. package/dist/schema.d.ts +7 -0
  37. package/dist/schema.js +142 -0
  38. package/dist/sort.d.ts +2 -0
  39. package/dist/sort.js +50 -0
  40. package/dist/testing/runPipeline.d.ts +9 -0
  41. package/dist/testing/runPipeline.js +22 -0
  42. package/dist/triggers/allowlist.d.ts +9 -0
  43. package/dist/triggers/allowlist.js +49 -0
  44. package/dist/triggers/cron.d.ts +10 -0
  45. package/dist/triggers/cron.js +44 -0
  46. package/dist/triggers/cronMatch.d.ts +10 -0
  47. package/dist/triggers/cronMatch.js +68 -0
  48. package/dist/triggers/http.d.ts +59 -0
  49. package/dist/triggers/http.js +300 -0
  50. package/package.json +25 -151
  51. package/NOTICE +0 -12
  52. package/kestrel.config.ts +0 -28
  53. package/layers/access/nuxt.config.ts +0 -5
  54. package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
  55. package/layers/access/server/middleware/access-guard.ts +0 -32
  56. package/layers/access/server/types.d.ts +0 -7
  57. package/layers/admin/app/components/AdminAccount.vue +0 -212
  58. package/layers/admin/app/components/AdminNav.vue +0 -44
  59. package/layers/admin/app/components/BlockFields.vue +0 -67
  60. package/layers/admin/app/components/BlockPreview.vue +0 -413
  61. package/layers/admin/app/components/BlockTree.vue +0 -433
  62. package/layers/admin/app/components/BlocksBody.vue +0 -163
  63. package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
  64. package/layers/admin/app/components/CollectionEditor.vue +0 -324
  65. package/layers/admin/app/components/CollectionList.vue +0 -429
  66. package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
  67. package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
  68. package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
  69. package/layers/admin/app/components/CollectionListPager.vue +0 -70
  70. package/layers/admin/app/components/CollectionListTable.vue +0 -347
  71. package/layers/admin/app/components/EditorStatus.vue +0 -211
  72. package/layers/admin/app/components/EditorUnsupported.vue +0 -28
  73. package/layers/admin/app/components/FieldsBody.vue +0 -23
  74. package/layers/admin/app/components/LocaleBar.vue +0 -107
  75. package/layers/admin/app/components/PageFields.vue +0 -129
  76. package/layers/admin/app/components/PageFieldsPane.vue +0 -39
  77. package/layers/admin/app/components/SeoFields.vue +0 -210
  78. package/layers/admin/app/components/SingletonEditor.vue +0 -93
  79. package/layers/admin/app/composables/useAuth.ts +0 -68
  80. package/layers/admin/app/composables/useBlockTree.ts +0 -95
  81. package/layers/admin/app/composables/useBlocks.ts +0 -7
  82. package/layers/admin/app/composables/useCachedResource.ts +0 -35
  83. package/layers/admin/app/composables/useCollectionOps.ts +0 -102
  84. package/layers/admin/app/composables/useCollections.ts +0 -6
  85. package/layers/admin/app/composables/useContentLocales.ts +0 -23
  86. package/layers/admin/app/composables/useEditForm.ts +0 -402
  87. package/layers/admin/app/composables/useLinkResolver.ts +0 -40
  88. package/layers/admin/app/composables/useListBatchActions.ts +0 -78
  89. package/layers/admin/app/composables/useListColumns.ts +0 -53
  90. package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
  91. package/layers/admin/app/composables/useListRows.ts +0 -75
  92. package/layers/admin/app/composables/useListSelection.ts +0 -28
  93. package/layers/admin/app/composables/useListUrlState.ts +0 -53
  94. package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
  95. package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
  96. package/layers/admin/app/composables/usePublishStatus.ts +0 -107
  97. package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
  98. package/layers/admin/app/composables/useTheme.ts +0 -36
  99. package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
  100. package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
  101. package/layers/admin/app/layouts/admin.vue +0 -266
  102. package/layers/admin/app/middleware/admin-auth.ts +0 -4
  103. package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
  104. package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
  105. package/layers/admin/app/pages/admin/index.vue +0 -118
  106. package/layers/admin/app/pages/admin/login.vue +0 -68
  107. package/layers/admin/app/pages/admin/media.vue +0 -28
  108. package/layers/admin/app/pages/admin/references.vue +0 -146
  109. package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
  110. package/layers/admin/app/plugins/reauth.client.ts +0 -20
  111. package/layers/admin/app/utils/admin-style-guard.ts +0 -20
  112. package/layers/admin/app/utils/block-tree.ts +0 -214
  113. package/layers/admin/app/utils/collection-ops.ts +0 -69
  114. package/layers/admin/app/utils/dead-refs.ts +0 -30
  115. package/layers/admin/app/utils/edit-form.ts +0 -267
  116. package/layers/admin/app/utils/editor-expose.ts +0 -36
  117. package/layers/admin/app/utils/editor-form-context.ts +0 -82
  118. package/layers/admin/app/utils/editor-registry.ts +0 -20
  119. package/layers/admin/app/utils/field-keys.ts +0 -11
  120. package/layers/admin/app/utils/list-cell.ts +0 -46
  121. package/layers/admin/app/utils/list-columns.ts +0 -142
  122. package/layers/admin/app/utils/list-query.ts +0 -96
  123. package/layers/admin/app/utils/nav-active.ts +0 -9
  124. package/layers/admin/app/utils/populate-blocks.ts +0 -288
  125. package/layers/admin/app/utils/preview-channel.ts +0 -62
  126. package/layers/admin/app/utils/preview-viewport.ts +0 -80
  127. package/layers/admin/app/utils/reauth.ts +0 -67
  128. package/layers/admin/app/utils/record-title.ts +0 -15
  129. package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
  130. package/layers/admin/app/utils/safe-redirect.ts +0 -12
  131. package/layers/admin/nuxt.config.ts +0 -20
  132. package/layers/auth/nuxt.config.ts +0 -1
  133. package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
  134. package/layers/collections/nuxt.config.ts +0 -1
  135. package/layers/collections/server/plugins/01.register.ts +0 -29
  136. package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
  137. package/layers/core/app/composables/layouts.ts +0 -5
  138. package/layers/core/app/utils/field-factories.ts +0 -129
  139. package/layers/core/app/utils/layouts.ts +0 -38
  140. package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
  141. package/layers/core/modules/auto-discovery/index.ts +0 -112
  142. package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
  143. package/layers/core/modules/auto-discovery/scan.ts +0 -170
  144. package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
  145. package/layers/core/modules/component-namespace/index.ts +0 -44
  146. package/layers/core/modules/component-namespace/shared.ts +0 -24
  147. package/layers/core/modules/kestrel/app-shell.ts +0 -55
  148. package/layers/core/modules/kestrel/auto-imports.ts +0 -44
  149. package/layers/core/modules/kestrel/index.ts +0 -144
  150. package/layers/core/modules/plugin-order/index.ts +0 -69
  151. package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
  152. package/layers/core/nuxt.config.ts +0 -10
  153. package/layers/core/server/api/[...path].ts +0 -98
  154. package/layers/core/server/plugins/00.config.ts +0 -12
  155. package/layers/core/server/plugins/00.migrate.ts +0 -12
  156. package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
  157. package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
  158. package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
  159. package/layers/core/server/plugins/02.schema-sync.ts +0 -58
  160. package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
  161. package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
  162. package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
  163. package/layers/core/server/tasks/db/migrate-module.ts +0 -83
  164. package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
  165. package/layers/core/server/tasks/db/migrate.ts +0 -50
  166. package/layers/core/server/utils/server-config.ts +0 -81
  167. package/layers/fields/nuxt.config.ts +0 -1
  168. package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
  169. package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
  170. package/layers/media/app/components/KestrelImg.vue +0 -94
  171. package/layers/media/app/components/MediaConflictDialog.vue +0 -35
  172. package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
  173. package/layers/media/app/components/MediaGrid.vue +0 -113
  174. package/layers/media/app/components/MediaImage.vue +0 -70
  175. package/layers/media/app/components/MediaLibrary.vue +0 -456
  176. package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
  177. package/layers/media/app/components/MediaPathBar.vue +0 -163
  178. package/layers/media/app/components/MediaPicker.vue +0 -19
  179. package/layers/media/app/components/MediaRenameDialog.vue +0 -32
  180. package/layers/media/app/components/MediaTable.vue +0 -75
  181. package/layers/media/app/components/MediaThumb.vue +0 -25
  182. package/layers/media/app/components/MediaToolbar.vue +0 -90
  183. package/layers/media/app/components/MediaUploadDialog.vue +0 -87
  184. package/layers/media/app/components/MediaViewer.vue +0 -137
  185. package/layers/media/app/components/field/Media.vue +0 -263
  186. package/layers/media/app/composables/useBlurUp.ts +0 -55
  187. package/layers/media/app/composables/useMediaClipboard.ts +0 -14
  188. package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
  189. package/layers/media/app/composables/useMediaDnd.ts +0 -56
  190. package/layers/media/app/composables/useMediaLibrary.ts +0 -152
  191. package/layers/media/app/composables/useMediaOps.ts +0 -50
  192. package/layers/media/app/composables/useMediaRelocate.ts +0 -39
  193. package/layers/media/app/composables/useMediaResolver.ts +0 -50
  194. package/layers/media/app/composables/useMediaUpload.ts +0 -93
  195. package/layers/media/app/composables/useMediaVariant.ts +0 -34
  196. package/layers/media/app/plugins/field-media.client.ts +0 -6
  197. package/layers/media/app/utils/ai-disclosure.ts +0 -19
  198. package/layers/media/app/utils/dnd.ts +0 -69
  199. package/layers/media/app/utils/library.ts +0 -81
  200. package/layers/media/app/utils/ops.ts +0 -60
  201. package/layers/media/app/utils/picture-model.ts +0 -125
  202. package/layers/media/app/utils/thumbhash.ts +0 -19
  203. package/layers/media/app/utils/variant-name.ts +0 -20
  204. package/layers/media/nuxt.config.ts +0 -7
  205. package/layers/media/server/middleware/ondemand-variants.ts +0 -38
  206. package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
  207. package/layers/media/server/plugins/02.register-media.ts +0 -22
  208. package/layers/media/server/plugins/04.variant-capture.ts +0 -27
  209. package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
  210. package/layers/media/server/tasks/media/backfill.ts +0 -17
  211. package/layers/public/app/app.vue +0 -5
  212. package/layers/public/app/components/BlockRenderer.vue +0 -56
  213. package/layers/public/app/components/KestrelLink.vue +0 -26
  214. package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
  215. package/layers/public/app/composables/public-page.ts +0 -24
  216. package/layers/public/app/error.vue +0 -12
  217. package/layers/public/app/layouts/default.vue +0 -5
  218. package/layers/public/app/pages/[...slug].vue +0 -239
  219. package/layers/public/app/pages/__kestrel/preview.vue +0 -46
  220. package/layers/public/app/utils/block-edit-context.ts +0 -16
  221. package/layers/public/app/utils/json-ld.ts +0 -139
  222. package/layers/public/app/utils/link-href.ts +0 -46
  223. package/layers/public/app/utils/page-head.ts +0 -84
  224. package/layers/public/app/utils/page-layout.ts +0 -18
  225. package/layers/public/app/utils/preview-protocol.ts +0 -117
  226. package/layers/public/app/utils/public-route.ts +0 -46
  227. package/layers/public/app/utils/site-head.ts +0 -43
  228. package/layers/public/modules/deploy-output/index.ts +0 -80
  229. package/layers/public/modules/prerender-routes/discover.ts +0 -37
  230. package/layers/public/modules/prerender-routes/index.ts +0 -88
  231. package/layers/public/modules/prerender-routes/routes.ts +0 -16
  232. package/layers/public/modules/prune-media/index.ts +0 -48
  233. package/layers/public/modules/prune-media/prune-media.ts +0 -81
  234. package/layers/public/nuxt.config.ts +0 -14
  235. package/layers/public/server/delivery-live/serve.ts +0 -111
  236. package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
  237. package/layers/public/server/pipelines/index.ts +0 -23
  238. package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
  239. package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
  240. package/layers/public/server/plugins/02.register-links.ts +0 -16
  241. package/layers/public/server/plugins/03.redirects.ts +0 -55
  242. package/layers/public/server/plugins/05.plan-publish.ts +0 -5
  243. package/layers/public/server/plugins/zz.publish.ts +0 -127
  244. package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
  245. package/layers/public/server/routes/llms.txt.get.ts +0 -66
  246. package/layers/public/server/routes/redirects.json.get.ts +0 -58
  247. package/layers/public/server/routes/robots.txt.get.ts +0 -11
  248. package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
  249. package/layers/public/server/tasks/publish/run.ts +0 -32
  250. package/layers/public/server/utils/publish/render-live.ts +0 -26
  251. package/layers/ui/app/assets/scss/_base.scss +0 -16
  252. package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
  253. package/layers/ui/app/assets/scss/_mixins.scss +0 -50
  254. package/layers/ui/app/assets/scss/_reset.scss +0 -65
  255. package/layers/ui/app/assets/scss/_tokens.scss +0 -183
  256. package/layers/ui/app/assets/scss/main.scss +0 -4
  257. package/layers/ui/app/components/field/Boolean.vue +0 -30
  258. package/layers/ui/app/components/field/Choice.vue +0 -73
  259. package/layers/ui/app/components/field/Datetime.vue +0 -98
  260. package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
  261. package/layers/ui/app/components/field/Json.vue +0 -62
  262. package/layers/ui/app/components/field/Layout.vue +0 -138
  263. package/layers/ui/app/components/field/Link.vue +0 -112
  264. package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
  265. package/layers/ui/app/components/field/Number.vue +0 -30
  266. package/layers/ui/app/components/field/Relation.vue +0 -49
  267. package/layers/ui/app/components/field/Renderer.vue +0 -30
  268. package/layers/ui/app/components/field/Repeater.vue +0 -460
  269. package/layers/ui/app/components/field/Richtext.vue +0 -26
  270. package/layers/ui/app/components/field/Slug.vue +0 -65
  271. package/layers/ui/app/components/field/Text.vue +0 -35
  272. package/layers/ui/app/components/field/Unsupported.vue +0 -34
  273. package/layers/ui/app/components/ui/Alert.vue +0 -50
  274. package/layers/ui/app/components/ui/Brand.vue +0 -22
  275. package/layers/ui/app/components/ui/Button.vue +0 -116
  276. package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
  277. package/layers/ui/app/components/ui/Checkbox.vue +0 -53
  278. package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
  279. package/layers/ui/app/components/ui/Combobox.vue +0 -345
  280. package/layers/ui/app/components/ui/DatePicker.vue +0 -92
  281. package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
  282. package/layers/ui/app/components/ui/Dialog.vue +0 -107
  283. package/layers/ui/app/components/ui/EmptyState.vue +0 -46
  284. package/layers/ui/app/components/ui/Field.vue +0 -58
  285. package/layers/ui/app/components/ui/Fieldset.vue +0 -65
  286. package/layers/ui/app/components/ui/Icon.vue +0 -44
  287. package/layers/ui/app/components/ui/Menu.vue +0 -54
  288. package/layers/ui/app/components/ui/NumberInput.vue +0 -175
  289. package/layers/ui/app/components/ui/Richtext.vue +0 -141
  290. package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
  291. package/layers/ui/app/components/ui/Select.vue +0 -52
  292. package/layers/ui/app/components/ui/TextInput.vue +0 -111
  293. package/layers/ui/app/components/ui/Textarea.vue +0 -39
  294. package/layers/ui/app/components/ui/TimeInput.vue +0 -18
  295. package/layers/ui/app/components/ui/Toasts.vue +0 -103
  296. package/layers/ui/app/components/ui/Tooltip.vue +0 -45
  297. package/layers/ui/app/composables/useAdminLang.ts +0 -8
  298. package/layers/ui/app/composables/useDragReorder.ts +0 -41
  299. package/layers/ui/app/composables/useEchoGuard.ts +0 -28
  300. package/layers/ui/app/composables/useLinkField.ts +0 -70
  301. package/layers/ui/app/composables/useRecordOptions.ts +0 -84
  302. package/layers/ui/app/composables/useRepeater.ts +0 -99
  303. package/layers/ui/app/composables/useT.ts +0 -32
  304. package/layers/ui/app/composables/useToast.ts +0 -46
  305. package/layers/ui/app/i18n/de.ts +0 -432
  306. package/layers/ui/app/i18n/en.ts +0 -434
  307. package/layers/ui/app/utils/boolean-field.ts +0 -10
  308. package/layers/ui/app/utils/field-component.ts +0 -22
  309. package/layers/ui/app/utils/field-empty.ts +0 -21
  310. package/layers/ui/app/utils/field-registry.ts +0 -36
  311. package/layers/ui/app/utils/humanize.ts +0 -14
  312. package/layers/ui/app/utils/icons.ts +0 -143
  313. package/layers/ui/app/utils/iso-date.ts +0 -18
  314. package/layers/ui/app/utils/localized.ts +0 -13
  315. package/layers/ui/app/utils/record-context.ts +0 -15
  316. package/layers/ui/app/utils/reorder.ts +0 -8
  317. package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
  318. package/layers/ui/app/utils/useFieldA11y.ts +0 -40
  319. package/layers/ui/nuxt.config.ts +0 -9
  320. package/nuxt.config.ts +0 -108
  321. package/scripts/consumer-template-ci.mjs +0 -160
  322. package/scripts/copy-create-payload.mjs +0 -49
  323. package/scripts/coverage-all.mjs +0 -163
  324. package/scripts/dashboard.mjs +0 -89
  325. package/scripts/hash-password.mjs +0 -29
  326. package/scripts/kestrel.mjs +0 -240
  327. package/scripts/lib/cli.mjs +0 -129
  328. package/scripts/lib/coverage-model.mjs +0 -51
  329. package/scripts/lib/password.mjs +0 -19
  330. package/scripts/lib/scaffold.mjs +0 -205
  331. package/scripts/port-graph.mjs +0 -350
  332. package/scripts/typecheck.mjs +0 -62
  333. package/templates/starter/README.md +0 -65
  334. package/templates/starter/_env.example +0 -17
  335. package/templates/starter/_gitignore +0 -26
  336. package/templates/starter/_package.json +0 -22
  337. package/templates/starter/app/app.vue +0 -7
  338. package/templates/starter/app/blocks/Prose.vue +0 -13
  339. package/templates/starter/app/layouts/default.vue +0 -6
  340. package/templates/starter/nuxt.config.ts +0 -23
  341. package/templates/starter/pnpm-workspace.yaml +0 -8
  342. package/templates/starter/tsconfig.json +0 -3
package/dist/load.js ADDED
@@ -0,0 +1,79 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { readdir, stat } from "node:fs/promises";
10
+ import { createRequire } from "node:module";
11
+ import { isAbsolute, resolve } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import { KestrelBootError } from "./errors.js";
14
+ async function importDefault(file, owner) {
15
+ let mod;
16
+ try {
17
+ mod = await import(__rewriteRelativeImportExtension(pathToFileURL(file).href));
18
+ }
19
+ catch (err) {
20
+ throw new KestrelBootError(owner, `cannot load ${file}: ${err instanceof Error ? err.message : String(err)}`);
21
+ }
22
+ const def = mod.default;
23
+ if (def === undefined)
24
+ throw new KestrelBootError(owner, `${file} has no default export`);
25
+ return def;
26
+ }
27
+ export async function loadConfig(file) {
28
+ const def = await importDefault(file, "kestrel.config");
29
+ if (typeof def !== "object" || def === null)
30
+ throw new KestrelBootError("kestrel.config", "default export is not an object");
31
+ return def;
32
+ }
33
+ export function resolveModuleFile(root, use) {
34
+ if (use.startsWith("./") || use.startsWith("../") || isAbsolute(use))
35
+ return resolve(root, use);
36
+ return createRequire(pathToFileURL(resolve(root, "package.json")).href).resolve(use);
37
+ }
38
+ export async function loadModules(root, config) {
39
+ const out = [];
40
+ for (const entry of config.modules) {
41
+ let file;
42
+ try {
43
+ file = resolveModuleFile(root, entry.use);
44
+ }
45
+ catch (err) {
46
+ throw new KestrelBootError(entry.use, `cannot resolve module: ${err instanceof Error ? err.message : String(err)}`);
47
+ }
48
+ const def = await importDefault(file, entry.use);
49
+ const m = def;
50
+ if (typeof m !== "object" || m === null || typeof m.name !== "string" || typeof m.setup !== "function" || !Array.isArray(m.provides) || !Array.isArray(m.requires) || m.configSchema === undefined) {
51
+ throw new KestrelBootError(entry.use, "default export is not a defineModule() result");
52
+ }
53
+ out.push(m);
54
+ }
55
+ return out;
56
+ }
57
+ export async function loadPipelines(root, dir) {
58
+ const abs = resolve(root, dir);
59
+ try {
60
+ if (!(await stat(abs)).isDirectory())
61
+ throw new KestrelBootError("pipelines", `${abs} is not a directory`);
62
+ }
63
+ catch (err) {
64
+ if (err.code === "ENOENT")
65
+ return [];
66
+ throw err;
67
+ }
68
+ const files = (await readdir(abs)).filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts")).sort();
69
+ const out = [];
70
+ for (const file of files) {
71
+ const def = await importDefault(resolve(abs, file), `pipelines/${file}`);
72
+ const p = def;
73
+ if (typeof p !== "object" || p === null || typeof p.name !== "string" || !Array.isArray(p.steps)) {
74
+ throw new KestrelBootError(`pipelines/${file}`, "default export is not a definePipeline() result");
75
+ }
76
+ out.push(p);
77
+ }
78
+ return out;
79
+ }
@@ -0,0 +1,18 @@
1
+ export interface StepLog {
2
+ runId: string;
3
+ parentRunId?: string;
4
+ pipeline: string;
5
+ step: string;
6
+ ms: number;
7
+ outcome: "ok" | `fail(${string})` | "error";
8
+ }
9
+ export interface Logger {
10
+ step(entry: StepLog): void;
11
+ info(message: string, data?: Record<string, unknown>): void;
12
+ warn?(message: string, data?: Record<string, unknown>): void;
13
+ error(message: string, data?: Record<string, unknown>): void;
14
+ }
15
+ export declare function logWarn(logger: Logger, message: string, data?: Record<string, unknown>): void;
16
+ export declare function localIso(date: Date): string;
17
+ export declare const consoleLogger: Logger;
18
+ export declare const silentLogger: Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,36 @@
1
+ export function logWarn(logger, message, data) {
2
+ if (logger.warn)
3
+ logger.warn(message, data);
4
+ else
5
+ logger.info(message, data);
6
+ }
7
+ // ISO 8601 with the local UTC offset (not "Z"), since consoleLogger runs on one machine
8
+ // and log lines should read in local time without losing offset information.
9
+ export function localIso(date) {
10
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
11
+ const offsetMinutes = -date.getTimezoneOffset();
12
+ const sign = offsetMinutes >= 0 ? "+" : "-";
13
+ const abs = Math.abs(offsetMinutes);
14
+ const offset = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
15
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}${offset}`;
16
+ }
17
+ export const consoleLogger = {
18
+ step(entry) {
19
+ console.log(JSON.stringify({ time: localIso(new Date()), level: "step", ...entry }));
20
+ },
21
+ info(message, data = {}) {
22
+ console.log(JSON.stringify({ time: localIso(new Date()), level: "info", message, ...data }));
23
+ },
24
+ warn(message, data = {}) {
25
+ console.warn(JSON.stringify({ time: localIso(new Date()), level: "warn", message, ...data }));
26
+ },
27
+ error(message, data = {}) {
28
+ console.error(JSON.stringify({ time: localIso(new Date()), level: "error", message, ...data }));
29
+ },
30
+ };
31
+ export const silentLogger = {
32
+ step() { },
33
+ info() { },
34
+ warn() { },
35
+ error() { },
36
+ };
@@ -0,0 +1,14 @@
1
+ import { type Step, type StepMap } from "./context.ts";
2
+ import type { StepDescription, StepDescriptions } from "./defineModule.ts";
3
+ export interface ResolvedStep {
4
+ name: string;
5
+ fn: Step;
6
+ description: StepDescription;
7
+ }
8
+ export declare class StepRegistry {
9
+ private readonly entries;
10
+ register(owner: string, prefix: string, steps: StepMap, descriptions?: StepDescriptions): void;
11
+ has(name: string): boolean;
12
+ names(): string[];
13
+ resolve(spec: string, owner?: string): ResolvedStep;
14
+ }
@@ -0,0 +1,87 @@
1
+ import { isStepFactory } from "./context.js";
2
+ import { KestrelBootError } from "./errors.js";
3
+ const CONTEXT_PATH = /^[a-zA-Z][A-Za-z0-9]*(\.[A-Za-z0-9_-]+)*\??$/;
4
+ // Walks the prototype chain (like `missingMethods` in defineContract.ts, via
5
+ // property access) so a class-instance step map registers its inherited methods too.
6
+ function stepKeys(steps) {
7
+ const keys = new Set();
8
+ for (let obj = steps; obj && obj !== Object.prototype; obj = Object.getPrototypeOf(obj)) {
9
+ for (const key of Object.getOwnPropertyNames(obj)) {
10
+ if (key !== "constructor")
11
+ keys.add(key);
12
+ }
13
+ }
14
+ return [...keys];
15
+ }
16
+ function checkPaths(owner, name, description) {
17
+ for (const path of description.reads) {
18
+ if (path.endsWith("?"))
19
+ throw new KestrelBootError(owner, `step "${name}" reads "${path}"; the trailing "?" is allowed in writes only`);
20
+ if (!CONTEXT_PATH.test(path))
21
+ throw new KestrelBootError(owner, `step "${name}" declares an invalid read path "${path}"`);
22
+ }
23
+ for (const path of description.writes) {
24
+ if (!CONTEXT_PATH.test(path))
25
+ throw new KestrelBootError(owner, `step "${name}" declares an invalid write path "${path}"`);
26
+ }
27
+ return description;
28
+ }
29
+ export class StepRegistry {
30
+ entries = new Map();
31
+ register(owner, prefix, steps, descriptions = {}) {
32
+ for (const key of stepKeys(steps)) {
33
+ const value = steps[key];
34
+ const name = `${prefix}.${key}`;
35
+ if (typeof value !== "function")
36
+ throw new KestrelBootError(owner, `step "${name}" is not a function`);
37
+ const fn = value;
38
+ const existing = this.entries.get(name);
39
+ if (existing)
40
+ throw new KestrelBootError(owner, `step "${name}" is already registered by ${existing.owner}`);
41
+ const describe = descriptions[key];
42
+ if (describe === undefined)
43
+ throw new KestrelBootError(owner, `step "${name}" has no describe() entry`);
44
+ if (typeof describe === "function") {
45
+ if (!isStepFactory(fn))
46
+ throw new KestrelBootError(owner, `step "${name}" takes no argument but its describe() entry is a function`);
47
+ }
48
+ else {
49
+ checkPaths(owner, name, describe);
50
+ }
51
+ this.entries.set(name, { owner, fn, describe });
52
+ }
53
+ for (const key of Object.keys(descriptions)) {
54
+ if (!(key in steps))
55
+ throw new KestrelBootError(owner, `describe() names step "${prefix}.${key}" which does not exist`);
56
+ }
57
+ }
58
+ has(name) {
59
+ return this.entries.has(name);
60
+ }
61
+ names() {
62
+ return [...this.entries.keys()];
63
+ }
64
+ resolve(spec, owner = "pipeline") {
65
+ const colon = spec.indexOf(":");
66
+ const name = colon === -1 ? spec : spec.slice(0, colon);
67
+ const arg = colon === -1 ? undefined : spec.slice(colon + 1);
68
+ const entry = this.entries.get(name);
69
+ if (!entry)
70
+ throw new KestrelBootError(owner, `unknown step "${name}"`);
71
+ const factory = isStepFactory(entry.fn);
72
+ if (arg === undefined) {
73
+ if (factory)
74
+ throw new KestrelBootError(owner, `step "${name}" requires an argument`);
75
+ return { name: spec, fn: entry.fn, description: entry.describe };
76
+ }
77
+ if (arg === "")
78
+ throw new KestrelBootError(owner, `step "${name}" has an empty argument`);
79
+ if (!factory)
80
+ throw new KestrelBootError(owner, `step "${name}" does not accept an argument`);
81
+ const built = entry.fn(arg);
82
+ if (typeof built !== "function")
83
+ throw new KestrelBootError(owner, `step "${name}" returned ${typeof built} instead of a step`);
84
+ const description = typeof entry.describe === "function" ? checkPaths(entry.owner, spec, entry.describe(arg)) : entry.describe;
85
+ return { name: spec, fn: built, description };
86
+ }
87
+ }
@@ -0,0 +1,19 @@
1
+ export interface Ok<T> {
2
+ readonly ok: true;
3
+ readonly value: T;
4
+ }
5
+ export interface Err<E> {
6
+ readonly ok: false;
7
+ readonly error: E;
8
+ }
9
+ export type Result<T, E> = Ok<T> | Err<E>;
10
+ export declare function ok(): Ok<void>;
11
+ export declare function ok<T>(value: T): Ok<T>;
12
+ export declare function err<E>(error: E): Err<E>;
13
+ export declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
14
+ export declare function isErr<T, E>(result: Result<T, E>): result is Err<E>;
15
+ export declare function match<T, E, A, B>(result: Result<T, E>, on: {
16
+ ok: (value: T) => A;
17
+ err: (error: E) => B;
18
+ }): A | B;
19
+ export declare function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T;
package/dist/result.js ADDED
@@ -0,0 +1,19 @@
1
+ export function ok(value) {
2
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
3
+ return { ok: true, value: value };
4
+ }
5
+ export function err(error) {
6
+ return { ok: false, error };
7
+ }
8
+ export function isOk(result) {
9
+ return result.ok;
10
+ }
11
+ export function isErr(result) {
12
+ return !result.ok;
13
+ }
14
+ export function match(result, on) {
15
+ return result.ok ? on.ok(result.value) : on.err(result.error);
16
+ }
17
+ export function unwrapOr(result, fallback) {
18
+ return result.ok ? result.value : fallback;
19
+ }
@@ -0,0 +1,29 @@
1
+ import { type ContextInput } from "./context.ts";
2
+ import type { Logger } from "./logger.ts";
3
+ import type { ResolvedStep } from "./registry.ts";
4
+ export interface ResolvedPipeline {
5
+ name: string;
6
+ steps: readonly ResolvedStep[];
7
+ }
8
+ export interface RunResult {
9
+ runId: string;
10
+ requestId?: string;
11
+ status: number;
12
+ result?: unknown;
13
+ error?: string;
14
+ code?: string;
15
+ retryable?: boolean;
16
+ step?: string;
17
+ details?: Record<string, unknown>;
18
+ }
19
+ export type Runner = (pipeline: string, input: ContextInput) => Promise<RunResult>;
20
+ export interface RunOptions {
21
+ validateInput: boolean;
22
+ }
23
+ export interface RunTracker {
24
+ track<T>(fn: () => Promise<T>): Promise<T>;
25
+ active(): number;
26
+ drain(timeoutMs: number): Promise<boolean>;
27
+ }
28
+ export declare function createRunTracker(): RunTracker;
29
+ export declare function runPipeline(pipeline: ResolvedPipeline, input: ContextInput, logger: Logger, options?: RunOptions): Promise<RunResult>;
package/dist/runner.js ADDED
@@ -0,0 +1,133 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createContext, DONE } from "./context.js";
3
+ import { failure, isKestrelError } from "./errors.js";
4
+ import { isErr } from "./result.js";
5
+ import { validateSchema } from "./schema.js";
6
+ export function createRunTracker() {
7
+ let active = 0;
8
+ const idle = new Set();
9
+ return {
10
+ async track(fn) {
11
+ active++;
12
+ try {
13
+ return await fn();
14
+ }
15
+ finally {
16
+ active--;
17
+ if (active === 0)
18
+ for (const resolve of [...idle])
19
+ resolve();
20
+ }
21
+ },
22
+ active: () => active,
23
+ drain(timeoutMs) {
24
+ if (active === 0)
25
+ return Promise.resolve(true);
26
+ return new Promise((resolve) => {
27
+ const onIdle = () => {
28
+ clearTimeout(timer);
29
+ idle.delete(onIdle);
30
+ resolve(true);
31
+ };
32
+ const timer = setTimeout(() => {
33
+ idle.delete(onIdle);
34
+ resolve(false);
35
+ }, Math.max(0, timeoutMs));
36
+ timer.unref();
37
+ idle.add(onIdle);
38
+ });
39
+ },
40
+ };
41
+ }
42
+ function isResult(value) {
43
+ if (typeof value !== "object" || value === null)
44
+ return false;
45
+ const result = value;
46
+ if (result.ok === true)
47
+ return "value" in result;
48
+ return result.ok === false && isKestrelError(result.error);
49
+ }
50
+ function isContext(value) {
51
+ if (typeof value !== "object" || value === null)
52
+ return false;
53
+ const ctx = value;
54
+ const record = (v) => typeof v === "object" && v !== null;
55
+ return record(ctx.trigger) && record(ctx.payload) && record(ctx.params) && record(ctx.headers) && Array.isArray(ctx.files) && typeof ctx.fail === "function" && typeof ctx.done === "function";
56
+ }
57
+ // Query parameters are merged into the payload by the HTTP trigger, so an `additionalProperties:
58
+ // false` input schema has to accept them too.
59
+ function payloadSchema(description) {
60
+ const input = description.input;
61
+ if (input === undefined)
62
+ return undefined;
63
+ if (description.query === undefined)
64
+ return input;
65
+ const properties = typeof input.properties === "object" && input.properties !== null ? input.properties : {};
66
+ return { ...input, properties: { ...properties, ...description.query } };
67
+ }
68
+ export async function runPipeline(pipeline, input, logger, options = { validateInput: false }) {
69
+ const runId = randomUUID();
70
+ let ctx = createContext(input, runId);
71
+ const requestId = ctx.requestId;
72
+ const trace = (result) => (requestId === undefined ? result : { ...result, requestId });
73
+ for (const step of pipeline.steps) {
74
+ const started = performance.now();
75
+ const log = (outcome) => logger.step({
76
+ runId,
77
+ ...(input.parentRunId === undefined ? {} : { parentRunId: input.parentRunId }),
78
+ pipeline: pipeline.name,
79
+ step: step.name,
80
+ ms: Math.round((performance.now() - started) * 100) / 100,
81
+ outcome,
82
+ });
83
+ if (options.validateInput) {
84
+ const schema = payloadSchema(step.description);
85
+ const problem = schema === undefined ? undefined : validateSchema(schema, ctx.payload)[0];
86
+ if (problem !== undefined) {
87
+ log("error");
88
+ const error = failure("INTERNAL", `dev: payload of step "${step.name}" in pipeline "${pipeline.name}" does not match describe().input at ${problem.path}: ${problem.message}`);
89
+ return trace({ runId, status: error.status, error: error.message, code: error.code, retryable: error.retryable, step: step.name });
90
+ }
91
+ }
92
+ try {
93
+ const out = await step.fn(ctx);
94
+ if (!isResult(out)) {
95
+ throw new Error(`step "${step.name}" returned ${typeof out} instead of a Result<Context>`);
96
+ }
97
+ if (isErr(out)) {
98
+ const error = out.error;
99
+ log(`fail(${error.code})`);
100
+ if (error.cause !== undefined) {
101
+ const cause = error.cause instanceof Error ? { stack: error.cause.stack ?? error.cause.message } : { cause: error.cause };
102
+ logger.error(`pipeline "${pipeline.name}" step "${step.name}" failed with a cause`, { runId, code: error.code, ...cause });
103
+ }
104
+ return trace({
105
+ runId,
106
+ status: error.status,
107
+ error: error.message,
108
+ code: error.code,
109
+ retryable: error.retryable,
110
+ step: step.name,
111
+ ...(error.details === undefined ? {} : { details: error.details }),
112
+ });
113
+ }
114
+ if (!isContext(out.value)) {
115
+ throw new Error(`step "${step.name}" returned ${typeof out.value} instead of a Result<Context>`);
116
+ }
117
+ ctx = Object.freeze(out.value);
118
+ log("ok");
119
+ if (Reflect.get(ctx, DONE) === true)
120
+ return trace({ runId, status: 200, result: ctx.result });
121
+ }
122
+ catch (thrown) {
123
+ log("error");
124
+ const error = thrown instanceof Error ? thrown : new Error(String(thrown));
125
+ logger.error(`pipeline "${pipeline.name}" step "${step.name}" threw`, { runId, stack: error.stack ?? error.message });
126
+ return trace({ runId, status: 500, error: `${pipeline.name}/${step.name}: ${error.message}`, code: "INTERNAL", retryable: false, step: step.name });
127
+ }
128
+ }
129
+ const done = { runId, status: 200 };
130
+ if (ctx.result !== undefined)
131
+ done.result = ctx.result;
132
+ return trace(done);
133
+ }
@@ -0,0 +1,7 @@
1
+ import type { JsonSchema } from "./defineModule.ts";
2
+ export interface SchemaProblem {
3
+ path: string;
4
+ message: string;
5
+ }
6
+ /** The subset of JSON Schema the shipped `describe().input` blocks use; unknown keywords are ignored. */
7
+ export declare function validateSchema(schema: JsonSchema, value: unknown): SchemaProblem[];
package/dist/schema.js ADDED
@@ -0,0 +1,142 @@
1
+ const ROOT = "$";
2
+ function child(path, key) {
3
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
4
+ }
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function subSchema(value) {
9
+ return isRecord(value) ? value : undefined;
10
+ }
11
+ function jsonType(value) {
12
+ if (value === null)
13
+ return "null";
14
+ if (Array.isArray(value))
15
+ return "array";
16
+ return typeof value;
17
+ }
18
+ function matchesType(value, type) {
19
+ if (type === "integer")
20
+ return typeof value === "number" && Number.isInteger(value);
21
+ if (type === "number")
22
+ return typeof value === "number" && Number.isFinite(value);
23
+ if (type === "null")
24
+ return value === null;
25
+ if (type === "array")
26
+ return Array.isArray(value);
27
+ if (type === "object")
28
+ return isRecord(value);
29
+ return jsonType(value) === type;
30
+ }
31
+ function typeNames(type) {
32
+ if (typeof type === "string")
33
+ return [type];
34
+ if (Array.isArray(type))
35
+ return type.filter((t) => typeof t === "string");
36
+ return [];
37
+ }
38
+ function deepEqual(a, b) {
39
+ if (a === b)
40
+ return true;
41
+ if (Array.isArray(a) && Array.isArray(b))
42
+ return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
43
+ if (isRecord(a) && isRecord(b)) {
44
+ const keys = Object.keys(a);
45
+ return keys.length === Object.keys(b).length && keys.every((k) => Object.hasOwn(b, k) && deepEqual(a[k], b[k]));
46
+ }
47
+ return false;
48
+ }
49
+ function present(object, key) {
50
+ return Object.hasOwn(object, key) && object[key] !== undefined;
51
+ }
52
+ function check(schema, value, path, problems) {
53
+ const types = typeNames(schema.type);
54
+ if (types.length > 0 && !types.some((t) => matchesType(value, t))) {
55
+ problems.push({ path, message: `expected ${types.join(" or ")}, got ${jsonType(value)}` });
56
+ return;
57
+ }
58
+ if (Array.isArray(schema.enum) && !schema.enum.some((allowed) => deepEqual(allowed, value))) {
59
+ problems.push({ path, message: `expected one of ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}` });
60
+ }
61
+ if (Object.hasOwn(schema, "const") && !deepEqual(schema.const, value)) {
62
+ problems.push({ path, message: `expected ${JSON.stringify(schema.const)}` });
63
+ }
64
+ if (typeof value === "string") {
65
+ const length = [...value].length;
66
+ if (typeof schema.pattern === "string" && !new RegExp(schema.pattern).test(value)) {
67
+ problems.push({ path, message: `does not match ${schema.pattern}` });
68
+ }
69
+ if (typeof schema.minLength === "number" && length < schema.minLength) {
70
+ problems.push({ path, message: `shorter than minLength ${schema.minLength}` });
71
+ }
72
+ if (typeof schema.maxLength === "number" && length > schema.maxLength) {
73
+ problems.push({ path, message: `longer than maxLength ${schema.maxLength}` });
74
+ }
75
+ }
76
+ if (typeof value === "number") {
77
+ if (typeof schema.minimum === "number" && value < schema.minimum)
78
+ problems.push({ path, message: `below minimum ${schema.minimum}` });
79
+ if (typeof schema.maximum === "number" && value > schema.maximum)
80
+ problems.push({ path, message: `above maximum ${schema.maximum}` });
81
+ }
82
+ if (Array.isArray(value)) {
83
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) {
84
+ problems.push({ path, message: `fewer than minItems ${schema.minItems}` });
85
+ }
86
+ const items = schema.items;
87
+ if (Array.isArray(items)) {
88
+ items.forEach((item, i) => {
89
+ const sub = subSchema(item);
90
+ if (sub && i < value.length)
91
+ check(sub, value[i], `${path}[${i}]`, problems);
92
+ });
93
+ }
94
+ else {
95
+ const sub = subSchema(items);
96
+ if (sub)
97
+ value.forEach((item, i) => check(sub, item, `${path}[${i}]`, problems));
98
+ }
99
+ }
100
+ if (isRecord(value)) {
101
+ const properties = isRecord(schema.properties) ? schema.properties : {};
102
+ if (Array.isArray(schema.required)) {
103
+ for (const key of schema.required) {
104
+ if (typeof key === "string" && !present(value, key))
105
+ problems.push({ path: child(path, key), message: "is required" });
106
+ }
107
+ }
108
+ for (const [key, raw] of Object.entries(properties)) {
109
+ const sub = subSchema(raw);
110
+ if (sub && present(value, key))
111
+ check(sub, value[key], child(path, key), problems);
112
+ }
113
+ if (schema.additionalProperties === false) {
114
+ for (const key of Object.keys(value)) {
115
+ if (!Object.hasOwn(properties, key))
116
+ problems.push({ path: child(path, key), message: "is not allowed by additionalProperties: false" });
117
+ }
118
+ }
119
+ }
120
+ if (Array.isArray(schema.oneOf)) {
121
+ const matched = schema.oneOf.filter((raw) => {
122
+ const sub = subSchema(raw);
123
+ return sub !== undefined && validateSchema(sub, value).length === 0;
124
+ }).length;
125
+ if (matched !== 1)
126
+ problems.push({ path, message: `matches ${String(matched)} of ${String(schema.oneOf.length)} oneOf schemas, expected exactly 1` });
127
+ }
128
+ if (Array.isArray(schema.anyOf)) {
129
+ const matched = schema.anyOf.some((raw) => {
130
+ const sub = subSchema(raw);
131
+ return sub !== undefined && validateSchema(sub, value).length === 0;
132
+ });
133
+ if (!matched)
134
+ problems.push({ path, message: `matches none of the ${String(schema.anyOf.length)} anyOf schemas` });
135
+ }
136
+ }
137
+ /** The subset of JSON Schema the shipped `describe().input` blocks use; unknown keywords are ignored. */
138
+ export function validateSchema(schema, value) {
139
+ const problems = [];
140
+ check(schema, value, ROOT, problems);
141
+ return problems;
142
+ }
package/dist/sort.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { ModuleDefinition } from "./defineModule.ts";
2
+ export declare function sortModules(modules: readonly ModuleDefinition[]): ModuleDefinition[];
package/dist/sort.js ADDED
@@ -0,0 +1,50 @@
1
+ import { KestrelBootError } from "./errors.js";
2
+ export function sortModules(modules) {
3
+ const providerOf = new Map();
4
+ for (const m of modules) {
5
+ for (const contract of m.provides) {
6
+ const other = providerOf.get(contract.name);
7
+ if (other)
8
+ throw new KestrelBootError(m.name, `contract "${contract.name}" is already provided by ${other.name}`);
9
+ providerOf.set(contract.name, m);
10
+ }
11
+ }
12
+ const dependsOn = new Map();
13
+ for (const m of modules) {
14
+ const deps = [];
15
+ for (const contract of m.requires) {
16
+ const provider = providerOf.get(contract.name);
17
+ if (!provider)
18
+ throw new KestrelBootError(m.name, `requires "${contract.name}" but no active module provides it`);
19
+ if (provider === m)
20
+ throw new KestrelBootError(m.name, `requires "${contract.name}" which it provides itself`);
21
+ deps.push(provider);
22
+ }
23
+ for (const contract of m.optional ?? []) {
24
+ const provider = providerOf.get(contract.name);
25
+ if (!provider)
26
+ continue;
27
+ if (provider === m)
28
+ throw new KestrelBootError(m.name, `optional "${contract.name}" which it provides itself`);
29
+ deps.push(provider);
30
+ }
31
+ dependsOn.set(m, deps);
32
+ }
33
+ const sorted = [];
34
+ const state = new Map();
35
+ const visit = (m, path) => {
36
+ const s = state.get(m);
37
+ if (s === "done")
38
+ return;
39
+ if (s === "visiting")
40
+ throw new KestrelBootError(m.name, `dependency cycle: ${[...path, m.name].join(" -> ")}`);
41
+ state.set(m, "visiting");
42
+ for (const dep of dependsOn.get(m) ?? [])
43
+ visit(dep, [...path, m.name]);
44
+ state.set(m, "done");
45
+ sorted.push(m);
46
+ };
47
+ for (const m of modules)
48
+ visit(m, []);
49
+ return sorted;
50
+ }
@@ -0,0 +1,9 @@
1
+ import type { ContextInput, Step, StepFactory } from "../context.ts";
2
+ import type { PipelineDefinition } from "../definePipeline.ts";
3
+ import { type Logger } from "../logger.ts";
4
+ import { type RunResult } from "../runner.ts";
5
+ export interface RunPipelineOptions {
6
+ steps: Record<string, Step | StepFactory>;
7
+ logger?: Logger;
8
+ }
9
+ export declare function runPipeline(pipeline: PipelineDefinition, input: Partial<ContextInput>, options: RunPipelineOptions): Promise<RunResult>;