inngest
Version:
Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.
1 lines • 13.1 kB
Source Map (JSON)
{"version":3,"file":"utils.cjs","names":["NonRetriableError","matchingValidators: EventValidator[]","validationPromises: Promise<void>[]","EventType","internalEvents","schemasByEvent: SchemasByTrigger","validatorsByTrigger: ValidatorsByTrigger","error: Error | undefined"],"sources":["../../../src/components/triggers/utils.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { internalEvents } from \"../../helpers/consts\";\nimport type { InngestFunction } from \"../InngestFunction\";\nimport { NonRetriableError } from \"../NonRetriableError\";\nimport { EventType } from \"./triggers\";\nimport type { AnySchema } from \"./typeHelpers\";\n\n/**\n * A validator function that validates event data against a schema. A `null`\n * value means no validation is needed (e.g. trigger has no schema).\n */\ntype EventValidator = ((data: unknown) => Promise<void>) | null;\n\n/**\n * Maps trigger names to their validators. A `null` value means no validation is\n * needed (e.g. trigger has no schema).\n */\ntype ValidatorsByTrigger = Record<string, EventValidator>;\n\n/**\n * Maps trigger names to their associated schemas. Multiple schemas can exist\n * for the same trigger name (e.g. from multiple events).\n */\ntype SchemasByTrigger = Record<string, AnySchema[]>;\n\nclass EventValidationError extends NonRetriableError {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n\n static fromIssues(\n issues: readonly StandardSchemaV1.Issue[],\n ): EventValidationError {\n if (issues.length === 0) {\n // Unreachable\n return new EventValidationError(\"Validation failed\");\n }\n\n const message = issues\n .map((issue) => {\n let path = \"value\";\n if (issue.path && issue.path.length > 0) {\n path = issue.path.join(\".\");\n }\n return `${path}: ${issue.message}`;\n })\n .join(\", \");\n\n return new EventValidationError(message);\n }\n}\n\n/**\n * Validates data against a single schema, throwing an EventValidationError if\n * validation fails.\n */\nasync function validateAgainstSchema(\n schema: AnySchema,\n data: unknown,\n): Promise<void> {\n const result = await schema[\"~standard\"].validate(data);\n if (result.issues) {\n throw EventValidationError.fromIssues(result.issues);\n }\n}\n\n/**\n * Finds all validators that match an event name via wildcard patterns.\n *\n * A wildcard trigger like \"user/*\" matches any event starting with \"user/\". An\n * event like \"user/foo/bar\" can match multiple wildcards (e.g. \"user/*\" and\n * \"user/foo/*\").\n *\n * @returns Array of validators for matching wildcard triggers. Includes `null`\n * entries for wildcards without schemas (meaning \"no validation\n * needed\").\n */\nfunction findWildcardValidators(\n eventName: string,\n validators: ValidatorsByTrigger,\n): EventValidator[] {\n const matchingValidators: EventValidator[] = [];\n\n for (const [triggerName, validator] of Object.entries(validators)) {\n const isWildcard = triggerName.endsWith(\"*\");\n if (!isWildcard) {\n continue;\n }\n\n const wildcardPrefix = triggerName.slice(0, -1);\n const matchesWildcard = eventName.startsWith(wildcardPrefix);\n if (matchesWildcard) {\n matchingValidators.push(validator);\n }\n }\n\n return matchingValidators;\n}\n\n/**\n * Creates a combined validator that runs all provided validators and succeeds\n * if at least one passes (i.e. union validation).\n */\nfunction createUnionValidator(validators: EventValidator[]): EventValidator {\n const nonNullValidators = validators.filter(isNotNull);\n\n if (nonNullValidators.length === 0) {\n return null;\n }\n\n return async (data: unknown) => {\n const promises = nonNullValidators.map((validator) => {\n return validator(data);\n });\n await throwIfAllRejected(promises);\n };\n}\n\n/**\n * Validates a tuple of events against a tuple of triggers. Throws an error if\n * there's at least 1 event fails validation.\n *\n * Some special behaviors:\n * - If no invoke trigger is present, invoke events are validated against all\n * event schemas.\n * - If 1 or more invoke triggers is present, invoke events are only validated\n * against the invoke trigger schemas.\n */\nexport async function validateEvents<\n TTriggers extends readonly InngestFunction.Trigger<string>[],\n>(\n events: readonly { name: string; data: unknown }[],\n triggers: TTriggers,\n): Promise<void> {\n const schemasByTrigger = createSchemasByTrigger(triggers);\n const validatorsByTrigger = createValidatorsByTrigger(schemasByTrigger);\n\n const validationPromises: Promise<void>[] = [];\n for (const event of events) {\n const validator = getValidatorForEvent(event.name, validatorsByTrigger);\n\n if (validator === null) {\n // Event is allowed but has no schema to validate against\n continue;\n }\n\n validationPromises.push(validator(event.data));\n }\n\n // Validate all events in parallel. First error will propagate.\n await Promise.all(validationPromises);\n}\n\n/**\n * Gets the appropriate validator for an event, handling both direct matches\n * and wildcard pattern matching.\n *\n * @throws EventValidationError if the event doesn't match any triggers\n */\nfunction getValidatorForEvent(\n eventName: string,\n validatorsByEvent: ValidatorsByTrigger,\n): EventValidator {\n // Case 1: Direct match - event name is explicitly registered\n const directValidator = validatorsByEvent[eventName];\n if (directValidator !== undefined) {\n return directValidator;\n }\n\n // Case 2: Wildcard match - event name matches one or more wildcard patterns\n const wildcardValidators = findWildcardValidators(\n eventName,\n validatorsByEvent,\n );\n\n if (wildcardValidators.length > 0) {\n // At least one wildcard matched. If any wildcard has no schema (null), we\n // still need to track it since it means \"this event is allowed\".\n return createUnionValidator(wildcardValidators);\n }\n\n // Case 3: No match - event is not recognized by any trigger. This can occur\n // when a trigger is removed but the app isn't resynced.\n throw new EventValidationError(`Event not found in triggers: ${eventName}`);\n}\n\n/**\n * Parses a trigger to extract its event name and optional schema.\n *\n * Triggers can be specified in several forms:\n * - Direct EventType: `eventType(\"my-event\")`\n * - Nested EventType: `{ event: eventType(\"my-event\") }`\n * - String event name: `{ event: \"my-event\" }`\n * - Cron trigger: `cron(\"0 0 * * *\")`\n *\n * @returns The event name and schema (null if no schema is attached)\n * @throws EventValidationError if the trigger format is invalid\n */\nfunction parseTrigger(trigger: InngestFunction.Trigger<string>): {\n eventName: string;\n schema: AnySchema | null;\n} {\n // Direct event type trigger (e.g. `eventType(\"my-event\")`)\n if (trigger instanceof EventType) {\n return { eventName: trigger.name, schema: trigger.schema };\n }\n\n // Nested event type trigger (e.g. `{ event: eventType(\"my-event\") }`)\n if (trigger.event instanceof EventType) {\n return { eventName: trigger.event.name, schema: trigger.event.schema };\n }\n\n // String event name trigger (e.g. `{ event: \"my-event\" }`)\n if (typeof trigger.event === \"string\") {\n return { eventName: trigger.event, schema: null };\n }\n\n // Cron trigger (e.g. `cron(\"0 0 * * *\")`)\n if (trigger.cron) {\n return { eventName: internalEvents.ScheduledTimer, schema: null };\n }\n\n throw new EventValidationError(\"Invalid trigger\");\n}\n\n/**\n * Create a map of trigger names to their schemas.\n */\nfunction createSchemasByTrigger(\n triggers: readonly InngestFunction.Trigger<string>[],\n): SchemasByTrigger {\n const schemasByEvent: SchemasByTrigger = {};\n\n for (const trigger of triggers) {\n const { eventName, schema } = parseTrigger(trigger);\n\n if (schema) {\n const existingSchemas = schemasByEvent[eventName] ?? [];\n schemasByEvent[eventName] = [...existingSchemas, schema];\n } else {\n schemasByEvent[eventName] = schemasByEvent[eventName] ?? [];\n }\n }\n\n return schemasByEvent;\n}\n\n/**\n * Creates a validator that validates data against multiple schemas, succeeding\n * if at least one schema passes (union validation).\n */\nfunction createSchemaUnionValidator(schemas: AnySchema[]): EventValidator {\n if (schemas.length === 0) {\n return null;\n }\n\n return async (data: unknown) => {\n const validationPromises = schemas.map((schema) =>\n validateAgainstSchema(schema, data),\n );\n await throwIfAllRejected(validationPromises);\n };\n}\n\n/**\n * Create a map of trigger names to their validators.\n *\n * If no invoke schemas are specified, the invoke schema is implicitly a union\n * of all event schemas.\n */\nfunction createValidatorsByTrigger(\n schemasByTrigger: SchemasByTrigger,\n): ValidatorsByTrigger {\n const validatorsByTrigger: ValidatorsByTrigger = {};\n\n // Create a validator for each event based on its schemas\n for (const [triggerName, schemas] of Object.entries(schemasByTrigger)) {\n validatorsByTrigger[triggerName] = createSchemaUnionValidator(schemas);\n }\n\n // Handle implicit invoke validation: if no explicit invoke trigger is\n // defined, validate invoked events against the union of all event schemas\n const hasExplicitInvokeValidator =\n validatorsByTrigger[internalEvents.FunctionInvoked] !== undefined;\n\n if (!hasExplicitInvokeValidator) {\n // No explicit invoke validator, so we need to build an implicit one. The\n // implicit validator is a union of all event schemas.\n\n const allSchemas = Object.values(schemasByTrigger).flat();\n validatorsByTrigger[internalEvents.FunctionInvoked] =\n createSchemaUnionValidator(allSchemas);\n }\n\n return validatorsByTrigger;\n}\n\n/**\n * Only throw if all promises are rejected.\n */\nasync function throwIfAllRejected(promises: Promise<void>[]) {\n const settled = await Promise.allSettled(promises);\n\n let error: Error | undefined;\n for (const result of settled) {\n if (result.status === \"rejected\") {\n error = result.reason;\n }\n if (result.status === \"fulfilled\") {\n return;\n }\n }\n if (error) {\n throw error;\n }\n}\n\nfunction isNotNull<T>(value: T): value is NonNullable<T> {\n return value !== null;\n}\n"],"mappings":"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"}