@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.
- package/README.md +26 -201
- package/dist/boot.d.ts +45 -0
- package/dist/boot.js +284 -0
- package/dist/cast.d.ts +3 -0
- package/dist/cast.js +6 -0
- package/dist/catalogue.d.ts +11 -0
- package/dist/catalogue.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +55 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.js +61 -0
- package/dist/dataflow.d.ts +3 -0
- package/dist/dataflow.js +65 -0
- package/dist/defineConfig.d.ts +162 -0
- package/dist/defineConfig.js +42 -0
- package/dist/defineContract.d.ts +12 -0
- package/dist/defineContract.js +16 -0
- package/dist/defineModule.d.ts +75 -0
- package/dist/defineModule.js +9 -0
- package/dist/definePipeline.d.ts +7 -0
- package/dist/definePipeline.js +11 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/load.d.ts +7 -0
- package/dist/load.js +79 -0
- package/dist/logger.d.ts +18 -0
- package/dist/logger.js +36 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +87 -0
- package/dist/result.d.ts +19 -0
- package/dist/result.js +19 -0
- package/dist/runner.d.ts +29 -0
- package/dist/runner.js +133 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +142 -0
- package/dist/sort.d.ts +2 -0
- package/dist/sort.js +50 -0
- package/dist/testing/runPipeline.d.ts +9 -0
- package/dist/testing/runPipeline.js +22 -0
- package/dist/triggers/allowlist.d.ts +9 -0
- package/dist/triggers/allowlist.js +49 -0
- package/dist/triggers/cron.d.ts +10 -0
- package/dist/triggers/cron.js +44 -0
- package/dist/triggers/cronMatch.d.ts +10 -0
- package/dist/triggers/cronMatch.js +68 -0
- package/dist/triggers/http.d.ts +59 -0
- package/dist/triggers/http.js +300 -0
- package/package.json +25 -151
- package/NOTICE +0 -12
- package/kestrel.config.ts +0 -28
- package/layers/access/nuxt.config.ts +0 -5
- package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
- package/layers/access/server/middleware/access-guard.ts +0 -32
- package/layers/access/server/types.d.ts +0 -7
- package/layers/admin/app/components/AdminAccount.vue +0 -212
- package/layers/admin/app/components/AdminNav.vue +0 -44
- package/layers/admin/app/components/BlockFields.vue +0 -67
- package/layers/admin/app/components/BlockPreview.vue +0 -413
- package/layers/admin/app/components/BlockTree.vue +0 -433
- package/layers/admin/app/components/BlocksBody.vue +0 -163
- package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
- package/layers/admin/app/components/CollectionEditor.vue +0 -324
- package/layers/admin/app/components/CollectionList.vue +0 -429
- package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
- package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
- package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
- package/layers/admin/app/components/CollectionListPager.vue +0 -70
- package/layers/admin/app/components/CollectionListTable.vue +0 -347
- package/layers/admin/app/components/EditorStatus.vue +0 -211
- package/layers/admin/app/components/EditorUnsupported.vue +0 -28
- package/layers/admin/app/components/FieldsBody.vue +0 -23
- package/layers/admin/app/components/LocaleBar.vue +0 -107
- package/layers/admin/app/components/PageFields.vue +0 -129
- package/layers/admin/app/components/PageFieldsPane.vue +0 -39
- package/layers/admin/app/components/SeoFields.vue +0 -210
- package/layers/admin/app/components/SingletonEditor.vue +0 -93
- package/layers/admin/app/composables/useAuth.ts +0 -68
- package/layers/admin/app/composables/useBlockTree.ts +0 -95
- package/layers/admin/app/composables/useBlocks.ts +0 -7
- package/layers/admin/app/composables/useCachedResource.ts +0 -35
- package/layers/admin/app/composables/useCollectionOps.ts +0 -102
- package/layers/admin/app/composables/useCollections.ts +0 -6
- package/layers/admin/app/composables/useContentLocales.ts +0 -23
- package/layers/admin/app/composables/useEditForm.ts +0 -402
- package/layers/admin/app/composables/useLinkResolver.ts +0 -40
- package/layers/admin/app/composables/useListBatchActions.ts +0 -78
- package/layers/admin/app/composables/useListColumns.ts +0 -53
- package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
- package/layers/admin/app/composables/useListRows.ts +0 -75
- package/layers/admin/app/composables/useListSelection.ts +0 -28
- package/layers/admin/app/composables/useListUrlState.ts +0 -53
- package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
- package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
- package/layers/admin/app/composables/usePublishStatus.ts +0 -107
- package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
- package/layers/admin/app/composables/useTheme.ts +0 -36
- package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
- package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
- package/layers/admin/app/layouts/admin.vue +0 -266
- package/layers/admin/app/middleware/admin-auth.ts +0 -4
- package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
- package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
- package/layers/admin/app/pages/admin/index.vue +0 -118
- package/layers/admin/app/pages/admin/login.vue +0 -68
- package/layers/admin/app/pages/admin/media.vue +0 -28
- package/layers/admin/app/pages/admin/references.vue +0 -146
- package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
- package/layers/admin/app/plugins/reauth.client.ts +0 -20
- package/layers/admin/app/utils/admin-style-guard.ts +0 -20
- package/layers/admin/app/utils/block-tree.ts +0 -214
- package/layers/admin/app/utils/collection-ops.ts +0 -69
- package/layers/admin/app/utils/dead-refs.ts +0 -30
- package/layers/admin/app/utils/edit-form.ts +0 -267
- package/layers/admin/app/utils/editor-expose.ts +0 -36
- package/layers/admin/app/utils/editor-form-context.ts +0 -82
- package/layers/admin/app/utils/editor-registry.ts +0 -20
- package/layers/admin/app/utils/field-keys.ts +0 -11
- package/layers/admin/app/utils/list-cell.ts +0 -46
- package/layers/admin/app/utils/list-columns.ts +0 -142
- package/layers/admin/app/utils/list-query.ts +0 -96
- package/layers/admin/app/utils/nav-active.ts +0 -9
- package/layers/admin/app/utils/populate-blocks.ts +0 -288
- package/layers/admin/app/utils/preview-channel.ts +0 -62
- package/layers/admin/app/utils/preview-viewport.ts +0 -80
- package/layers/admin/app/utils/reauth.ts +0 -67
- package/layers/admin/app/utils/record-title.ts +0 -15
- package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
- package/layers/admin/app/utils/safe-redirect.ts +0 -12
- package/layers/admin/nuxt.config.ts +0 -20
- package/layers/auth/nuxt.config.ts +0 -1
- package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
- package/layers/collections/nuxt.config.ts +0 -1
- package/layers/collections/server/plugins/01.register.ts +0 -29
- package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
- package/layers/core/app/composables/layouts.ts +0 -5
- package/layers/core/app/utils/field-factories.ts +0 -129
- package/layers/core/app/utils/layouts.ts +0 -38
- package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
- package/layers/core/modules/auto-discovery/index.ts +0 -112
- package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
- package/layers/core/modules/auto-discovery/scan.ts +0 -170
- package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
- package/layers/core/modules/component-namespace/index.ts +0 -44
- package/layers/core/modules/component-namespace/shared.ts +0 -24
- package/layers/core/modules/kestrel/app-shell.ts +0 -55
- package/layers/core/modules/kestrel/auto-imports.ts +0 -44
- package/layers/core/modules/kestrel/index.ts +0 -144
- package/layers/core/modules/plugin-order/index.ts +0 -69
- package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
- package/layers/core/nuxt.config.ts +0 -10
- package/layers/core/server/api/[...path].ts +0 -98
- package/layers/core/server/plugins/00.config.ts +0 -12
- package/layers/core/server/plugins/00.migrate.ts +0 -12
- package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
- package/layers/core/server/plugins/02.schema-sync.ts +0 -58
- package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
- package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
- package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
- package/layers/core/server/tasks/db/migrate-module.ts +0 -83
- package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
- package/layers/core/server/tasks/db/migrate.ts +0 -50
- package/layers/core/server/utils/server-config.ts +0 -81
- package/layers/fields/nuxt.config.ts +0 -1
- package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
- package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
- package/layers/media/app/components/KestrelImg.vue +0 -94
- package/layers/media/app/components/MediaConflictDialog.vue +0 -35
- package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
- package/layers/media/app/components/MediaGrid.vue +0 -113
- package/layers/media/app/components/MediaImage.vue +0 -70
- package/layers/media/app/components/MediaLibrary.vue +0 -456
- package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
- package/layers/media/app/components/MediaPathBar.vue +0 -163
- package/layers/media/app/components/MediaPicker.vue +0 -19
- package/layers/media/app/components/MediaRenameDialog.vue +0 -32
- package/layers/media/app/components/MediaTable.vue +0 -75
- package/layers/media/app/components/MediaThumb.vue +0 -25
- package/layers/media/app/components/MediaToolbar.vue +0 -90
- package/layers/media/app/components/MediaUploadDialog.vue +0 -87
- package/layers/media/app/components/MediaViewer.vue +0 -137
- package/layers/media/app/components/field/Media.vue +0 -263
- package/layers/media/app/composables/useBlurUp.ts +0 -55
- package/layers/media/app/composables/useMediaClipboard.ts +0 -14
- package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
- package/layers/media/app/composables/useMediaDnd.ts +0 -56
- package/layers/media/app/composables/useMediaLibrary.ts +0 -152
- package/layers/media/app/composables/useMediaOps.ts +0 -50
- package/layers/media/app/composables/useMediaRelocate.ts +0 -39
- package/layers/media/app/composables/useMediaResolver.ts +0 -50
- package/layers/media/app/composables/useMediaUpload.ts +0 -93
- package/layers/media/app/composables/useMediaVariant.ts +0 -34
- package/layers/media/app/plugins/field-media.client.ts +0 -6
- package/layers/media/app/utils/ai-disclosure.ts +0 -19
- package/layers/media/app/utils/dnd.ts +0 -69
- package/layers/media/app/utils/library.ts +0 -81
- package/layers/media/app/utils/ops.ts +0 -60
- package/layers/media/app/utils/picture-model.ts +0 -125
- package/layers/media/app/utils/thumbhash.ts +0 -19
- package/layers/media/app/utils/variant-name.ts +0 -20
- package/layers/media/nuxt.config.ts +0 -7
- package/layers/media/server/middleware/ondemand-variants.ts +0 -38
- package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
- package/layers/media/server/plugins/02.register-media.ts +0 -22
- package/layers/media/server/plugins/04.variant-capture.ts +0 -27
- package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
- package/layers/media/server/tasks/media/backfill.ts +0 -17
- package/layers/public/app/app.vue +0 -5
- package/layers/public/app/components/BlockRenderer.vue +0 -56
- package/layers/public/app/components/KestrelLink.vue +0 -26
- package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
- package/layers/public/app/composables/public-page.ts +0 -24
- package/layers/public/app/error.vue +0 -12
- package/layers/public/app/layouts/default.vue +0 -5
- package/layers/public/app/pages/[...slug].vue +0 -239
- package/layers/public/app/pages/__kestrel/preview.vue +0 -46
- package/layers/public/app/utils/block-edit-context.ts +0 -16
- package/layers/public/app/utils/json-ld.ts +0 -139
- package/layers/public/app/utils/link-href.ts +0 -46
- package/layers/public/app/utils/page-head.ts +0 -84
- package/layers/public/app/utils/page-layout.ts +0 -18
- package/layers/public/app/utils/preview-protocol.ts +0 -117
- package/layers/public/app/utils/public-route.ts +0 -46
- package/layers/public/app/utils/site-head.ts +0 -43
- package/layers/public/modules/deploy-output/index.ts +0 -80
- package/layers/public/modules/prerender-routes/discover.ts +0 -37
- package/layers/public/modules/prerender-routes/index.ts +0 -88
- package/layers/public/modules/prerender-routes/routes.ts +0 -16
- package/layers/public/modules/prune-media/index.ts +0 -48
- package/layers/public/modules/prune-media/prune-media.ts +0 -81
- package/layers/public/nuxt.config.ts +0 -14
- package/layers/public/server/delivery-live/serve.ts +0 -111
- package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
- package/layers/public/server/pipelines/index.ts +0 -23
- package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
- package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
- package/layers/public/server/plugins/02.register-links.ts +0 -16
- package/layers/public/server/plugins/03.redirects.ts +0 -55
- package/layers/public/server/plugins/05.plan-publish.ts +0 -5
- package/layers/public/server/plugins/zz.publish.ts +0 -127
- package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
- package/layers/public/server/routes/llms.txt.get.ts +0 -66
- package/layers/public/server/routes/redirects.json.get.ts +0 -58
- package/layers/public/server/routes/robots.txt.get.ts +0 -11
- package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
- package/layers/public/server/tasks/publish/run.ts +0 -32
- package/layers/public/server/utils/publish/render-live.ts +0 -26
- package/layers/ui/app/assets/scss/_base.scss +0 -16
- package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
- package/layers/ui/app/assets/scss/_mixins.scss +0 -50
- package/layers/ui/app/assets/scss/_reset.scss +0 -65
- package/layers/ui/app/assets/scss/_tokens.scss +0 -183
- package/layers/ui/app/assets/scss/main.scss +0 -4
- package/layers/ui/app/components/field/Boolean.vue +0 -30
- package/layers/ui/app/components/field/Choice.vue +0 -73
- package/layers/ui/app/components/field/Datetime.vue +0 -98
- package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
- package/layers/ui/app/components/field/Json.vue +0 -62
- package/layers/ui/app/components/field/Layout.vue +0 -138
- package/layers/ui/app/components/field/Link.vue +0 -112
- package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
- package/layers/ui/app/components/field/Number.vue +0 -30
- package/layers/ui/app/components/field/Relation.vue +0 -49
- package/layers/ui/app/components/field/Renderer.vue +0 -30
- package/layers/ui/app/components/field/Repeater.vue +0 -460
- package/layers/ui/app/components/field/Richtext.vue +0 -26
- package/layers/ui/app/components/field/Slug.vue +0 -65
- package/layers/ui/app/components/field/Text.vue +0 -35
- package/layers/ui/app/components/field/Unsupported.vue +0 -34
- package/layers/ui/app/components/ui/Alert.vue +0 -50
- package/layers/ui/app/components/ui/Brand.vue +0 -22
- package/layers/ui/app/components/ui/Button.vue +0 -116
- package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
- package/layers/ui/app/components/ui/Checkbox.vue +0 -53
- package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
- package/layers/ui/app/components/ui/Combobox.vue +0 -345
- package/layers/ui/app/components/ui/DatePicker.vue +0 -92
- package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
- package/layers/ui/app/components/ui/Dialog.vue +0 -107
- package/layers/ui/app/components/ui/EmptyState.vue +0 -46
- package/layers/ui/app/components/ui/Field.vue +0 -58
- package/layers/ui/app/components/ui/Fieldset.vue +0 -65
- package/layers/ui/app/components/ui/Icon.vue +0 -44
- package/layers/ui/app/components/ui/Menu.vue +0 -54
- package/layers/ui/app/components/ui/NumberInput.vue +0 -175
- package/layers/ui/app/components/ui/Richtext.vue +0 -141
- package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
- package/layers/ui/app/components/ui/Select.vue +0 -52
- package/layers/ui/app/components/ui/TextInput.vue +0 -111
- package/layers/ui/app/components/ui/Textarea.vue +0 -39
- package/layers/ui/app/components/ui/TimeInput.vue +0 -18
- package/layers/ui/app/components/ui/Toasts.vue +0 -103
- package/layers/ui/app/components/ui/Tooltip.vue +0 -45
- package/layers/ui/app/composables/useAdminLang.ts +0 -8
- package/layers/ui/app/composables/useDragReorder.ts +0 -41
- package/layers/ui/app/composables/useEchoGuard.ts +0 -28
- package/layers/ui/app/composables/useLinkField.ts +0 -70
- package/layers/ui/app/composables/useRecordOptions.ts +0 -84
- package/layers/ui/app/composables/useRepeater.ts +0 -99
- package/layers/ui/app/composables/useT.ts +0 -32
- package/layers/ui/app/composables/useToast.ts +0 -46
- package/layers/ui/app/i18n/de.ts +0 -432
- package/layers/ui/app/i18n/en.ts +0 -434
- package/layers/ui/app/utils/boolean-field.ts +0 -10
- package/layers/ui/app/utils/field-component.ts +0 -22
- package/layers/ui/app/utils/field-empty.ts +0 -21
- package/layers/ui/app/utils/field-registry.ts +0 -36
- package/layers/ui/app/utils/humanize.ts +0 -14
- package/layers/ui/app/utils/icons.ts +0 -143
- package/layers/ui/app/utils/iso-date.ts +0 -18
- package/layers/ui/app/utils/localized.ts +0 -13
- package/layers/ui/app/utils/record-context.ts +0 -15
- package/layers/ui/app/utils/reorder.ts +0 -8
- package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
- package/layers/ui/app/utils/useFieldA11y.ts +0 -40
- package/layers/ui/nuxt.config.ts +0 -9
- package/nuxt.config.ts +0 -108
- package/scripts/consumer-template-ci.mjs +0 -160
- package/scripts/copy-create-payload.mjs +0 -49
- package/scripts/coverage-all.mjs +0 -163
- package/scripts/dashboard.mjs +0 -89
- package/scripts/hash-password.mjs +0 -29
- package/scripts/kestrel.mjs +0 -240
- package/scripts/lib/cli.mjs +0 -129
- package/scripts/lib/coverage-model.mjs +0 -51
- package/scripts/lib/password.mjs +0 -19
- package/scripts/lib/scaffold.mjs +0 -205
- package/scripts/port-graph.mjs +0 -350
- package/scripts/typecheck.mjs +0 -62
- package/templates/starter/README.md +0 -65
- package/templates/starter/_env.example +0 -17
- package/templates/starter/_gitignore +0 -26
- package/templates/starter/_package.json +0 -22
- package/templates/starter/app/app.vue +0 -7
- package/templates/starter/app/blocks/Prose.vue +0 -13
- package/templates/starter/app/layouts/default.vue +0 -6
- package/templates/starter/nuxt.config.ts +0 -23
- package/templates/starter/pnpm-workspace.yaml +0 -8
- package/templates/starter/tsconfig.json +0 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type CoreCode, type KestrelError } from "./errors.ts";
|
|
2
|
+
import { type Err, type Ok, type Result } from "./result.ts";
|
|
3
|
+
export type TriggerKind = "http" | "event" | "cron";
|
|
4
|
+
export interface Trigger {
|
|
5
|
+
kind: TriggerKind;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
declare global {
|
|
9
|
+
namespace Kestrel {
|
|
10
|
+
interface ContextExtensions {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export interface UploadedFile {
|
|
15
|
+
field: string;
|
|
16
|
+
filename: string;
|
|
17
|
+
contentType: string;
|
|
18
|
+
data: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
export interface BinaryResult {
|
|
21
|
+
binary: true;
|
|
22
|
+
data: Uint8Array;
|
|
23
|
+
contentType: string;
|
|
24
|
+
filename?: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
export declare function binaryResult(data: Uint8Array, contentType: string, filename?: string): BinaryResult;
|
|
28
|
+
export declare function isBinaryResult(value: unknown): value is BinaryResult;
|
|
29
|
+
export interface Context extends Kestrel.ContextExtensions {
|
|
30
|
+
readonly runId: string;
|
|
31
|
+
requestId?: string;
|
|
32
|
+
trigger: Trigger;
|
|
33
|
+
payload: Record<string, unknown>;
|
|
34
|
+
params: Record<string, string>;
|
|
35
|
+
headers: Record<string, string>;
|
|
36
|
+
files: UploadedFile[];
|
|
37
|
+
ip?: string;
|
|
38
|
+
result?: unknown;
|
|
39
|
+
fail(code: CoreCode, message: string, details?: Record<string, unknown>): Err<KestrelError>;
|
|
40
|
+
fail(error: KestrelError): Err<KestrelError>;
|
|
41
|
+
done(result: unknown): Ok<Context>;
|
|
42
|
+
}
|
|
43
|
+
export type StepResult = Result<Context, KestrelError>;
|
|
44
|
+
export type Step = (ctx: Context) => Promise<StepResult>;
|
|
45
|
+
export type StepFactory = (arg: string) => Step;
|
|
46
|
+
export type StepMap = Record<string, Step | StepFactory>;
|
|
47
|
+
export declare const DONE: unique symbol;
|
|
48
|
+
/** A Step and a StepFactory are both 1-ary functions; only this brand tells them apart at boot. */
|
|
49
|
+
export declare function stepFactory<F extends StepFactory>(factory: F): F;
|
|
50
|
+
export declare function isStepFactory(fn: unknown): fn is StepFactory;
|
|
51
|
+
export interface ContextInput {
|
|
52
|
+
trigger: Trigger;
|
|
53
|
+
parentRunId?: string;
|
|
54
|
+
payload?: Record<string, unknown>;
|
|
55
|
+
params?: Record<string, string>;
|
|
56
|
+
headers?: Record<string, string>;
|
|
57
|
+
files?: UploadedFile[];
|
|
58
|
+
ip?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function first(value: unknown): string | undefined;
|
|
61
|
+
export declare function requestIdOf(value: unknown): string | undefined;
|
|
62
|
+
export declare function createContext(input: ContextInput, runId?: string): Context;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { failure } from "./errors.js";
|
|
3
|
+
import { err, ok } from "./result.js";
|
|
4
|
+
export function binaryResult(data, contentType, filename) {
|
|
5
|
+
const result = { binary: true, data, contentType };
|
|
6
|
+
if (filename !== undefined)
|
|
7
|
+
result.filename = filename;
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
export function isBinaryResult(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && value.binary === true && value.data instanceof Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
export const DONE = Symbol.for("kestrel.done");
|
|
14
|
+
const FACTORY = Symbol.for("kestrel.stepFactory");
|
|
15
|
+
/** A Step and a StepFactory are both 1-ary functions; only this brand tells them apart at boot. */
|
|
16
|
+
export function stepFactory(factory) {
|
|
17
|
+
Object.defineProperty(factory, FACTORY, { value: true });
|
|
18
|
+
return factory;
|
|
19
|
+
}
|
|
20
|
+
export function isStepFactory(fn) {
|
|
21
|
+
return typeof fn === "function" && Reflect.get(fn, FACTORY) === true;
|
|
22
|
+
}
|
|
23
|
+
export function first(value) {
|
|
24
|
+
if (typeof value === "string")
|
|
25
|
+
return value;
|
|
26
|
+
if (Array.isArray(value) && typeof value[0] === "string")
|
|
27
|
+
return value[0];
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** The value is echoed into a response header, so anything but short printable ASCII is dropped. */
|
|
31
|
+
const REQUEST_ID = /^[\x20-\x7e]{1,200}$/;
|
|
32
|
+
export function requestIdOf(value) {
|
|
33
|
+
const id = first(value);
|
|
34
|
+
return id !== undefined && REQUEST_ID.test(id) ? id : undefined;
|
|
35
|
+
}
|
|
36
|
+
export function createContext(input, runId = randomUUID()) {
|
|
37
|
+
const ctx = {
|
|
38
|
+
runId,
|
|
39
|
+
trigger: input.trigger,
|
|
40
|
+
payload: input.payload ?? {},
|
|
41
|
+
params: input.params ?? {},
|
|
42
|
+
headers: input.headers ?? {},
|
|
43
|
+
files: input.files ?? [],
|
|
44
|
+
fail(codeOrError, message, details) {
|
|
45
|
+
if (typeof codeOrError !== "string")
|
|
46
|
+
return err(codeOrError);
|
|
47
|
+
return err(failure(codeOrError, message ?? "", details === undefined ? {} : { details }));
|
|
48
|
+
},
|
|
49
|
+
done(result) {
|
|
50
|
+
const next = { ...this, result };
|
|
51
|
+
Object.defineProperty(next, DONE, { value: true });
|
|
52
|
+
return ok(Object.freeze(next));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
if (input.ip !== undefined)
|
|
56
|
+
ctx.ip = input.ip;
|
|
57
|
+
const requestId = requestIdOf(input.headers?.["x-request-id"]);
|
|
58
|
+
if (requestId !== undefined)
|
|
59
|
+
ctx.requestId = requestId;
|
|
60
|
+
return Object.freeze(ctx);
|
|
61
|
+
}
|
package/dist/dataflow.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { KestrelBootError } from "./errors.js";
|
|
2
|
+
const CONTEXT_KEYS = ["payload", "headers", "files", "ip", "params"];
|
|
3
|
+
function routeParams(route) {
|
|
4
|
+
const names = new Set();
|
|
5
|
+
for (const segment of route.segments) {
|
|
6
|
+
if (segment.startsWith(":") || segment.startsWith("*"))
|
|
7
|
+
names.add(segment.slice(1));
|
|
8
|
+
}
|
|
9
|
+
return names;
|
|
10
|
+
}
|
|
11
|
+
/** Only a param every route binds is guaranteed to be there, whichever route served the run. */
|
|
12
|
+
function commonParams(routes) {
|
|
13
|
+
const [first, ...rest] = routes;
|
|
14
|
+
if (first === undefined)
|
|
15
|
+
return [];
|
|
16
|
+
return [...routeParams(first)].filter((name) => rest.every((route) => routeParams(route).has(name)));
|
|
17
|
+
}
|
|
18
|
+
function satisfied(read, available) {
|
|
19
|
+
if (available.has(read))
|
|
20
|
+
return true;
|
|
21
|
+
for (const written of available) {
|
|
22
|
+
if (read.startsWith(`${written}.`) || written.startsWith(`${read}.`))
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
/** A plain write of a parent path replaces whatever was written under it. */
|
|
28
|
+
function apply(write, sets) {
|
|
29
|
+
if (write.endsWith("?"))
|
|
30
|
+
return;
|
|
31
|
+
for (const set of sets) {
|
|
32
|
+
for (const existing of [...set]) {
|
|
33
|
+
if (existing.startsWith(`${write}.`))
|
|
34
|
+
set.delete(existing);
|
|
35
|
+
}
|
|
36
|
+
set.add(write);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function checkDataflow(pipeline, httpRoutes, hasCronTrigger, hasEventTrigger) {
|
|
40
|
+
const available = new Set(CONTEXT_KEYS);
|
|
41
|
+
for (const param of commonParams(httpRoutes))
|
|
42
|
+
available.add(`params.${param}`);
|
|
43
|
+
const written = new Set();
|
|
44
|
+
for (const step of pipeline.steps) {
|
|
45
|
+
for (const read of step.description.reads) {
|
|
46
|
+
// The client owns the payload; the dev validator checks it against describe().input instead.
|
|
47
|
+
if (read.startsWith("payload."))
|
|
48
|
+
continue;
|
|
49
|
+
let ok;
|
|
50
|
+
if (read.startsWith("params.")) {
|
|
51
|
+
// An event envelope carries opaque params, so it can satisfy any of them; a pipeline with
|
|
52
|
+
// no trigger at all is only reachable through run(), where the caller supplies them.
|
|
53
|
+
ok = available.has(read) || hasEventTrigger || (httpRoutes.length === 0 && !hasCronTrigger);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
ok = satisfied(read, available);
|
|
57
|
+
}
|
|
58
|
+
if (!ok) {
|
|
59
|
+
throw new KestrelBootError(`pipelines/${pipeline.name}`, `step "${step.name}" reads "${read}" but no earlier step writes it (earlier writes: ${written.size === 0 ? "none" : [...written].join(", ")})`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const write of step.description.writes)
|
|
63
|
+
apply(write, [available, written]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const configSchema: z.ZodObject<{
|
|
3
|
+
modules: z.ZodArray<z.ZodObject<{
|
|
4
|
+
use: z.ZodString;
|
|
5
|
+
config: z.ZodOptional<z.ZodUnknown>;
|
|
6
|
+
}, "strict", z.ZodTypeAny, {
|
|
7
|
+
use: string;
|
|
8
|
+
config?: unknown;
|
|
9
|
+
}, {
|
|
10
|
+
use: string;
|
|
11
|
+
config?: unknown;
|
|
12
|
+
}>, "many">;
|
|
13
|
+
triggers: z.ZodArray<z.ZodUnion<[z.ZodObject<{
|
|
14
|
+
http: z.ZodString;
|
|
15
|
+
pipeline: z.ZodString;
|
|
16
|
+
}, "strict", z.ZodTypeAny, {
|
|
17
|
+
http: string;
|
|
18
|
+
pipeline: string;
|
|
19
|
+
}, {
|
|
20
|
+
http: string;
|
|
21
|
+
pipeline: string;
|
|
22
|
+
}>, z.ZodObject<{
|
|
23
|
+
event: z.ZodString;
|
|
24
|
+
pipeline: z.ZodString;
|
|
25
|
+
}, "strict", z.ZodTypeAny, {
|
|
26
|
+
event: string;
|
|
27
|
+
pipeline: string;
|
|
28
|
+
}, {
|
|
29
|
+
event: string;
|
|
30
|
+
pipeline: string;
|
|
31
|
+
}>, z.ZodObject<{
|
|
32
|
+
cron: z.ZodString;
|
|
33
|
+
pipeline: z.ZodString;
|
|
34
|
+
}, "strict", z.ZodTypeAny, {
|
|
35
|
+
cron: string;
|
|
36
|
+
pipeline: string;
|
|
37
|
+
}, {
|
|
38
|
+
cron: string;
|
|
39
|
+
pipeline: string;
|
|
40
|
+
}>]>, "many">;
|
|
41
|
+
http: z.ZodDefault<z.ZodUnion<[z.ZodNull, z.ZodObject<{
|
|
42
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
host: z.ZodDefault<z.ZodString>;
|
|
44
|
+
corsOrigin: z.ZodOptional<z.ZodString>;
|
|
45
|
+
maxBodyBytes: z.ZodDefault<z.ZodNumber>;
|
|
46
|
+
trustProxy: z.ZodDefault<z.ZodBoolean>;
|
|
47
|
+
allow: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
48
|
+
healthPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
inlineTypes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
50
|
+
timeouts: z.ZodDefault<z.ZodObject<{
|
|
51
|
+
requestMs: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
headersMs: z.ZodDefault<z.ZodNumber>;
|
|
53
|
+
keepAliveMs: z.ZodDefault<z.ZodNumber>;
|
|
54
|
+
}, "strict", z.ZodTypeAny, {
|
|
55
|
+
requestMs: number;
|
|
56
|
+
headersMs: number;
|
|
57
|
+
keepAliveMs: number;
|
|
58
|
+
}, {
|
|
59
|
+
requestMs?: number | undefined;
|
|
60
|
+
headersMs?: number | undefined;
|
|
61
|
+
keepAliveMs?: number | undefined;
|
|
62
|
+
}>>;
|
|
63
|
+
}, "strict", z.ZodTypeAny, {
|
|
64
|
+
port: number;
|
|
65
|
+
host: string;
|
|
66
|
+
maxBodyBytes: number;
|
|
67
|
+
trustProxy: boolean;
|
|
68
|
+
allow: string[];
|
|
69
|
+
healthPath: string | null;
|
|
70
|
+
inlineTypes: string[];
|
|
71
|
+
timeouts: {
|
|
72
|
+
requestMs: number;
|
|
73
|
+
headersMs: number;
|
|
74
|
+
keepAliveMs: number;
|
|
75
|
+
};
|
|
76
|
+
corsOrigin?: string | undefined;
|
|
77
|
+
}, {
|
|
78
|
+
port?: number | undefined;
|
|
79
|
+
host?: string | undefined;
|
|
80
|
+
corsOrigin?: string | undefined;
|
|
81
|
+
maxBodyBytes?: number | undefined;
|
|
82
|
+
trustProxy?: boolean | undefined;
|
|
83
|
+
allow?: string[] | undefined;
|
|
84
|
+
healthPath?: string | null | undefined;
|
|
85
|
+
inlineTypes?: string[] | undefined;
|
|
86
|
+
timeouts?: {
|
|
87
|
+
requestMs?: number | undefined;
|
|
88
|
+
headersMs?: number | undefined;
|
|
89
|
+
keepAliveMs?: number | undefined;
|
|
90
|
+
} | undefined;
|
|
91
|
+
}>]>>;
|
|
92
|
+
pipelinesDir: z.ZodDefault<z.ZodString>;
|
|
93
|
+
shutdownTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
94
|
+
}, "strict", z.ZodTypeAny, {
|
|
95
|
+
http: {
|
|
96
|
+
port: number;
|
|
97
|
+
host: string;
|
|
98
|
+
maxBodyBytes: number;
|
|
99
|
+
trustProxy: boolean;
|
|
100
|
+
allow: string[];
|
|
101
|
+
healthPath: string | null;
|
|
102
|
+
inlineTypes: string[];
|
|
103
|
+
timeouts: {
|
|
104
|
+
requestMs: number;
|
|
105
|
+
headersMs: number;
|
|
106
|
+
keepAliveMs: number;
|
|
107
|
+
};
|
|
108
|
+
corsOrigin?: string | undefined;
|
|
109
|
+
} | null;
|
|
110
|
+
modules: {
|
|
111
|
+
use: string;
|
|
112
|
+
config?: unknown;
|
|
113
|
+
}[];
|
|
114
|
+
triggers: ({
|
|
115
|
+
http: string;
|
|
116
|
+
pipeline: string;
|
|
117
|
+
} | {
|
|
118
|
+
event: string;
|
|
119
|
+
pipeline: string;
|
|
120
|
+
} | {
|
|
121
|
+
cron: string;
|
|
122
|
+
pipeline: string;
|
|
123
|
+
})[];
|
|
124
|
+
pipelinesDir: string;
|
|
125
|
+
shutdownTimeoutMs: number;
|
|
126
|
+
}, {
|
|
127
|
+
modules: {
|
|
128
|
+
use: string;
|
|
129
|
+
config?: unknown;
|
|
130
|
+
}[];
|
|
131
|
+
triggers: ({
|
|
132
|
+
http: string;
|
|
133
|
+
pipeline: string;
|
|
134
|
+
} | {
|
|
135
|
+
event: string;
|
|
136
|
+
pipeline: string;
|
|
137
|
+
} | {
|
|
138
|
+
cron: string;
|
|
139
|
+
pipeline: string;
|
|
140
|
+
})[];
|
|
141
|
+
http?: {
|
|
142
|
+
port?: number | undefined;
|
|
143
|
+
host?: string | undefined;
|
|
144
|
+
corsOrigin?: string | undefined;
|
|
145
|
+
maxBodyBytes?: number | undefined;
|
|
146
|
+
trustProxy?: boolean | undefined;
|
|
147
|
+
allow?: string[] | undefined;
|
|
148
|
+
healthPath?: string | null | undefined;
|
|
149
|
+
inlineTypes?: string[] | undefined;
|
|
150
|
+
timeouts?: {
|
|
151
|
+
requestMs?: number | undefined;
|
|
152
|
+
headersMs?: number | undefined;
|
|
153
|
+
keepAliveMs?: number | undefined;
|
|
154
|
+
} | undefined;
|
|
155
|
+
} | null | undefined;
|
|
156
|
+
pipelinesDir?: string | undefined;
|
|
157
|
+
shutdownTimeoutMs?: number | undefined;
|
|
158
|
+
}>;
|
|
159
|
+
export type KestrelConfig = z.output<typeof configSchema>;
|
|
160
|
+
export type KestrelConfigInput = z.input<typeof configSchema>;
|
|
161
|
+
export type TriggerConfig = KestrelConfig["triggers"][number];
|
|
162
|
+
export declare function defineConfig(config: KestrelConfigInput): KestrelConfigInput;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const triggerSchema = z.union([
|
|
3
|
+
z.object({ http: z.string().regex(/^(GET|POST|PUT|PATCH|DELETE) \/\S*$/, "expected e.g. \"GET /pages/:id\""), pipeline: z.string().min(1) }).strict(),
|
|
4
|
+
z.object({ event: z.string().min(1), pipeline: z.string().min(1) }).strict(),
|
|
5
|
+
z.object({ cron: z.string().min(1), pipeline: z.string().min(1) }).strict(),
|
|
6
|
+
]);
|
|
7
|
+
export const configSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
modules: z.array(z.object({ use: z.string().min(1), config: z.unknown().optional() }).strict()),
|
|
10
|
+
triggers: z.array(triggerSchema),
|
|
11
|
+
http: z
|
|
12
|
+
.union([
|
|
13
|
+
z.null(),
|
|
14
|
+
z
|
|
15
|
+
.object({
|
|
16
|
+
port: z.number().int().min(0).max(65535).default(3000),
|
|
17
|
+
host: z.string().default("127.0.0.1"),
|
|
18
|
+
corsOrigin: z.string().min(1).optional(),
|
|
19
|
+
maxBodyBytes: z.number().int().positive().default(10 * 1024 * 1024),
|
|
20
|
+
trustProxy: z.boolean().default(false),
|
|
21
|
+
allow: z.array(z.string().min(1)).default([]),
|
|
22
|
+
healthPath: z.string().regex(/^\/\S*$/).nullable().default("/health"),
|
|
23
|
+
inlineTypes: z.array(z.string().min(1)).default([]),
|
|
24
|
+
timeouts: z
|
|
25
|
+
.object({
|
|
26
|
+
requestMs: z.number().int().nonnegative().default(30_000),
|
|
27
|
+
headersMs: z.number().int().nonnegative().default(10_000),
|
|
28
|
+
keepAliveMs: z.number().int().nonnegative().default(5_000),
|
|
29
|
+
})
|
|
30
|
+
.strict()
|
|
31
|
+
.default({}),
|
|
32
|
+
})
|
|
33
|
+
.strict(),
|
|
34
|
+
])
|
|
35
|
+
.default({}),
|
|
36
|
+
pipelinesDir: z.string().default("./pipelines"),
|
|
37
|
+
shutdownTimeoutMs: z.number().int().nonnegative().default(30_000),
|
|
38
|
+
})
|
|
39
|
+
.strict();
|
|
40
|
+
export function defineConfig(config) {
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Contract<T> {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly methods: readonly string[];
|
|
4
|
+
readonly instance?: T;
|
|
5
|
+
}
|
|
6
|
+
type MethodKeys<T> = {
|
|
7
|
+
[K in keyof T]-?: T[K] extends Function ? K : never;
|
|
8
|
+
}[keyof T] & string;
|
|
9
|
+
type CompleteMethodList<T, M extends readonly MethodKeys<T>[]> = [MethodKeys<T>] extends [M[number]] ? M : never;
|
|
10
|
+
export declare function defineContract<T>(): <const M extends readonly MethodKeys<T>[]>(name: string, methods: CompleteMethodList<T, M>) => Contract<T>;
|
|
11
|
+
export declare function missingMethods<T>(contract: Contract<T>, instance: unknown): string[];
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Curried so M is inferred fresh at the second call, independent of the CompleteMethodList check on its own parameter.
|
|
2
|
+
export function defineContract() {
|
|
3
|
+
return (name, methods) => {
|
|
4
|
+
if (!/^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(name))
|
|
5
|
+
throw new Error(`defineContract: name "${name}" must look like "<capability>@<major>"`);
|
|
6
|
+
if (methods.length === 0)
|
|
7
|
+
throw new Error(`defineContract: contract "${name}" declares no methods`);
|
|
8
|
+
return { name, methods };
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function missingMethods(contract, instance) {
|
|
12
|
+
if (instance === null || (typeof instance !== "object" && typeof instance !== "function"))
|
|
13
|
+
return [...contract.methods];
|
|
14
|
+
const obj = instance;
|
|
15
|
+
return contract.methods.filter((m) => typeof obj[m] !== "function");
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ZodTypeAny, output } from "zod";
|
|
2
|
+
import type { StepFactory, StepMap } from "./context.ts";
|
|
3
|
+
import type { Contract } from "./defineContract.ts";
|
|
4
|
+
import type { Logger } from "./logger.ts";
|
|
5
|
+
import type { Runner } from "./runner.ts";
|
|
6
|
+
export interface Deps {
|
|
7
|
+
get<T>(contract: Contract<T>): T;
|
|
8
|
+
find<T>(contract: Contract<T>): T | undefined;
|
|
9
|
+
logger: Logger;
|
|
10
|
+
root: string;
|
|
11
|
+
}
|
|
12
|
+
export type JsonSchema = Record<string, unknown>;
|
|
13
|
+
/** A context key optionally followed by dotted sub-keys; a trailing "?" marks a conditional write. */
|
|
14
|
+
export type ContextPath = string;
|
|
15
|
+
export interface StepDescription {
|
|
16
|
+
summary: string;
|
|
17
|
+
reads: readonly ContextPath[];
|
|
18
|
+
writes: readonly ContextPath[];
|
|
19
|
+
input?: JsonSchema;
|
|
20
|
+
output?: JsonSchema;
|
|
21
|
+
/** Merges into the currently active output schema (properties + required union) instead of replacing it; becomes the output if there was none yet. */
|
|
22
|
+
extendsOutput?: JsonSchema;
|
|
23
|
+
/** Like extendsOutput, but merges into `properties.items.items` of the active output when it is a list result; falls back to a root merge otherwise. */
|
|
24
|
+
extendsItems?: JsonSchema;
|
|
25
|
+
query?: Record<string, JsonSchema>;
|
|
26
|
+
errors?: Record<number, string>;
|
|
27
|
+
security?: "required" | "optional";
|
|
28
|
+
multipart?: boolean;
|
|
29
|
+
binary?: boolean;
|
|
30
|
+
}
|
|
31
|
+
type DescriptionOf<F> = F extends StepFactory ? (arg: string) => StepDescription : StepDescription;
|
|
32
|
+
export type StepDescriptions<T extends StepMap = StepMap> = {
|
|
33
|
+
[K in keyof T & string]: DescriptionOf<T[K]>;
|
|
34
|
+
};
|
|
35
|
+
export interface EventEntry {
|
|
36
|
+
event: string;
|
|
37
|
+
pipeline: string;
|
|
38
|
+
}
|
|
39
|
+
/** `event` subscribes the module's own bus to the configured entries and returns the stop function the core calls on `stop()`. */
|
|
40
|
+
export interface ModuleTriggers<P> {
|
|
41
|
+
event?(instance: P, entries: readonly EventEntry[], run: Runner, logger: Logger): () => void;
|
|
42
|
+
}
|
|
43
|
+
export interface ModuleDefinition<N extends string = string, T extends StepMap = StepMap> {
|
|
44
|
+
name: N;
|
|
45
|
+
provides: readonly Contract<unknown>[];
|
|
46
|
+
requires: readonly Contract<unknown>[];
|
|
47
|
+
optional?: readonly Contract<unknown>[];
|
|
48
|
+
configSchema: ZodTypeAny;
|
|
49
|
+
setup(config: unknown, deps: Deps): Promise<unknown>;
|
|
50
|
+
steps?(instance: unknown): T;
|
|
51
|
+
describe?(instance: unknown): StepDescriptions<T>;
|
|
52
|
+
triggers?: ModuleTriggers<unknown>;
|
|
53
|
+
teardown?(instance: unknown): void | Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
type WithSteps<P, T extends StepMap> = {
|
|
56
|
+
steps(instance: P): T;
|
|
57
|
+
describe(instance: P): StepDescriptions<T>;
|
|
58
|
+
};
|
|
59
|
+
type WithoutSteps = {
|
|
60
|
+
steps?: never;
|
|
61
|
+
describe?: never;
|
|
62
|
+
};
|
|
63
|
+
export type ModuleInput<N extends string, S extends ZodTypeAny, P, T extends StepMap> = {
|
|
64
|
+
name: N;
|
|
65
|
+
provides: readonly Contract<unknown>[];
|
|
66
|
+
requires: readonly Contract<unknown>[];
|
|
67
|
+
optional?: readonly Contract<unknown>[];
|
|
68
|
+
configSchema: S;
|
|
69
|
+
setup(config: output<S>, deps: Deps): Promise<P>;
|
|
70
|
+
triggers?: ModuleTriggers<P>;
|
|
71
|
+
teardown?(instance: P): void | Promise<void>;
|
|
72
|
+
} & (WithSteps<P, T> | WithoutSteps);
|
|
73
|
+
export declare function defineModule<const N extends string, S extends ZodTypeAny, P, T extends StepMap = Record<never, never>>(def: ModuleInput<N, S, P, T>): ModuleDefinition<N, T>;
|
|
74
|
+
export declare function stepPrefix(moduleName: string): string;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function defineModule(def) {
|
|
2
|
+
if (!/^[a-z0-9-]+\/[a-z0-9-]+$/.test(def.name)) {
|
|
3
|
+
throw new Error(`defineModule: name "${def.name}" must look like "<module>/<submodule>"`);
|
|
4
|
+
}
|
|
5
|
+
return def;
|
|
6
|
+
}
|
|
7
|
+
export function stepPrefix(moduleName) {
|
|
8
|
+
return moduleName.split("/")[0] ?? moduleName;
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface PipelineDefinition<S extends string = string> {
|
|
2
|
+
name: string;
|
|
3
|
+
steps: readonly S[];
|
|
4
|
+
}
|
|
5
|
+
export declare function definePipeline<S extends string = string>(def: PipelineDefinition<S>): PipelineDefinition<S>;
|
|
6
|
+
/** Binds `definePipeline` to a catalogue so pipeline files need no generic argument. */
|
|
7
|
+
export declare function pipelineDefiner<S extends string>(): (def: PipelineDefinition<S>) => PipelineDefinition<S>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function definePipeline(def) {
|
|
2
|
+
if (def.name.trim() === "")
|
|
3
|
+
throw new Error("definePipeline: name must not be empty");
|
|
4
|
+
if (def.steps.length === 0)
|
|
5
|
+
throw new Error(`definePipeline: pipeline "${def.name}" has no steps`);
|
|
6
|
+
return def;
|
|
7
|
+
}
|
|
8
|
+
/** Binds `definePipeline` to a catalogue so pipeline files need no generic argument. */
|
|
9
|
+
export function pipelineDefiner() {
|
|
10
|
+
return definePipeline;
|
|
11
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class KestrelBootError extends Error {
|
|
2
|
+
readonly module: string;
|
|
3
|
+
readonly reason: string;
|
|
4
|
+
constructor(module: string, reason: string);
|
|
5
|
+
}
|
|
6
|
+
export type CoreCode = "VALIDATION" | "NOT_FOUND" | "CONFLICT" | "FORBIDDEN" | "UNAUTHENTICATED" | "RATE_LIMITED" | "DANGLING_REF" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED" | "TRANSIENT" | "INTERNAL";
|
|
7
|
+
export interface KestrelError<C extends string = string> {
|
|
8
|
+
readonly code: C;
|
|
9
|
+
readonly status: number;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
readonly retryable: boolean;
|
|
12
|
+
readonly details?: Record<string, unknown>;
|
|
13
|
+
readonly cause?: unknown;
|
|
14
|
+
}
|
|
15
|
+
/** The one place a core code becomes an HTTP status. Nothing else maps codes to numbers. */
|
|
16
|
+
export declare const STATUS_OF: Readonly<Record<CoreCode, number>>;
|
|
17
|
+
export interface FailureOptions {
|
|
18
|
+
details?: Record<string, unknown>;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare function failure<C extends CoreCode>(code: C, message: string, options?: FailureOptions): KestrelError<C>;
|
|
22
|
+
/** For codes a contract declares itself; `status` is fixed by the contract file, never at a call site. */
|
|
23
|
+
export declare function customFailure<C extends string>(code: C, status: number, message: string, options?: FailureOptions & {
|
|
24
|
+
retryable?: boolean;
|
|
25
|
+
}): KestrelError<C>;
|
|
26
|
+
export declare function isKestrelError(value: unknown): value is KestrelError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class KestrelBootError extends Error {
|
|
2
|
+
module;
|
|
3
|
+
reason;
|
|
4
|
+
constructor(module, reason) {
|
|
5
|
+
super(`[${module}] ${reason}`);
|
|
6
|
+
this.name = "KestrelBootError";
|
|
7
|
+
this.module = module;
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** The one place a core code becomes an HTTP status. Nothing else maps codes to numbers. */
|
|
12
|
+
export const STATUS_OF = {
|
|
13
|
+
VALIDATION: 400,
|
|
14
|
+
DANGLING_REF: 400,
|
|
15
|
+
UNAUTHENTICATED: 401,
|
|
16
|
+
FORBIDDEN: 403,
|
|
17
|
+
NOT_FOUND: 404,
|
|
18
|
+
CONFLICT: 409,
|
|
19
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
20
|
+
UNSUPPORTED: 415,
|
|
21
|
+
RATE_LIMITED: 429,
|
|
22
|
+
INTERNAL: 500,
|
|
23
|
+
TRANSIENT: 503,
|
|
24
|
+
};
|
|
25
|
+
const RETRYABLE = new Set(["TRANSIENT", "RATE_LIMITED"]);
|
|
26
|
+
export function failure(code, message, options = {}) {
|
|
27
|
+
const error = { code, status: STATUS_OF[code], message, retryable: RETRYABLE.has(code) };
|
|
28
|
+
if (options.details !== undefined)
|
|
29
|
+
error.details = options.details;
|
|
30
|
+
if (options.cause !== undefined)
|
|
31
|
+
error.cause = options.cause;
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
/** For codes a contract declares itself; `status` is fixed by the contract file, never at a call site. */
|
|
35
|
+
export function customFailure(code, status, message, options = {}) {
|
|
36
|
+
const error = { code, status, message, retryable: options.retryable ?? false };
|
|
37
|
+
if (options.details !== undefined)
|
|
38
|
+
error.details = options.details;
|
|
39
|
+
if (options.cause !== undefined)
|
|
40
|
+
error.cause = options.cause;
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
export function isKestrelError(value) {
|
|
44
|
+
if (typeof value !== "object" || value === null)
|
|
45
|
+
return false;
|
|
46
|
+
const error = value;
|
|
47
|
+
return typeof error.code === "string" && typeof error.status === "number" && typeof error.message === "string" && typeof error.retryable === "boolean";
|
|
48
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { boot, type Kestrel, type BootInput, type Triggers } from "./boot.ts";
|
|
2
|
+
export { matchRoute, routePattern, parseRequestBody, parseQuery, buildResponse, responseForRun, errorResponse, clientIp, BodyTooLarge, type Route, type ParsedBody, type HttpResponse, type HttpTimeouts, type ResponseMeta } from "./triggers/http.ts";
|
|
3
|
+
export { defineConfig, type KestrelConfig, type KestrelConfigInput } from "./defineConfig.ts";
|
|
4
|
+
export { defineContract, type Contract } from "./defineContract.ts";
|
|
5
|
+
export { defineModule, type ModuleDefinition, type ModuleInput, type ModuleTriggers, type Deps, type EventEntry, type ContextPath, type JsonSchema, type StepDescription, type StepDescriptions } from "./defineModule.ts";
|
|
6
|
+
export type { ResolvedPipeline } from "./runner.ts";
|
|
7
|
+
export type { ResolvedStep } from "./registry.ts";
|
|
8
|
+
export { definePipeline, pipelineDefiner, type PipelineDefinition } from "./definePipeline.ts";
|
|
9
|
+
export type { StepsOf, StepCatalogue } from "./catalogue.ts";
|
|
10
|
+
export { createContext, binaryResult, isBinaryResult, first, requestIdOf, stepFactory, isStepFactory, type BinaryResult, type Context, type ContextInput, type Step, type StepFactory, type StepMap, type StepResult, type Trigger, type UploadedFile } from "./context.ts";
|
|
11
|
+
export { KestrelBootError, STATUS_OF, failure, customFailure, isKestrelError, type CoreCode, type FailureOptions, type KestrelError } from "./errors.ts";
|
|
12
|
+
export { ok, err, isOk, isErr, match, unwrapOr, type Ok, type Err, type Result } from "./result.ts";
|
|
13
|
+
export { boundaryCast, type Boundary } from "./cast.ts";
|
|
14
|
+
export { loadConfig, loadModules, loadPipelines } from "./load.ts";
|
|
15
|
+
export { consoleLogger, silentLogger, logWarn, type Logger, type StepLog } from "./logger.ts";
|
|
16
|
+
export type { RunResult, Runner, RunTracker, RunOptions } from "./runner.ts";
|
|
17
|
+
export { checkDataflow } from "./dataflow.ts";
|
|
18
|
+
export { validateSchema, type SchemaProblem } from "./schema.ts";
|
|
19
|
+
export type { StopOptions, StopResult } from "./boot.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { boot } from "./boot.js";
|
|
2
|
+
export { matchRoute, routePattern, parseRequestBody, parseQuery, buildResponse, responseForRun, errorResponse, clientIp, BodyTooLarge } from "./triggers/http.js";
|
|
3
|
+
export { defineConfig } from "./defineConfig.js";
|
|
4
|
+
export { defineContract } from "./defineContract.js";
|
|
5
|
+
export { defineModule } from "./defineModule.js";
|
|
6
|
+
export { definePipeline, pipelineDefiner } from "./definePipeline.js";
|
|
7
|
+
export { createContext, binaryResult, isBinaryResult, first, requestIdOf, stepFactory, isStepFactory } from "./context.js";
|
|
8
|
+
export { KestrelBootError, STATUS_OF, failure, customFailure, isKestrelError } from "./errors.js";
|
|
9
|
+
export { ok, err, isOk, isErr, match, unwrapOr } from "./result.js";
|
|
10
|
+
export { boundaryCast } from "./cast.js";
|
|
11
|
+
export { loadConfig, loadModules, loadPipelines } from "./load.js";
|
|
12
|
+
export { consoleLogger, silentLogger, logWarn } from "./logger.js";
|
|
13
|
+
export { checkDataflow } from "./dataflow.js";
|
|
14
|
+
export { validateSchema } from "./schema.js";
|
package/dist/load.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KestrelConfigInput } from "./defineConfig.ts";
|
|
2
|
+
import type { ModuleDefinition } from "./defineModule.ts";
|
|
3
|
+
import type { PipelineDefinition } from "./definePipeline.ts";
|
|
4
|
+
export declare function loadConfig(file: string): Promise<KestrelConfigInput>;
|
|
5
|
+
export declare function resolveModuleFile(root: string, use: string): string;
|
|
6
|
+
export declare function loadModules(root: string, config: KestrelConfigInput): Promise<ModuleDefinition[]>;
|
|
7
|
+
export declare function loadPipelines(root: string, dir: string): Promise<PipelineDefinition[]>;
|