@oomol-lab/open-flow 0.1.0-beta.73 → 0.1.0-beta.75
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/LICENSE +36 -0
- package/NOTICE +7 -0
- package/README.md +41 -0
- package/dist/browser/{ContentIcon-CUQHjQIw.js → ContentIcon-FCpKiwjp.js} +2 -1
- package/dist/browser/{FlowCanvas-Dtf8_cQp.js → FlowCanvas-dzRBnlUT.js} +1 -1
- package/dist/browser/{createResourceDialog-BWJkjiOO.js → createResourceDialog-BeYDdUl-.js} +2 -2
- package/dist/browser/{dialog-CUy_fwx9.js → dialog-ZtpsFbl4.js} +337 -31
- package/dist/browser/{exportSvg-BOoy7-Db.js → exportSvg-DqXw1yf-.js} +2 -2
- package/dist/browser/{flowChanges-CgSnSqqP.js → flowChanges-b_5UobFj.js} +586 -87
- package/dist/browser/{flowWorkspace-Bf7H-r0t.js → flowWorkspace-ItojZbO4.js} +21162 -20242
- package/dist/browser/preview.css +254 -179
- package/dist/browser/preview.js +22201 -20625
- package/dist/browser/{typeScriptSession-B0hC-C8y.js → typeScriptSession-CSRwWmZm.js} +1 -1
- package/dist/browser/ui.css +72 -2
- package/dist/browser/workbench.css +254 -179
- package/dist/browser/workbench.js +22 -78
- package/dist/browser/{workbenchSelect-C3e4J93X.js → workbenchSelect-M441Lz0d.js} +1 -1
- package/dist/command/index.js +42 -42
- package/dist/common/authoring.d.ts +5 -5
- package/dist/common/authoringExamples.d.ts +3 -2
- package/dist/common/authoringFields.d.ts +1 -1
- package/dist/common/authoringSchema.d.ts +95 -8
- package/dist/common/connector-proxy.d.ts +1 -1
- package/dist/common/control-api-conformance.d.ts +1 -1
- package/dist/common/control-api-conformance.js +119 -31
- package/dist/common/control-api.d.ts +4 -3
- package/dist/common/control-api.js +117 -29
- package/dist/common/control-requests.d.ts +5 -4
- package/dist/common/control-requests.js +437 -103
- package/dist/common/cron-trigger.d.ts +1 -1
- package/dist/common/decision.d.ts +1 -1
- package/dist/common/draftOperations.d.ts +1 -1
- package/dist/common/engine-contract.d.ts +4 -2
- package/dist/common/event-source-definition.d.ts +1 -1
- package/dist/common/flow-agent.d.ts +1 -1
- package/dist/{browser → common}/flow-authoring-node.d.ts +3 -3
- package/dist/{browser → common}/flow-authoring.js +131 -30
- package/dist/{browser → common}/flow-change-schema.d.ts +177 -6
- package/dist/{browser → common}/flow-change.d.ts +35 -15
- package/dist/{browser → common}/flow-change.js +133 -32
- package/dist/common/flow-connectionUsage.d.ts +1 -1
- package/dist/common/flow-encoding.d.ts +1 -1
- package/dist/common/flow-encoding.js +89 -23
- package/dist/common/flow-errorHandling.d.ts +1 -1
- package/dist/common/flow-graph.d.ts +3 -2
- package/dist/common/flow-modules.d.ts +1 -1
- package/dist/common/flow-promptTemplate.d.ts +1 -1
- package/dist/common/flow-retry.d.ts +2 -2
- package/dist/common/flow-semantics.d.ts +1 -1
- package/dist/common/flow-semantics.js +966 -614
- package/dist/common/flow-workflowFile.d.ts +1 -1
- package/dist/common/flowInspection.d.ts +3 -3
- package/dist/common/http-request.d.ts +156 -0
- package/dist/common/http-request.js +5254 -0
- package/dist/common/integration-trigger.d.ts +1 -1
- package/dist/common/licenses.md +118 -1
- package/dist/common/liveDetails.d.ts +1 -1
- package/dist/common/mcp.d.ts +5 -4
- package/dist/common/mcp.js +117 -29
- package/dist/common/openapi.d.ts +1 -1
- package/dist/common/poll-trigger.d.ts +1 -1
- package/dist/common/run-events.js +11 -9
- package/dist/common/run-results.d.ts +8 -1
- package/dist/common/runtime-contract.d.ts +8 -2
- package/dist/common/runtime-contract.js +25 -5
- package/dist/common/scheduler-checkpoint.d.ts +1 -1
- package/dist/common/scheduler-dependencies.d.ts +1 -1
- package/dist/common/scheduler-node-execution.d.ts +1 -1
- package/dist/common/scheduler.d.ts +3 -3
- package/dist/common/scheduler.js +101 -33
- package/dist/common/trigger-catalog.d.ts +1 -1
- package/dist/common/trigger-config-options.d.ts +1 -1
- package/dist/common/trigger-config.d.ts +1 -1
- package/dist/common/trigger-contract.d.ts +2 -2
- package/dist/common/trigger-localization.d.ts +1 -1
- package/dist/common/webhook-response.d.ts +1 -3
- package/dist/common/webhook-trigger.d.ts +1 -1
- package/dist/common/webhook-trigger.js +12 -11
- package/dist/common/workflow.d.ts +17 -1
- package/dist/common/workflow.js +68 -22
- package/dist/index.d.ts +6 -3
- package/package.json +11 -6
- package/skills/open-flow/references/flow-authoring.md +1 -0
- package/skills/open-flow/references/flow-n8n-conversion.md +19 -15
- package/src/base/common/json.ts +90 -0
- package/src/canvas/common/commands.ts +15 -0
- package/src/connector/common/actionSchema.ts +168 -0
- package/src/connector/common/proxy.ts +44 -0
- package/src/control/common/api.ts +1506 -0
- package/src/control/common/authoring.ts +923 -0
- package/src/control/common/authoringCondition.ts +110 -0
- package/src/control/common/authoringDiagnosticFields.ts +136 -0
- package/src/control/common/authoringExamples.ts +200 -0
- package/src/control/common/authoringFields.ts +53 -0
- package/src/control/common/authoringLlm.ts +70 -0
- package/src/control/common/authoringOpenApi.ts +129 -0
- package/src/control/common/authoringSchema.ts +410 -0
- package/src/control/common/authoringTools.ts +123 -0
- package/src/control/common/authoringTriggers.ts +26 -0
- package/src/control/common/checkDecoders.ts +65 -0
- package/src/control/common/conformance.ts +1989 -0
- package/src/control/common/connectorDecoders.ts +370 -0
- package/src/control/common/connectorQueries.ts +61 -0
- package/src/control/common/decoding.ts +60 -0
- package/src/control/common/draftOperations.ts +61 -0
- package/src/control/common/errors.ts +114 -0
- package/src/control/common/eventSourceSchemas.ts +13 -0
- package/src/control/common/eventSources.ts +186 -0
- package/src/control/common/flowDecoders.ts +153 -0
- package/src/control/common/flowInspection.ts +181 -0
- package/src/control/common/flowNotifications.ts +37 -0
- package/src/control/common/liveDetails.ts +124 -0
- package/src/control/common/mcp.ts +241 -0
- package/src/control/common/mcpConformance.ts +142 -0
- package/src/control/common/providerAccess.ts +28 -0
- package/src/control/common/providerIconSprite.ts +42 -0
- package/src/control/common/publicationDecoders.ts +98 -0
- package/src/control/common/random.ts +16 -0
- package/src/control/common/requests.ts +112 -0
- package/src/control/common/resultQuery.ts +8 -0
- package/src/control/common/results.ts +176 -0
- package/src/control/common/revisionDecoders.ts +52 -0
- package/src/control/common/runDecoders.ts +240 -0
- package/src/control/common/triggerCatalog.ts +89 -0
- package/src/control/common/triggerDecoders.ts +175 -0
- package/src/decision/common/decision.ts +168 -0
- package/src/event-source/common/catalog.generated.json +65 -0
- package/src/event-source/common/definition.ts +66 -0
- package/src/event-source/common/index.ts +99 -0
- package/src/execution/common/checkpoint.ts +311 -0
- package/src/execution/common/dependencies.ts +440 -0
- package/src/execution/common/engineContract.ts +84 -0
- package/src/execution/common/events.ts +331 -0
- package/src/execution/common/node-execution.ts +469 -0
- package/src/execution/common/runLifecycle.ts +250 -0
- package/src/execution/common/runtime.ts +563 -0
- package/src/execution/common/scheduler.ts +697 -0
- package/src/execution/common/wait.ts +36 -0
- package/src/flow/common/agent.ts +305 -0
- package/src/flow/common/authoring.ts +30 -0
- package/src/flow/common/change.ts +1037 -0
- package/src/flow/common/changeSchema.ts +792 -0
- package/src/flow/common/codePermissions.ts +2 -0
- package/src/flow/common/condition.ts +265 -0
- package/src/flow/common/connectionUsage.ts +131 -0
- package/src/flow/common/edgeChanges.ts +35 -0
- package/src/flow/common/encoding.ts +437 -0
- package/src/flow/common/errorHandling.ts +34 -0
- package/src/flow/common/graph.ts +898 -0
- package/src/flow/common/inputValue.ts +16 -0
- package/src/flow/common/json.ts +8 -0
- package/src/flow/common/moduleChanges.ts +31 -0
- package/src/flow/common/modules.ts +292 -0
- package/src/flow/common/nodeChanges.ts +762 -0
- package/src/flow/common/portChanges.ts +36 -0
- package/src/flow/common/promptTemplate.ts +15 -0
- package/src/flow/common/retry.ts +20 -0
- package/src/flow/common/schema.ts +162 -0
- package/src/flow/common/schemaCompare.ts +176 -0
- package/src/flow/common/schemaComparer.ts +178 -0
- package/src/flow/common/schemaKeywords.ts +53 -0
- package/src/flow/common/semantics.ts +531 -0
- package/src/flow/common/sourceField.ts +35 -0
- package/src/flow/common/triggerScheduleSchema.ts +8 -0
- package/src/flow/common/wait.ts +30 -0
- package/src/flow/common/webhookMethod.ts +7 -0
- package/src/flow/common/webhookResponse.ts +71 -0
- package/src/flow/common/workflowFile.ts +97 -0
- package/src/http/common/authBindings.ts +12 -0
- package/src/http/common/headers.ts +5 -0
- package/src/http/common/httpRequest.ts +251 -0
- package/src/json-schema-subset/checker/array.ts +142 -0
- package/src/json-schema-subset/checker/cache.ts +57 -0
- package/src/json-schema-subset/checker/checker.ts +230 -0
- package/src/json-schema-subset/checker/enumAndConst.ts +113 -0
- package/src/json-schema-subset/checker/equals.ts +93 -0
- package/src/json-schema-subset/checker/numeric/checker.ts +50 -0
- package/src/json-schema-subset/checker/numeric/float.ts +121 -0
- package/src/json-schema-subset/checker/numeric/index.ts +1 -0
- package/src/json-schema-subset/checker/numeric/integer.ts +119 -0
- package/src/json-schema-subset/checker/numeric/integerNormalizer.ts +72 -0
- package/src/json-schema-subset/checker/object.ts +354 -0
- package/src/json-schema-subset/checker/range.ts +91 -0
- package/src/json-schema-subset/checker/string.ts +60 -0
- package/src/json-schema-subset/checker/tools/index.ts +3 -0
- package/src/json-schema-subset/checker/tools/mark.ts +82 -0
- package/src/json-schema-subset/checker/tools/quantum.ts +106 -0
- package/src/json-schema-subset/checker/tools/range.ts +210 -0
- package/src/json-schema-subset/checker/validator.ts +23 -0
- package/src/json-schema-subset/compiler/array.ts +163 -0
- package/src/json-schema-subset/compiler/combination.ts +109 -0
- package/src/json-schema-subset/compiler/common.ts +99 -0
- package/src/json-schema-subset/compiler/compiler.ts +188 -0
- package/src/json-schema-subset/compiler/enumAndConst.ts +75 -0
- package/src/json-schema-subset/compiler/index.ts +11 -0
- package/src/json-schema-subset/compiler/object.ts +411 -0
- package/src/json-schema-subset/compiler/primitive.ts +144 -0
- package/src/json-schema-subset/compiler/utils.ts +24 -0
- package/src/json-schema-subset/compiler/wrapper.ts +192 -0
- package/src/json-schema-subset/error.ts +55 -0
- package/src/json-schema-subset/expression/calculator.ts +442 -0
- package/src/json-schema-subset/expression/combination.ts +49 -0
- package/src/json-schema-subset/expression/expression.ts +191 -0
- package/src/json-schema-subset/expression/index.ts +25 -0
- package/src/json-schema-subset/expression/result.ts +88 -0
- package/src/json-schema-subset/expression/types.ts +36 -0
- package/src/json-schema-subset/extends/extendsContext.ts +89 -0
- package/src/json-schema-subset/extends/index.ts +2 -0
- package/src/json-schema-subset/extends/types.ts +12 -0
- package/src/json-schema-subset/global.ts +60 -0
- package/src/json-schema-subset/index.ts +8 -0
- package/src/json-schema-subset/preprocessing.ts +74 -0
- package/src/json-schema-subset/types.ts +8 -0
- package/src/json-schema-subset/utils.ts +95 -0
- package/src/localization/common/languages.ts +118 -0
- package/src/localization/common/timeZone.ts +25 -0
- package/src/openapi/common/openapi.ts +459 -0
- package/src/trigger/common/config.ts +56 -0
- package/src/trigger/common/configOptions.ts +15 -0
- package/src/trigger/common/configSchema.ts +8 -0
- package/src/trigger/common/contract.ts +132 -0
- package/src/trigger/common/cron.ts +151 -0
- package/src/trigger/common/integration.ts +498 -0
- package/src/trigger/common/poll.ts +187 -0
- package/src/trigger/common/webhook.ts +413 -0
- package/src/trigger/providers/airtable/locales/fr.json +18 -0
- package/src/trigger/providers/airtable/locales/ja.json +18 -0
- package/src/trigger/providers/airtable/locales/ko.json +18 -0
- package/src/trigger/providers/airtable/locales/ru.json +18 -0
- package/src/trigger/providers/airtable/locales/zh-CN.json +18 -0
- package/src/trigger/providers/airtable/locales/zh-TW.json +18 -0
- package/src/trigger/providers/asana/locales/fr.json +15 -0
- package/src/trigger/providers/asana/locales/ja.json +15 -0
- package/src/trigger/providers/asana/locales/ko.json +15 -0
- package/src/trigger/providers/asana/locales/ru.json +15 -0
- package/src/trigger/providers/asana/locales/zh-CN.json +15 -0
- package/src/trigger/providers/asana/locales/zh-TW.json +15 -0
- package/src/trigger/providers/calendly/locales/fr.json +16 -0
- package/src/trigger/providers/calendly/locales/ja.json +16 -0
- package/src/trigger/providers/calendly/locales/ko.json +16 -0
- package/src/trigger/providers/calendly/locales/ru.json +16 -0
- package/src/trigger/providers/calendly/locales/zh-CN.json +16 -0
- package/src/trigger/providers/calendly/locales/zh-TW.json +16 -0
- package/src/trigger/providers/catalog.generated.json +4996 -0
- package/src/trigger/providers/clickup/locales/fr.json +16 -0
- package/src/trigger/providers/clickup/locales/ja.json +16 -0
- package/src/trigger/providers/clickup/locales/ko.json +16 -0
- package/src/trigger/providers/clickup/locales/ru.json +16 -0
- package/src/trigger/providers/clickup/locales/zh-CN.json +16 -0
- package/src/trigger/providers/clickup/locales/zh-TW.json +16 -0
- package/src/trigger/providers/definitions.ts +19 -0
- package/src/trigger/providers/feishu/locales/fr.json +20 -0
- package/src/trigger/providers/feishu/locales/ja.json +20 -0
- package/src/trigger/providers/feishu/locales/ko.json +20 -0
- package/src/trigger/providers/feishu/locales/ru.json +20 -0
- package/src/trigger/providers/feishu/locales/zh-CN.json +20 -0
- package/src/trigger/providers/feishu/locales/zh-TW.json +20 -0
- package/src/trigger/providers/fieldDescriptions.ts +392 -0
- package/src/trigger/providers/fieldLabels.ts +256 -0
- package/src/trigger/providers/fireflies/locales/fr.json +17 -0
- package/src/trigger/providers/fireflies/locales/ja.json +17 -0
- package/src/trigger/providers/fireflies/locales/ko.json +17 -0
- package/src/trigger/providers/fireflies/locales/ru.json +17 -0
- package/src/trigger/providers/fireflies/locales/zh-CN.json +17 -0
- package/src/trigger/providers/fireflies/locales/zh-TW.json +17 -0
- package/src/trigger/providers/github/locales/fr.json +30 -0
- package/src/trigger/providers/github/locales/ja.json +30 -0
- package/src/trigger/providers/github/locales/ko.json +30 -0
- package/src/trigger/providers/github/locales/ru.json +30 -0
- package/src/trigger/providers/github/locales/zh-CN.json +30 -0
- package/src/trigger/providers/github/locales/zh-TW.json +30 -0
- package/src/trigger/providers/gitlab/locales/fr.json +18 -0
- package/src/trigger/providers/gitlab/locales/ja.json +18 -0
- package/src/trigger/providers/gitlab/locales/ko.json +18 -0
- package/src/trigger/providers/gitlab/locales/ru.json +18 -0
- package/src/trigger/providers/gitlab/locales/zh-CN.json +18 -0
- package/src/trigger/providers/gitlab/locales/zh-TW.json +18 -0
- package/src/trigger/providers/gmail/locales/fr.json +18 -0
- package/src/trigger/providers/gmail/locales/ja.json +18 -0
- package/src/trigger/providers/gmail/locales/ko.json +18 -0
- package/src/trigger/providers/gmail/locales/ru.json +18 -0
- package/src/trigger/providers/gmail/locales/zh-CN.json +18 -0
- package/src/trigger/providers/gmail/locales/zh-TW.json +18 -0
- package/src/trigger/providers/google-calendar/locales/fr.json +15 -0
- package/src/trigger/providers/google-calendar/locales/ja.json +15 -0
- package/src/trigger/providers/google-calendar/locales/ko.json +15 -0
- package/src/trigger/providers/google-calendar/locales/ru.json +15 -0
- package/src/trigger/providers/google-calendar/locales/zh-CN.json +15 -0
- package/src/trigger/providers/google-calendar/locales/zh-TW.json +15 -0
- package/src/trigger/providers/google-drive/locales/fr.json +48 -0
- package/src/trigger/providers/google-drive/locales/ja.json +48 -0
- package/src/trigger/providers/google-drive/locales/ko.json +48 -0
- package/src/trigger/providers/google-drive/locales/ru.json +48 -0
- package/src/trigger/providers/google-drive/locales/zh-CN.json +48 -0
- package/src/trigger/providers/google-drive/locales/zh-TW.json +48 -0
- package/src/trigger/providers/google-sheets/locales/fr.json +19 -0
- package/src/trigger/providers/google-sheets/locales/ja.json +19 -0
- package/src/trigger/providers/google-sheets/locales/ko.json +19 -0
- package/src/trigger/providers/google-sheets/locales/ru.json +19 -0
- package/src/trigger/providers/google-sheets/locales/zh-CN.json +19 -0
- package/src/trigger/providers/google-sheets/locales/zh-TW.json +19 -0
- package/src/trigger/providers/intercom/locales/fr.json +17 -0
- package/src/trigger/providers/intercom/locales/ja.json +17 -0
- package/src/trigger/providers/intercom/locales/ko.json +17 -0
- package/src/trigger/providers/intercom/locales/ru.json +17 -0
- package/src/trigger/providers/intercom/locales/zh-CN.json +17 -0
- package/src/trigger/providers/intercom/locales/zh-TW.json +17 -0
- package/src/trigger/providers/jira/locales/fr.json +17 -0
- package/src/trigger/providers/jira/locales/ja.json +17 -0
- package/src/trigger/providers/jira/locales/ko.json +17 -0
- package/src/trigger/providers/jira/locales/ru.json +17 -0
- package/src/trigger/providers/jira/locales/zh-CN.json +17 -0
- package/src/trigger/providers/jira/locales/zh-TW.json +17 -0
- package/src/trigger/providers/linear/locales/fr.json +13 -0
- package/src/trigger/providers/linear/locales/ja.json +13 -0
- package/src/trigger/providers/linear/locales/ko.json +13 -0
- package/src/trigger/providers/linear/locales/ru.json +13 -0
- package/src/trigger/providers/linear/locales/zh-CN.json +13 -0
- package/src/trigger/providers/linear/locales/zh-TW.json +13 -0
- package/src/trigger/providers/locales/fr.ts +71 -0
- package/src/trigger/providers/locales/ja.ts +71 -0
- package/src/trigger/providers/locales/ko.ts +71 -0
- package/src/trigger/providers/locales/ru.ts +71 -0
- package/src/trigger/providers/locales/zh-CN.ts +71 -0
- package/src/trigger/providers/locales/zh-TW.ts +71 -0
- package/src/trigger/providers/localization.ts +59 -0
- package/src/trigger/providers/notion/locales/fr.json +16 -0
- package/src/trigger/providers/notion/locales/ja.json +16 -0
- package/src/trigger/providers/notion/locales/ko.json +16 -0
- package/src/trigger/providers/notion/locales/ru.json +16 -0
- package/src/trigger/providers/notion/locales/zh-CN.json +16 -0
- package/src/trigger/providers/notion/locales/zh-TW.json +16 -0
- package/src/trigger/providers/one-drive/locales/fr.json +16 -0
- package/src/trigger/providers/one-drive/locales/ja.json +16 -0
- package/src/trigger/providers/one-drive/locales/ko.json +16 -0
- package/src/trigger/providers/one-drive/locales/ru.json +16 -0
- package/src/trigger/providers/one-drive/locales/zh-CN.json +16 -0
- package/src/trigger/providers/one-drive/locales/zh-TW.json +16 -0
- package/src/trigger/providers/outlook/locales/fr.json +18 -0
- package/src/trigger/providers/outlook/locales/ja.json +18 -0
- package/src/trigger/providers/outlook/locales/ko.json +18 -0
- package/src/trigger/providers/outlook/locales/ru.json +18 -0
- package/src/trigger/providers/outlook/locales/zh-CN.json +18 -0
- package/src/trigger/providers/outlook/locales/zh-TW.json +18 -0
- package/src/trigger/providers/pandadoc/locales/fr.json +12 -0
- package/src/trigger/providers/pandadoc/locales/ja.json +12 -0
- package/src/trigger/providers/pandadoc/locales/ko.json +12 -0
- package/src/trigger/providers/pandadoc/locales/ru.json +12 -0
- package/src/trigger/providers/pandadoc/locales/zh-CN.json +12 -0
- package/src/trigger/providers/pandadoc/locales/zh-TW.json +12 -0
- package/src/trigger/providers/pipedrive/locales/fr.json +15 -0
- package/src/trigger/providers/pipedrive/locales/ja.json +15 -0
- package/src/trigger/providers/pipedrive/locales/ko.json +15 -0
- package/src/trigger/providers/pipedrive/locales/ru.json +15 -0
- package/src/trigger/providers/pipedrive/locales/zh-CN.json +15 -0
- package/src/trigger/providers/pipedrive/locales/zh-TW.json +15 -0
- package/src/trigger/providers/remote.ts +163 -0
- package/src/trigger/providers/resend/locales/fr.json +16 -0
- package/src/trigger/providers/resend/locales/ja.json +16 -0
- package/src/trigger/providers/resend/locales/ko.json +16 -0
- package/src/trigger/providers/resend/locales/ru.json +16 -0
- package/src/trigger/providers/resend/locales/zh-CN.json +16 -0
- package/src/trigger/providers/resend/locales/zh-TW.json +16 -0
- package/src/trigger/providers/sentry/locales/fr.json +19 -0
- package/src/trigger/providers/sentry/locales/ja.json +19 -0
- package/src/trigger/providers/sentry/locales/ko.json +19 -0
- package/src/trigger/providers/sentry/locales/ru.json +19 -0
- package/src/trigger/providers/sentry/locales/zh-CN.json +19 -0
- package/src/trigger/providers/sentry/locales/zh-TW.json +19 -0
- package/src/trigger/providers/shopify/locales/fr.json +18 -0
- package/src/trigger/providers/shopify/locales/ja.json +18 -0
- package/src/trigger/providers/shopify/locales/ko.json +18 -0
- package/src/trigger/providers/shopify/locales/ru.json +18 -0
- package/src/trigger/providers/shopify/locales/zh-CN.json +18 -0
- package/src/trigger/providers/shopify/locales/zh-TW.json +18 -0
- package/src/trigger/providers/slack/locales/fr.json +18 -0
- package/src/trigger/providers/slack/locales/ja.json +18 -0
- package/src/trigger/providers/slack/locales/ko.json +18 -0
- package/src/trigger/providers/slack/locales/ru.json +18 -0
- package/src/trigger/providers/slack/locales/zh-CN.json +18 -0
- package/src/trigger/providers/slack/locales/zh-TW.json +18 -0
- package/src/trigger/providers/slackbot/locales/fr.json +23 -0
- package/src/trigger/providers/slackbot/locales/ja.json +23 -0
- package/src/trigger/providers/slackbot/locales/ko.json +23 -0
- package/src/trigger/providers/slackbot/locales/ru.json +23 -0
- package/src/trigger/providers/slackbot/locales/zh-CN.json +23 -0
- package/src/trigger/providers/slackbot/locales/zh-TW.json +23 -0
- package/src/trigger/providers/source-events.ts +25 -0
- package/src/trigger/providers/stripe/locales/fr.json +17 -0
- package/src/trigger/providers/stripe/locales/ja.json +17 -0
- package/src/trigger/providers/stripe/locales/ko.json +17 -0
- package/src/trigger/providers/stripe/locales/ru.json +17 -0
- package/src/trigger/providers/stripe/locales/zh-CN.json +17 -0
- package/src/trigger/providers/stripe/locales/zh-TW.json +17 -0
- package/src/trigger/providers/tally/locales/fr.json +12 -0
- package/src/trigger/providers/tally/locales/ja.json +12 -0
- package/src/trigger/providers/tally/locales/ko.json +12 -0
- package/src/trigger/providers/tally/locales/ru.json +12 -0
- package/src/trigger/providers/tally/locales/zh-CN.json +12 -0
- package/src/trigger/providers/tally/locales/zh-TW.json +12 -0
- package/src/trigger/providers/telegram/locales/fr.json +17 -0
- package/src/trigger/providers/telegram/locales/ja.json +17 -0
- package/src/trigger/providers/telegram/locales/ko.json +17 -0
- package/src/trigger/providers/telegram/locales/ru.json +17 -0
- package/src/trigger/providers/telegram/locales/zh-CN.json +17 -0
- package/src/trigger/providers/telegram/locales/zh-TW.json +17 -0
- package/src/trigger/providers/typeform/locales/fr.json +12 -0
- package/src/trigger/providers/typeform/locales/ja.json +12 -0
- package/src/trigger/providers/typeform/locales/ko.json +12 -0
- package/src/trigger/providers/typeform/locales/ru.json +12 -0
- package/src/trigger/providers/typeform/locales/zh-CN.json +12 -0
- package/src/trigger/providers/typeform/locales/zh-TW.json +12 -0
- package/src/trigger/providers/vercel/locales/fr.json +18 -0
- package/src/trigger/providers/vercel/locales/ja.json +18 -0
- package/src/trigger/providers/vercel/locales/ko.json +18 -0
- package/src/trigger/providers/vercel/locales/ru.json +18 -0
- package/src/trigger/providers/vercel/locales/zh-CN.json +18 -0
- package/src/trigger/providers/vercel/locales/zh-TW.json +18 -0
- package/src/trigger/providers/woocommerce/locales/fr.json +19 -0
- package/src/trigger/providers/woocommerce/locales/ja.json +19 -0
- package/src/trigger/providers/woocommerce/locales/ko.json +19 -0
- package/src/trigger/providers/woocommerce/locales/ru.json +19 -0
- package/src/trigger/providers/woocommerce/locales/zh-CN.json +19 -0
- package/src/trigger/providers/woocommerce/locales/zh-TW.json +19 -0
- package/src/trigger/providers/zendesk/locales/fr.json +15 -0
- package/src/trigger/providers/zendesk/locales/ja.json +15 -0
- package/src/trigger/providers/zendesk/locales/ko.json +15 -0
- package/src/trigger/providers/zendesk/locales/ru.json +15 -0
- package/src/trigger/providers/zendesk/locales/zh-CN.json +15 -0
- package/src/trigger/providers/zendesk/locales/zh-TW.json +15 -0
- package/src/trigger/providers/zoom/locales/fr.json +17 -0
- package/src/trigger/providers/zoom/locales/ja.json +17 -0
- package/src/trigger/providers/zoom/locales/ko.json +17 -0
- package/src/trigger/providers/zoom/locales/ru.json +17 -0
- package/src/trigger/providers/zoom/locales/zh-CN.json +17 -0
- package/src/trigger/providers/zoom/locales/zh-TW.json +17 -0
- package/src/types/index.ts +112 -0
- package/src/workflow/common/workflow.ts +273 -0
- package/dist/browser/lib-DrKav3HG.js +0 -11403
- /package/dist/{browser → common}/flow-authoring-edge.d.ts +0 -0
- /package/dist/{browser → common}/flow-authoring-module.d.ts +0 -0
- /package/dist/{browser → common}/flow-authoring.d.ts +0 -0
- /package/dist/{browser → common}/flow-input-value.d.ts +0 -0
- /package/dist/{browser → common}/webhook-method.d.ts +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { JsonValue, TriggerSchedule } from '../../flow/common/change.ts'
|
|
2
|
+
|
|
3
|
+
import { Cron } from 'croner'
|
|
4
|
+
|
|
5
|
+
const minuteMs = 60_000
|
|
6
|
+
const hourMs = 60 * minuteMs
|
|
7
|
+
const dayMs = 24 * hourMs
|
|
8
|
+
const weekAnchorMs = 4 * dayMs
|
|
9
|
+
const maximumIntervalMs = 366 * dayMs
|
|
10
|
+
const encoder = new TextEncoder()
|
|
11
|
+
|
|
12
|
+
const unitMs = {
|
|
13
|
+
day: dayMs,
|
|
14
|
+
hour: hourMs,
|
|
15
|
+
minute: minuteMs,
|
|
16
|
+
week: 7 * dayMs,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
export function validateTriggerSchedule(rules: readonly TriggerSchedule[]): void {
|
|
20
|
+
if (rules.length == 0) throw new TypeError('At least one schedule rule is required.')
|
|
21
|
+
for (const rule of rules) {
|
|
22
|
+
if (rule.type == 'every') {
|
|
23
|
+
if (!Number.isSafeInteger(rule.value) || rule.value < 1) throw new TypeError('Every rule value must be a positive integer.')
|
|
24
|
+
if ((rule.unit == 'month' && rule.value > 12) || (rule.unit != 'month' && rule.value * unitMs[rule.unit] > maximumIntervalMs)) {
|
|
25
|
+
throw new TypeError('Every rule interval must not exceed one year.')
|
|
26
|
+
}
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
Intl.DateTimeFormat('en-US', { timeZone: rule.timezone })
|
|
31
|
+
} catch {
|
|
32
|
+
throw new TypeError(`Invalid IANA timezone: ${rule.timezone}`)
|
|
33
|
+
}
|
|
34
|
+
validateCronExpression(rule.expression, rule.timezone)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function validateCronExpression(expression: string, timezone: string): void {
|
|
39
|
+
if (expression.trim().split(/\s+/).length != 5) throw new TypeError('Cron expression must have exactly five fields.')
|
|
40
|
+
let cron: Cron
|
|
41
|
+
try {
|
|
42
|
+
cron = new Cron(expression, { timezone })
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new TypeError(`Invalid cron expression: ${error instanceof Error ? error.message : String(error)}`, { cause: error })
|
|
45
|
+
}
|
|
46
|
+
if (cron.nextRun() == null) throw new TypeError('Cron expression has no future occurrence.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function nextTriggerScheduledAt(rules: readonly TriggerSchedule[], afterMs: number): number {
|
|
50
|
+
let earliest = Infinity
|
|
51
|
+
for (const rule of rules) earliest = Math.min(earliest, nextForRule(rule, afterMs))
|
|
52
|
+
if (!Number.isFinite(earliest)) throw new Error('Schedule has no future occurrence.')
|
|
53
|
+
return earliest
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function scheduledTriggerOccurrenceId(bindingId: string, runtimeVersion: number, scheduledAt: string): Promise<string> {
|
|
57
|
+
const value = encoder.encode(`${bindingId}\0${runtimeVersion}\0${scheduledAt}`)
|
|
58
|
+
const digest = await crypto.subtle.digest('SHA-256', value)
|
|
59
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CronConformanceFixture {
|
|
63
|
+
readonly publishedAt: string
|
|
64
|
+
readonly rules: readonly TriggerSchedule[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CronConformanceHarness {
|
|
68
|
+
dispose(): Promise<void>
|
|
69
|
+
outputs(): Promise<readonly Readonly<Record<string, JsonValue>>[]>
|
|
70
|
+
republish(at: string, rules: readonly TriggerSchedule[]): Promise<void>
|
|
71
|
+
retire(at: string): Promise<void>
|
|
72
|
+
tick(at: string): Promise<void>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CronConformanceCase {
|
|
76
|
+
readonly fixture: CronConformanceFixture
|
|
77
|
+
readonly name: string
|
|
78
|
+
verify(harness: CronConformanceHarness): Promise<void>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const hourly: readonly TriggerSchedule[] = [{ type: 'every', unit: 'hour', value: 1 }]
|
|
82
|
+
const minutely: readonly TriggerSchedule[] = [{ type: 'every', unit: 'minute', value: 1 }]
|
|
83
|
+
|
|
84
|
+
export const cronConformanceCases: readonly CronConformanceCase[] = [
|
|
85
|
+
{
|
|
86
|
+
fixture: { publishedAt: '2026-08-21T00:30:00.000Z', rules: hourly },
|
|
87
|
+
name: 'admits an exact schedule grid once',
|
|
88
|
+
async verify(harness) {
|
|
89
|
+
await harness.tick('2026-08-21T01:00:00.000Z')
|
|
90
|
+
await harness.tick('2026-08-21T01:00:00.000Z')
|
|
91
|
+
equal(await harness.outputs(), [{ scheduledAt: '2026-08-21T01:00:00.000Z' }], 'Exact schedule outputs')
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
fixture: { publishedAt: '2026-08-21T00:00:30.000Z', rules: minutely },
|
|
96
|
+
name: 'coalesces missed grids and preserves the earliest due time',
|
|
97
|
+
async verify(harness) {
|
|
98
|
+
await harness.tick('2026-08-21T00:03:30.000Z')
|
|
99
|
+
equal(await harness.outputs(), [{ scheduledAt: '2026-08-21T00:01:00.000Z' }], 'Delayed schedule outputs')
|
|
100
|
+
await harness.tick('2026-08-21T00:04:00.000Z')
|
|
101
|
+
equal(await harness.outputs(), [{ scheduledAt: '2026-08-21T00:01:00.000Z' }, { scheduledAt: '2026-08-21T00:04:00.000Z' }], 'Advanced schedule outputs')
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
fixture: { publishedAt: '2026-08-21T00:00:30.000Z', rules: minutely },
|
|
106
|
+
name: 'resets the schedule when its publication changes',
|
|
107
|
+
async verify(harness) {
|
|
108
|
+
await harness.republish('2026-08-21T00:00:45.000Z', hourly)
|
|
109
|
+
await harness.tick('2026-08-21T00:01:00.000Z')
|
|
110
|
+
equal(await harness.outputs(), [], 'Outputs from the previous publication')
|
|
111
|
+
await harness.tick('2026-08-21T01:00:00.000Z')
|
|
112
|
+
equal(await harness.outputs(), [{ scheduledAt: '2026-08-21T01:00:00.000Z' }], 'Republished schedule outputs')
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
fixture: { publishedAt: '2026-08-21T00:30:00.000Z', rules: hourly },
|
|
117
|
+
name: 'stops admitting runs after retirement',
|
|
118
|
+
async verify(harness) {
|
|
119
|
+
await harness.retire('2026-08-21T00:45:00.000Z')
|
|
120
|
+
await harness.tick('2026-08-21T01:00:00.000Z')
|
|
121
|
+
equal(await harness.outputs(), [], 'Retired schedule outputs')
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
function nextForRule(rule: TriggerSchedule, afterMs: number): number {
|
|
127
|
+
if (rule.type == 'cron') {
|
|
128
|
+
const next = new Cron(rule.expression, { timezone: rule.timezone }).nextRun(new Date(afterMs))
|
|
129
|
+
return next?.getTime() ?? Infinity
|
|
130
|
+
}
|
|
131
|
+
if (rule.unit == 'month') {
|
|
132
|
+
const after = new Date(afterMs)
|
|
133
|
+
const monthIndex = (after.getUTCFullYear() - 1970) * 12 + after.getUTCMonth()
|
|
134
|
+
let gridMonth = Math.floor(monthIndex / rule.value) * rule.value
|
|
135
|
+
let candidate = Date.UTC(1970, gridMonth, 1)
|
|
136
|
+
while (candidate <= afterMs) {
|
|
137
|
+
gridMonth += rule.value
|
|
138
|
+
candidate = Date.UTC(1970, gridMonth, 1)
|
|
139
|
+
}
|
|
140
|
+
return candidate
|
|
141
|
+
}
|
|
142
|
+
const period = rule.value * unitMs[rule.unit]
|
|
143
|
+
const anchor = rule.unit == 'week' ? weekAnchorMs : 0
|
|
144
|
+
return anchor + (Math.floor((afterMs - anchor) / period) + 1) * period
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function equal(actual: unknown, expected: unknown, message: string): void {
|
|
148
|
+
if (JSON.stringify(actual) != JSON.stringify(expected)) {
|
|
149
|
+
throw new Error(`${message}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}.`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
export { resolveTriggerConfig, triggerConfigValues } from './config.ts'
|
|
2
|
+
import type { ConnectorProxy } from '../../connector/common/proxy.ts'
|
|
3
|
+
import type { IntegrationEndpointMethod, JsonValue, TriggerKeySnapshot } from '../../flow/common/change.ts'
|
|
4
|
+
import type { TriggerConfigOption, TriggerConfigOptionsContext } from './configOptions.ts'
|
|
5
|
+
|
|
6
|
+
import { dequal } from 'dequal/lite'
|
|
7
|
+
import { isJsonObject, isJsonValue } from '../../base/common/json.ts'
|
|
8
|
+
|
|
9
|
+
export const maximumIntegrationBodyBytes = 64 * 1024
|
|
10
|
+
export const maximumIntegrationDeliveryPages = 5
|
|
11
|
+
|
|
12
|
+
const endpointPattern = /^\/v1\/integrations\/(endpoint_[0-9a-f]{32})$/
|
|
13
|
+
const encoder = new TextEncoder()
|
|
14
|
+
|
|
15
|
+
export interface ListenerReadContext {
|
|
16
|
+
readonly checkpoint: JsonValue
|
|
17
|
+
readonly config: Readonly<Record<string, JsonValue>>
|
|
18
|
+
readonly connector: ConnectorProxy
|
|
19
|
+
readonly now: Date
|
|
20
|
+
readonly signal?: AbortSignal
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ListenerPage {
|
|
24
|
+
readonly checkpoint: JsonValue
|
|
25
|
+
readonly dedupeKey: string
|
|
26
|
+
readonly hasMore: boolean
|
|
27
|
+
readonly outputs: Readonly<Record<string, JsonValue>> | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ListenerSource {
|
|
31
|
+
readonly intervalMs: number
|
|
32
|
+
readonly read: (context: ListenerReadContext) => Promise<ListenerPage>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateListenerPage(value: unknown): asserts value is ListenerPage {
|
|
36
|
+
if (!isJsonObject(value) || value.checkpoint == null || !isJsonValue(value.checkpoint) || typeof value.hasMore != 'boolean') {
|
|
37
|
+
throw new TypeError('Listener page must contain a JSON checkpoint and a boolean hasMore.')
|
|
38
|
+
}
|
|
39
|
+
if (typeof value.dedupeKey != 'string' || value.dedupeKey.length == 0 || encoder.encode(value.dedupeKey).byteLength > 1024) {
|
|
40
|
+
throw new TypeError('Listener page identity must contain between 1 and 1024 bytes.')
|
|
41
|
+
}
|
|
42
|
+
if (value.outputs !== null && !isJsonObject(value.outputs)) throw new TypeError('Listener page outputs must be an object or null.')
|
|
43
|
+
if (encoder.encode(JSON.stringify(value.checkpoint)).byteLength > 64 * 1024) {
|
|
44
|
+
throw new TypeError('Listener checkpoint exceeds 64 KiB.')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type IntegrationReceiveResult =
|
|
49
|
+
| { readonly outcome: 'wake' }
|
|
50
|
+
| {
|
|
51
|
+
readonly checkpoint?: JsonValue
|
|
52
|
+
readonly continue?: boolean
|
|
53
|
+
readonly dedupeKey?: string
|
|
54
|
+
readonly outcome: 'event'
|
|
55
|
+
readonly outputs: Readonly<Record<string, JsonValue>>
|
|
56
|
+
}
|
|
57
|
+
| { readonly checkpoint?: JsonValue; readonly continue?: boolean; readonly outcome: 'ignored'; readonly reason: string }
|
|
58
|
+
| {
|
|
59
|
+
readonly body: string
|
|
60
|
+
readonly contentType: string
|
|
61
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
62
|
+
readonly outcome: 'respond'
|
|
63
|
+
readonly status: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface IntegrationStateContext {
|
|
67
|
+
readonly checkpoint: JsonValue
|
|
68
|
+
readonly subscription: Readonly<Record<string, JsonValue>>
|
|
69
|
+
readonly saveCheckpoint: (checkpoint: JsonValue) => Promise<void>
|
|
70
|
+
readonly saveSubscription: (subscription: Readonly<Record<string, JsonValue>>, reconcileAt: Date) => Promise<void>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface IntegrationReceiveContext {
|
|
74
|
+
readonly eventSourceId?: string
|
|
75
|
+
readonly signal?: AbortSignal
|
|
76
|
+
readonly admit: boolean
|
|
77
|
+
readonly allow?: () => Promise<boolean>
|
|
78
|
+
readonly bindingId: string
|
|
79
|
+
readonly callbackSecret: string
|
|
80
|
+
readonly config: Readonly<Record<string, JsonValue>>
|
|
81
|
+
readonly connector: ConnectorProxy
|
|
82
|
+
readonly current: boolean
|
|
83
|
+
readonly header: (name: string) => string | undefined
|
|
84
|
+
readonly method: IntegrationEndpointMethod
|
|
85
|
+
readonly now: Date
|
|
86
|
+
readonly payload: JsonValue
|
|
87
|
+
readonly query: (name: string) => string | undefined
|
|
88
|
+
readonly rawBody: Uint8Array
|
|
89
|
+
readonly state?: IntegrationStateContext
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface IntegrationReconcileContext {
|
|
93
|
+
readonly active: boolean
|
|
94
|
+
readonly callbackSecret: string
|
|
95
|
+
readonly config: Readonly<Record<string, JsonValue>>
|
|
96
|
+
readonly connector: ConnectorProxy
|
|
97
|
+
readonly endpointUrl: string
|
|
98
|
+
readonly idempotencyKey: string
|
|
99
|
+
readonly now: Date
|
|
100
|
+
readonly signal?: AbortSignal
|
|
101
|
+
readonly state?: IntegrationStateContext
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface IntegrationReconcileResult {
|
|
105
|
+
readonly outcome: 'pending' | 'ready'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface IntegrationDefinition {
|
|
109
|
+
readonly eventSource?: 'feishu' | 'slack' | 'intercom' | 'sentry' | 'zoom' | 'fireflies' | 'jira'
|
|
110
|
+
readonly configOptions?: (context: TriggerConfigOptionsContext) => Promise<readonly TriggerConfigOption[]>
|
|
111
|
+
readonly listener?: ListenerSource
|
|
112
|
+
readonly initialState?: { readonly checkpoint: JsonValue; readonly subscription: Readonly<Record<string, JsonValue>> }
|
|
113
|
+
readonly receive: (context: IntegrationReceiveContext) => IntegrationReceiveResult | Promise<IntegrationReceiveResult>
|
|
114
|
+
readonly reconcile: (context: IntegrationReconcileContext) => Promise<IntegrationReconcileResult>
|
|
115
|
+
readonly snapshot: TriggerKeySnapshot & { readonly type: 'integration' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class IntegrationConnectionError extends Error {
|
|
119
|
+
override readonly name = 'IntegrationConnectionError'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class PermanentIntegrationError extends Error {
|
|
123
|
+
override readonly name = 'PermanentIntegrationError'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class TransientIntegrationError extends Error {
|
|
127
|
+
override readonly name = 'TransientIntegrationError'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function integrationEndpointId(url: URL): string | undefined {
|
|
131
|
+
return endpointPattern.exec(url.pathname)?.[1]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function integrationCallbackSecret(callbackKey: string | undefined, endpointId: string): Promise<string> {
|
|
135
|
+
if (callbackKey == null || callbackKey.length == 0) throw new TypeError('Integration callback key is required.')
|
|
136
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(callbackKey), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign'])
|
|
137
|
+
const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder.encode(`open-flow-integration/v1/${endpointId}`)))
|
|
138
|
+
return btoa(String.fromCharCode(...signature))
|
|
139
|
+
.replaceAll('+', '-')
|
|
140
|
+
.replaceAll('/', '_')
|
|
141
|
+
.replaceAll('=', '')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function integrationOccurrenceId(bindingId: string, runtimeVersion: number, definitionKey: string, dedupeKey: string | null): Promise<string> {
|
|
145
|
+
if (dedupeKey == null) return `integration_${crypto.randomUUID().replaceAll('-', '')}`
|
|
146
|
+
const source = JSON.stringify([1, 'integration-occurrence', bindingId, runtimeVersion, definitionKey, dedupeKey])
|
|
147
|
+
const bytes = new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(source)))
|
|
148
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface IntegrationConformanceFixture {
|
|
152
|
+
readonly config: Readonly<Record<string, JsonValue>>
|
|
153
|
+
readonly connectionId: string
|
|
154
|
+
readonly definition: Pick<IntegrationDefinition, 'initialState' | 'listener' | 'receive' | 'reconcile'>
|
|
155
|
+
readonly publishedAt: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface IntegrationConformanceState {
|
|
159
|
+
readonly checkpoint: JsonValue | null
|
|
160
|
+
readonly health: 'healthy' | 'initializing'
|
|
161
|
+
readonly payloads: readonly JsonValue[]
|
|
162
|
+
readonly receiveCalls: number
|
|
163
|
+
readonly reconcileCalls: number
|
|
164
|
+
readonly runtimeVersion: number
|
|
165
|
+
readonly subscription: Readonly<Record<string, JsonValue>> | null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface IntegrationConformanceHarness {
|
|
169
|
+
readonly callbackSecret: string
|
|
170
|
+
readonly endpointUrl: string
|
|
171
|
+
dispose(): Promise<void>
|
|
172
|
+
reconcile(at: string): Promise<void>
|
|
173
|
+
republish(at: string): Promise<void>
|
|
174
|
+
request(request: Request): Promise<Response>
|
|
175
|
+
restart(): Promise<void>
|
|
176
|
+
retire(at: string): Promise<void>
|
|
177
|
+
state(): Promise<IntegrationConformanceState>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface IntegrationConformanceCase {
|
|
181
|
+
readonly fixture: IntegrationConformanceFixture
|
|
182
|
+
readonly name: string
|
|
183
|
+
verify(harness: IntegrationConformanceHarness): Promise<void>
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const conformanceDefinition: IntegrationConformanceFixture['definition'] = {
|
|
187
|
+
initialState: { checkpoint: null, subscription: {} },
|
|
188
|
+
receive(context): IntegrationReceiveResult {
|
|
189
|
+
if (context.header('x-integration-secret') != context.callbackSecret) {
|
|
190
|
+
return { body: '', contentType: 'text/plain', outcome: 'respond', status: 404 }
|
|
191
|
+
}
|
|
192
|
+
const value = record(context.payload)
|
|
193
|
+
if (value?.action == 'handshake') {
|
|
194
|
+
return {
|
|
195
|
+
body: 'accepted',
|
|
196
|
+
contentType: 'text/plain',
|
|
197
|
+
headers: { 'x-integration-response': 'conformance' },
|
|
198
|
+
outcome: 'respond',
|
|
199
|
+
status: 201,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (value?.action == 'ignored') return { outcome: 'ignored', reason: 'Conformance delivery was ignored.' }
|
|
203
|
+
if (value?.action == 'pages') {
|
|
204
|
+
const checkpoint = record(context.state?.checkpoint)
|
|
205
|
+
const page = typeof checkpoint?.page == 'number' ? checkpoint.page + 1 : 1
|
|
206
|
+
const pages = typeof value.pages == 'number' ? value.pages : 1
|
|
207
|
+
const deliveryId = typeof value.deliveryId == 'string' ? value.deliveryId : 'pages'
|
|
208
|
+
return {
|
|
209
|
+
checkpoint: { page },
|
|
210
|
+
continue: page < pages,
|
|
211
|
+
dedupeKey: `${deliveryId}:${page}`,
|
|
212
|
+
outcome: 'event',
|
|
213
|
+
outputs: { payload: { body: { page }, deliveryId, event: 'conformance' } },
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const deliveryId = typeof value?.deliveryId == 'string' ? value.deliveryId : 'delivery'
|
|
217
|
+
const body = record(value?.body) ?? {}
|
|
218
|
+
return {
|
|
219
|
+
checkpoint: { deliveryId },
|
|
220
|
+
dedupeKey: deliveryId,
|
|
221
|
+
outcome: 'event',
|
|
222
|
+
outputs: { payload: { body, deliveryId, event: 'conformance' } },
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
async reconcile(context) {
|
|
226
|
+
if (context.state == null) throw new PermanentIntegrationError('Conformance Integration state is missing.')
|
|
227
|
+
await context.state.saveSubscription(context.active ? { endpointUrl: context.endpointUrl } : {}, new Date(context.now.getTime() + 60_000))
|
|
228
|
+
return { outcome: 'ready' }
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fixture: IntegrationConformanceFixture = {
|
|
233
|
+
config: { source: 'primary' },
|
|
234
|
+
connectionId: 'connection-primary',
|
|
235
|
+
definition: conformanceDefinition,
|
|
236
|
+
publishedAt: '2026-08-21T00:00:30.000Z',
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const integrationConformanceCases: readonly IntegrationConformanceCase[] = [
|
|
240
|
+
{
|
|
241
|
+
fixture,
|
|
242
|
+
name: 'enforces callback routing, method, body, and handshake boundaries',
|
|
243
|
+
async verify(harness) {
|
|
244
|
+
const base = new URL(harness.endpointUrl)
|
|
245
|
+
await response(await call(harness, { url: new URL('/v1/integrations/not-an-endpoint', base).href }), { status: 404 }, 'Malformed endpoint')
|
|
246
|
+
await response(
|
|
247
|
+
await call(harness, { url: new URL('/v1/integrations/endpoint_00000000000000000000000000000000', base).href }),
|
|
248
|
+
{ status: 404 },
|
|
249
|
+
'Unknown endpoint',
|
|
250
|
+
)
|
|
251
|
+
await response(await call(harness, { method: 'GET' }), { headers: { allow: 'POST' }, status: 405 }, 'Method')
|
|
252
|
+
await response(await call(harness, { body: '{' }), { status: 400 }, 'Invalid JSON')
|
|
253
|
+
await response(
|
|
254
|
+
await call(harness, { body: 'a=1', headers: { 'content-type': 'application/x-www-form-urlencoded' } }),
|
|
255
|
+
{ status: 400 },
|
|
256
|
+
'Unsupported body format',
|
|
257
|
+
)
|
|
258
|
+
await response(await call(harness, { body: 'x'.repeat(maximumIntegrationBodyBytes + 1) }), { status: 413 }, 'Oversized body')
|
|
259
|
+
await response(
|
|
260
|
+
await delivery(harness, { action: 'handshake' }),
|
|
261
|
+
{ body: 'accepted', headers: { 'content-type': 'text/plain', 'x-integration-response': 'conformance' }, status: 201 },
|
|
262
|
+
'Handshake',
|
|
263
|
+
)
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
fixture,
|
|
268
|
+
name: 'verifies initializing callbacks before activating the subscription',
|
|
269
|
+
async verify(harness) {
|
|
270
|
+
await response(await delivery(harness, { action: 'event', body: { value: 'before' }, deliveryId: 'before' }, false), { status: 404 }, 'Secret')
|
|
271
|
+
await response(await delivery(harness, { action: 'event', body: { value: 'before' }, deliveryId: 'before' }), { status: 202 }, 'Initializing delivery')
|
|
272
|
+
equal(
|
|
273
|
+
await harness.state(),
|
|
274
|
+
{
|
|
275
|
+
checkpoint: null,
|
|
276
|
+
health: 'initializing',
|
|
277
|
+
payloads: [],
|
|
278
|
+
receiveCalls: 2,
|
|
279
|
+
reconcileCalls: 0,
|
|
280
|
+
runtimeVersion: 1,
|
|
281
|
+
subscription: {},
|
|
282
|
+
},
|
|
283
|
+
'Initializing state',
|
|
284
|
+
)
|
|
285
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
286
|
+
equal(
|
|
287
|
+
await harness.state(),
|
|
288
|
+
{
|
|
289
|
+
checkpoint: null,
|
|
290
|
+
health: 'healthy',
|
|
291
|
+
payloads: [],
|
|
292
|
+
receiveCalls: 2,
|
|
293
|
+
reconcileCalls: 1,
|
|
294
|
+
runtimeVersion: 1,
|
|
295
|
+
subscription: { endpointUrl: harness.endpointUrl },
|
|
296
|
+
},
|
|
297
|
+
'Active subscription',
|
|
298
|
+
)
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
fixture,
|
|
303
|
+
name: 'deduplicates keyed events and rejects conflicting payloads',
|
|
304
|
+
async verify(harness) {
|
|
305
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
306
|
+
const first = { action: 'event', body: { value: 'first' }, deliveryId: 'delivery-1' }
|
|
307
|
+
await response(await delivery(harness, first), { status: 202 }, 'Initial event')
|
|
308
|
+
await response(await delivery(harness, first), { status: 202 }, 'Repeated event')
|
|
309
|
+
await response(await delivery(harness, { ...first, body: { value: 'changed' } }), { status: 409 }, 'Conflicting event')
|
|
310
|
+
const state = await harness.state()
|
|
311
|
+
equal(state.checkpoint, { deliveryId: 'delivery-1' }, 'Event checkpoint')
|
|
312
|
+
equal(state.payloads, [{ body: { value: 'first' }, deliveryId: 'delivery-1', event: 'conformance' }], 'Event payloads')
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
fixture,
|
|
317
|
+
name: 'bounds callback pages and resumes from the durable checkpoint',
|
|
318
|
+
async verify(harness) {
|
|
319
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
320
|
+
const pages = { action: 'pages', deliveryId: 'delivery-pages', pages: 7 }
|
|
321
|
+
await response(await delivery(harness, pages), { status: 202 }, 'First page batch')
|
|
322
|
+
let state = await harness.state()
|
|
323
|
+
equal(state.checkpoint, { page: 5 }, 'Bounded checkpoint')
|
|
324
|
+
equal(state.payloads.length, 5, 'Bounded payload count')
|
|
325
|
+
await response(await delivery(harness, pages), { status: 202 }, 'Resumed page batch')
|
|
326
|
+
state = await harness.state()
|
|
327
|
+
equal(state.checkpoint, { page: 7 }, 'Resumed checkpoint')
|
|
328
|
+
equal(state.payloads.length, 7, 'Resumed payload count')
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
fixture,
|
|
333
|
+
name: 'restores subscription state and callback admission after restart',
|
|
334
|
+
async verify(harness) {
|
|
335
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
336
|
+
await delivery(harness, { action: 'event', body: { value: 'before' }, deliveryId: 'before' })
|
|
337
|
+
await harness.restart()
|
|
338
|
+
await delivery(harness, { action: 'event', body: { value: 'after' }, deliveryId: 'after' })
|
|
339
|
+
const state = await harness.state()
|
|
340
|
+
equal(state.checkpoint, { deliveryId: 'after' }, 'Restart checkpoint')
|
|
341
|
+
equal(
|
|
342
|
+
state.payloads,
|
|
343
|
+
[
|
|
344
|
+
{ body: { value: 'before' }, deliveryId: 'before', event: 'conformance' },
|
|
345
|
+
{ body: { value: 'after' }, deliveryId: 'after', event: 'conformance' },
|
|
346
|
+
],
|
|
347
|
+
'Restart payloads',
|
|
348
|
+
)
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
fixture,
|
|
353
|
+
name: 'retires the old runtime before activating a republished target',
|
|
354
|
+
async verify(harness) {
|
|
355
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
356
|
+
const event = { action: 'event', body: { value: 'event' }, deliveryId: 'same-delivery' }
|
|
357
|
+
await delivery(harness, event)
|
|
358
|
+
const endpointUrl = harness.endpointUrl
|
|
359
|
+
await harness.republish('2026-08-21T00:00:40.000Z')
|
|
360
|
+
equal(harness.endpointUrl, endpointUrl, 'Republished endpoint')
|
|
361
|
+
await response(await delivery(harness, event), { status: 202 }, 'Old runtime delivery')
|
|
362
|
+
equal((await harness.state()).payloads.length, 1, 'Old runtime payload count')
|
|
363
|
+
await harness.reconcile('2026-08-21T00:00:41.000Z')
|
|
364
|
+
await response(await delivery(harness, event), { status: 202 }, 'New runtime delivery')
|
|
365
|
+
const state = await harness.state()
|
|
366
|
+
equal(state.runtimeVersion, 2, 'Republished runtime')
|
|
367
|
+
equal(state.health, 'healthy', 'Republished health')
|
|
368
|
+
equal(state.payloads.length, 2, 'Republished payload count')
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
fixture,
|
|
373
|
+
name: 'retires the remote subscription and rejects later callbacks',
|
|
374
|
+
async verify(harness) {
|
|
375
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
376
|
+
await harness.retire('2026-08-21T00:00:40.000Z')
|
|
377
|
+
await response(await delivery(harness, { action: 'event', deliveryId: 'retired' }), { status: 404 }, 'Retired callback')
|
|
378
|
+
await harness.reconcile('2026-08-21T00:00:41.000Z')
|
|
379
|
+
await response(await delivery(harness, { action: 'event', deliveryId: 'retired' }), { status: 404 }, 'Reconciled retirement')
|
|
380
|
+
equal((await harness.state()).payloads, [], 'Retired payloads')
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
const listenerFixture: IntegrationConformanceFixture = {
|
|
386
|
+
...fixture,
|
|
387
|
+
definition: {
|
|
388
|
+
initialState: { checkpoint: null, subscription: {} },
|
|
389
|
+
async reconcile({ active, state, now }) {
|
|
390
|
+
if (active && state?.checkpoint == null) await state!.saveCheckpoint(0)
|
|
391
|
+
if (active) await state!.saveSubscription({}, new Date(now.getTime() + 3_600_000))
|
|
392
|
+
return { outcome: 'ready' }
|
|
393
|
+
},
|
|
394
|
+
receive(context) {
|
|
395
|
+
return context.header('x-integration-secret') == context.callbackSecret
|
|
396
|
+
? { outcome: 'wake' }
|
|
397
|
+
: { outcome: 'respond', status: 404, body: '', contentType: 'text/plain' }
|
|
398
|
+
},
|
|
399
|
+
listener: {
|
|
400
|
+
intervalMs: 60_000,
|
|
401
|
+
async read({ checkpoint, now }) {
|
|
402
|
+
const cursor = Number(checkpoint)
|
|
403
|
+
const available = now.getTime() >= Date.parse('2026-08-21T00:01:31.000Z') ? 8 : 7
|
|
404
|
+
return cursor < available
|
|
405
|
+
? {
|
|
406
|
+
checkpoint: cursor + 1,
|
|
407
|
+
dedupeKey: String(cursor),
|
|
408
|
+
hasMore: cursor + 1 < available,
|
|
409
|
+
outputs: { payload: { body: {}, deliveryId: String(cursor), event: 'change' } },
|
|
410
|
+
}
|
|
411
|
+
: { checkpoint, dedupeKey: String(cursor), hasMore: false, outputs: null }
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export const listenerConformanceCases: readonly IntegrationConformanceCase[] = [
|
|
418
|
+
{
|
|
419
|
+
fixture: listenerFixture,
|
|
420
|
+
name: 'drains listener pages without a callback and resumes periodic scanning after restart',
|
|
421
|
+
async verify(harness) {
|
|
422
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
423
|
+
equal((await harness.state()).checkpoint, 7, 'Drained cursor')
|
|
424
|
+
equal((await harness.state()).payloads.length, 7, 'Drained pages')
|
|
425
|
+
await harness.restart()
|
|
426
|
+
await harness.reconcile('2026-08-21T00:01:31.000Z')
|
|
427
|
+
equal((await harness.state()).checkpoint, 8, 'Periodic cursor')
|
|
428
|
+
equal((await harness.state()).payloads.length, 8, 'Periodic pages')
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
fixture: listenerFixture,
|
|
433
|
+
name: 'persists verified listener wakes across restart without duplicating accepted pages',
|
|
434
|
+
async verify(harness) {
|
|
435
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
436
|
+
await response(await delivery(harness, {}, false), { status: 404 }, 'Invalid wake')
|
|
437
|
+
await response(await delivery(harness, {}), { status: 202 }, 'First wake')
|
|
438
|
+
await response(await delivery(harness, {}), { status: 202 }, 'Duplicate wake')
|
|
439
|
+
await harness.restart()
|
|
440
|
+
await harness.reconcile('2026-08-21T00:00:32.000Z')
|
|
441
|
+
equal((await harness.state()).checkpoint, 7, 'Retained cursor')
|
|
442
|
+
equal((await harness.state()).payloads.length, 7, 'Deduplicated pages')
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
fixture: listenerFixture,
|
|
447
|
+
name: 'preserves a listener cursor on unchanged publication and fences retirement',
|
|
448
|
+
async verify(harness) {
|
|
449
|
+
await harness.reconcile('2026-08-21T00:00:31.000Z')
|
|
450
|
+
await harness.republish('2026-08-21T00:00:40.000Z')
|
|
451
|
+
equal((await harness.state()).checkpoint, 7, 'Republished cursor')
|
|
452
|
+
await harness.reconcile('2026-08-21T00:01:31.000Z')
|
|
453
|
+
equal((await harness.state()).payloads.length, 8, 'Republished pages')
|
|
454
|
+
await harness.retire('2026-08-21T00:01:40.000Z')
|
|
455
|
+
await response(await delivery(harness, {}), { status: 404 }, 'Retired wake')
|
|
456
|
+
await harness.reconcile('2026-08-21T00:02:40.000Z')
|
|
457
|
+
equal((await harness.state()).payloads.length, 8, 'Retired pages')
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
]
|
|
461
|
+
|
|
462
|
+
function call(
|
|
463
|
+
harness: IntegrationConformanceHarness,
|
|
464
|
+
input: { readonly body?: string; readonly headers?: Readonly<Record<string, string>>; readonly method?: string; readonly url?: string } = {},
|
|
465
|
+
): Promise<Response> {
|
|
466
|
+
return harness.request(
|
|
467
|
+
new Request(input.url ?? harness.endpointUrl, {
|
|
468
|
+
...(input.body == null ? {} : { body: input.body }),
|
|
469
|
+
headers: { ...(input.body == null ? {} : { 'content-type': 'application/json' }), ...input.headers },
|
|
470
|
+
method: input.method ?? 'POST',
|
|
471
|
+
}),
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function delivery(harness: IntegrationConformanceHarness, body: Readonly<Record<string, JsonValue>>, authenticated = true): Promise<Response> {
|
|
476
|
+
return call(harness, {
|
|
477
|
+
body: JSON.stringify(body),
|
|
478
|
+
headers: authenticated ? { 'x-integration-secret': harness.callbackSecret } : {},
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function equal(actual: unknown, expected: unknown, message: string): void {
|
|
483
|
+
if (!dequal(actual, expected)) throw new Error(`${message}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}.`)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function record(value: unknown): Readonly<Record<string, JsonValue>> | undefined {
|
|
487
|
+
return value != null && typeof value == 'object' && !Array.isArray(value) ? (value as Readonly<Record<string, JsonValue>>) : undefined
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function response(
|
|
491
|
+
actual: Response,
|
|
492
|
+
expected: { readonly body?: string; readonly headers?: Readonly<Record<string, string | null>>; readonly status: number },
|
|
493
|
+
message: string,
|
|
494
|
+
): Promise<void> {
|
|
495
|
+
equal(actual.status, expected.status, `${message} status`)
|
|
496
|
+
if (expected.body != null) equal(await actual.text(), expected.body, `${message} body`)
|
|
497
|
+
for (const [name, value] of Object.entries(expected.headers ?? {})) equal(actual.headers.get(name), value, `${message} header ${name}`)
|
|
498
|
+
}
|