@useairfoil/connector-kit 0.5.0 → 0.5.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/METRICS.md +78 -0
- package/README.md +190 -21
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth-DvU_CZ7C.js +42 -0
- package/dist/auth-DvU_CZ7C.js.map +1 -0
- package/dist/connector-app/index.d.ts +14 -9
- package/dist/connector-app/index.js +9 -6
- package/dist/connector-app-d7Z-SZLQ.js +64 -0
- package/dist/connector-app-d7Z-SZLQ.js.map +1 -0
- package/dist/constants-BRhIX_rz.js +27 -0
- package/dist/constants-BRhIX_rz.js.map +1 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/{index-DK9YcR6W.d.ts → index-Byr0Lf0C.d.ts} +4 -4
- package/dist/index-CBT-JX-T.d.ts +173 -0
- package/dist/index-CTHVudvQ.d.ts +41 -0
- package/dist/index-Coh_SkBs.d.ts +95 -0
- package/dist/index-Cq2IzY7T.d.ts +80 -0
- package/dist/{index-DyuT2jzP.d.ts → index-D54ySAKn.d.ts} +1 -1
- package/dist/{index-CQJsFJs3.d.ts → index-DKWIh6M1.d.ts} +7 -5
- package/dist/{index-BlhJCpEk.d.ts → index-DtJX_tic.d.ts} +39 -10
- package/dist/{index-8KnVpoZp.d.ts → index-J42AZaXU.d.ts} +11 -5
- package/dist/index-o1-Z3n7V.d.ts +55 -0
- package/dist/index.d.ts +14 -9
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/ingestion/index.d.ts +5 -5
- package/dist/ingestion/index.js +7 -4
- package/dist/ingestion-BQo4cbGw.js +200 -0
- package/dist/ingestion-BQo4cbGw.js.map +1 -0
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.js +4 -0
- package/dist/manifest-ByE6YPG0.js +221 -0
- package/dist/manifest-ByE6YPG0.js.map +1 -0
- package/dist/metrics-CVAk1ALR.d.ts +53 -0
- package/dist/metrics-CWPy2Aqc.js +96 -0
- package/dist/metrics-CWPy2Aqc.js.map +1 -0
- package/dist/metrics.d.ts +4 -0
- package/dist/metrics.js +3 -0
- package/dist/publisher/index.d.ts +4 -4
- package/dist/publisher/index.js +3 -2
- package/dist/{publisher-DusgYvaV.js → publisher-BYT3RLVO.js} +2 -2
- package/dist/{publisher-DusgYvaV.js.map → publisher-BYT3RLVO.js.map} +1 -1
- package/dist/runtime-config/index.d.ts +2 -0
- package/dist/runtime-config/index.js +4 -0
- package/dist/runtime-config-BpB-d_bF.js +51 -0
- package/dist/runtime-config-BpB-d_bF.js.map +1 -0
- package/dist/server-CXTQ7odl.js +183 -0
- package/dist/server-CXTQ7odl.js.map +1 -0
- package/dist/{telemetry-p0dE5Mdu.js → service-DuFiQwid.js} +30 -10
- package/dist/service-DuFiQwid.js.map +1 -0
- package/dist/{service-B7g_lHMI.d.ts → service-IE5qaf9x.d.ts} +3 -3
- package/dist/state-store/index.d.ts +3 -4
- package/dist/state-store/index.js +4 -2
- package/dist/state-store-yl6LiTQ0.js +198 -0
- package/dist/state-store-yl6LiTQ0.js.map +1 -0
- package/dist/status/index.d.ts +5 -0
- package/dist/status/index.js +6 -0
- package/dist/status-B-oZBc33.js +84 -0
- package/dist/status-B-oZBc33.js.map +1 -0
- package/dist/{types-ZNrA-Dn0.d.ts → types-B35J5YPu.d.ts} +20 -6
- package/dist/webhook/index.d.ts +5 -4
- package/dist/webhook/index.js +5 -3
- package/dist/{webhook-BSA-tXtU.js → webhook-DI-Afxkg.js} +2 -2
- package/dist/{webhook-BSA-tXtU.js.map → webhook-DI-Afxkg.js.map} +1 -1
- package/package.json +36 -7
- package/src/auth/index.ts +1 -0
- package/src/auth/token-cache.ts +54 -0
- package/src/connector-app/check.ts +76 -0
- package/src/connector-app/index.ts +1 -0
- package/src/connector-app/start.ts +4 -0
- package/src/core/builder.ts +8 -3
- package/src/core/types.ts +20 -2
- package/src/index.ts +5 -0
- package/src/ingestion/engine.ts +246 -133
- package/src/manifest/config.ts +232 -0
- package/src/manifest/index.ts +14 -0
- package/src/manifest/manifest.ts +31 -0
- package/src/manifest/number-schema.ts +17 -0
- package/src/manifest/schema.ts +160 -0
- package/src/metrics.ts +128 -0
- package/src/publisher/instrumented.ts +68 -0
- package/src/runtime-config/constants.ts +27 -0
- package/src/runtime-config/error.ts +13 -0
- package/src/runtime-config/index.ts +3 -0
- package/src/runtime-config/layer.ts +56 -0
- package/src/state-store/index.ts +2 -1
- package/src/state-store/keys.ts +22 -0
- package/src/state-store/layer-key-value-store.ts +132 -0
- package/src/state-store/layer-memory.ts +11 -0
- package/src/state-store/layer-sql.ts +50 -0
- package/src/state-store/schema.ts +57 -0
- package/src/state-store/service.ts +39 -6
- package/src/state-store/state.ts +17 -0
- package/src/status/index.ts +2 -0
- package/src/status/route.ts +77 -0
- package/src/status/types.ts +33 -0
- package/src/telemetry.ts +46 -4
- package/src/webhook/server.ts +54 -19
- package/dist/connector-app-CvqbfN6R.js +0 -32
- package/dist/connector-app-CvqbfN6R.js.map +0 -1
- package/dist/index-DaNW63Mv.d.ts +0 -21
- package/dist/ingestion-CMCroK7Y.js +0 -171
- package/dist/ingestion-CMCroK7Y.js.map +0 -1
- package/dist/server-DAeFWT4o.js +0 -113
- package/dist/server-DAeFWT4o.js.map +0 -1
- package/dist/state-store-DG_4QpWG.js +0 -31
- package/dist/state-store-DG_4QpWG.js.map +0 -1
- package/dist/telemetry-p0dE5Mdu.js.map +0 -1
- package/src/state-store/memory.ts +0 -25
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-ByE6YPG0.js","names":["define","document: Record<string, string | number | boolean>","value: unknown"],"sources":["../src/manifest/number-schema.ts","../src/manifest/config.ts","../src/manifest/manifest.ts","../src/manifest/schema.ts","../src/manifest/index.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\nexport type NumberConstraints = {\n readonly integer?: boolean;\n readonly minimum?: number;\n};\n\nexport const makeNumberSchema = (constraints: NumberConstraints) => {\n if (constraints.integer) {\n return constraints.minimum === undefined\n ? Schema.Int\n : Schema.Int.check(Schema.isGreaterThanOrEqualTo(constraints.minimum));\n }\n return constraints.minimum === undefined\n ? Schema.Finite\n : Schema.Finite.check(Schema.isGreaterThanOrEqualTo(constraints.minimum));\n};\n","import { Config, Option, Redacted, Schema, Struct } from \"effect\";\n\nimport { isPlatformRuntimeKey } from \"../runtime-config/constants\";\nimport { makeNumberSchema, type NumberConstraints } from \"./number-schema\";\n\ntype ConfigFieldSpecBase = {\n readonly runtimeKey: string;\n readonly required: boolean;\n readonly description?: string;\n};\n\ntype ConfigFieldDefinition = ConfigFieldSpecBase &\n (\n | {\n readonly type: \"string\";\n readonly secret: false;\n readonly default?: string;\n readonly values?: never;\n }\n | {\n readonly type: \"string\";\n readonly secret: true;\n readonly default?: never;\n readonly values?: never;\n }\n | {\n readonly type: \"number\";\n readonly secret: false;\n readonly default?: number;\n readonly values?: never;\n readonly integer?: boolean;\n readonly minimum?: number;\n }\n | {\n readonly type: \"boolean\";\n readonly secret: false;\n readonly default?: boolean;\n readonly values?: never;\n }\n | {\n readonly type: \"select\";\n readonly secret: false;\n readonly default?: string;\n readonly values: readonly [string, ...Array<string>];\n }\n );\n\n/** Serializable, browser-safe metadata for one user-provided connector setting. */\nexport type ConfigFieldSpec = ConfigFieldDefinition & { readonly name: string };\n\n/** Pairs an Effect runtime config with the metadata used to generate its form field. */\nexport type ConfigField<A> = {\n readonly config: Config.Config<A>;\n readonly spec: ConfigFieldDefinition;\n};\n\ntype FieldBaseOptions = {\n readonly runtimeKey: string;\n readonly description?: string;\n};\n\ntype FieldOptions<A extends string | number | boolean = string | number | boolean> =\n FieldBaseOptions &\n (\n | {\n readonly required?: true;\n readonly default?: A;\n }\n | {\n readonly required: false;\n readonly default: A;\n }\n );\n\ntype NumberFieldOptions = FieldOptions<number> & NumberConstraints;\n\ntype FieldsRecord = Record<string, ConfigField<unknown>>;\n\n/** Preserves each field's value type while `Struct.map` extracts its Effect Config. */\ninterface ConfigFieldToConfig extends Struct.Lambda {\n <A>(field: ConfigField<A>): Config.Config<A>;\n readonly \"~lambda.out\": this[\"~lambda.in\"] extends ConfigField<infer A>\n ? Config.Config<A>\n : never;\n}\n\nconst configFieldToConfig = Struct.lambda<ConfigFieldToConfig>((field) => field.config);\n\ntype ConfigOf<Fields extends FieldsRecord> = {\n readonly [Key in keyof Fields]: Fields[Key] extends ConfigField<infer A> ? A : never;\n};\n\n/** Effect config and serializable manifest metadata produced from one field definition. */\nexport type ConnectorConfigDef<Fields extends FieldsRecord> = {\n readonly fields: {\n readonly [Key in keyof Fields]: Fields[Key] extends ConfigField<infer A>\n ? Config.Config<A>\n : never;\n };\n readonly config: Config.Config<ConfigOf<Fields>>;\n readonly spec: ReadonlyArray<ConfigFieldSpec>;\n};\n\nexport type ConfigValuesOf<Def extends ConnectorConfigDef<FieldsRecord>> =\n Def extends ConnectorConfigDef<infer Fields> ? ConfigOf<Fields> : never;\n\nconst withDefault = <A extends string | number | boolean>(\n config: Config.Config<A>,\n defaultValue: A | undefined,\n) => (defaultValue === undefined ? config : Config.withDefault(config, defaultValue));\n\n/** Defines a required non-empty string, optionally with a default. */\nexport const string = (options: FieldOptions<string>): ConfigField<string> => ({\n config: withDefault(Config.nonEmptyString(options.runtimeKey), options.default),\n spec: {\n runtimeKey: options.runtimeKey,\n type: \"string\",\n required: options.required ?? true,\n secret: false,\n ...(options.description !== undefined ? { description: options.description } : {}),\n ...(options.default !== undefined ? { default: options.default } : {}),\n },\n});\n\n/** Defines a finite number with optional integer, minimum, and default constraints. */\nexport const number = (options: NumberFieldOptions): ConfigField<number> => {\n const schema = makeNumberSchema(options);\n const defaultValue =\n options.default === undefined ? undefined : Schema.decodeUnknownSync(schema)(options.default);\n\n return {\n config: withDefault(Config.schema(schema, options.runtimeKey), defaultValue),\n spec: {\n runtimeKey: options.runtimeKey,\n type: \"number\",\n required: options.required ?? true,\n secret: false,\n ...(options.description !== undefined ? { description: options.description } : {}),\n ...(defaultValue !== undefined ? { default: defaultValue } : {}),\n ...(options.integer !== undefined ? { integer: options.integer } : {}),\n ...(options.minimum !== undefined ? { minimum: options.minimum } : {}),\n },\n };\n};\n\n/** Defines a boolean, optionally with a default. */\nexport const boolean = (options: FieldOptions<boolean>): ConfigField<boolean> => ({\n config: withDefault(Config.boolean(options.runtimeKey), options.default),\n spec: {\n runtimeKey: options.runtimeKey,\n type: \"boolean\",\n required: options.required ?? true,\n secret: false,\n ...(options.description !== undefined ? { description: options.description } : {}),\n ...(options.default !== undefined ? { default: options.default } : {}),\n },\n});\n\n/** Defines a string literal choice whose values are preserved in the inferred type. */\nexport const select = <const Values extends readonly [string, ...Array<string>]>(\n options: FieldOptions<Values[number]> & { readonly values: Values },\n): ConfigField<Values[number]> => ({\n config: withDefault(Config.literals(options.values, options.runtimeKey), options.default),\n spec: {\n runtimeKey: options.runtimeKey,\n type: \"select\",\n required: options.required ?? true,\n secret: false,\n values: options.values,\n ...(options.description !== undefined ? { description: options.description } : {}),\n ...(options.default !== undefined ? { default: options.default } : {}),\n },\n});\n\n/** Defines a required, non-empty secret decoded as `Redacted<string>`. */\nexport const secret = (\n options: FieldBaseOptions & { readonly required?: true },\n): ConfigField<Redacted.Redacted<string>> => ({\n config: Config.schema(Schema.Redacted(Schema.NonEmptyString), options.runtimeKey),\n spec: {\n runtimeKey: options.runtimeKey,\n type: \"string\",\n required: options.required ?? true,\n secret: true,\n ...(options.description !== undefined ? { description: options.description } : {}),\n },\n});\n\n/** Makes an existing field optional and represents a missing value as `Option.none()`. */\nexport const optional = <A>(field: ConfigField<A>): ConfigField<Option.Option<A>> => ({\n config: Config.option(field.config),\n spec: {\n ...field.spec,\n required: false,\n },\n});\n\n/**\n * Defines user-facing connector config once for both Effect runtime loading and\n * frontend form generation. The object keys become logical form/API field names;\n * `runtimeKey` remains the key read by Effect Config.\n *\n * @example\n * ```ts\n * const config = define({\n * apiUrl: string({ runtimeKey: \"API_URL\" }),\n * token: secret({ runtimeKey: \"API_TOKEN\" }),\n * });\n *\n * const RuntimeConfig = config.config;\n * const formFields = config.spec;\n * ```\n */\nexport const define = <const Fields extends FieldsRecord>(\n fields: Fields,\n): ConnectorConfigDef<Fields> => {\n const spec = Object.entries(fields).map(([name, field]) => ({ name, ...field.spec }));\n const platformField = spec.find((field) => isPlatformRuntimeKey(field.runtimeKey));\n if (platformField !== undefined) {\n throw new Error(`Connector runtime key is platform-owned: ${platformField.runtimeKey}`);\n }\n\n const configFields = Struct.map(fields, configFieldToConfig);\n\n return {\n fields: configFields,\n // Effect's recursive Config.Wrap cannot reduce this generic mapped type,\n // although ConfigFieldToConfig preserves the value type of every property.\n config: Config.unwrap(configFields as Config.Wrap<ConfigOf<Fields>>),\n spec,\n };\n};\n","import type { ConfigFieldSpec } from \"./config\";\n\nexport type ResourceCapability = \"backfill\" | \"changes\" | \"webhook\";\n\n/** Browser-safe connector metadata consumed by the platform and runtime adapters. */\nexport type ConnectorManifest = {\n readonly name: string;\n readonly title: string;\n readonly description?: string;\n readonly config: ReadonlyArray<ConfigFieldSpec>;\n readonly resources: ReadonlyArray<{\n readonly name: string;\n readonly capabilities: ReadonlyArray<ResourceCapability>;\n }>;\n};\n\n/**\n * Defines browser-safe connector metadata while preserving literal names and capabilities.\n *\n * @example\n * ```ts\n * const manifest = define({\n * name: \"producer-example\",\n * title: \"Example\",\n * config: ExampleConfig.spec,\n * resources: [{ name: \"orders\", capabilities: [\"backfill\"] }],\n * });\n * ```\n */\nexport const define = <const Manifest extends ConnectorManifest>(manifest: Manifest): Manifest =>\n manifest;\n","import { Predicate, Schema, SchemaTransformation, type Types } from \"effect\";\n\nimport type { ConfigFieldSpec } from \"./config\";\nimport type { ConnectorManifest } from \"./manifest\";\n\nimport { makeNumberSchema } from \"./number-schema\";\n\ntype FieldValueOf<Field extends ConfigFieldSpec> = Field[\"type\"] extends \"number\"\n ? number\n : Field[\"type\"] extends \"boolean\"\n ? boolean\n : Field[\"values\"] extends ReadonlyArray<string>\n ? Field[\"values\"][number]\n : string;\n\nexport type ConnectorConfigValues<Manifest extends ConnectorManifest> = Types.Simplify<\n {\n readonly [Field in Manifest[\"config\"][number] as Field[\"required\"] extends true\n ? Field[\"name\"]\n : never]: FieldValueOf<Field>;\n } & {\n readonly [Field in Manifest[\"config\"][number] as Field[\"required\"] extends true\n ? never\n : Field[\"name\"]]?: FieldValueOf<Field> | undefined;\n }\n> &\n object;\n\nexport type ConnectorConfigSchema<Manifest extends ConnectorManifest> = Schema.Codec<\n ConnectorConfigValues<Manifest>,\n unknown\n>;\n\n/** Flat primitive document keyed by the runtime keys consumed by Effect Config. */\nexport type RuntimeConfigDocument = Readonly<Record<string, string | number | boolean>>;\n\n// Browser forms submit numbers and booleans as strings, while API callers may\n// already send their canonical JSON representations. These schemas accept both.\nconst numberFromForm = (field: Extract<ConfigFieldSpec, { readonly type: \"number\" }>) => {\n const numberSchema = makeNumberSchema(field);\n return Schema.Union([\n numberSchema,\n Schema.NonEmptyString.pipe(\n Schema.decodeTo(numberSchema, SchemaTransformation.numberFromString),\n ),\n ]);\n};\n\nconst BooleanFromForm = Schema.Union([\n Schema.Boolean,\n Schema.Literals([\"true\", \"false\"]).pipe(\n Schema.decodeTo(\n Schema.Boolean,\n SchemaTransformation.transform({\n decode: (value) => value === \"true\",\n encode: (value) => (value ? \"true\" : \"false\"),\n }),\n ),\n ),\n]);\n\n// An empty optional form control means \"not supplied\", not an empty config value.\nconst EmptyStringAsUndefined = Schema.Literal(\"\").pipe(\n Schema.decodeTo(\n Schema.Undefined,\n SchemaTransformation.transform({ decode: () => undefined, encode: () => \"\" }),\n ),\n);\n\nconst baseFieldSchema = (field: ConfigFieldSpec): Schema.Codec<unknown, unknown> => {\n switch (field.type) {\n case \"number\":\n return numberFromForm(field);\n case \"boolean\":\n return BooleanFromForm;\n case \"select\":\n return Schema.Literals(field.values);\n case \"string\":\n return Schema.NonEmptyString;\n }\n};\n\nconst fieldSchema = (field: ConfigFieldSpec) =>\n field.required\n ? baseFieldSchema(field)\n : Schema.optional(Schema.Union([EmptyStringAsUndefined, baseFieldSchema(field)]));\n\n/**\n * Builds an Effect Schema for logical form/API values from a connector manifest.\n *\n * @example\n * ```ts\n * const decode = Schema.decodeUnknownEffect(configSchema(manifest));\n * const values = decode({\n * apiUrl: \"https://example.com\",\n * retries: \"3\",\n * });\n * ```\n */\nexport const configSchema = <const Manifest extends ConnectorManifest>(\n manifest: Manifest,\n): ConnectorConfigSchema<Manifest> => {\n const fields = Object.fromEntries(\n manifest.config.map((field) => [field.name, fieldSchema(field)]),\n );\n // Object.fromEntries cannot retain keys discovered from the runtime manifest;\n // the public mapped type restores the same relationship for callers.\n return Schema.Struct(fields) as unknown as ConnectorConfigSchema<Manifest>;\n};\n\n/** Decodes browser/API config, applies manifest defaults, and rejects undeclared fields. */\nexport const decodeConfig = <const Manifest extends ConnectorManifest>(\n manifest: Manifest,\n input: unknown,\n) => {\n const defaults = Object.fromEntries(\n manifest.config.flatMap((field) =>\n field.default === undefined ? [] : [[field.name, field.default]],\n ),\n );\n const optionalFields = new Set(\n manifest.config.flatMap((field) => (field.required ? [] : [field.name])),\n );\n const normalizedInput = Predicate.isObject(input)\n ? Object.fromEntries(\n Object.entries(input).filter(([name, value]) => value !== \"\" || !optionalFields.has(name)),\n )\n : input;\n const inputWithDefaults = Predicate.isObject(normalizedInput)\n ? Object.assign({}, defaults, normalizedInput)\n : normalizedInput;\n\n return Schema.decodeUnknownEffect(configSchema(manifest))(inputWithDefaults, {\n onExcessProperty: \"error\",\n });\n};\n\n/**\n * Maps validated logical values to the flat runtime-key document mounted in a pod.\n * Missing optional values are omitted.\n *\n * @example\n * ```ts\n * toRuntimeDocument(manifest, { apiUrl: \"https://example.com\" });\n * // { API_URL: \"https://example.com\" }\n * ```\n */\nexport const toRuntimeDocument = <const Manifest extends ConnectorManifest>(\n manifest: Manifest,\n values: ConnectorConfigValues<Manifest>,\n): RuntimeConfigDocument => {\n const document: Record<string, string | number | boolean> = {};\n for (const field of manifest.config) {\n const value: unknown = Reflect.get(values, field.name);\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n document[field.runtimeKey] = value;\n }\n }\n return document;\n};\n","export {\n boolean,\n define as defineConfig,\n number,\n optional,\n secret,\n select,\n string,\n} from \"./config\";\nexport type { ConfigField, ConfigFieldSpec, ConfigValuesOf, ConnectorConfigDef } from \"./config\";\nexport { define } from \"./manifest\";\nexport type { ConnectorManifest, ResourceCapability } from \"./manifest\";\nexport { configSchema, decodeConfig, toRuntimeDocument } from \"./schema\";\nexport type { ConnectorConfigSchema, ConnectorConfigValues, RuntimeConfigDocument } from \"./schema\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { v as SyncState } from "./types-B35J5YPu.js";
|
|
2
|
+
import { Effect, Metric } from "effect";
|
|
3
|
+
import * as effect_Duration0 from "effect/Duration";
|
|
4
|
+
|
|
5
|
+
//#region src/metrics.d.ts
|
|
6
|
+
declare namespace metrics_d_exports {
|
|
7
|
+
export { ApiRetryMetricAttrs, ApiRetryReason, BatchOutcome, BatchSource, LastSuccessMetricAttrs, ResourceMetricAttrs, SyncStateAttrs, WebhookMetricAttrs, WebhookOutcome, apiRetriesTotal, batchSize, batchesTotal, entitiesDeletedTotal, entitiesUpsertedTotal, lastSuccessTimestamp, publishDuration, recordApiRetry, recordWebhookRequest, setLastSuccessTimestamp, setSyncState, setWebhookQueueDepth, syncState, webhookQueueDepth, webhookRequestsTotal };
|
|
8
|
+
}
|
|
9
|
+
type BatchSource = "backfill" | "changes" | "webhook";
|
|
10
|
+
type BatchOutcome = "accepted" | "rejected" | "error";
|
|
11
|
+
type ApiRetryReason = "transport" | "timeout" | "rate_limit" | "server_error";
|
|
12
|
+
type WebhookOutcome = "ok" | "read_error" | "invalid_json" | "invalid_payload" | "rejected" | "handler_error";
|
|
13
|
+
type ResourceMetricAttrs = {
|
|
14
|
+
readonly connector: string;
|
|
15
|
+
readonly resource: string;
|
|
16
|
+
readonly source: BatchSource;
|
|
17
|
+
};
|
|
18
|
+
type WebhookMetricAttrs = {
|
|
19
|
+
readonly connector: string;
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly outcome: WebhookOutcome;
|
|
22
|
+
};
|
|
23
|
+
type SyncStateAttrs = {
|
|
24
|
+
readonly connector: string;
|
|
25
|
+
readonly resource: string;
|
|
26
|
+
};
|
|
27
|
+
type LastSuccessMetricAttrs = {
|
|
28
|
+
readonly connector: string;
|
|
29
|
+
readonly resource: string;
|
|
30
|
+
readonly source: "backfill" | "changes";
|
|
31
|
+
};
|
|
32
|
+
type ApiRetryMetricAttrs = {
|
|
33
|
+
readonly connector: string;
|
|
34
|
+
readonly reason: ApiRetryReason;
|
|
35
|
+
};
|
|
36
|
+
declare const entitiesUpsertedTotal: Metric.Counter<number>;
|
|
37
|
+
declare const entitiesDeletedTotal: Metric.Counter<number>;
|
|
38
|
+
declare const batchesTotal: Metric.Counter<number>;
|
|
39
|
+
declare const batchSize: Metric.Histogram<number>;
|
|
40
|
+
declare const publishDuration: Metric.Histogram<effect_Duration0.Duration>;
|
|
41
|
+
declare const webhookRequestsTotal: Metric.Counter<number>;
|
|
42
|
+
declare const webhookQueueDepth: Metric.Gauge<number>;
|
|
43
|
+
declare const syncState: Metric.Gauge<number>;
|
|
44
|
+
declare const lastSuccessTimestamp: Metric.Gauge<number>;
|
|
45
|
+
declare const apiRetriesTotal: Metric.Counter<number>;
|
|
46
|
+
declare const recordWebhookRequest: (attrs: WebhookMetricAttrs) => Effect.Effect<void, never, never>;
|
|
47
|
+
declare const setWebhookQueueDepth: (connector: string, depth: number) => Effect.Effect<void, never, never>;
|
|
48
|
+
declare const setLastSuccessTimestamp: (attrs: LastSuccessMetricAttrs, lastSuccessAt?: string) => Effect.Effect<void, never, never>;
|
|
49
|
+
declare const recordApiRetry: (attrs: ApiRetryMetricAttrs) => Effect.Effect<void, never, never>;
|
|
50
|
+
declare const setSyncState: (attrs: SyncStateAttrs, current: SyncState) => Effect.Effect<void, never, never>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { syncState as C, setWebhookQueueDepth as S, webhookRequestsTotal as T, publishDuration as _, LastSuccessMetricAttrs as a, setLastSuccessTimestamp as b, WebhookMetricAttrs as c, batchSize as d, batchesTotal as f, metrics_d_exports as g, lastSuccessTimestamp as h, BatchSource as i, WebhookOutcome as l, entitiesUpsertedTotal as m, ApiRetryReason as n, ResourceMetricAttrs as o, entitiesDeletedTotal as p, BatchOutcome as r, SyncStateAttrs as s, ApiRetryMetricAttrs as t, apiRetriesTotal as u, recordApiRetry as v, webhookQueueDepth as w, setSyncState as x, recordWebhookRequest as y };
|
|
53
|
+
//# sourceMappingURL=metrics-CVAk1ALR.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { DateTime, Effect, Metric, Option } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/metrics.ts
|
|
5
|
+
var metrics_exports = /* @__PURE__ */ __export({
|
|
6
|
+
apiRetriesTotal: () => apiRetriesTotal,
|
|
7
|
+
batchSize: () => batchSize,
|
|
8
|
+
batchesTotal: () => batchesTotal,
|
|
9
|
+
entitiesDeletedTotal: () => entitiesDeletedTotal,
|
|
10
|
+
entitiesUpsertedTotal: () => entitiesUpsertedTotal,
|
|
11
|
+
lastSuccessTimestamp: () => lastSuccessTimestamp,
|
|
12
|
+
publishDuration: () => publishDuration,
|
|
13
|
+
recordApiRetry: () => recordApiRetry,
|
|
14
|
+
recordWebhookRequest: () => recordWebhookRequest,
|
|
15
|
+
setLastSuccessTimestamp: () => setLastSuccessTimestamp,
|
|
16
|
+
setSyncState: () => setSyncState,
|
|
17
|
+
setWebhookQueueDepth: () => setWebhookQueueDepth,
|
|
18
|
+
syncState: () => syncState,
|
|
19
|
+
webhookQueueDepth: () => webhookQueueDepth,
|
|
20
|
+
webhookRequestsTotal: () => webhookRequestsTotal
|
|
21
|
+
});
|
|
22
|
+
const entitiesUpsertedTotal = Metric.counter("airfoil_connector_entities_upserted_total", {
|
|
23
|
+
description: "Total entity upsert mutations published by connector sources",
|
|
24
|
+
incremental: true
|
|
25
|
+
});
|
|
26
|
+
const entitiesDeletedTotal = Metric.counter("airfoil_connector_entities_deleted_total", {
|
|
27
|
+
description: "Total entity delete mutations published by connector sources",
|
|
28
|
+
incremental: true
|
|
29
|
+
});
|
|
30
|
+
const batchesTotal = Metric.counter("airfoil_connector_batches_total", {
|
|
31
|
+
description: "Total resource batches published by connector sources",
|
|
32
|
+
incremental: true
|
|
33
|
+
});
|
|
34
|
+
const batchSize = Metric.histogram("airfoil_connector_batch_size", {
|
|
35
|
+
description: "Distribution of resource mutation batch sizes",
|
|
36
|
+
boundaries: [
|
|
37
|
+
1,
|
|
38
|
+
5,
|
|
39
|
+
10,
|
|
40
|
+
25,
|
|
41
|
+
50,
|
|
42
|
+
100,
|
|
43
|
+
250,
|
|
44
|
+
500,
|
|
45
|
+
1e3
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
const publishDuration = Metric.timer("airfoil_connector_publish_duration_ms", {
|
|
49
|
+
description: "Publisher call duration in milliseconds",
|
|
50
|
+
boundaries: [
|
|
51
|
+
1,
|
|
52
|
+
5,
|
|
53
|
+
10,
|
|
54
|
+
25,
|
|
55
|
+
50,
|
|
56
|
+
100,
|
|
57
|
+
250,
|
|
58
|
+
500,
|
|
59
|
+
1e3,
|
|
60
|
+
2500,
|
|
61
|
+
5e3,
|
|
62
|
+
1e4
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
const webhookRequestsTotal = Metric.counter("airfoil_connector_webhook_requests_total", {
|
|
66
|
+
description: "Total webhook HTTP requests handled by connector routes",
|
|
67
|
+
incremental: true
|
|
68
|
+
});
|
|
69
|
+
const webhookQueueDepth = Metric.gauge("airfoil_connector_webhook_queue_depth", { description: "Current queued webhook batches waiting for publish" });
|
|
70
|
+
const syncState = Metric.gauge("airfoil_connector_sync_state", { description: "Connector resource sync state as a Prometheus state-set gauge" });
|
|
71
|
+
const lastSuccessTimestamp = Metric.gauge("airfoil_connector_last_success_timestamp_seconds", { description: "Unix timestamp of the last successful durable source checkpoint" });
|
|
72
|
+
const apiRetriesTotal = Metric.counter("airfoil_connector_api_retries_total", {
|
|
73
|
+
description: "Total provider API retries performed by the connector",
|
|
74
|
+
incremental: true
|
|
75
|
+
});
|
|
76
|
+
const recordWebhookRequest = (attrs) => Metric.update(Metric.withAttributes(webhookRequestsTotal, attrs), 1);
|
|
77
|
+
const setWebhookQueueDepth = (connector, depth) => Metric.update(Metric.withAttributes(webhookQueueDepth, { connector }), depth);
|
|
78
|
+
const setLastSuccessTimestamp = (attrs, lastSuccessAt) => {
|
|
79
|
+
const timestamp = lastSuccessAt === void 0 ? 0 : DateTime.make(lastSuccessAt).pipe(Option.map((dateTime) => DateTime.toEpochMillis(dateTime) / 1e3), Option.getOrElse(() => 0));
|
|
80
|
+
return Metric.update(Metric.withAttributes(lastSuccessTimestamp, attrs), timestamp);
|
|
81
|
+
};
|
|
82
|
+
const recordApiRetry = (attrs) => Metric.update(Metric.withAttributes(apiRetriesTotal, attrs), 1);
|
|
83
|
+
const syncStates = [
|
|
84
|
+
"pending",
|
|
85
|
+
"backfilling",
|
|
86
|
+
"live",
|
|
87
|
+
"error"
|
|
88
|
+
];
|
|
89
|
+
const setSyncState = (attrs, current) => Effect.forEach(syncStates, (state) => Metric.update(Metric.withAttributes(syncState, {
|
|
90
|
+
...attrs,
|
|
91
|
+
state
|
|
92
|
+
}), state === current ? 1 : 0), { discard: true });
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { entitiesUpsertedTotal as a, publishDuration as c, setLastSuccessTimestamp as d, setSyncState as f, webhookRequestsTotal as g, webhookQueueDepth as h, entitiesDeletedTotal as i, recordApiRetry as l, syncState as m, batchSize as n, lastSuccessTimestamp as o, setWebhookQueueDepth as p, batchesTotal as r, metrics_exports as s, apiRetriesTotal as t, recordWebhookRequest as u };
|
|
96
|
+
//# sourceMappingURL=metrics-CWPy2Aqc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-CWPy2Aqc.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import { DateTime, Effect, Metric, Option } from \"effect\";\n\nimport type { SyncState } from \"./core/types\";\n\nexport type BatchSource = \"backfill\" | \"changes\" | \"webhook\";\nexport type BatchOutcome = \"accepted\" | \"rejected\" | \"error\";\nexport type ApiRetryReason = \"transport\" | \"timeout\" | \"rate_limit\" | \"server_error\";\nexport type WebhookOutcome =\n | \"ok\"\n | \"read_error\"\n | \"invalid_json\"\n | \"invalid_payload\"\n | \"rejected\"\n | \"handler_error\";\n\nexport type ResourceMetricAttrs = {\n readonly connector: string;\n readonly resource: string;\n readonly source: BatchSource;\n};\n\nexport type WebhookMetricAttrs = {\n readonly connector: string;\n readonly path: string;\n readonly outcome: WebhookOutcome;\n};\n\nexport type SyncStateAttrs = {\n readonly connector: string;\n readonly resource: string;\n};\n\nexport type LastSuccessMetricAttrs = {\n readonly connector: string;\n readonly resource: string;\n readonly source: \"backfill\" | \"changes\";\n};\n\nexport type ApiRetryMetricAttrs = {\n readonly connector: string;\n readonly reason: ApiRetryReason;\n};\n\nexport const entitiesUpsertedTotal = Metric.counter(\"airfoil_connector_entities_upserted_total\", {\n description: \"Total entity upsert mutations published by connector sources\",\n incremental: true,\n});\n\nexport const entitiesDeletedTotal = Metric.counter(\"airfoil_connector_entities_deleted_total\", {\n description: \"Total entity delete mutations published by connector sources\",\n incremental: true,\n});\n\nexport const batchesTotal = Metric.counter(\"airfoil_connector_batches_total\", {\n description: \"Total resource batches published by connector sources\",\n incremental: true,\n});\n\nexport const batchSize = Metric.histogram(\"airfoil_connector_batch_size\", {\n description: \"Distribution of resource mutation batch sizes\",\n boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000],\n});\n\nexport const publishDuration = Metric.timer(\"airfoil_connector_publish_duration_ms\", {\n description: \"Publisher call duration in milliseconds\",\n boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],\n});\n\nexport const webhookRequestsTotal = Metric.counter(\"airfoil_connector_webhook_requests_total\", {\n description: \"Total webhook HTTP requests handled by connector routes\",\n incremental: true,\n});\n\nexport const webhookQueueDepth = Metric.gauge(\"airfoil_connector_webhook_queue_depth\", {\n description: \"Current queued webhook batches waiting for publish\",\n});\n\nexport const syncState = Metric.gauge(\"airfoil_connector_sync_state\", {\n description: \"Connector resource sync state as a Prometheus state-set gauge\",\n});\n\nexport const lastSuccessTimestamp = Metric.gauge(\n \"airfoil_connector_last_success_timestamp_seconds\",\n {\n description: \"Unix timestamp of the last successful durable source checkpoint\",\n },\n);\n\nexport const apiRetriesTotal = Metric.counter(\"airfoil_connector_api_retries_total\", {\n description: \"Total provider API retries performed by the connector\",\n incremental: true,\n});\n\nexport const recordWebhookRequest = (attrs: WebhookMetricAttrs) =>\n Metric.update(Metric.withAttributes(webhookRequestsTotal, attrs), 1);\n\nexport const setWebhookQueueDepth = (connector: string, depth: number) =>\n Metric.update(Metric.withAttributes(webhookQueueDepth, { connector }), depth);\n\nexport const setLastSuccessTimestamp = (attrs: LastSuccessMetricAttrs, lastSuccessAt?: string) => {\n const timestamp =\n lastSuccessAt === undefined\n ? 0\n : DateTime.make(lastSuccessAt).pipe(\n Option.map((dateTime) => DateTime.toEpochMillis(dateTime) / 1000),\n Option.getOrElse(() => 0),\n );\n return Metric.update(Metric.withAttributes(lastSuccessTimestamp, attrs), timestamp);\n};\n\nexport const recordApiRetry = (attrs: ApiRetryMetricAttrs) =>\n Metric.update(Metric.withAttributes(apiRetriesTotal, attrs), 1);\n\nconst syncStates = [\"pending\", \"backfilling\", \"live\", \"error\"] as const;\n\nexport const setSyncState = (attrs: SyncStateAttrs, current: SyncState) =>\n Effect.forEach(\n syncStates,\n (state) =>\n Metric.update(\n Metric.withAttributes(syncState, {\n ...attrs,\n state,\n }),\n state === current ? 1 : 0,\n ),\n { discard: true },\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,wBAAwB,OAAO,QAAQ,6CAA6C;CAC/F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,uBAAuB,OAAO,QAAQ,4CAA4C;CAC7F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,eAAe,OAAO,QAAQ,mCAAmC;CAC5E,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,YAAY,OAAO,UAAU,gCAAgC;CACxE,aAAa;CACb,YAAY;EAAC;EAAG;EAAG;EAAI;EAAI;EAAI;EAAK;EAAK;EAAK;EAAK;CACpD,CAAC;AAEF,MAAa,kBAAkB,OAAO,MAAM,yCAAyC;CACnF,aAAa;CACb,YAAY;EAAC;EAAG;EAAG;EAAI;EAAI;EAAI;EAAK;EAAK;EAAK;EAAM;EAAM;EAAM;EAAM;CACvE,CAAC;AAEF,MAAa,uBAAuB,OAAO,QAAQ,4CAA4C;CAC7F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,oBAAoB,OAAO,MAAM,yCAAyC,EACrF,aAAa,sDACd,CAAC;AAEF,MAAa,YAAY,OAAO,MAAM,gCAAgC,EACpE,aAAa,iEACd,CAAC;AAEF,MAAa,uBAAuB,OAAO,MACzC,oDACA,EACE,aAAa,mEACd,CACF;AAED,MAAa,kBAAkB,OAAO,QAAQ,uCAAuC;CACnF,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,wBAAwB,UACnC,OAAO,OAAO,OAAO,eAAe,sBAAsB,MAAM,EAAE,EAAE;AAEtE,MAAa,wBAAwB,WAAmB,UACtD,OAAO,OAAO,OAAO,eAAe,mBAAmB,EAAE,WAAW,CAAC,EAAE,MAAM;AAE/E,MAAa,2BAA2B,OAA+B,kBAA2B;CAChG,MAAM,YACJ,kBAAkB,SACd,IACA,SAAS,KAAK,cAAc,CAAC,KAC3B,OAAO,KAAK,aAAa,SAAS,cAAc,SAAS,GAAG,IAAK,EACjE,OAAO,gBAAgB,EAAE,CAC1B;AACP,QAAO,OAAO,OAAO,OAAO,eAAe,sBAAsB,MAAM,EAAE,UAAU;;AAGrF,MAAa,kBAAkB,UAC7B,OAAO,OAAO,OAAO,eAAe,iBAAiB,MAAM,EAAE,EAAE;AAEjE,MAAM,aAAa;CAAC;CAAW;CAAe;CAAQ;CAAQ;AAE9D,MAAa,gBAAgB,OAAuB,YAClD,OAAO,QACL,aACC,UACC,OAAO,OACL,OAAO,eAAe,WAAW;CAC/B,GAAG;CACH;CACD,CAAC,EACF,UAAU,UAAU,IAAI,EACzB,EACH,EAAE,SAAS,MAAM,CAClB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./index-D54ySAKn.js";
|
|
2
|
+
import "./types-B35J5YPu.js";
|
|
3
|
+
import { C as syncState, S as setWebhookQueueDepth, T as webhookRequestsTotal, _ as publishDuration, a as LastSuccessMetricAttrs, b as setLastSuccessTimestamp, c as WebhookMetricAttrs, d as batchSize, f as batchesTotal, h as lastSuccessTimestamp, i as BatchSource, l as WebhookOutcome, m as entitiesUpsertedTotal, n as ApiRetryReason, o as ResourceMetricAttrs, p as entitiesDeletedTotal, r as BatchOutcome, s as SyncStateAttrs, t as ApiRetryMetricAttrs, u as apiRetriesTotal, v as recordApiRetry, w as webhookQueueDepth, x as setSyncState, y as recordWebhookRequest } from "./metrics-CVAk1ALR.js";
|
|
4
|
+
export { ApiRetryMetricAttrs, ApiRetryReason, BatchOutcome, BatchSource, LastSuccessMetricAttrs, ResourceMetricAttrs, SyncStateAttrs, WebhookMetricAttrs, WebhookOutcome, apiRetriesTotal, batchSize, batchesTotal, entitiesDeletedTotal, entitiesUpsertedTotal, lastSuccessTimestamp, publishDuration, recordApiRetry, recordWebhookRequest, setLastSuccessTimestamp, setSyncState, setWebhookQueueDepth, syncState, webhookQueueDepth, webhookRequestsTotal };
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as entitiesUpsertedTotal, c as publishDuration, d as setLastSuccessTimestamp, f as setSyncState, g as webhookRequestsTotal, h as webhookQueueDepth, i as entitiesDeletedTotal, l as recordApiRetry, m as syncState, n as batchSize, o as lastSuccessTimestamp, p as setWebhookQueueDepth, r as batchesTotal, t as apiRetriesTotal, u as recordWebhookRequest } from "./metrics-CWPy2Aqc.js";
|
|
2
|
+
|
|
3
|
+
export { apiRetriesTotal, batchSize, batchesTotal, entitiesDeletedTotal, entitiesUpsertedTotal, lastSuccessTimestamp, publishDuration, recordApiRetry, recordWebhookRequest, setLastSuccessTimestamp, setSyncState, setWebhookQueueDepth, syncState, webhookQueueDepth, webhookRequestsTotal };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import { c as ResourceBatch } from "../types-
|
|
3
|
-
import { a as PublisherService, i as Publisher, n as PublishOptions, r as PublishSource, t as PublishAck } from "../service-
|
|
4
|
-
import { i as layerWings, n as layerConsole, r as WingsPublisherConfig } from "../index-
|
|
1
|
+
import "../index-D54ySAKn.js";
|
|
2
|
+
import { c as ResourceBatch } from "../types-B35J5YPu.js";
|
|
3
|
+
import { a as PublisherService, i as Publisher, n as PublishOptions, r as PublishSource, t as PublishAck } from "../service-IE5qaf9x.js";
|
|
4
|
+
import { i as layerWings, n as layerConsole, r as WingsPublisherConfig } from "../index-Byr0Lf0C.js";
|
|
5
5
|
export { PublishAck, PublishOptions, PublishSource, Publisher, PublisherService, ResourceBatch, WingsPublisherConfig, layerConsole, layerWings };
|
package/dist/publisher/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../errors-DHWylqRZ.js";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import "../constants-BRhIX_rz.js";
|
|
3
|
+
import { t as Publisher } from "../service-DuFiQwid.js";
|
|
4
|
+
import { n as layerWings, r as layerConsole } from "../publisher-BYT3RLVO.js";
|
|
4
5
|
|
|
5
6
|
export { Publisher, layerConsole, layerWings };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
2
|
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
3
|
-
import {
|
|
3
|
+
import { a as SpanName, t as Publisher } from "./service-DuFiQwid.js";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
5
5
|
import * as Wings from "@useairfoil/wings";
|
|
6
6
|
|
|
@@ -128,4 +128,4 @@ var publisher_exports = /* @__PURE__ */ __export({
|
|
|
128
128
|
|
|
129
129
|
//#endregion
|
|
130
130
|
export { layerWings as n, layerConsole as r, publisher_exports as t };
|
|
131
|
-
//# sourceMappingURL=publisher-
|
|
131
|
+
//# sourceMappingURL=publisher-BYT3RLVO.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publisher-DusgYvaV.js","names":["service: PublisherService","upserts: Rows[]","deletes: Rows[]"],"sources":["../src/publisher/console.ts","../src/publisher/wings.ts","../src/publisher/index.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\n\nimport { Publisher } from \"./service\";\n\nexport const layerConsole = Layer.succeed(Publisher)({\n publish: ({ resource, source, batch }) =>\n Effect.gen(function* () {\n yield* Effect.logInfo(`[publisher] -> Source: ${source} | Resource: ${resource}`).pipe(\n Effect.annotateLogs({\n mutations: batch.mutations.length,\n cursor: batch.cursor,\n source,\n }),\n );\n\n return { status: \"accepted\" as const, resource };\n }),\n});\n","import * as Wings from \"@useairfoil/wings\";\nimport { Effect, Layer } from \"effect\";\n\nimport type { ConnectorDefinition, ResourceDefinition } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport { SpanName } from \"../telemetry\";\nimport { Publisher, type PublishAck, type PublisherService } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\nexport type WingsTableMapping = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\nexport type WingsPublisherConfig = {\n readonly connector: ConnectorDefinition;\n readonly tables: Record<string, string | WingsTableMapping>;\n};\n\ntype PublisherEntry = {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly publisher: Wings.WingsClient.Publisher;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n readonly keyField: Wings.Cluster.ArrowType.Field;\n readonly versionField: Wings.Cluster.ArrowType.Field;\n};\n\nconst normalizeMapping = (mapping: string | WingsTableMapping): WingsTableMapping =>\n typeof mapping === \"string\" ? { name: mapping } : mapping;\n\nconst buildRecordBatch = (rows: ReadonlyArray<Rows>) => {\n const table = Wings.Arrow.tableFromJSON(Array.from(rows));\n const [batch] = table.batches;\n return batch\n ? Effect.succeed(batch)\n : Effect.fail(new ConnectorError({ message: \"No rows to publish\" }));\n};\n\nconst validateTableMapping = (options: {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n}) =>\n Effect.try({\n try: () => {\n const keyField = Wings.TableUtils.getKeyField(options.table);\n const versionField = Wings.TableUtils.getVersionField(options.table);\n const partitionField = Wings.TableUtils.getPartitionField(options.table);\n\n if (keyField.name !== options.resource.key) {\n throw new Error(\n `Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`,\n );\n }\n if (versionField.name !== options.resource.version) {\n throw new Error(\n `Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`,\n );\n }\n if (options.resource.partition?.required === true && !options.partitionValue) {\n throw new Error(`Resource ${options.resource.name} requires a partition value`);\n }\n if (partitionField && !options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is partitioned but no partition value was provided`,\n );\n }\n if (!partitionField && options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is not partitioned but a partition value was provided`,\n );\n }\n\n return { keyField, versionField };\n },\n catch: (cause) =>\n new ConnectorError({\n message: `Invalid Wings table mapping for ${options.resource.name}`,\n cause,\n }),\n });\n\nconst accepted = (entry: PublisherEntry): PublishAck => ({\n status: \"accepted\",\n resource: entry.resource.name,\n partition: entry.partitionValue,\n});\n\nconst rejected = (entry: PublisherEntry, reason: string, rejectedRows: number): PublishAck => ({\n status: \"rejected\",\n resource: entry.resource.name,\n reason,\n rejectedRows,\n partition: entry.partitionValue,\n});\n\nexport const layerWings = (\n config: WingsPublisherConfig,\n): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>\n Layer.effect(Publisher)(\n Effect.gen(function* () {\n const entries = new Map<string, PublisherEntry>();\n const clusterClient = yield* Wings.WingsClient.clusterClient;\n\n for (const resource of config.connector.resources) {\n const mappingInput = config.tables[resource.name];\n if (!mappingInput) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Missing table for ${resource.name}` }),\n );\n }\n\n const mapping = normalizeMapping(mappingInput);\n const table = yield* clusterClient.getTable({ name: mapping.name }).pipe(\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Failed to resolve Wings table for ${resource.name}`,\n cause: error,\n }),\n ),\n );\n const { keyField, versionField } = yield* validateTableMapping({\n resource,\n table,\n partitionValue: mapping.partitionValue,\n });\n\n const publisher = yield* Wings.WingsClient.publisher({\n table,\n partitionValue: mapping.partitionValue,\n }).pipe(\n Effect.mapError((error) => new ConnectorError({ message: error.message, cause: error })),\n );\n\n entries.set(resource.name, {\n resource,\n table,\n publisher,\n partitionValue: mapping.partitionValue,\n keyField,\n versionField,\n });\n }\n\n const pushRows = (\n entry: PublisherEntry,\n operation: \"upsert\" | \"delete\",\n rows: ReadonlyArray<Rows>,\n ) =>\n Effect.gen(function* () {\n if (rows.length === 0) return accepted(entry);\n const batch = yield* buildRecordBatch(rows);\n return yield* entry.publisher\n .push({ operation, batch, partitionValue: entry.partitionValue })\n .pipe(\n Effect.matchEffect({\n onFailure: (error) => Effect.succeed(rejected(entry, error.message, rows.length)),\n onSuccess: () => Effect.succeed(accepted(entry)),\n }),\n );\n });\n\n const service: PublisherService = {\n publish: Effect.fn(SpanName.publish, { kind: \"producer\" })(function* ({\n resource,\n source: _source,\n batch,\n }) {\n const entry = entries.get(resource);\n if (!entry) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Unknown resource ${resource}` }),\n );\n }\n\n if (batch.mutations.length === 0) return accepted(entry);\n\n const upserts: Rows[] = [];\n const deletes: Rows[] = [];\n for (const mutation of batch.mutations) {\n if (mutation.op === \"upsert\") {\n upserts.push(mutation.row as Rows);\n } else {\n deletes.push({\n [entry.keyField.name]: mutation.key,\n [entry.versionField.name]: mutation.version,\n });\n }\n }\n\n const upsertAck = yield* pushRows(entry, \"upsert\", upserts);\n if (upsertAck.status === \"rejected\") return upsertAck;\n\n return yield* pushRows(entry, \"delete\", deletes);\n }),\n };\n\n return Publisher.of(service);\n }),\n );\n","export type { ResourceBatch } from \"../core/types\";\nexport type { PublishAck, PublishOptions, PublisherService, PublishSource } from \"./service\";\nexport type { WingsPublisherConfig } from \"./wings\";\n\nexport { layerConsole } from \"./console\";\nexport { Publisher } from \"./service\";\nexport { layerWings } from \"./wings\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publisher-BYT3RLVO.js","names":["service: PublisherService","upserts: Rows[]","deletes: Rows[]"],"sources":["../src/publisher/console.ts","../src/publisher/wings.ts","../src/publisher/index.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\n\nimport { Publisher } from \"./service\";\n\nexport const layerConsole = Layer.succeed(Publisher)({\n publish: ({ resource, source, batch }) =>\n Effect.gen(function* () {\n yield* Effect.logInfo(`[publisher] -> Source: ${source} | Resource: ${resource}`).pipe(\n Effect.annotateLogs({\n mutations: batch.mutations.length,\n cursor: batch.cursor,\n source,\n }),\n );\n\n return { status: \"accepted\" as const, resource };\n }),\n});\n","import * as Wings from \"@useairfoil/wings\";\nimport { Effect, Layer } from \"effect\";\n\nimport type { ConnectorDefinition, ResourceDefinition } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport { SpanName } from \"../telemetry\";\nimport { Publisher, type PublishAck, type PublisherService } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\nexport type WingsTableMapping = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\nexport type WingsPublisherConfig = {\n readonly connector: ConnectorDefinition;\n readonly tables: Record<string, string | WingsTableMapping>;\n};\n\ntype PublisherEntry = {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly publisher: Wings.WingsClient.Publisher;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n readonly keyField: Wings.Cluster.ArrowType.Field;\n readonly versionField: Wings.Cluster.ArrowType.Field;\n};\n\nconst normalizeMapping = (mapping: string | WingsTableMapping): WingsTableMapping =>\n typeof mapping === \"string\" ? { name: mapping } : mapping;\n\nconst buildRecordBatch = (rows: ReadonlyArray<Rows>) => {\n const table = Wings.Arrow.tableFromJSON(Array.from(rows));\n const [batch] = table.batches;\n return batch\n ? Effect.succeed(batch)\n : Effect.fail(new ConnectorError({ message: \"No rows to publish\" }));\n};\n\nconst validateTableMapping = (options: {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n}) =>\n Effect.try({\n try: () => {\n const keyField = Wings.TableUtils.getKeyField(options.table);\n const versionField = Wings.TableUtils.getVersionField(options.table);\n const partitionField = Wings.TableUtils.getPartitionField(options.table);\n\n if (keyField.name !== options.resource.key) {\n throw new Error(\n `Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`,\n );\n }\n if (versionField.name !== options.resource.version) {\n throw new Error(\n `Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`,\n );\n }\n if (options.resource.partition?.required === true && !options.partitionValue) {\n throw new Error(`Resource ${options.resource.name} requires a partition value`);\n }\n if (partitionField && !options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is partitioned but no partition value was provided`,\n );\n }\n if (!partitionField && options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is not partitioned but a partition value was provided`,\n );\n }\n\n return { keyField, versionField };\n },\n catch: (cause) =>\n new ConnectorError({\n message: `Invalid Wings table mapping for ${options.resource.name}`,\n cause,\n }),\n });\n\nconst accepted = (entry: PublisherEntry): PublishAck => ({\n status: \"accepted\",\n resource: entry.resource.name,\n partition: entry.partitionValue,\n});\n\nconst rejected = (entry: PublisherEntry, reason: string, rejectedRows: number): PublishAck => ({\n status: \"rejected\",\n resource: entry.resource.name,\n reason,\n rejectedRows,\n partition: entry.partitionValue,\n});\n\nexport const layerWings = (\n config: WingsPublisherConfig,\n): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>\n Layer.effect(Publisher)(\n Effect.gen(function* () {\n const entries = new Map<string, PublisherEntry>();\n const clusterClient = yield* Wings.WingsClient.clusterClient;\n\n for (const resource of config.connector.resources) {\n const mappingInput = config.tables[resource.name];\n if (!mappingInput) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Missing table for ${resource.name}` }),\n );\n }\n\n const mapping = normalizeMapping(mappingInput);\n const table = yield* clusterClient.getTable({ name: mapping.name }).pipe(\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Failed to resolve Wings table for ${resource.name}`,\n cause: error,\n }),\n ),\n );\n const { keyField, versionField } = yield* validateTableMapping({\n resource,\n table,\n partitionValue: mapping.partitionValue,\n });\n\n const publisher = yield* Wings.WingsClient.publisher({\n table,\n partitionValue: mapping.partitionValue,\n }).pipe(\n Effect.mapError((error) => new ConnectorError({ message: error.message, cause: error })),\n );\n\n entries.set(resource.name, {\n resource,\n table,\n publisher,\n partitionValue: mapping.partitionValue,\n keyField,\n versionField,\n });\n }\n\n const pushRows = (\n entry: PublisherEntry,\n operation: \"upsert\" | \"delete\",\n rows: ReadonlyArray<Rows>,\n ) =>\n Effect.gen(function* () {\n if (rows.length === 0) return accepted(entry);\n const batch = yield* buildRecordBatch(rows);\n return yield* entry.publisher\n .push({ operation, batch, partitionValue: entry.partitionValue })\n .pipe(\n Effect.matchEffect({\n onFailure: (error) => Effect.succeed(rejected(entry, error.message, rows.length)),\n onSuccess: () => Effect.succeed(accepted(entry)),\n }),\n );\n });\n\n const service: PublisherService = {\n publish: Effect.fn(SpanName.publish, { kind: \"producer\" })(function* ({\n resource,\n source: _source,\n batch,\n }) {\n const entry = entries.get(resource);\n if (!entry) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Unknown resource ${resource}` }),\n );\n }\n\n if (batch.mutations.length === 0) return accepted(entry);\n\n const upserts: Rows[] = [];\n const deletes: Rows[] = [];\n for (const mutation of batch.mutations) {\n if (mutation.op === \"upsert\") {\n upserts.push(mutation.row as Rows);\n } else {\n deletes.push({\n [entry.keyField.name]: mutation.key,\n [entry.versionField.name]: mutation.version,\n });\n }\n }\n\n const upsertAck = yield* pushRows(entry, \"upsert\", upserts);\n if (upsertAck.status === \"rejected\") return upsertAck;\n\n return yield* pushRows(entry, \"delete\", deletes);\n }),\n };\n\n return Publisher.of(service);\n }),\n );\n","export type { ResourceBatch } from \"../core/types\";\nexport type { PublishAck, PublishOptions, PublisherService, PublishSource } from \"./service\";\nexport type { WingsPublisherConfig } from \"./wings\";\n\nexport { layerConsole } from \"./console\";\nexport { Publisher } from \"./service\";\nexport { layerWings } from \"./wings\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as RuntimeConfigError, i as PlatformRuntimeKey, n as layerHosted, o as RuntimeConfigErrorCode, r as PlatformRuntimeDefault } from "../index-o1-Z3n7V.js";
|
|
2
|
+
export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, RuntimeConfigErrorCode, layerHosted };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "../constants-BRhIX_rz.js";
|
|
2
|
+
import { n as layerHosted, r as RuntimeConfigError } from "../runtime-config-BpB-d_bF.js";
|
|
3
|
+
|
|
4
|
+
export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, layerHosted };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-BRhIX_rz.js";
|
|
3
|
+
import { Config, ConfigProvider, Data, Effect, FileSystem, Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/runtime-config/error.ts
|
|
6
|
+
/** Expected hosted-config failure with a stable code and diagnostic cause. */
|
|
7
|
+
var RuntimeConfigError = class extends Data.TaggedError("RuntimeConfigError") {};
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/runtime-config/layer.ts
|
|
11
|
+
const providerFromFile = Effect.gen(function* () {
|
|
12
|
+
const configPath = yield* Config.nonEmptyString(PlatformRuntimeKey.configPath).pipe(Effect.mapError((error) => new RuntimeConfigError({
|
|
13
|
+
code: "CONFIG_PATH_MISSING",
|
|
14
|
+
message: `${PlatformRuntimeKey.configPath} is required in hosted mode`,
|
|
15
|
+
cause: error.cause
|
|
16
|
+
})));
|
|
17
|
+
const contents = yield* (yield* FileSystem.FileSystem).readFileString(configPath).pipe(Effect.mapError((cause) => new RuntimeConfigError({
|
|
18
|
+
code: "CONFIG_FILE_UNREADABLE",
|
|
19
|
+
message: "Connector config file could not be read",
|
|
20
|
+
cause
|
|
21
|
+
})));
|
|
22
|
+
const document = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(contents).pipe(Effect.mapError((error) => new RuntimeConfigError({
|
|
23
|
+
code: "CONFIG_JSON_INVALID",
|
|
24
|
+
message: "Connector config is not valid JSON",
|
|
25
|
+
cause: error
|
|
26
|
+
})));
|
|
27
|
+
return ConfigProvider.fromUnknown(document, { preserveEmptyStrings: true });
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Loads the required JSON file and lets environment values override it per key.
|
|
31
|
+
* Connector field validation remains in the connector's Effect Config.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* program.pipe(Effect.provide(layerHosted()));
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const layerHosted = () => ConfigProvider.layerAdd(providerFromFile);
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/runtime-config/index.ts
|
|
42
|
+
var runtime_config_exports = /* @__PURE__ */ __export({
|
|
43
|
+
PlatformRuntimeDefault: () => PlatformRuntimeDefault,
|
|
44
|
+
PlatformRuntimeKey: () => PlatformRuntimeKey,
|
|
45
|
+
RuntimeConfigError: () => RuntimeConfigError,
|
|
46
|
+
layerHosted: () => layerHosted
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { layerHosted as n, RuntimeConfigError as r, runtime_config_exports as t };
|
|
51
|
+
//# sourceMappingURL=runtime-config-BpB-d_bF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-config-BpB-d_bF.js","names":["providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n>"],"sources":["../src/runtime-config/error.ts","../src/runtime-config/layer.ts","../src/runtime-config/index.ts"],"sourcesContent":["import { Data } from \"effect\";\n\nexport type RuntimeConfigErrorCode =\n | \"CONFIG_PATH_MISSING\"\n | \"CONFIG_FILE_UNREADABLE\"\n | \"CONFIG_JSON_INVALID\";\n\n/** Expected hosted-config failure with a stable code and diagnostic cause. */\nexport class RuntimeConfigError extends Data.TaggedError(\"RuntimeConfigError\")<{\n readonly code: RuntimeConfigErrorCode;\n readonly message: string;\n readonly cause: Error;\n}> {}\n","import { Config, ConfigProvider, Effect, FileSystem, Schema } from \"effect\";\n\nimport { PlatformRuntimeKey } from \"./constants\";\nimport { RuntimeConfigError } from \"./error\";\n\nconst providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n> = Effect.gen(function* () {\n const configPath = yield* Config.nonEmptyString(PlatformRuntimeKey.configPath).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_PATH_MISSING\",\n message: `${PlatformRuntimeKey.configPath} is required in hosted mode`,\n cause: error.cause,\n }),\n ),\n );\n const fileSystem = yield* FileSystem.FileSystem;\n const contents = yield* fileSystem.readFileString(configPath).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"CONFIG_FILE_UNREADABLE\",\n message: \"Connector config file could not be read\",\n cause,\n }),\n ),\n );\n const document = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(\n contents,\n ).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_JSON_INVALID\",\n message: \"Connector config is not valid JSON\",\n cause: error,\n }),\n ),\n );\n return ConfigProvider.fromUnknown(document, { preserveEmptyStrings: true });\n});\n\n/**\n * Loads the required JSON file and lets environment values override it per key.\n * Connector field validation remains in the connector's Effect Config.\n *\n * @example\n * ```ts\n * program.pipe(Effect.provide(layerHosted()));\n * ```\n */\nexport const layerHosted = () => ConfigProvider.layerAdd(providerFromFile);\n","export { RuntimeConfigError, type RuntimeConfigErrorCode } from \"./error\";\nexport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"./constants\";\nexport { layerHosted } from \"./layer\";\n"],"mappings":";;;;;;AAQA,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;ACPH,MAAMA,mBAIF,OAAO,IAAI,aAAa;CAC1B,MAAM,aAAa,OAAO,OAAO,eAAe,mBAAmB,WAAW,CAAC,KAC7E,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS,GAAG,mBAAmB,WAAW;EAC1C,OAAO,MAAM;EACd,CAAC,CACL,CACF;CAED,MAAM,WAAW,QADE,OAAO,WAAW,YACF,eAAe,WAAW,CAAC,KAC5D,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT;EACD,CAAC,CACL,CACF;CACD,MAAM,WAAW,OAAO,OAAO,oBAAoB,OAAO,eAAe,OAAO,QAAQ,CAAC,CACvF,SACD,CAAC,KACA,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT,OAAO;EACR,CAAC,CACL,CACF;AACD,QAAO,eAAe,YAAY,UAAU,EAAE,sBAAsB,MAAM,CAAC;EAC3E;;;;;;;;;;AAWF,MAAa,oBAAoB,eAAe,SAAS,iBAAiB"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
2
|
+
import { a as entitiesUpsertedTotal, c as publishDuration, i as entitiesDeletedTotal, n as batchSize, p as setWebhookQueueDepth, r as batchesTotal, u as recordWebhookRequest } from "./metrics-CWPy2Aqc.js";
|
|
3
|
+
import { a as SpanName, i as EventName, n as Attr, o as addCurrentSpanEvent, r as EventAttr, s as annotateError, t as Publisher } from "./service-DuFiQwid.js";
|
|
4
|
+
import { Cause, Context, Effect, Metric, Queue, Schema } from "effect";
|
|
5
|
+
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
6
|
+
|
|
7
|
+
//#region src/publisher/instrumented.ts
|
|
8
|
+
const publishBatch = Effect.fnUntraced(function* (options) {
|
|
9
|
+
const metric = {
|
|
10
|
+
connector: options.connector ?? "unknown",
|
|
11
|
+
resource: options.resource,
|
|
12
|
+
source: options.source
|
|
13
|
+
};
|
|
14
|
+
const upserts = options.batch.mutations.filter((mutation) => mutation.op === "upsert").length;
|
|
15
|
+
const deletes = options.batch.mutations.length - upserts;
|
|
16
|
+
const publisher = yield* Publisher;
|
|
17
|
+
const [duration, ack] = yield* Effect.timed(publisher.publish({
|
|
18
|
+
resource: options.resource,
|
|
19
|
+
source: options.source,
|
|
20
|
+
batch: options.batch
|
|
21
|
+
})).pipe(Effect.catchCause((cause) => Metric.update(Metric.withAttributes(batchesTotal, {
|
|
22
|
+
...metric,
|
|
23
|
+
outcome: "error"
|
|
24
|
+
}), 1).pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
25
|
+
yield* Metric.update(Metric.withAttributes(publishDuration, metric), duration);
|
|
26
|
+
yield* Metric.update(Metric.withAttributes(batchesTotal, {
|
|
27
|
+
...metric,
|
|
28
|
+
outcome: ack.status
|
|
29
|
+
}), 1);
|
|
30
|
+
yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === "accepted" });
|
|
31
|
+
if (ack.status === "rejected") {
|
|
32
|
+
yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: "publish" });
|
|
33
|
+
return yield* Effect.fail(new ConnectorError({ message: `Publisher rejected batch for ${options.resource}: ${ack.reason}` }));
|
|
34
|
+
}
|
|
35
|
+
yield* Metric.update(Metric.withAttributes(entitiesUpsertedTotal, metric), upserts);
|
|
36
|
+
yield* Metric.update(Metric.withAttributes(entitiesDeletedTotal, metric), deletes);
|
|
37
|
+
yield* Metric.update(Metric.withAttributes(batchSize, metric), options.batch.mutations.length);
|
|
38
|
+
if (options.batch.cursor !== void 0) yield* addCurrentSpanEvent(EventName.batchCheckpoint, { [EventAttr.batchCursor]: options.batch.cursor });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/webhook/server.ts
|
|
43
|
+
var WebhookQueue = class extends Context.Service()("@useairfoil/connector-kit/WebhookQueue") {};
|
|
44
|
+
const resourceBatch = (mutations) => ({ mutations });
|
|
45
|
+
const decodeAndHandleWebhook = (resourceName, webhook, payload) => Schema.decodeUnknownEffect(webhook.schema)(payload).pipe(Effect.mapError((cause) => new ConnectorError({
|
|
46
|
+
message: `Invalid webhook payload for resource ${resourceName}`,
|
|
47
|
+
cause
|
|
48
|
+
})), Effect.flatMap((decoded) => webhook.handler({ payload: decoded })));
|
|
49
|
+
const makeRouteContext = (request, rawBody, payload, batches) => ({
|
|
50
|
+
request,
|
|
51
|
+
rawBody,
|
|
52
|
+
payload,
|
|
53
|
+
to: (resource, payload$1) => Effect.gen(function* () {
|
|
54
|
+
if (!resource.webhook) return yield* Effect.fail(new ConnectorError({ message: `Resource ${resource.name} does not define a webhook handler` }));
|
|
55
|
+
const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload$1);
|
|
56
|
+
batches.push({
|
|
57
|
+
resource: resource.name,
|
|
58
|
+
batch: resourceBatch(mutations)
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
const compactBatches = (batches) => {
|
|
63
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const item of batches) {
|
|
65
|
+
const current = grouped.get(item.resource) ?? [];
|
|
66
|
+
grouped.set(item.resource, [...current, ...item.batch.mutations]);
|
|
67
|
+
}
|
|
68
|
+
return Array.from(grouped.entries()).map(([resource, mutations]) => ({
|
|
69
|
+
resource,
|
|
70
|
+
batch: resourceBatch(mutations)
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
const publishBatches = (batches, options) => Effect.gen(function* () {
|
|
74
|
+
yield* Effect.forEach(batches, (batch) => publishBatch({
|
|
75
|
+
connector: options.connectorName,
|
|
76
|
+
resource: batch.resource,
|
|
77
|
+
source: "webhook",
|
|
78
|
+
batch: batch.batch
|
|
79
|
+
}), { concurrency: "unbounded" });
|
|
80
|
+
});
|
|
81
|
+
const badRequest = (message) => HttpServerResponse.jsonUnsafe({
|
|
82
|
+
ok: false,
|
|
83
|
+
error: message
|
|
84
|
+
}, { status: 400 });
|
|
85
|
+
const readRawBody = (request) => request.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer)));
|
|
86
|
+
const parseJson = (rawBody) => Effect.try({
|
|
87
|
+
try: () => JSON.parse(new TextDecoder().decode(rawBody)),
|
|
88
|
+
catch: (cause) => new ConnectorError({
|
|
89
|
+
message: "Invalid webhook JSON",
|
|
90
|
+
cause
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
const makeHandler = (route, options) => Effect.gen(function* () {
|
|
94
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
95
|
+
const collected = [];
|
|
96
|
+
const rawBodyResult = yield* readRawBody(request).pipe(Effect.match({
|
|
97
|
+
onFailure: () => ({
|
|
98
|
+
_tag: "Error",
|
|
99
|
+
message: "Failed to read webhook body"
|
|
100
|
+
}),
|
|
101
|
+
onSuccess: (value) => ({
|
|
102
|
+
_tag: "Success",
|
|
103
|
+
value
|
|
104
|
+
})
|
|
105
|
+
}));
|
|
106
|
+
if (rawBodyResult._tag === "Error") {
|
|
107
|
+
yield* recordWebhookRequest({
|
|
108
|
+
connector: options.connectorName,
|
|
109
|
+
path: String(route.path),
|
|
110
|
+
outcome: "read_error"
|
|
111
|
+
});
|
|
112
|
+
return badRequest(rawBodyResult.message);
|
|
113
|
+
}
|
|
114
|
+
const jsonResult = yield* parseJson(rawBodyResult.value).pipe(Effect.match({
|
|
115
|
+
onFailure: (error) => ({
|
|
116
|
+
_tag: "Error",
|
|
117
|
+
message: error.message
|
|
118
|
+
}),
|
|
119
|
+
onSuccess: (value) => ({
|
|
120
|
+
_tag: "Success",
|
|
121
|
+
value
|
|
122
|
+
})
|
|
123
|
+
}));
|
|
124
|
+
if (jsonResult._tag === "Error") {
|
|
125
|
+
yield* recordWebhookRequest({
|
|
126
|
+
connector: options.connectorName,
|
|
127
|
+
path: String(route.path),
|
|
128
|
+
outcome: "invalid_json"
|
|
129
|
+
});
|
|
130
|
+
return badRequest(jsonResult.message);
|
|
131
|
+
}
|
|
132
|
+
const payloadResult = yield* Schema.decodeUnknownEffect(route.schema)(jsonResult.value).pipe(Effect.match({
|
|
133
|
+
onFailure: () => ({
|
|
134
|
+
_tag: "Error",
|
|
135
|
+
message: "Invalid webhook payload"
|
|
136
|
+
}),
|
|
137
|
+
onSuccess: (value) => ({
|
|
138
|
+
_tag: "Success",
|
|
139
|
+
value
|
|
140
|
+
})
|
|
141
|
+
}));
|
|
142
|
+
if (payloadResult._tag === "Error") {
|
|
143
|
+
yield* recordWebhookRequest({
|
|
144
|
+
connector: options.connectorName,
|
|
145
|
+
path: String(route.path),
|
|
146
|
+
outcome: "invalid_payload"
|
|
147
|
+
});
|
|
148
|
+
return badRequest(payloadResult.message);
|
|
149
|
+
}
|
|
150
|
+
const response = yield* Effect.withSpan(route.handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected)).pipe(Effect.tapError((error) => annotateError("webhook_handle", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
|
|
151
|
+
const batches = compactBatches(collected);
|
|
152
|
+
if (route.ackMode === "after-enqueue") {
|
|
153
|
+
const queue = yield* WebhookQueue;
|
|
154
|
+
yield* Queue.offerAll(queue.queue, batches);
|
|
155
|
+
const depth = yield* Queue.size(queue.queue);
|
|
156
|
+
yield* setWebhookQueueDepth(options.connectorName, depth);
|
|
157
|
+
yield* recordWebhookRequest({
|
|
158
|
+
connector: options.connectorName,
|
|
159
|
+
path: String(route.path),
|
|
160
|
+
outcome: response.status >= 400 ? "rejected" : "ok"
|
|
161
|
+
});
|
|
162
|
+
return response;
|
|
163
|
+
}
|
|
164
|
+
yield* Effect.withSpan(publishBatches(batches, { connectorName: options.connectorName }).pipe(Effect.tapError((error) => annotateError("webhook_publish", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
|
|
165
|
+
yield* recordWebhookRequest({
|
|
166
|
+
connector: options.connectorName,
|
|
167
|
+
path: String(route.path),
|
|
168
|
+
outcome: response.status >= 400 ? "rejected" : "ok"
|
|
169
|
+
});
|
|
170
|
+
return response;
|
|
171
|
+
}).pipe(Effect.catchCause((cause) => recordWebhookRequest({
|
|
172
|
+
connector: options.connectorName,
|
|
173
|
+
path: String(route.path),
|
|
174
|
+
outcome: "handler_error"
|
|
175
|
+
}).pipe(Effect.andThen(Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`)), Effect.andThen(Effect.succeed(HttpServerResponse.jsonUnsafe({
|
|
176
|
+
ok: false,
|
|
177
|
+
error: "Webhook handler failed"
|
|
178
|
+
}, { status: 500 }))))));
|
|
179
|
+
const router = (routes, options) => HttpRouter.addAll(routes.map((route) => HttpRouter.route("POST", route.path, makeHandler(route, options))));
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
export { router as n, publishBatch as r, WebhookQueue as t };
|
|
183
|
+
//# sourceMappingURL=server-CXTQ7odl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-CXTQ7odl.js","names":["Metrics.batchesTotal","Metrics.publishDuration","Metrics.entitiesUpsertedTotal","Metrics.entitiesDeletedTotal","Metrics.batchSize","payload","collected: Array<QueuedWebhookBatch>","Metrics.recordWebhookRequest","Metrics.setWebhookQueueDepth"],"sources":["../src/publisher/instrumented.ts","../src/webhook/server.ts"],"sourcesContent":["import { Effect, Metric } from \"effect\";\n\nimport type { ResourceBatch } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { Attr, EventAttr, EventName, addCurrentSpanEvent } from \"../telemetry\";\nimport { Publisher, type PublishSource } from \"./service\";\n\nexport const publishBatch = Effect.fnUntraced(function* (options: {\n readonly connector?: string;\n readonly resource: string;\n readonly source: PublishSource;\n readonly batch: ResourceBatch;\n}) {\n const metric = {\n connector: options.connector ?? \"unknown\",\n resource: options.resource,\n source: options.source,\n };\n const upserts = options.batch.mutations.filter((mutation) => mutation.op === \"upsert\").length;\n const deletes = options.batch.mutations.length - upserts;\n\n const publisher = yield* Publisher;\n const [duration, ack] = yield* Effect.timed(\n publisher.publish({\n resource: options.resource,\n source: options.source,\n batch: options.batch,\n }),\n ).pipe(\n Effect.catchCause((cause) =>\n Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: \"error\" }),\n 1,\n ).pipe(Effect.andThen(Effect.failCause(cause))),\n ),\n );\n\n yield* Metric.update(Metric.withAttributes(Metrics.publishDuration, metric), duration);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: ack.status }),\n 1,\n );\n\n yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === \"accepted\" });\n if (ack.status === \"rejected\") {\n yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: \"publish\" });\n return yield* Effect.fail(\n new ConnectorError({\n message: `Publisher rejected batch for ${options.resource}: ${ack.reason}`,\n }),\n );\n }\n\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesUpsertedTotal, metric), upserts);\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesDeletedTotal, metric), deletes);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchSize, metric),\n options.batch.mutations.length,\n );\n\n if (options.batch.cursor !== undefined) {\n yield* addCurrentSpanEvent(EventName.batchCheckpoint, {\n [EventAttr.batchCursor]: options.batch.cursor,\n });\n }\n});\n","import { Cause, Context, Effect, Queue, Schema } from \"effect\";\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type {\n ResourceBatch,\n ResourceDefinition,\n ResourceMutation,\n WebhookHandler,\n WebhookRoute,\n WebhookRouteContext,\n} from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { publishBatch } from \"../publisher/instrumented\";\nimport { Attr, SpanName, annotateError } from \"../telemetry\";\n\nexport type QueuedWebhookBatch = {\n readonly resource: string;\n readonly batch: ResourceBatch;\n};\n\nexport class WebhookQueue extends Context.Service<\n WebhookQueue,\n {\n readonly queue: Queue.Queue<QueuedWebhookBatch>;\n }\n>()(\"@useairfoil/connector-kit/WebhookQueue\") {}\n\nconst resourceBatch = <Row extends object>(\n mutations: ReadonlyArray<ResourceMutation<Row>>,\n): ResourceBatch<Row> => ({\n mutations,\n});\n\nconst decodeAndHandleWebhook = <Row extends object, Payload>(\n resourceName: string,\n webhook: WebhookHandler<Row, Payload>,\n payload: unknown,\n) =>\n Schema.decodeUnknownEffect(webhook.schema)(payload).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({\n message: `Invalid webhook payload for resource ${resourceName}`,\n cause,\n }),\n ),\n Effect.flatMap((decoded) => webhook.handler({ payload: decoded })),\n );\n\nconst makeRouteContext = (\n request: HttpServerRequest.HttpServerRequest,\n rawBody: Uint8Array,\n payload: unknown,\n batches: Array<QueuedWebhookBatch>,\n): WebhookRouteContext<ReadonlyArray<ResourceDefinition>, unknown> => ({\n request,\n rawBody,\n payload,\n to: (resource, payload) =>\n Effect.gen(function* () {\n if (!resource.webhook) {\n return yield* Effect.fail(\n new ConnectorError({\n message: `Resource ${resource.name} does not define a webhook handler`,\n }),\n );\n }\n\n const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload);\n batches.push({\n resource: resource.name,\n batch: resourceBatch(mutations),\n });\n }),\n});\n\nconst compactBatches = (batches: ReadonlyArray<QueuedWebhookBatch>) => {\n const grouped = new Map<string, ResourceMutation[]>();\n for (const item of batches) {\n const current = grouped.get(item.resource) ?? [];\n grouped.set(item.resource, [...current, ...item.batch.mutations]);\n }\n return Array.from(grouped.entries()).map(([resource, mutations]) => ({\n resource,\n batch: resourceBatch(mutations),\n }));\n};\n\nconst publishBatches = (\n batches: ReadonlyArray<QueuedWebhookBatch>,\n options: { readonly connectorName: string },\n) =>\n Effect.gen(function* () {\n yield* Effect.forEach(\n batches,\n (batch) =>\n publishBatch({\n connector: options.connectorName,\n resource: batch.resource,\n source: \"webhook\",\n batch: batch.batch,\n }),\n { concurrency: \"unbounded\" },\n );\n });\n\nconst badRequest = (message: string) =>\n HttpServerResponse.jsonUnsafe({ ok: false, error: message }, { status: 400 });\n\nconst readRawBody = (request: HttpServerRequest.HttpServerRequest) =>\n request.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer)));\n\nconst parseJson = (rawBody: Uint8Array) =>\n Effect.try({\n try: () => JSON.parse(new TextDecoder().decode(rawBody)),\n catch: (cause) => new ConnectorError({ message: \"Invalid webhook JSON\", cause }),\n });\n\nconst makeHandler = (route: WebhookRoute, options: { readonly connectorName: string }) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const collected: Array<QueuedWebhookBatch> = [];\n\n const rawBodyResult = yield* readRawBody(request).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Failed to read webhook body\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (rawBodyResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"read_error\",\n });\n return badRequest(rawBodyResult.message);\n }\n\n const jsonResult = yield* parseJson(rawBodyResult.value).pipe(\n Effect.match({\n onFailure: (error) => ({ _tag: \"Error\" as const, message: error.message }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (jsonResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_json\",\n });\n return badRequest(jsonResult.message);\n }\n\n const payloadResult = yield* Schema.decodeUnknownEffect(route.schema)(jsonResult.value).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Invalid webhook payload\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (payloadResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_payload\",\n });\n return badRequest(payloadResult.message);\n }\n\n const response = yield* Effect.withSpan(\n route\n .handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected))\n .pipe(Effect.tapError((error) => annotateError(\"webhook_handle\", error))),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n const batches = compactBatches(collected);\n\n if (route.ackMode === \"after-enqueue\") {\n const queue = yield* WebhookQueue;\n yield* Queue.offerAll(queue.queue, batches);\n const depth = yield* Queue.size(queue.queue);\n yield* Metrics.setWebhookQueueDepth(options.connectorName, depth);\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }\n\n yield* Effect.withSpan(\n publishBatches(batches, { connectorName: options.connectorName }).pipe(\n Effect.tapError((error) => annotateError(\"webhook_publish\", error)),\n ),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }).pipe(\n Effect.catchCause((cause) =>\n Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"handler_error\",\n }).pipe(\n Effect.andThen(Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`)),\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n { ok: false, error: \"Webhook handler failed\" },\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n );\n\nexport const router = (\n routes: ReadonlyArray<WebhookRoute>,\n options: { readonly connectorName: string },\n) =>\n HttpRouter.addAll(\n routes.map((route) => HttpRouter.route(\"POST\", route.path, makeHandler(route, options))),\n );\n"],"mappings":"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"}
|