@fayz-ai/core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/access/index.js.map +1 -1
- package/dist/chunk-BEGXYHS4.js +566 -0
- package/dist/chunk-BEGXYHS4.js.map +1 -0
- package/dist/chunk-ED6NBH6U.js +348 -0
- package/dist/chunk-ED6NBH6U.js.map +1 -0
- package/dist/chunk-EWKX4VUM.js +429 -0
- package/dist/chunk-EWKX4VUM.js.map +1 -0
- package/dist/chunk-LARZWDHA.js +319 -0
- package/dist/chunk-LARZWDHA.js.map +1 -0
- package/dist/chunk-OXQGRJZT.js +125 -0
- package/dist/chunk-OXQGRJZT.js.map +1 -0
- package/dist/chunk-RRYFXUQX.js +1247 -0
- package/dist/chunk-RRYFXUQX.js.map +1 -0
- package/dist/{chunk-5RJVUC6J.js → chunk-TDI6F6PT.js} +373 -25
- package/dist/chunk-TDI6F6PT.js.map +1 -0
- package/dist/{chunk-V2MSAUHO.js → chunk-UVKH6RDX.js} +211 -335
- package/dist/chunk-UVKH6RDX.js.map +1 -0
- package/dist/chunk-V53GR4UT.js +289 -0
- package/dist/chunk-V53GR4UT.js.map +1 -0
- package/dist/connector-runtime/index.d.ts +15 -0
- package/dist/connector-runtime/index.d.ts.map +1 -0
- package/dist/connector-runtime/index.js +3 -0
- package/dist/connector-runtime/index.js.map +1 -0
- package/dist/data/archetype.d.ts.map +1 -1
- package/dist/data/backend-env.d.ts +38 -0
- package/dist/data/backend-env.d.ts.map +1 -0
- package/dist/data/bulk.d.ts +24 -0
- package/dist/data/bulk.d.ts.map +1 -1
- package/dist/data/cached.d.ts.map +1 -1
- package/dist/data/custom-fields.d.ts +5 -0
- package/dist/data/custom-fields.d.ts.map +1 -0
- package/dist/data/index.d.ts +2 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +3 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/platform-api.d.ts.map +1 -1
- package/dist/data/resolve.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +8 -0
- package/dist/data/types.d.ts.map +1 -1
- package/dist/entity/events.d.ts +14 -0
- package/dist/entity/events.d.ts.map +1 -0
- package/dist/events/envelope.d.ts +31 -0
- package/dist/events/envelope.d.ts.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/log.d.ts +44 -0
- package/dist/events/log.d.ts.map +1 -0
- package/dist/events/types.d.ts +40 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/fields/store.d.ts +45 -0
- package/dist/fields/store.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/shell-translations.en.d.ts.map +1 -1
- package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
- package/dist/index.d.ts +45 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +543 -56
- package/dist/index.js.map +1 -1
- package/dist/integrations/events.d.ts +7 -0
- package/dist/integrations/events.d.ts.map +1 -0
- package/dist/integrations/index.d.ts +533 -29
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +3 -1
- package/dist/integrations/permissions.d.ts +132 -0
- package/dist/integrations/permissions.d.ts.map +1 -0
- package/dist/integrations/request.d.ts +170 -0
- package/dist/integrations/request.d.ts.map +1 -0
- package/dist/integrations/runtime-token.d.ts +33 -0
- package/dist/integrations/runtime-token.d.ts.map +1 -0
- package/dist/integrations/runtime.d.ts +398 -0
- package/dist/integrations/runtime.d.ts.map +1 -0
- package/dist/integrations/settings.d.ts +102 -0
- package/dist/integrations/settings.d.ts.map +1 -0
- package/dist/integrations/store.d.ts +145 -0
- package/dist/integrations/store.d.ts.map +1 -0
- package/dist/{address/index.d.ts → lib/address.d.ts} +1 -1
- package/dist/{address/index.d.ts.map → lib/address.d.ts.map} +1 -1
- package/dist/{phone/index.d.ts → lib/phone.d.ts} +1 -1
- package/dist/lib/phone.d.ts.map +1 -0
- package/dist/{seo/index.d.ts → lib/seo.d.ts} +18 -1
- package/dist/lib/seo.d.ts.map +1 -0
- package/dist/{tenant/index.d.ts → lib/tenant.d.ts} +1 -1
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/manifest/index.d.ts +1 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/messaging/index.d.ts +3 -0
- package/dist/messaging/index.d.ts.map +1 -0
- package/dist/messaging/registry.d.ts +35 -0
- package/dist/messaging/registry.d.ts.map +1 -0
- package/dist/messaging/types.d.ts +67 -0
- package/dist/messaging/types.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -1
- package/dist/plugin/runtime.d.ts +17 -0
- package/dist/plugin/runtime.d.ts.map +1 -1
- package/dist/registry/block-catalog.d.ts +78 -0
- package/dist/registry/block-catalog.d.ts.map +1 -0
- package/dist/registry/block-constraints.d.ts +64 -0
- package/dist/registry/block-constraints.d.ts.map +1 -0
- package/dist/registry/block-schema.d.ts +69 -0
- package/dist/registry/block-schema.d.ts.map +1 -0
- package/dist/registry/blocks.d.ts +84 -0
- package/dist/registry/blocks.d.ts.map +1 -0
- package/dist/registry/entities.d.ts +179 -0
- package/dist/registry/entities.d.ts.map +1 -0
- package/dist/registry/extension-points.d.ts +117 -0
- package/dist/registry/extension-points.d.ts.map +1 -0
- package/dist/registry/handles.d.ts +41 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/index.d.ts +41 -41
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/loaders.d.ts +55 -0
- package/dist/registry/loaders.d.ts.map +1 -0
- package/dist/registry/registry-class.d.ts +157 -0
- package/dist/registry/registry-class.d.ts.map +1 -0
- package/dist/registry/schema-check.d.ts +14 -0
- package/dist/registry/schema-check.d.ts.map +1 -0
- package/dist/router/index.d.ts +78 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1 -1
- package/dist/{shell-translations.pt-br-2GWRQRRP.js → shell-translations.pt-br-BM5ZFSKJ.js} +72 -3
- package/dist/shell-translations.pt-br-BM5ZFSKJ.js.map +1 -0
- package/dist/telemetry/errors.d.ts +87 -0
- package/dist/telemetry/errors.d.ts.map +1 -0
- package/dist/testing/index.js +4 -1
- package/dist/{testing/index.d.ts → testing.d.ts} +5 -3
- package/dist/testing.d.ts.map +1 -0
- package/dist/types/analytics.d.ts +213 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/crud.d.ts +37 -3
- package/dist/types/crud.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +138 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/package.json +9 -17
- package/dist/blocks/index.d.ts +0 -25
- package/dist/blocks/index.d.ts.map +0 -1
- package/dist/chunk-5RJVUC6J.js.map +0 -1
- package/dist/chunk-L4TLMYGC.js +0 -42
- package/dist/chunk-L4TLMYGC.js.map +0 -1
- package/dist/chunk-V2MSAUHO.js.map +0 -1
- package/dist/chunk-X5THHG6T.js +0 -77
- package/dist/chunk-X5THHG6T.js.map +0 -1
- package/dist/chunk-Y2C2FBQP.js +0 -35
- package/dist/chunk-Y2C2FBQP.js.map +0 -1
- package/dist/chunk-YH25Y4FW.js +0 -311
- package/dist/chunk-YH25Y4FW.js.map +0 -1
- package/dist/phone/index.d.ts.map +0 -1
- package/dist/seo/index.d.ts.map +0 -1
- package/dist/shell-translations.pt-br-2GWRQRRP.js.map +0 -1
- package/dist/tenant/index.d.ts.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/src/access/denial.ts +0 -33
- package/src/access/engine.test.ts +0 -48
- package/src/access/engine.ts +0 -136
- package/src/access/index.ts +0 -14
- package/src/access/limits.ts +0 -18
- package/src/address/index.ts +0 -188
- package/src/app/render.tsx +0 -69
- package/src/blocks/index.tsx +0 -68
- package/src/data/archetype.ts +0 -255
- package/src/data/bulk.ts +0 -35
- package/src/data/cached.ts +0 -55
- package/src/data/count.test.ts +0 -152
- package/src/data/count.ts +0 -131
- package/src/data/filters.test.ts +0 -70
- package/src/data/filters.ts +0 -90
- package/src/data/index.ts +0 -11
- package/src/data/mock.ts +0 -90
- package/src/data/platform-api.ts +0 -209
- package/src/data/refresh.ts +0 -115
- package/src/data/resolve.ts +0 -126
- package/src/data/supabase.ts +0 -230
- package/src/data/types.ts +0 -50
- package/src/entity/index.ts +0 -2
- package/src/entity/registry.ts +0 -49
- package/src/events/index.tsx +0 -82
- package/src/i18n/index.ts +0 -476
- package/src/i18n/shell-translations.en.ts +0 -875
- package/src/i18n/shell-translations.pt-br.ts +0 -873
- package/src/index.ts +0 -217
- package/src/integrations/index.ts +0 -237
- package/src/lib/cache.ts +0 -118
- package/src/lib/csv.ts +0 -54
- package/src/lib/format.ts +0 -58
- package/src/manifest/app-manifest.schema.json +0 -676
- package/src/manifest/index.ts +0 -518
- package/src/manifest/serialize.ts +0 -52
- package/src/phone/index.ts +0 -85
- package/src/plugin/createSafeDataProvider.ts +0 -30
- package/src/plugin/index.ts +0 -1
- package/src/plugin/runtime.ts +0 -331
- package/src/registry/index.ts +0 -163
- package/src/router/history.ts +0 -86
- package/src/router/index.ts +0 -29
- package/src/runtime/index.ts +0 -15
- package/src/runtime/oauth.ts +0 -219
- package/src/search/engine.ts +0 -559
- package/src/search/index.ts +0 -25
- package/src/search/text.ts +0 -206
- package/src/search/types.ts +0 -75
- package/src/seo/index.ts +0 -128
- package/src/tenant/index.ts +0 -38
- package/src/testing/index.ts +0 -116
- package/src/types/auth.ts +0 -41
- package/src/types/billing.ts +0 -39
- package/src/types/crud.ts +0 -184
- package/src/types/entities.ts +0 -142
- package/src/types/entitlements.ts +0 -45
- package/src/types/i18n.ts +0 -7
- package/src/types/index.ts +0 -11
- package/src/types/org.ts +0 -137
- package/src/types/payment.ts +0 -44
- package/src/types/permissions.ts +0 -32
- package/src/types/plugins.ts +0 -480
- package/src/types/theme.ts +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/events.ts","../src/integrations/permissions.ts","../src/integrations/runtime-token.ts","../src/integrations/settings.ts","../src/integrations/store.ts","../src/integrations/request.ts","../src/integrations/index.ts"],"names":[],"mappings":"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","file":"chunk-BEGXYHS4.js","sourcesContent":["// Event-key matching, in its own module so ./permissions can use it without\n// importing the barrel that re-exports ./permissions.\n\n/**\n * Whether a subscription pattern covers an event key. One implementation on\n * purpose: the ingress, the settings UI and the docs' examples all have to agree\n * on what `blog.post.*` means, and a second matcher is a silent divergence.\n */\nexport function eventKeyMatches(pattern: string, key: string): boolean {\n if (pattern === '*') return true\n if (!pattern.endsWith('*')) return pattern === key\n const prefix = pattern.slice(0, -1)\n return key.startsWith(prefix)\n}\n","// ---------------------------------------------------------------------------\n// What a connector needs — one declaration, three consumers\n// ---------------------------------------------------------------------------\n// A connector already said fragments of this in four places: `entities` named\n// record types with no notion of read or write, `subscribesTo` named events with\n// no notion of why, `managedAutomations` named reactions, and `invokedAt` named\n// call sites. None of them could answer the question a tenant actually asks\n// before authorizing — *what will this thing be able to touch, and why?* — and\n// two of them could contradict each other without anything noticing.\n//\n// So there is one list now. It is written once and read three times:\n//\n// 1. the consent view, before the tenant authorizes (packages/admin);\n// 2. \"quando ele age\", which now says what each moment reaches;\n// 3. eventually the minted credential's scope — FAY-1348 §D7: *\"a credencial\n// autoriza só as RPCs declaradas do conector … 'o que este conector pode\n// tocar' vira lista legível, e é a mesma lista para a nossa edge function e\n// para o servidor do parceiro.\"*\n//\n// Consumer 3 DOES NOT EXIST YET. Nothing on this platform reads a permission and\n// refuses a call — the token minting that would (FAY-1381) landed only its\n// browser-initiated half. That is why {@link connectorGrantScopes} exists and why\n// nothing calls it: the scope string is the shape D7 will enforce, written down\n// now so the declaration is already the right shape when enforcement arrives.\n// Until then the copy in front of the tenant says this is what the connector\n// DECLARES, never what the platform GUARANTEES. See the `connectors.permissions.*`\n// strings in ../i18n.\n//\n// The grammar is OAuth consent's, because that is the one people can already\n// read: a permission names a RESOURCE, the ACTIONS it needs on it, and a REASON\n// short enough to judge. Grouped by resource, read told apart from write,\n// anything sensitive marked.\n\nimport { eventKeyMatches } from './events'\n\n/**\n * What a permission is about.\n *\n * Two kinds and not one, because the actions differ and conflating them is how a\n * declaration stops being checkable: you cannot `listen` to a record type, and\n * \"write\" to an event means emitting a new fact, which is a different act from\n * updating a row.\n */\nexport type ConnectorResourceKind =\n /** A canonical record type in this app — `booking`, `movement`, `product`. */\n | 'data'\n /** A domain fact — an exact event key or a `*`-suffixed prefix. */\n | 'event'\n\n/**\n * Everything a connector can ask for. Which of these are legal depends on the\n * resource kind — see {@link permissionActions}.\n *\n * The three event actions are the founder's own words, and they are three\n * genuinely different exposures: `read` is the history that already exists,\n * `listen` is every fact from now on, `emit` is the connector putting facts into\n * the app that the app's own automations will then act on.\n */\nexport type ConnectorPermissionAction =\n /** Data: reads rows. Event: reads facts already recorded. */\n | 'read'\n /** Data only: creates, updates or deletes rows. */\n | 'write'\n /** Event only: receives matching facts as they happen. */\n | 'listen'\n /** Event only: publishes facts into this app. */\n | 'emit'\n\nconst DATA_ACTIONS = ['read', 'write'] as const\nconst EVENT_ACTIONS = ['read', 'listen', 'emit'] as const\n\n/** The actions a resource kind admits. Closed, and the contract check reads it. */\nexport function permissionActions(kind: ConnectorResourceKind): readonly ConnectorPermissionAction[] {\n return kind === 'data' ? DATA_ACTIONS : EVENT_ACTIONS\n}\n\n/**\n * One thing a connector needs access to, and why.\n *\n * `reason` is required and it is the field that makes the rest worth showing. A\n * list of `booking: read, write` is a schema dump; *\"para criar o evento no\n * Google quando um horário é marcado\"* is something a salon owner can agree or\n * disagree with. Everything else on this screen is checkable by a machine; this\n * is the part only the tenant can judge, so it cannot be optional.\n */\nexport interface ConnectorPermission {\n kind: ConnectorResourceKind\n /** Entity name (`'booking'`) or event key/prefix (`'agenda.booking_created'`, `'blog.post.*'`). */\n resource: string\n /** What it needs to do. Non-empty, and legal for `kind`. */\n actions: ConnectorPermissionAction[]\n /** One sentence, in the tenant's language, that a tenant can judge. */\n reason: string\n /**\n * Money, identity, health, or anything a leak of which is a reportable event.\n * Marked because the reader scans this list rather than reading it, and a bank\n * statement should not look like a product name on the way past.\n */\n sensitive?: boolean\n}\n\n/** Whether a permission names a record type. */\nexport function isDataPermission(permission: ConnectorPermission): boolean {\n return permission.kind === 'data'\n}\n\n/** Whether a permission names a domain fact. */\nexport function isEventPermission(permission: ConnectorPermission): boolean {\n return permission.kind === 'event'\n}\n\n/** Whether a permission asks for an action. */\nexport function grants(permission: ConnectorPermission, action: ConnectorPermissionAction): boolean {\n return permission.actions.includes(action)\n}\n\n/** Anything that carries a permission list — a definition, or a plain object in a test. */\nexport interface PermissionBearing {\n permissions?: ConnectorPermission[]\n /** Legacy mirror of the `data` resources. See {@link connectorEntities}. */\n entities?: string[]\n /** Legacy mirror of the `listen` resources. See {@link connectorSubscribesTo}. */\n subscribesTo?: string[]\n}\n\n/** The permission list, or an empty one. Undeclared means the connector says nothing. */\nexport function connectorPermissions(def: PermissionBearing | undefined): ConnectorPermission[] {\n return def?.permissions ?? []\n}\n\n/**\n * The record types a connector touches — derived from `permissions`, falling\n * back to the legacy `entities` field.\n *\n * Derived and not duplicated: a connector that declares permissions must not\n * also maintain a parallel list of names, because the two would drift and the\n * screen would show one of them. A connector that declares BOTH is not merged —\n * the contract check refuses it unless they agree exactly, which is the whole\n * point of keeping the old field readable at all.\n */\nexport function connectorEntities(def: PermissionBearing | undefined): string[] {\n const declared = connectorPermissions(def).filter(isDataPermission).map((p) => p.resource)\n if (declared.length > 0) return unique(declared)\n return unique(def?.entities ?? [])\n}\n\n/**\n * The event patterns a connector wants delivered — derived from the `listen`\n * permissions, falling back to the legacy `subscribesTo` field.\n *\n * This is the ingress's filter, and it reads through here rather than off the\n * field so a connector that declares only `permissions` is still heard. Same\n * agreement rule as {@link connectorEntities}.\n */\nexport function connectorSubscribesTo(def: PermissionBearing | undefined): string[] {\n const declared = connectorPermissions(def)\n .filter((p) => isEventPermission(p) && grants(p, 'listen'))\n .map((p) => p.resource)\n if (declared.length > 0) return unique(declared)\n return unique(def?.subscribesTo ?? [])\n}\n\n/** Whether a connector asked to hear about this event. Undeclared = hears nothing. */\nexport function connectorSubscribes(def: PermissionBearing | undefined, key: string): boolean {\n return connectorSubscribesTo(def).some((pattern) => eventKeyMatches(pattern, key))\n}\n\n/** Whether the connector asks to change anything at all — data or events. */\nexport function permissionsWrite(def: PermissionBearing | undefined): boolean {\n return connectorPermissions(def).some((p) => grants(p, 'write') || grants(p, 'emit'))\n}\n\n/** Whether anything in the list was marked sensitive. */\nexport function hasSensitivePermission(def: PermissionBearing | undefined): boolean {\n return connectorPermissions(def).some((p) => p.sensitive === true)\n}\n\n/**\n * The list a consent view renders: data first, then events; within each,\n * sensitive first, then read-only, then alphabetical.\n *\n * Read-only last within a kind is deliberate. What a reader must not miss is\n * what changes their records, so the ordering puts the consequential rows where\n * the eye lands and leaves the harmless ones to be skimmed. Sorting is here and\n * not in the component so the order is assertable without rendering.\n */\nexport function sortedPermissions(def: PermissionBearing | undefined): ConnectorPermission[] {\n return [...connectorPermissions(def)].sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === 'data' ? -1 : 1\n const sensitivity = Number(b.sensitive ?? false) - Number(a.sensitive ?? false)\n if (sensitivity !== 0) return sensitivity\n const mutating = Number(mutates(b)) - Number(mutates(a))\n if (mutating !== 0) return mutating\n return a.resource.localeCompare(b.resource)\n })\n}\n\nfunction mutates(permission: ConnectorPermission): boolean {\n return grants(permission, 'write') || grants(permission, 'emit')\n}\n\n/**\n * The permissions grouped as the consent view shows them.\n *\n * One entry per resource, because a resource is what a person reasons about — a\n * screen that lists `booking · leitura` and `booking · escrita` as two rows asks\n * the reader to do the grouping themselves.\n */\nexport function groupedPermissions(def: PermissionBearing | undefined): {\n data: ConnectorPermission[]\n events: ConnectorPermission[]\n} {\n const sorted = sortedPermissions(def)\n return {\n data: sorted.filter(isDataPermission),\n events: sorted.filter(isEventPermission),\n }\n}\n\n/**\n * The declaration as scope strings — `data:booking:write`, `event:blog.post.*:listen`.\n *\n * **Nothing enforces these today.** This is the third consumer from the header,\n * and it is written now for one reason: FAY-1348 §D7 says the minted credential\n * authorizes exactly the connector's declared list, and a list that has to be\n * reshaped when that lands is a list that will be reshaped wrongly. One resource\n * yields one scope per action, sorted, so the string set is stable enough to be\n * compared across a mint and a renewal.\n */\nexport function connectorGrantScopes(def: PermissionBearing | undefined): string[] {\n const scopes: string[] = []\n for (const permission of connectorPermissions(def)) {\n for (const action of permission.actions) scopes.push(`${permission.kind}:${permission.resource}:${action}`)\n }\n return unique(scopes).sort()\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)]\n}\n","// ---------------------------------------------------------------------------\n// The runtime token, where a connector can reach it\n// ---------------------------------------------------------------------------\n// FAY-1389 gave the data plane a way to READ a credential\n// (`redeemConnectorCredential`) and was explicit about the constraint: an edge\n// function cannot mint a runtime token, it forwards the one the request\n// carried. Which leaves the question of who puts it on the request.\n//\n// For the two flows that already existed there was an answer, because the app\n// hands the token straight to the thing that needs it — `runtimeToken` is an\n// option of `createFayzConnectorCredentialSink` and of\n// `createFayzConnectorOAuthStarter`. A PULL connector has no such seam: it\n// invokes its own edge function from a panel, and until now had nowhere to ask.\n//\n// So this is a port, for the same reason the credential sink is one: who mints\n// the token is a DEPLOYMENT fact. The app that already writes\n//\n// const runtimeToken = createFayzRuntimeTokenSource({ projectId, supabase })\n// setConnectorCredentialSink(createFayzConnectorCredentialSink({ …, runtimeToken }))\n// setConnectorOAuthStarter(createFayzConnectorOAuthStarter({ …, runtimeToken }))\n//\n// adds one line with the handle it already has:\n//\n// setConnectorRuntimeTokenSource(runtimeToken)\n//\n// Installing none is a valid answer. A connector that needs one then refuses,\n// which is the correct behaviour for an app whose data plane could not prove\n// which tenant it is running for — the alternative is an edge function that\n// answers without knowing, and that is the whole thing FAY-1389 closed.\n//\n// It lives in `@fayz-ai/core` rather than in `@fayz-ai/sdk` because the callers\n// are plugins, and plugins already depend on core. Nothing here mints, caches\n// or validates a token: the source does all three (see\n// `createFayzRuntimeTokenSource`), and duplicating any of it would be a second\n// cache to go stale.\n\n/** What the hub knows about who is asking — a SELECTOR, never an assertion. */\nexport interface ConnectorRuntimeTokenRequest {\n /**\n * Which of the caller's own organizations this call is for.\n *\n * The platform proves membership from the session behind the token and\n * refuses anything outside it, so naming one narrows and can never widen.\n * Without it, an operator who belongs to two schools cannot be told apart\n * from one who belongs to neither.\n */\n tenantId?: string\n}\n\n/** A token, or something that produces one. Same shape the SDK's source has. */\nexport type ConnectorRuntimeTokenSource =\n | string\n | ((\n request?: ConnectorRuntimeTokenRequest,\n ) => string | undefined | null | Promise<string | undefined | null>)\n\n/** No token, so the call was not made. Distinct from a call that was refused. */\nexport class ConnectorRuntimeTokenUnavailableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConnectorRuntimeTokenUnavailableError'\n }\n}\n\nlet _source: ConnectorRuntimeTokenSource | null = null\n\nexport function setConnectorRuntimeTokenSource(source: ConnectorRuntimeTokenSource | null): void {\n _source = source\n}\n\n/** Null when the app configured none — callers must refuse, never fall back. */\nexport function getConnectorRuntimeTokenSource(): ConnectorRuntimeTokenSource | null {\n return _source\n}\n\n/**\n * The token to forward to a data plane, or a refusal.\n *\n * Throws rather than returning empty on both failure modes, and they are told\n * apart in the message: no source configured is a deployment that was never\n * wired, an empty answer is a session that cannot mint one right now. A caller\n * that treated either as \"carry on\" would send an unauthenticated request to a\n * function that must not answer it.\n */\nexport async function connectorRuntimeToken(request?: ConnectorRuntimeTokenRequest): Promise<string> {\n const source = _source\n if (!source) {\n throw new ConnectorRuntimeTokenUnavailableError(\n 'This app installed no connector runtime-token source, so the data plane cannot prove which tenant it is for',\n )\n }\n const value = typeof source === 'function' ? await source(request) : source\n const token = typeof value === 'string' ? value.trim() : ''\n if (!token) {\n throw new ConnectorRuntimeTokenUnavailableError(\n 'No runtime token is available for this session, so the call was not made',\n )\n }\n return token\n}\n\n// The header both halves name. It is DECLARED in ./runtime.ts and re-exported\n// here: the edge function that reads it cannot import this module (React and\n// supabase-js come with it), and `@fayz-ai/core/connector-runtime` is the entry\n// built to load under Deno. One constant, two importers, no second spelling.\nexport { CONNECTOR_RUNTIME_TOKEN_HEADER } from './runtime'\n","// ---------------------------------------------------------------------------\n// Operational settings — what a connector needs to WORK, not to be authorized\n// ---------------------------------------------------------------------------\n// `ConnectorField` used to carry two unrelated things. \"Qual é a sua chave\" is\n// needed to CONNECT: one-time, and the connection cannot exist without it.\n// \"Quais formas de pagamento você aceita\" is needed to OPERATE: it changes with\n// the season, and asking for it on the connect screen makes a merchant decide\n// commercial policy before they are allowed to plug the provider in. Worse, it\n// made \"aceito boleto agora\" look like re-authorizing the account.\n//\n// So they are two declarations. `fields` stays the credential form; `settings`\n// is this — asked for AFTER the connection exists, rendered through the same\n// settings rows every plugin's Settings tab already uses.\n//\n// Two consequences the types carry rather than a convention:\n//\n// 1. There is no `password` setting type, and there never will be. A settings\n// key is written to `plg_connections.settings`, a jsonb any member of the\n// tenant can read; the credential path is `fields` + the platform sink and\n// it is the only one.\n// 2. A setting can be REQUIRED TO OPERATE without being required to connect.\n// Melhor Envio quotes nothing without an origin postcode, but refusing the\n// connection over it is the very conflation this file undoes. The\n// connection is made and reported as `pending` — \"conectado, falta\n// configurar\" — which is a state `plg_connections.status` already has.\n//\n// This module is imported by BOTH ./index.ts (the contract) and ./store.ts (the\n// persistence), so it declares no dependency on either.\n\n/**\n * How a setting is asked for.\n *\n * Closed, and short on purpose: each member is a control someone has to build,\n * localize and test. It grew to exactly what the two real cases needed — a\n * multi-select for payment methods, a constrained text for a postcode — and the\n * next one is added by whoever has the next real case.\n */\nexport type ConnectorSettingType =\n /** One line, optionally shaped by `pattern` (a postcode, a CNPJ). */\n | 'text'\n /** Exactly one of `options`. */\n | 'select'\n /** Any number of `options`; persisted as a `string[]`. */\n | 'multi-select'\n /** On/off; persisted as a boolean. */\n | 'toggle'\n\n/** One choice of a `select` / `multi-select`. */\nexport interface ConnectorSettingOption {\n value: string\n label: string\n}\n\n/** A value a setting can hold. Never a secret — see the module header. */\nexport type ConnectorSettingValue = string | string[] | boolean\n\n/**\n * One operational setting a connector exposes once it is connected.\n *\n * The declaration is what kills the free-text box: \"pix, boleto, credit_card\"\n * typed into an input is three chances to misspell a value the storefront then\n * silently does not offer. A `multi-select` over declared options cannot be\n * misspelled, and it is the same declaration the store validates writes against.\n */\nexport interface ConnectorSetting {\n /** Key inside `plg_connections.settings`. Must not collide with a credential field. */\n key: string\n label: string\n /** The sentence under the label — the same affordance every settings row has. */\n description?: string\n type: ConnectorSettingType\n /** Required for `select` and `multi-select`, meaningless elsewhere. */\n options?: ConnectorSettingOption[]\n /** `text` only. */\n placeholder?: string\n /**\n * `text` only: the shape the value must have, as a RegExp source anchored by\n * the renderer. A postcode is eight digits or it is not a postcode, and\n * finding that out at the first quote is finding it out from a buyer.\n */\n pattern?: string\n /** What the reader is told when `pattern` refuses. Required whenever `pattern` is set. */\n patternHint?: string\n /**\n * The connector cannot do its job until this is chosen — and is still\n * connectable without it. See the module header.\n */\n requiredToOperate?: boolean\n /** Offered when the connection has no value yet. Never written on its own. */\n defaultValue?: ConnectorSettingValue\n}\n\n/**\n * A setting counts as chosen when it holds something.\n *\n * `''` and `[]` are not values: an empty multi-select is a merchant who accepts\n * no payment method, which is indistinguishable from one who has not decided,\n * and treating it as decided is what would let a store go live accepting\n * nothing. `false` IS a value — a toggle turned off was answered.\n */\nexport function isSettingSet(value: unknown): boolean {\n if (value === undefined || value === null) return false\n if (typeof value === 'string') return value.trim().length > 0\n if (Array.isArray(value)) return value.length > 0\n return true\n}\n\n/** The declared settings of a connector, or none. */\nexport function connectorSettings(def: { settings?: ConnectorSetting[] }): ConnectorSetting[] {\n return def.settings ?? []\n}\n\n/**\n * Which required-to-operate settings this connection has not got.\n *\n * Keys, never values — the answer is rendered as copy and persisted into\n * `status_detail`, and neither is a place for what the tenant typed.\n */\nexport function missingRequiredSettings(\n settings: ConnectorSetting[] | undefined,\n values: Record<string, unknown> | undefined | null,\n): string[] {\n return (settings ?? [])\n .filter((setting) => setting.requiredToOperate && !isSettingSet(values?.[setting.key]))\n .map((setting) => setting.key)\n}\n\n/** What {@link settingsStatus} contributes to a `ConnectorStatus`. */\nexport interface SettingsReadiness {\n /** Connected, and unable to work until something is chosen. */\n needsSetup: boolean\n /** The keys that are missing. Present only when `needsSetup`. */\n missingSettings?: string[]\n}\n\n/**\n * The setup half of a connector's status, derived from the row the list already\n * read.\n *\n * Derived and not merely read back from `status`, because the declaration is the\n * truth: a connector that gains a required setting in a new version must report\n * \"falta configurar\" on connections saved before that version existed, and no\n * write happened to those rows to say so.\n */\nexport function settingsStatus(\n settings: ConnectorSetting[] | undefined,\n connection: { active?: boolean; settings?: Record<string, unknown> } | null | undefined,\n): SettingsReadiness {\n if (!connection?.active) return { needsSetup: false }\n const missing = missingRequiredSettings(settings, connection.settings)\n return missing.length > 0 ? { needsSetup: true, missingSettings: missing } : { needsSetup: false }\n}\n\n/** `status_detail` for a connection waiting on configuration. Keys only. */\nexport function setupStatusDetail(missing: string[]): string {\n return `setup: ${missing.join(', ')}`\n}\n","import { getSupabaseClientOptional } from '../data/supabase'\nimport { getActiveTenantId } from '../lib/tenant'\nimport type { ConnectionConfig, ConnectorField, ConnectorStatus, Cursor, SyncRun } from './index'\nimport {\n missingRequiredSettings,\n setupStatusDetail,\n type ConnectorSetting,\n type ConnectorSettingValue,\n} from './settings'\n\n/**\n * The one data layer every connector shares. A connector declares a\n * ConnectorDefinition and writes no SQL: `plg_connections` and `plg_sync_runs`\n * (migration 029) are read and written from here.\n *\n * `recordRun` is deliberately absent. A run is a server-side write — the table\n * carries no INSERT grant for `authenticated`, because a history the\n * application can write is not a history.\n *\n * Opt-in, not mandatory: a connector whose truth lives entirely at the provider\n * (Stripe Connect) stores nothing here rather than keeping a second source of\n * truth that derives from the first.\n */\n\nexport const CONNECTIONS_TABLE = 'plg_connections'\nexport const SYNC_RUNS_TABLE = 'plg_sync_runs'\n\n/** A settings key that is not declared, or is a credential. */\nexport class ConnectionSettingsError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConnectionSettingsError'\n }\n}\n\nexport interface ConnectionStoreOptions {\n /** The connector's declared fields — the allow-list `save()` checks against. */\n fields?: ConnectorField[]\n /**\n * The connector's declared operational settings. Their keys are allowed, and\n * the required-to-operate ones decide whether a saved connection reads as\n * `connected` or as `pending` — see {@link createConnectionStore}.\n */\n settings?: ConnectorSetting[]\n /** Non-credential keys the connector writes itself (a chosen calendar, an import mode). */\n settingsKeys?: string[]\n /** Which account at the provider. `''` (default) is the single-account case. */\n instanceKey?: string\n /** Tenant override; defaults to the active tenant. */\n tenantId?: string\n /** Supabase client override; defaults to the globally registered one. */\n client?: unknown\n}\n\n/** The mutable half of a connection. `status` is absent on purpose: it is observed server-side. */\nexport interface ConnectionSaveInput {\n settings?: Record<string, unknown>\n displayName?: string | null\n active?: boolean\n providerAccountKey?: string | null\n}\n\nexport interface ConnectionStore {\n readonly connectorId: string\n readonly instanceKey: string\n get(): Promise<ConnectionConfig | null>\n save(input?: ConnectionSaveInput): Promise<ConnectionConfig>\n disconnect(): Promise<void>\n listRuns(options?: { limit?: number }): Promise<SyncRun[]>\n}\n\n/**\n * The keys a connector is allowed to put in `settings`: its declared\n * non-credential fields, plus anything it named in `settingsKeys`.\n *\n * Rejecting is the point. Without it, \"settings never holds a credential\" is a\n * comment in a migration — three connectors already broke that comment. With\n * it, a `type: 'password'` field arriving as a settings key is an error with a\n * name, and the doctor has a rule it can run.\n */\nexport function assertSettingsAllowed(\n settings: Record<string, unknown>,\n options: Pick<ConnectionStoreOptions, 'fields' | 'settings' | 'settingsKeys'>,\n connectorId = 'connector',\n): void {\n const secret = new Set((options.fields ?? []).filter((f) => f.type === 'password').map((f) => f.key))\n const allowed = new Set<string>([\n ...(options.fields ?? []).filter((f) => f.type !== 'password').map((f) => f.key),\n // A declared setting IS its own allow-list entry. Requiring it in\n // `settingsKeys` as well is two places to add a key and one place to forget.\n ...(options.settings ?? []).map((s) => s.key),\n ...(options.settingsKeys ?? []),\n ])\n for (const key of Object.keys(settings)) {\n if (secret.has(key)) {\n throw new ConnectionSettingsError(\n `[${connectorId}] \"${key}\" is a credential field — credentials never reach plg_connections.settings.`,\n )\n }\n if (!allowed.has(key)) {\n throw new ConnectionSettingsError(\n `[${connectorId}] unknown settings key \"${key}\". Declare it in the connector's fields or settingsKeys.`,\n )\n }\n }\n}\n\n// ── Row mapping ─────────────────────────────────────────────────────────────\n// Written out rather than run through the generic snake↔camel mapper: these two\n// tables are a contract, and a column silently renamed by a heuristic is the\n// kind of thing that only shows up in a pool.\n\ntype Row = Record<string, unknown>\n\nfunction toConnection(row: Row): ConnectionConfig {\n return {\n id: row.id as string,\n tenantId: row.tenant_id as string,\n connectorId: row.connector_id as string,\n instanceKey: (row.instance_key as string) ?? '',\n displayName: (row.display_name as string | null) ?? null,\n active: row.active as boolean,\n status: row.status as ConnectionConfig['status'],\n statusDetail: (row.status_detail as string | null) ?? null,\n settings: (row.settings as Record<string, unknown>) ?? {},\n providerAccountKey: (row.provider_account_key as string | null) ?? null,\n cursors: (row.cursors as Record<string, Cursor>) ?? {},\n lastSyncAt: (row.last_sync_at as string | null) ?? null,\n createdAt: row.created_at as string,\n updatedAt: row.updated_at as string,\n }\n}\n\nfunction toRun(row: Row): SyncRun {\n const stats = (row.stats as Record<string, unknown>) ?? {}\n return {\n id: row.id as string,\n tenantId: row.tenant_id as string,\n connectionId: row.connection_id as string,\n connectorId: row.connector_id as string,\n direction: row.direction as SyncRun['direction'],\n triggerKind: row.trigger_kind as SyncRun['triggerKind'],\n status: row.status as SyncRun['status'],\n stream: (row.stream as string | null) ?? null,\n fetched: (row.fetched as number) ?? 0,\n written: (row.written as number) ?? 0,\n stats,\n cursor: (row.cursor as string | null) ?? null,\n correlationId: (row.correlation_id as string | null) ?? null,\n // `stats.message` is the pre-031 home; read it so a pool that has not\n // applied 031 yet still shows the sentence rather than a blank row.\n message: (row.message as string | null) ?? (stats.message as string | undefined) ?? null,\n error: (row.error as string | null) ?? null,\n startedAt: row.started_at as string,\n finishedAt: (row.finished_at as string | null) ?? null,\n }\n}\n\n// ── The narrow slice of supabase-js this needs ──────────────────────────────\n\ninterface Answer<T> {\n data: T\n error: { message: string } | null\n}\n\ninterface Filterable extends PromiseLike<Answer<Row[] | null>> {\n eq: (column: string, value: unknown) => Filterable\n in: (column: string, values: readonly unknown[]) => Filterable\n order: (column: string, opts: { ascending: boolean }) => Filterable\n limit: (n: number) => Filterable\n select: (columns?: string) => Filterable\n single: () => Promise<Answer<Row | null>>\n maybeSingle: () => Promise<Answer<Row | null>>\n}\n\ninterface Table {\n select: (columns?: string) => Filterable\n upsert: (values: Row, opts?: { onConflict?: string }) => Filterable\n update: (values: Row) => Filterable\n}\n\ninterface Client {\n from: (table: string) => Table\n}\n\nfunction unwrap<T>(answer: Answer<T>, what: string): T {\n if (answer.error) throw new Error(`[@fayz-ai/core] ${what}: ${answer.error.message}`)\n return answer.data\n}\n\n/**\n * The connection store for one connector, on one account.\n *\n * @param connectorId Stable connector id, e.g. 'google-calendar'.\n */\nexport function createConnectionStore(connectorId: string, options: ConnectionStoreOptions = {}): ConnectionStore {\n const instanceKey = options.instanceKey ?? ''\n\n function client(): Client {\n const c = (options.client ?? getSupabaseClientOptional()) as Client | null\n if (!c) throw new Error('[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.')\n return c\n }\n\n function tenantId(): string {\n const id = options.tenantId ?? getActiveTenantId()\n if (!id) throw new Error(`[@fayz-ai/core] createConnectionStore(${connectorId}): no active tenant.`)\n return id\n }\n\n function scoped(q: Filterable): Filterable {\n return q.eq('tenant_id', tenantId()).eq('connector_id', connectorId).eq('instance_key', instanceKey)\n }\n\n const store: ConnectionStore = {\n connectorId,\n instanceKey,\n\n async get() {\n const row = unwrap(\n await scoped(client().from(CONNECTIONS_TABLE).select('*')).maybeSingle(),\n `read ${CONNECTIONS_TABLE}`,\n )\n return row ? toConnection(row) : null\n },\n\n async save(input: ConnectionSaveInput = {}) {\n const patch = input.settings ?? {}\n assertSettingsAllowed(patch, options, connectorId)\n\n // MERGE BY KEY, never replace. Every bespoke saveConnection today upserts\n // the whole jsonb, so a panel and an edge function writing different keys\n // erase each other in silence. Read-modify-write is the cheap half of the\n // fix; the atomic half is a `settings || patch` RPC, still owed.\n const current = await store.get()\n const settings = { ...(current?.settings ?? {}), ...patch }\n\n const row: Row = {\n tenant_id: tenantId(),\n connector_id: connectorId,\n instance_key: instanceKey,\n settings,\n updated_at: new Date().toISOString(),\n }\n if (input.displayName !== undefined) row.display_name = input.displayName\n if (input.active !== undefined) row.active = input.active\n if (input.providerAccountKey !== undefined) row.provider_account_key = input.providerAccountKey\n if (current?.id) row.id = current.id\n\n // The ONE dimension of `status` the app owns: whether the tenant has\n // chosen the settings the connector cannot operate without. Provider\n // health ('error', 'revoked') is observed server-side and is left exactly\n // as found — a connection the provider revoked must not read as connected\n // again because someone picked a payment method.\n //\n // A connector that declares NO settings has nothing to be pending ON, and\n // it used to fall through this block entirely — so the row kept the\n // table's `pending` default forever. Bling authorized at the provider,\n // came back, and still read as not connected, which sent the merchant\n // round the consent screen again. \"Nothing to fill in\" is ready, not\n // waiting.\n const missing = options.settings?.length\n ? missingRequiredSettings(options.settings, settings)\n : []\n // Turning a connection ON again is the one case that may clear `revoked`:\n // the tenant just re-authorized. A settings save must not, which is why\n // this reads `input.active` rather than the row's.\n const reactivating = input.active === true\n if (missing.length > 0) {\n row.status = 'pending'\n row.status_detail = setupStatusDetail(missing)\n } else if (!current || current.status === 'pending' || (reactivating && current.status === 'revoked')) {\n row.status = 'connected'\n row.status_detail = null\n }\n\n const saved = unwrap(\n await client()\n .from(CONNECTIONS_TABLE)\n .upsert(row, { onConflict: 'tenant_id,connector_id,instance_key' })\n .select('*')\n .single(),\n `write ${CONNECTIONS_TABLE}`,\n )\n if (!saved) throw new Error(`[@fayz-ai/core] write ${CONNECTIONS_TABLE}: no row returned.`)\n return toConnection(saved)\n },\n\n async disconnect() {\n // Soft: the run history outlives the connection, and `active` is the\n // tenant's switch. Settings and cursors are cleared so a reconnect starts\n // from nothing rather than resuming a cursor the provider no longer honours.\n const answer = await scoped(\n client().from(CONNECTIONS_TABLE).update({\n active: false,\n status: 'revoked',\n settings: {},\n cursors: {},\n updated_at: new Date().toISOString(),\n }),\n )\n unwrap(answer, `disconnect ${CONNECTIONS_TABLE}`)\n },\n\n async listRuns(opts = {}) {\n const connection = await store.get()\n if (!connection?.id) return []\n const answer = await client()\n .from(SYNC_RUNS_TABLE)\n .select('*')\n .eq('connection_id', connection.id)\n .order('started_at', { ascending: false })\n .limit(opts.limit ?? 20)\n const rows = unwrap(answer, `read ${SYNC_RUNS_TABLE}`)\n return (rows ?? []).map(toRun)\n },\n }\n\n return store\n}\n\n// ── Reading many connections at once ────────────────────────────────────────\n\n/** Map key for {@link listConnections}. A connection is (connector, account). */\nexport function connectionKey(connectorId: string, instanceKey = ''): string {\n return `${connectorId}\u0000${instanceKey}`\n}\n\nexport interface ListConnectionsOptions {\n /** Tenant override; defaults to the active tenant. */\n tenantId?: string\n /** Supabase client override; defaults to the globally registered one. */\n client?: unknown\n}\n\n/**\n * Every named connector's row in ONE read.\n *\n * The connectors screen used to cost one `plg_connections` GET per connector,\n * because each connector asked for its own row through its own store — N round\n * trips to fill one list. They all share the table, so `connector_id=in.(…)`\n * answers all of them at once.\n *\n * The tenant is part of the QUERY, never of a cache key alone: a caller that\n * holds this map across a tenant switch is holding the wrong tenant's rows, so\n * the map is deliberately returned, not stored.\n */\nexport async function listConnections(\n connectorIds: readonly string[],\n options: ListConnectionsOptions = {},\n): Promise<Map<string, ConnectionConfig>> {\n const found = new Map<string, ConnectionConfig>()\n const ids = [...new Set(connectorIds)].filter(Boolean)\n if (ids.length === 0) return found\n\n const c = (options.client ?? getSupabaseClientOptional()) as Client | null\n if (!c) throw new Error('[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.')\n const tenantId = options.tenantId ?? getActiveTenantId()\n if (!tenantId) throw new Error('[@fayz-ai/core] listConnections: no active tenant.')\n\n const rows = unwrap(\n await c.from(CONNECTIONS_TABLE).select('*').eq('tenant_id', tenantId).in('connector_id', ids),\n `read ${CONNECTIONS_TABLE}`,\n )\n for (const row of rows ?? []) {\n const connection = toConnection(row)\n found.set(connectionKey(connection.connectorId, connection.instanceKey), connection)\n }\n return found\n}\n\n/** What {@link connectionStatus} contributes to a ConnectorDefinition. */\nexport interface ConnectionBackedStatus {\n instanceKey: string\n statusFromConnection: (connection: ConnectionConfig | null) => ConnectorStatus\n getStatus: () => Promise<ConnectorStatus>\n}\n\n/**\n * Wire a connector's status badge to its `plg_connections` row.\n *\n * One `derive` answers both doors: `getStatus()` for a screen showing this\n * connector alone, and `statusFromConnection` for a screen showing a list — the\n * list hands it a row it already read in a batch, so N connectors cost one\n * request instead of N. Spread the result into the definition.\n *\n * A connector whose truth lives at the provider (Stripe Connect) writes its own\n * `getStatus` and declares no `statusFromConnection`; the list then reads it\n * one-by-one, which is correct because there is no shared row to batch.\n */\n/** What {@link connectionSettings} contributes to a ConnectorDefinition. */\nexport interface ConnectionSettingsIO {\n getSettings: () => Promise<Record<string, unknown>>\n saveSettings: (values: Record<string, ConnectorSettingValue>) => Promise<void>\n}\n\n/**\n * Wire a connector's settings form to its `plg_connections.settings` jsonb.\n *\n * Spread into the definition, the same way {@link connectionStatus} is. Every\n * store-backed connector would otherwise write the same four lines, and the one\n * that wrote them slightly differently would be the one that replaced the jsonb\n * instead of merging into it.\n */\nexport function connectionSettings(store: ConnectionStore): ConnectionSettingsIO {\n return {\n async getSettings() {\n return (await store.get())?.settings ?? {}\n },\n async saveSettings(values) {\n await store.save({ settings: values })\n },\n }\n}\n\n/** What {@link connectionRuns} contributes to a ConnectorDefinition. */\nexport interface ConnectionRunsIO {\n listRuns: (options?: { limit?: number }) => Promise<SyncRun[]>\n}\n\n/**\n * Wire a connector's history to its `plg_sync_runs` rows.\n *\n * Spread into the definition, like {@link connectionStatus} and\n * {@link connectionSettings}. Swallowing is the point: a history is never the\n * reason a connector's screen fails to render, and \"no rows\" and \"unreadable\"\n * are the same answer to somebody asking what has happened.\n */\nexport function connectionRuns(store: ConnectionStore | (() => ConnectionStore)): ConnectionRunsIO {\n return {\n async listRuns(options) {\n try {\n // Either shape, because both are already in use: a module-level store\n // (Melhor Envio) and a factory called per read (Bling). A factory is the\n // safer default — nothing is resolved until somebody asks.\n return await (typeof store === 'function' ? store() : store).listRuns(options)\n } catch {\n return []\n }\n },\n }\n}\n\nexport function connectionStatus(\n connectorId: string,\n derive: (connection: ConnectionConfig | null) => ConnectorStatus,\n options: { instanceKey?: string; store?: () => ConnectionStore } = {},\n): ConnectionBackedStatus {\n const instanceKey = options.instanceKey ?? ''\n return {\n instanceKey,\n statusFromConnection: derive,\n async getStatus() {\n try {\n const store = options.store?.() ?? createConnectionStore(connectorId, { instanceKey })\n return derive(await store.get())\n } catch {\n // No client, no tenant, or an unreadable row — \"not connected\" is the\n // honest answer for a badge, and never a broken list.\n return { connected: false }\n }\n },\n }\n}\n","// ---------------------------------------------------------------------------\n// The request plane — the connector the buyer is waiting on\n// ---------------------------------------------------------------------------\n// gcal, PlugBank and RankLayer are asynchronous: nobody is waiting, a failed run\n// is retried later, and the audit trail is the product. Checkout inverts all\n// three. The shopper is on the page, there is a budget, and a failure has to be\n// answered NOW — with a worse answer, or with a refusal, but never with a\n// spinner that never ends.\n//\n// So the request plane needs four things the sync plane never asked for, and\n// they live here rather than inside AppMax or Melhor Envio because both need\n// all four:\n//\n// 1. a LATENCY BUDGET, declared by the connector, enforced by the caller;\n// 2. FAILURE SEMANTICS as a field, because the two connectors want opposite\n// ones — freight that fails must not stop the sale, payment that fails must\n// stop it — and \"which one\" is a property of the connector, not a decision\n// re-taken in every screen that happens to call it;\n// 3. an IDEMPOTENCY story keyed by the BUYER's intent, so a double-clicked\n// \"pagar\" is one charge;\n// 4. the line between a QUOTE and an EFFECT. A quote is not a run: see\n// {@link runEffect} and packages/db/migrations/029+030.\n//\n// What the plane deliberately does NOT get is a row per invocation. See the\n// header of 029_connections.sql.\nimport type { ConnectorDefinition, ConnectorDirection, SyncTrigger } from './index'\n\n/**\n * What the connector does when the provider does not answer in time.\n *\n * `degrade` — serve a worse answer and let the buyer continue (shipping falls\n * back to the configured zone; the sale is never blocked by a carrier's API).\n * `block` — refuse the step and tell the buyer why (a payment that did not\n * confirm must not read as a confirmed order).\n */\nexport type RequestFailureMode = 'degrade' | 'block'\n\n/** Why the provider's answer was not used. Codes, not copy — the UI writes the pt-BR. */\nexport type DegradeReason =\n /** The latency budget was spent before the provider answered. */\n | 'timeout'\n /** The provider answered with an error, or the call threw. */\n | 'provider-error'\n /** There is no live connection for this connector — nothing was called. */\n | 'not-connected'\n\n/** The answer, plus where it came from. Provenance is not optional: a fallback price shown as a provider price is how a store quotes one number and charges another. */\nexport interface RequestOutcome<T> {\n value: T\n source: 'provider' | 'fallback'\n /** Wall time spent on the provider attempt. Reported even when it timed out. */\n elapsedMs: number\n /** Present exactly when `source` is 'fallback'. */\n degraded?: { reason: DegradeReason; detail?: string }\n}\n\n/**\n * A `block` connector refused the step. Carries a reason code the checkout maps\n * to copy — it is thrown AT the buyer's request, so it must say something.\n */\nexport class RequestBlockedError extends Error {\n readonly connectorId: string\n readonly reason: DegradeReason\n readonly elapsedMs: number\n constructor(connectorId: string, reason: DegradeReason, elapsedMs: number, detail?: string) {\n super(`[${connectorId}] request plane blocked: ${reason}${detail ? ` — ${detail}` : ''}`)\n this.name = 'RequestBlockedError'\n this.connectorId = connectorId\n this.reason = reason\n this.elapsedMs = elapsedMs\n }\n}\n\n/**\n * The connector's declaration is wrong. Never reaches a buyer: it fires the\n * first time the connector is exercised, which is a test or a dev boot.\n */\nexport class RequestPlaneError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'RequestPlaneError'\n }\n}\n\n/** A connector's request-plane declaration, after validation. */\nexport interface ResolvedRequestPlane {\n latencyBudgetMs: number\n onFailure: RequestFailureMode\n}\n\ntype RequestPlaneDecl = Pick<ConnectorDefinition, 'id' | 'planes' | 'latencyBudgetMs' | 'onFailure'>\n\n/**\n * Read a connector's request-plane declaration, or say why it has none.\n *\n * There is no default budget on purpose. A default is a number nobody chose,\n * and this one decides how long a stranger stares at a spinner; \"3000 because\n * that is what the helper does\" is exactly the decision the contract exists to\n * force someone to make.\n */\nexport function requestPlaneSpec(def: RequestPlaneDecl): ResolvedRequestPlane {\n const planes = def.planes ?? ['sync']\n if (!planes.includes('request')) {\n throw new RequestPlaneError(`[${def.id}] is not on the request plane — declare planes: ['request'].`)\n }\n if (typeof def.latencyBudgetMs !== 'number' || !Number.isFinite(def.latencyBudgetMs) || def.latencyBudgetMs <= 0) {\n throw new RequestPlaneError(\n `[${def.id}] request-plane connector must declare latencyBudgetMs — how long the buyer may wait.`,\n )\n }\n if (def.onFailure !== 'degrade' && def.onFailure !== 'block') {\n throw new RequestPlaneError(\n `[${def.id}] request-plane connector must declare onFailure: 'degrade' | 'block'. ` +\n `Shipping degrades (the sale continues); payment blocks (the order does not confirm).`,\n )\n }\n return { latencyBudgetMs: def.latencyBudgetMs, onFailure: def.onFailure }\n}\n\n/** True when the connector declares a usable request plane. Never throws — for a UI badge. */\nexport function hasRequestPlane(def: RequestPlaneDecl): boolean {\n try {\n requestPlaneSpec(def)\n return true\n } catch {\n return false\n }\n}\n\nexport interface RequestAttempt<T> {\n /**\n * The provider call. The signal aborts when the budget is spent — honour it\n * so the socket closes, but the budget is enforced regardless of whether you do.\n */\n call(signal: AbortSignal): Promise<T>\n /**\n * The worse answer, for `degrade` connectors. Must be LOCAL (a database row, a\n * configured zone) — a fallback that itself calls a network is a second budget\n * nobody declared. Required when `onFailure` is 'degrade'.\n */\n fallback?(reason: DegradeReason, detail?: string): T | Promise<T>\n /** Skip the provider entirely — no live connection, connector switched off. */\n skip?: boolean\n /** Injectable clock, for tests that assert elapsed time without sleeping. */\n now?(): number\n}\n\n/**\n * Run one request-plane invocation under the connector's declared budget and\n * failure semantics.\n *\n * Writes nothing. A quote is not a run: it has no fetched, no written and no\n * cursor, it is issued for an `anon` shopper with no tenant-scoped write path,\n * and one row per quote would make plg_sync_runs the largest table in the pool\n * within weeks. Effects go through {@link runEffect}, which does file a row.\n */\nexport async function runRequest<T>(def: RequestPlaneDecl, attempt: RequestAttempt<T>): Promise<RequestOutcome<T>> {\n const spec = requestPlaneSpec(def)\n const now = attempt.now ?? (() => Date.now())\n const started = now()\n\n const degradeOrBlock = async (reason: DegradeReason, detail?: string): Promise<RequestOutcome<T>> => {\n const elapsedMs = now() - started\n if (spec.onFailure === 'block') throw new RequestBlockedError(def.id, reason, elapsedMs, detail)\n if (!attempt.fallback) {\n // Degrading with nothing to degrade TO is blocking with worse manners: the\n // buyer gets an exception from a connector that promised never to throw.\n throw new RequestPlaneError(\n `[${def.id}] declares onFailure: 'degrade' but the call supplied no fallback. ` +\n `Degrading requires an answer to degrade to.`,\n )\n }\n return {\n value: await attempt.fallback(reason, detail),\n source: 'fallback',\n elapsedMs,\n degraded: { reason, detail },\n }\n }\n\n if (attempt.skip) return degradeOrBlock('not-connected')\n\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n // Raced rather than merely signalled: a provider client that ignores the\n // AbortSignal would otherwise hold the page for as long as it likes, and the\n // budget would be documentation.\n const budget = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), spec.latencyBudgetMs)\n })\n\n try {\n const raced = await Promise.race([\n attempt.call(controller.signal).then((value) => ({ ok: true as const, value })),\n budget,\n ])\n if (raced === 'timeout') {\n controller.abort()\n return await degradeOrBlock('timeout', `budget of ${spec.latencyBudgetMs}ms spent`)\n }\n return { value: raced.value, source: 'provider', elapsedMs: now() - started }\n } catch (error) {\n if (error instanceof RequestBlockedError || error instanceof RequestPlaneError) throw error\n return await degradeOrBlock('provider-error', error instanceof Error ? error.message : String(error))\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Effects — the half of the request plane that IS a run\n// ---------------------------------------------------------------------------\n// Capturing a payment and buying a label are not quotes. They move money and\n// they are worth one row each: they have a status, an error, a duration and a\n// stats bag, which is exactly plg_sync_runs. What they additionally need — and\n// the sync plane never did, because nobody double-clicks a cron — is a claim\n// that survives a retry.\n\nexport type EffectStatus = 'success' | 'partial' | 'error'\n\nexport interface EffectClaimInput {\n /** Which connection is acting. The tenant is resolved FROM it, never passed in. */\n connectionId: string\n /** Buyer-derived. See {@link buyerIdempotencyKey}. */\n idempotencyKey: string\n direction: ConnectorDirection\n triggerKind: SyncTrigger\n stream?: string\n}\n\nexport interface EffectClaim {\n /** The plg_sync_runs row this effect owns — the same id on every retry of the key. */\n runId: string\n /** `false` = the key already ran. Do not call the provider. */\n claimed: boolean\n}\n\n/**\n * The durable claim. The one implementation that matters is\n * `plg_claim_effect` / `plg_settle_effect` (packages/db 030): the run row IS the\n * claim, so \"file the run\" and \"win the race\" are one statement and cannot\n * disagree. Both are service_role only — the shopper never reaches them.\n */\nexport interface EffectLedger {\n claim(input: EffectClaimInput): Promise<EffectClaim>\n settle(input: { runId: string; status: EffectStatus; stats?: Record<string, unknown>; error?: string }): Promise<void>\n}\n\nexport interface EffectOutcome<T> {\n runId: string\n /** `true` when THIS call reached the provider. `false` = the key had already run. */\n performed: boolean\n value?: T\n}\n\n/**\n * Perform an effect at most once per idempotency key.\n *\n * The claim comes first and the provider call second, in that order and never\n * the reverse: a claim written after a successful capture leaves a window in\n * which the retry captures again. A claim that then fails is settled as an\n * error and stays claimed — the retry does not re-charge, an operator decides.\n */\nexport async function runEffect<T>(\n ledger: EffectLedger,\n input: EffectClaimInput,\n perform: (runId: string) => Promise<T>,\n): Promise<EffectOutcome<T>> {\n if (!input.idempotencyKey) {\n throw new RequestPlaneError('runEffect requires an idempotencyKey — an effect nobody can name twice is an effect that runs twice.')\n }\n const claim = await ledger.claim(input)\n if (!claim.claimed) return { runId: claim.runId, performed: false }\n\n try {\n const value = await perform(claim.runId)\n await ledger.settle({ runId: claim.runId, status: 'success' })\n return { runId: claim.runId, performed: true, value }\n } catch (error) {\n await ledger.settle({\n runId: claim.runId,\n status: 'error',\n error: error instanceof Error ? error.message : String(error),\n })\n throw error\n }\n}\n\n/** The slice of supabase-js the ledger calls. Two RPCs, no table access. */\ninterface RpcClient {\n rpc(fn: string, args: Record<string, unknown>): PromiseLike<{ data: unknown; error: { message: string } | null }>\n}\n\n/**\n * The shipped {@link EffectLedger}: `plg_claim_effect` / `plg_settle_effect`\n * (packages/db 030). Both are service_role only, so this is constructed inside\n * the data plane — an edge function — and never in a page the shopper loads.\n */\nexport function createRpcEffectLedger(client: RpcClient): EffectLedger {\n const call = async (fn: string, args: Record<string, unknown>): Promise<unknown> => {\n const { data, error } = await client.rpc(fn, args)\n if (error) throw new Error(`[@fayz-ai/core] ${fn}: ${error.message}`)\n return data\n }\n\n return {\n async claim(input) {\n // The function RETURNS TABLE, so PostgREST answers with a one-row array.\n const rows = (await call('plg_claim_effect', {\n p_connection_id: input.connectionId,\n p_idempotency_key: input.idempotencyKey,\n p_direction: input.direction,\n p_trigger_kind: input.triggerKind,\n p_stream: input.stream ?? null,\n })) as Array<{ run_id: string; claimed: boolean }> | { run_id: string; claimed: boolean } | null\n const row = Array.isArray(rows) ? rows[0] : rows\n if (!row?.run_id) throw new Error('[@fayz-ai/core] plg_claim_effect returned no run')\n return { runId: row.run_id, claimed: !!row.claimed }\n },\n async settle(input) {\n await call('plg_settle_effect', {\n p_run_id: input.runId,\n p_status: input.status,\n p_stats: input.stats ?? {},\n p_error: input.error ?? null,\n })\n },\n }\n}\n\n/**\n * The key the buyer's intent produces — never the provider's id.\n *\n * The provider's id only exists AFTER the call that must not happen twice, so a\n * key derived from it cannot prevent the second one. `pagar` clicked twice is the\n * same order for the same amount, and that is what has to hash to one key.\n *\n * The amount is part of it deliberately: a re-tried capture for a DIFFERENT\n * amount is a different intent and must be allowed through, not silently\n * swallowed as a duplicate.\n */\nexport function buyerIdempotencyKey(parts: { effect: string; subject: string; amountCents?: number }): string {\n const { effect, subject, amountCents } = parts\n if (!effect || !subject) {\n throw new RequestPlaneError('buyerIdempotencyKey needs both an effect and a subject (the order, the shipment).')\n }\n // Readable on purpose: this string shows up in a support conversation about a\n // charge, and an opaque hash there costs an hour.\n return amountCents === undefined ? `${effect}:${subject}` : `${effect}:${subject}:${amountCents}`\n}\n","// ---------------------------------------------------------------------------\n// Integration Connector spine\n// ---------------------------------------------------------------------------\n// An integration is a connector that keeps canonical Fayz data in agreement\n// with an external provider (Google Calendar, open banking, Shopify, …). This\n// module is the thin shared contract both native (SDK) and client-built\n// connectors implement. The heavy lifting (auth, fetch/push, idempotent\n// upserts) runs in the data plane — typically a Supabase Edge Function — while\n// the control plane (a settings tab + this contract) lives in the app/plugin.\n//\n// Design notes live in docs/integrations/bling-integration-brief.md. Proven precedents:\n// the Fayz runtime OAuth broker (packages/core/src/runtime/oauth.ts) and the\n// bank-statement edge functions in the predecessor app.\nimport type * as React from 'react'\nimport type { ConnectorPermission } from './permissions'\nimport type { RequestFailureMode } from './request'\nimport type { Completeness, Cursor, RateLimitBudget } from './runtime'\nimport type { ConnectorSetting, ConnectorSettingValue } from './settings'\n\n// What a connector NEEDS ACCESS TO, declared apart from what it does with it.\n// See ./permissions.ts for the grammar and for what does and does not enforce it.\nexport {\n permissionActions,\n isDataPermission,\n isEventPermission,\n grants,\n connectorPermissions,\n connectorEntities,\n connectorSubscribesTo,\n connectorSubscribes,\n permissionsWrite,\n hasSensitivePermission,\n sortedPermissions,\n groupedPermissions,\n connectorGrantScopes,\n} from './permissions'\nexport type {\n ConnectorPermission,\n ConnectorPermissionAction,\n ConnectorResourceKind,\n PermissionBearing,\n} from './permissions'\n\nexport { eventKeyMatches } from './events'\n\n// Where a PULL connector gets the token its own edge function has to forward.\n// See ./runtime-token.ts for why this is a port and not a helper.\nexport {\n CONNECTOR_RUNTIME_TOKEN_HEADER,\n ConnectorRuntimeTokenUnavailableError,\n connectorRuntimeToken,\n getConnectorRuntimeTokenSource,\n setConnectorRuntimeTokenSource,\n} from './runtime-token'\nexport type { ConnectorRuntimeTokenRequest, ConnectorRuntimeTokenSource } from './runtime-token'\n\nexport {\n createConnectionStore,\n listConnections,\n connectionKey,\n connectionStatus,\n connectionSettings,\n connectionRuns,\n assertSettingsAllowed,\n ConnectionSettingsError,\n CONNECTIONS_TABLE,\n SYNC_RUNS_TABLE,\n} from './store'\nexport type {\n ConnectionStore,\n ConnectionStoreOptions,\n ConnectionSaveInput,\n ConnectionBackedStatus,\n ConnectionSettingsIO,\n ConnectionRunsIO,\n ListConnectionsOptions,\n} from './store'\n\n// Operational configuration — what a connector needs to WORK, declared apart\n// from what it needs to be AUTHORIZED. See ./settings.ts for why they split.\nexport {\n connectorSettings,\n isSettingSet,\n missingRequiredSettings,\n settingsStatus,\n setupStatusDetail,\n} from './settings'\nexport type {\n ConnectorSetting,\n ConnectorSettingType,\n ConnectorSettingOption,\n ConnectorSettingValue,\n SettingsReadiness,\n} from './settings'\n\nexport {\n runRequest,\n runEffect,\n requestPlaneSpec,\n hasRequestPlane,\n buyerIdempotencyKey,\n createRpcEffectLedger,\n RequestBlockedError,\n RequestPlaneError,\n} from './request'\nexport type {\n RequestFailureMode,\n DegradeReason,\n RequestOutcome,\n RequestAttempt,\n ResolvedRequestPlane,\n EffectLedger,\n EffectClaim,\n EffectClaimInput,\n EffectOutcome,\n EffectStatus,\n} from './request'\n// The data-plane mechanics. They live in ./runtime because that module has no\n// runtime imports and is built to its own entry (`@fayz-ai/core/connector-runtime`),\n// so a Deno edge function can load it without pulling supabase-js or React in.\nexport {\n ConnectorHttpError,\n RateLimitExceededError,\n parseRetryAfter,\n backoffDelay,\n classify,\n statusNeedsBody,\n fetchWithRetry,\n isExpiring,\n paginate,\n createRateLimiter,\n sliceWindow,\n advanceWindow,\n serializeCursor,\n reconcileMirror,\n cursorCompleteness,\n} from './runtime'\nexport type {\n Cursor,\n Completeness,\n Verdict,\n AttemptOutcome,\n ClassifyOptions,\n BackoffOptions,\n FetchWithRetryOptions,\n MinimalResponse,\n PageResult,\n PaginateOptions,\n PaginateResult,\n RateLimitBudget,\n RateLimiter,\n RateLimiterOptions,\n MirrorRecord,\n ReconcileInput,\n ReconcileResult,\n} from './runtime'\n\n/** How a connector authenticates to its provider. */\nexport type IntegrationAuthKind = 'oauth' | 'api-key' | 'mtls'\n\n// ---------------------------------------------------------------------------\n// Categories — the shelf a connector is filed under\n// ---------------------------------------------------------------------------\n// The vocabulary is the platform catalogue's, mirrored here because three\n// things have to agree on it and only one of them lives in that repo: the\n// connector that declares a category, the badge that renders it, and the\n// catalogue row it joins to by slug. A free-form string agreed with none of\n// them — `bling` shipped as `'commerce'`, a word the catalogue has never used,\n// and the badge silently rendered nothing for a year's worth of screens.\n//\n// The rule for a new connector, in one line: **take the category from the\n// catalogue row with the same slug; if there is no such row yet, pick the shelf\n// a shopkeeper would look on, and add the row.** The list below is closed on\n// purpose — widening it is a decision, and a decision needs the seed changed\n// too, which is exactly the review this is meant to force.\n//\n// Source of truth: fayz/packages/db/prisma/seeds/connectors.seed.ts.\nexport const CONNECTOR_CATEGORIES = [\n /** The provider IS a database or backend the app is built on. */\n 'database',\n /** Money in: gateways, acquirers, Pix. */\n 'payments',\n /** A bank account read (open banking, statements) — not a gateway. */\n 'banking',\n /** Stock, invoicing, back office. */\n 'erp',\n /** Freight quotes, labels, tracking. */\n 'logistics',\n /** Someone else's storefront the business also sells on (iFood, Shopee). */\n 'marketplace',\n /** The business's OWN online store. */\n 'ecommerce',\n /** Contacts, deals, pipeline. */\n 'crm',\n /** Courses sold by a third party (Hotmart, Kiwify). */\n 'courses',\n /** Bookings and calendars. */\n 'scheduling',\n /** Conversations: WhatsApp, SMS, email campaigns. */\n 'messaging',\n /** Measurement, pixels, SEO. */\n 'analytics',\n /** Presence on a map or a directory (Google Meu Negócio). */\n 'local',\n /** Models and agent frameworks. */\n 'ai',\n /** Wiring between other tools (Zapier, n8n). */\n 'automation',\n] as const\n\n/** A shelf in the connector catalogue. See {@link CONNECTOR_CATEGORIES}. */\nexport type ConnectorCategory = (typeof CONNECTOR_CATEGORIES)[number]\n\nconst CATEGORY_SET: ReadonlySet<string> = new Set<string>(CONNECTOR_CATEGORIES)\n\n/** Whether a string is a category the catalogue knows. */\nexport function isConnectorCategory(value: unknown): value is ConnectorCategory {\n return typeof value === 'string' && CATEGORY_SET.has(value)\n}\n\n// ---------------------------------------------------------------------------\n// Planes — the two ways a connector gets honoured\n// ---------------------------------------------------------------------------\n// \"Integração\" and \"conector\" are the same noun. What differs is the plane it\n// runs on:\n//\n// - 'agentic' — the provider's tools are handed to the assistant. Nothing is\n// copied: the agent calls the provider at the moment it needs the answer,\n// so there is no mirror to drift and no de-para to maintain. Preferred\n// whenever the provider offers it.\n// - 'sync' — the old-school pipeline: authorize, map, run, keep an audit\n// trail. Required when the data must exist *inside* Fayz — to be listed,\n// filtered, reported on, or read while the provider is unreachable.\n// - 'request' — the provider is asked, in line, for an answer that is used\n// once and never stored: a shipping quote, a checkout session. It has a\n// connection like any other connector, but it has no runs. Recording one\n// row per quote would make plg_sync_runs the largest table in the pool\n// within weeks, and the caller is an anonymous buyer with no tenant-scoped\n// write path anyway. The *effects* a quote leads to (capture the payment,\n// buy the label) are ordinary sync runs.\n//\n// A connector may support both; `preferredPlane` encodes the house rule.\nexport type ConnectorPlane = 'agentic' | 'sync' | 'request'\n\n/** Which side reaches for the other, from this app's point of view. */\nexport type ConnectorDirection = 'inbound' | 'outbound'\n\n/** A tool the assistant gains once a connector is resolved on the agentic plane. */\nexport interface ConnectorAgentTool {\n name: string\n description?: string\n}\n\n/** Which way data flows for a given capability. */\nexport type SyncDirection = 'inbound' | 'outbound' | 'bidirectional'\n\n/** What causes a sync to run. */\nexport type SyncTrigger = 'on-write' | 'scheduled' | 'manual' | 'webhook'\n\n/** One thing a connector can sync (an entity + its direction + triggers). */\nexport interface ConnectorCapability {\n /** Canonical entity synced, e.g. 'booking', 'movement'. */\n entity: string\n direction: SyncDirection\n triggers: SyncTrigger[]\n}\n\n// `Cursor`, `Completeness` and `cursorCompleteness` are declared in ./runtime and\n// re-exported above — the data plane needs them and cannot import this module.\n\n/**\n * One row of `public.plg_connections` (migration 029) — a tenant's connection to\n * ONE account at one provider. Two accounts are two rows, separated by\n * `instanceKey`; two resources inside one account are two streams of one row.\n *\n * There is no credential field because the table has no credential column.\n */\nexport interface ConnectionConfig {\n id?: string\n tenantId?: string\n /** Connector id, e.g. 'google-calendar', 'plugbank'. */\n connectorId: string\n /** Which account at the provider. `''` for the connectors that only ever have one. */\n instanceKey: string\n displayName?: string | null\n /** The tenant's switch. */\n active: boolean\n /** Health, observed server-side — not a mirror of `active`. */\n status: ConnectionStatusValue\n statusDetail?: string | null\n /** Provider-specific config, restricted to the connector's declared field keys. Never secrets. */\n settings: Record<string, unknown>\n /** The provider's own id for the account — how an inbound webhook finds the tenant. */\n providerAccountKey?: string | null\n /** Resume state keyed BY STREAM: Google keeps a syncToken per calendar, not per connection. */\n cursors: Record<string, Cursor>\n lastSyncAt?: string | null\n createdAt?: string\n updatedAt?: string\n}\n\n/** Server-observed health of a connection (`plg_connections.status`). */\nexport type ConnectionStatusValue = 'pending' | 'connected' | 'error' | 'revoked'\n\n/** One row of `public.plg_sync_runs` (migration 029) — one execution of the sync plane. */\nexport interface SyncRun {\n id?: string\n tenantId?: string\n /** Which connection ran. Without it two bank accounts share one history. */\n connectionId: string\n connectorId: string\n direction: ConnectorDirection\n /** `trigger` is a reserved word in Postgres; the column is `trigger_kind`. */\n triggerKind: SyncTrigger\n status: 'running' | 'success' | 'partial' | 'error'\n /** Which resource inside the account this run touched. */\n stream?: string | null\n fetched?: number\n written?: number\n /** Counters that are the connector's own: duplicates, discovered, period_from/to. */\n stats?: Record<string, unknown>\n /** The cursor this run advanced to, serialised. */\n cursor?: string | null\n /** Ties an outbound push to the inbound echo it will provoke. */\n correlationId?: string | null\n /**\n * What the run has to SAY, on any status (migration 031). `error` is what went\n * WRONG; a successful run with something to report used to smuggle it through\n * `stats.message`, which is the ad-hoc convention the generic table exists to\n * prevent.\n */\n message?: string | null\n error?: string | null\n startedAt?: string\n finishedAt?: string | null\n}\n\n/**\n * What a connector's sync returns. Distinct from {@link SyncRun}: `completeness`\n * is not a column, it is the verdict the engine needs BEFORE it decides whether\n * records missing from this result may be cancelled locally.\n */\nexport interface SyncResult {\n status: 'success' | 'partial' | 'error'\n completeness: Completeness\n fetched?: number\n written?: number\n /** Advanced cursor, to be written back under its stream key. */\n cursor?: Cursor\n stats?: Record<string, unknown>\n error?: string\n}\n\nexport interface TestConnectionResult {\n ok: boolean\n message?: string\n}\n\n/**\n * A connector's control-plane descriptor. The data-plane verbs (testConnection,\n * sync) are usually thin clients that invoke a Supabase Edge Function — the\n * function holds credentials and does the real work.\n */\nexport interface Connector {\n /** Stable connector id, e.g. 'google-calendar', 'plugbank'. */\n id: string\n /** Provider key matching ConnectionConfig.provider. */\n provider: string\n /** Plugin this connector extends, e.g. 'agenda', 'financial'. */\n pluginId: string\n authKind: IntegrationAuthKind\n capabilities: ConnectorCapability[]\n /** Validate the stored credentials/config without syncing. */\n testConnection?(config: ConnectionConfig): Promise<TestConnectionResult>\n /** Run a sync for the given direction; the caller files the run server-side. */\n sync?(input: { config: ConnectionConfig; direction: SyncDirection; trigger: SyncTrigger }): Promise<SyncResult>\n}\n\n// ---------------------------------------------------------------------------\n// Connector UI contract — how an addon plugin EXTENDS a host plugin's settings\n// ---------------------------------------------------------------------------\n// An addon plugin declares one or more ConnectorDefinitions on its manifest\n// (`connectors: [...]`). The runtime groups them by `hostPluginId`, and the host\n// plugin's settings panel renders them in a unified \"Integrations\" tab — the\n// same connect/credentials experience for every connector. The connector-\n// specific bits arrive through four named seams and not one catch-all panel:\n// `ConnectorStatus.issues` (what is wrong), `listRuns` (what has happened),\n// `RunActions` (make something happen) and `ExtraPanel` (configuration).\n\n/**\n * An input the connect step renders — **what authorizes the connection**, and\n * nothing else.\n *\n * The rule, in one line: if the connection cannot be established without it, it\n * is a field; if it changes how a live connection behaves, it is a\n * {@link ConnectorSetting}. An API key is a field. The account identifier the\n * key is scoped to is a field. Which payment methods the store accepts, or which\n * postcode it ships from, is not — see ./settings.ts.\n *\n * `type: 'password'` is the marker the credential seam reads: a password field\n * never reaches connector-author code and never reaches\n * `plg_connections.settings`.\n */\nexport interface ConnectorField {\n key: string\n label: string\n type: 'text' | 'password'\n placeholder?: string\n /**\n * Route this to the credential sink even though it is not masked.\n *\n * For the half of a credential PAIR that is not itself confidential — AppMax\n * issues `client_id` alongside `client_secret`, and the id is useless to\n * anyone without the secret. Masking it would only stop the merchant checking\n * they pasted the right one; letting it fall through to the connector's own\n * `saveConnection` would split one credential across two stores, which is\n * how half of it ends up in a pool table.\n */\n secret?: boolean\n}\n\n/**\n * A door the merchant goes through AT THE PROVIDER before the connect step can\n * be finished — and the reason the hub does not have to know which provider.\n *\n * Not every partner hands back a code on a redirect. AppMax issues the\n * `client_id`/`client_secret` pair when the merchant INSTALLS our app inside\n * their own AppMax admin (\"chaves emitidas na instalação do app\"), and only\n * then shows them something to copy. An OAuth button there would be a button\n * that lies: nothing comes back, and pressing it could never connect anything.\n * What is true is a two-step gesture — go install it there, come back and paste\n * what it showed you — so the gesture is declared and the panel renders it\n * above {@link ConnectorDefinition.fields}.\n *\n * Declared and not hardcoded in the UI because the address is a fact about\n * THIS app's registration at that provider, which the hub cannot know and a\n * second connector with the same gesture will spell differently.\n *\n * An OAuth connector declares none: consent is the install.\n */\nexport interface ConnectorProviderInstall {\n /** Where the merchant installs this app at the provider. Opened in a new tab. */\n url: string\n}\n\n/**\n * What the hub knows about who is connecting, handed to\n * {@link ConnectorDefinition.startOAuth}.\n *\n * `tenantId` is a SELECTOR and never an assertion: the platform proves\n * membership from the token it was called with and refuses anything outside it,\n * so naming an organization here narrows which of the caller's own orgs the\n * consent is for and can never widen it. Without it a merchant who belongs to\n * two organizations cannot be told apart from one who belongs to neither.\n */\nexport interface ConnectorConnectContext {\n tenantId?: string\n}\n\n/**\n * An automation the connector owns rather than the tenant.\n *\n * \"New sale → send it to Bling\" is not a preference someone chose; it is what\n * the integration IS. So it appears in the automations screen only while the\n * connection is live, and the screen must not offer to delete it — a deleted\n * row would leave the integration connected and silently half-wired, which\n * reads as \"Bling stopped working\" weeks later. Disconnecting is the way out.\n */\nexport interface ConnectorManagedAutomation {\n /** Domain event that fires it. Must be covered by `subscribesTo`. */\n eventKey: string\n /** What the tenant is told it does (\"Send the sale to Bling\"). */\n label: string\n /** Whether the tenant may switch it off and stay connected. Default false. */\n optional?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// \"Acionado em quais momentos\" — the call sites a connector participates in\n// ---------------------------------------------------------------------------\n// Three of the four answers already had a vocabulary. `subscribesTo` says which\n// facts it listens to, `managedAutomations` says which reactions it owns, and\n// `trigger_kind` on a run says what fired a sync. The gap was the REQUEST plane:\n// a connector called synchronously has runs to show for none of it, so a reader\n// asking \"quando o Melhor Envio é acionado?\" found nothing — the two moments\n// that matter (the buyer asking for freight, the order buying a label) were\n// facts about `packages/shop`, written down nowhere the connector's own page\n// could read.\n//\n// So this is the fourth answer and deliberately not a fourth vocabulary: same\n// shape as a managed automation (a stable key, the sentence a tenant reads),\n// and where a moment is a sync it names its `SyncTrigger` rather than inventing\n// a second word for \"webhook\".\n\n/** One call site that invokes a connector, and what happens there. */\nexport interface ConnectorInvocation {\n /**\n * Stable call-site key, `<plugin>.<surface>.<verb>` —\n * `shop.checkout.shipping_quote`. Not an event key: nothing publishes it, it\n * names a place in the code, and it is what a support conversation and a trace\n * will call the same call.\n */\n key: string\n /** What the tenant is told happens there (\"O cliente pede as opções de frete\"). */\n label: string\n /**\n * Which plane carries the call. Default `'request'` — the buyer is waiting.\n * `'sync'` for the effects a request leads to (capture the payment, buy the\n * label), which DO file a run.\n */\n plane?: Extract<ConnectorPlane, 'request' | 'sync'>\n /** Sync moments only: what fires it, in `plg_sync_runs.trigger_kind`'s words. */\n trigger?: SyncTrigger\n /**\n * Which declared resources this call reaches — `permissions[].resource` names.\n *\n * The half of \"quando ele age\" that was missing: the section said *when* a\n * connector is called and never *what it gets at*. Naming the resource rather\n * than restating the access is what keeps the two from disagreeing — a moment\n * cannot reach something the connector did not ask for, and the contract check\n * enforces exactly that.\n */\n touches?: string[]\n}\n\n/** The plane a moment runs on when it does not say. */\nexport function invocationPlane(moment: ConnectorInvocation): Extract<ConnectorPlane, 'request' | 'sync'> {\n return moment.plane ?? 'request'\n}\n\n/**\n * The two entries a first load has when the provider's catalogue is big enough\n * that pulling it in line is not an option.\n *\n * Keeping them apart is forced by the budget, not by taste: at 3 req/s a\n * ten-thousand-product catalogue is nearly an hour, and a connect screen that\n * waits for it is a connect screen nobody finishes.\n */\nexport interface ConnectorFirstLoad {\n /** How many records the fast phase pulls before answering. */\n sampleSize: number\n /** Pages the fast phase may spend. A sample that pages forever is a backfill. */\n samplePages?: number\n /** Which stream the phases walk (`'products'`, `'orders'`). */\n stream: string\n}\n\nexport interface ConnectorStatus {\n connected: boolean\n detail?: string\n /**\n * What is wrong with a live connection, in sentences a merchant can act on.\n *\n * `detail` is one line and is what a LIST row has room for; a connection can\n * be unwell in more than one way at once (the watch expired AND the timezone\n * drifted), and a connector that had more to say used to say it in its own\n * panel — which put status on the settings tab and left `detail` repeating the\n * first item. Declaring both is what lets the surface render the list once and\n * fall back to `detail` when there is nothing to enumerate.\n */\n issues?: string[]\n /** Which plane the live connection is running on (when connected). */\n plane?: ConnectorPlane\n /**\n * Connected, and unable to do its job until a required-to-operate setting is\n * chosen — `plg_connections.status = 'pending'`.\n *\n * Not a third kind of \"connected\": the connection is real, the authorization\n * happened, and nothing about it needs redoing. What is missing is\n * configuration, which is why it is reported here rather than by pretending\n * the connect step failed. See {@link settingsStatus}.\n */\n needsSetup?: boolean\n /** Which setting keys are missing. Keys only — never what the tenant typed. */\n missingSettings?: string[]\n}\n\n/**\n * UI-facing connector descriptor. Built on the data-plane `Connector` concept\n * (`pluginId` → `hostPluginId`), plus the control-plane hooks the unified\n * Integrations hub calls. Lives in core so `PluginManifest.connectors` can type\n * it; the rendering hub lives in `@fayz-ai/admin`.\n */\nexport interface ConnectorDefinition {\n /** Stable connector id, e.g. 'google-calendar', 'plugbank'. */\n id: string\n /**\n * The platform catalogue slug this connector IS, when it differs from `id`.\n *\n * `id` names the implementation and `slug` names the provider, and they are\n * only usually the same word: `plugbank` is the catalogue's `tecnospeed`.\n * The tie matters twice — it is what lends\n * an installed connector the catalogue's localized copy and brand mark, and\n * what tells a slug a module declared in {@link PluginManifest.integrations}\n * apart from one still to be built.\n */\n catalogSlug?: string\n /** The plugin this connector extends, e.g. 'financial', 'agenda'. */\n hostPluginId: string\n name: string\n description?: string\n /** Lucide icon name. */\n icon?: string\n /**\n * The connector's own brand mark — a URL or an inline data URI. Preferred\n * over the catalogue's icon and over `icon`, so a connector the Fayz\n * directory doesn't carry still shows up as itself.\n */\n logoUrl?: string\n authKind: IntegrationAuthKind\n /** Planes this connector can be honoured on. Default: `['sync']`. */\n planes?: ConnectorPlane[]\n /**\n * Request plane only, and required there: how long the buyer may wait before\n * `onFailure` applies. No default — see {@link requestPlaneSpec}.\n */\n latencyBudgetMs?: number\n /**\n * Request plane only, and required there. `'degrade'` serves a worse answer\n * and the sale continues; `'block'` refuses the step and the buyer is told\n * why. A field, not a decision re-taken in every screen that calls it.\n */\n onFailure?: RequestFailureMode\n /** Default `'outbound'` — this app reaching a provider. */\n direction?: ConnectorDirection\n /** What the assistant gains on the agentic plane — shown before connecting. */\n agentTools?: ConnectorAgentTool[]\n /**\n * **What this connector needs access to, and why** — the one declaration the\n * consent view, \"quando ele age\" and (eventually) the minted credential all\n * read. See ./permissions.ts.\n *\n * It SUBSUMES {@link entities} and {@link subscribesTo}: declare permissions\n * and both are derived ({@link connectorEntities}, {@link connectorSubscribesTo}).\n * Declaring a permission and a contradicting legacy field fails the contract\n * check rather than being merged — a connector that says it writes `booking`\n * in one field and never mentions `booking` in the other has a bug, and\n * picking a winner would hide it.\n */\n permissions?: ConnectorPermission[]\n /**\n * Canonical entities this connector touches ('booking', 'movement').\n *\n * Superseded by {@link permissions}, which says read or write rather than only\n * the name. Still read for connectors built against an older core; when both\n * are present they must name the same set. Read it through\n * {@link connectorEntities}, never off the field.\n */\n entities?: string[]\n /**\n * The catalogue shelf, from the closed vocabulary in\n * {@link CONNECTOR_CATEGORIES}. Typed rather than free-form because the badge\n * that renders it is keyed by this exact string: an unlisted one renders no\n * badge at all, which is invisible in review and invisible in the screenshot.\n */\n category?: ConnectorCategory\n /**\n * The provider's call budget for ONE account, declared rather than discovered.\n *\n * The scheduler owns the clock ({@link createRateLimiter}); a `sleep` inside a\n * connector's fetch helper cannot express a daily ceiling and cannot be shared\n * between two streams pulling at the same time. Declaring it is also what\n * makes \"never one request per product\" a checkable statement instead of a\n * habit — see {@link firstLoad}.\n */\n rateLimit?: RateLimitBudget\n /**\n * How the first load enters, when the catalogue is too large to fetch in line.\n *\n * Two phases, because they answer different questions: `sample` returns in\n * seconds and is what a briefing or a generated storefront is designed\n * against; `backfill` runs in the background against the budget and is what\n * makes the mirror complete. Absent = the first load is just a sync.\n */\n firstLoad?: ConnectorFirstLoad\n /** Provider documentation, linked from the connector's detail. */\n docsUrl?: string\n /**\n * What AUTHORIZES the connection — the connect step renders exactly these and\n * nothing more (api-key / mtls). An OAuth connector declares none: consent is\n * the whole form.\n *\n * A connector whose credential lives in the Fayz platform declares none\n * either, and the connect step says so rather than drawing an empty grid.\n */\n fields?: ConnectorField[]\n /**\n * The provider-side install the merchant must do first, when there is one.\n * Rendered above `fields`. See {@link ConnectorProviderInstall}.\n */\n providerInstall?: ConnectorProviderInstall\n /**\n * How the live connection BEHAVES — asked for only after it exists.\n *\n * The split is the point. Everything here changes with the season and none of\n * it belongs in front of someone who has not connected yet; see ./settings.ts.\n * Declaring a setting also allow-lists its key for\n * `plg_connections.settings`, so a connector needs it in one place, not two.\n */\n settings?: ConnectorSetting[]\n /** Current values for the settings form. Required whenever `settings` is declared. */\n getSettings?(): Promise<Record<string, unknown>>\n /** Persist a settings patch — merged by key, never replacing the jsonb. */\n saveSettings?(values: Record<string, ConnectorSettingValue>): Promise<void>\n /**\n * The moments this connector is CALLED at — the request plane's answer to\n * \"acionado quando?\". See {@link ConnectorInvocation}.\n */\n invokedAt?: ConnectorInvocation[]\n /**\n * Domain-event keys this connector reacts to — exact keys or `*`-suffixed\n * prefixes (`agenda.booking_created`, `blog.post.*`). The ingress reads it to\n * decide which facts are worth waking the connector for; without it every\n * connector would have to be handed every event and filter it itself.\n *\n * Superseded by a `kind: 'event'` {@link permissions} entry carrying `listen`,\n * which says the same thing plus why. Same agreement rule as {@link entities}.\n * Read it through {@link connectorSubscribesTo}, never off the field.\n */\n subscribesTo?: string[]\n /** Automations this connector owns. See {@link ConnectorManagedAutomation}. */\n managedAutomations?: ConnectorManagedAutomation[]\n /** Current connection state for the status badge. */\n getStatus(): Promise<ConnectorStatus>\n /**\n * The same answer as {@link getStatus}, derived from a `plg_connections` row a\n * caller already holds.\n *\n * Declaring it is what lets a list of N connectors resolve in ONE batched read\n * (`listConnections`) instead of N `getStatus()` round trips — the connectors\n * screen used to issue one request per row. Pure by contract: it is handed a\n * row, it must not fetch.\n *\n * Absent means \"my truth is not in that table\" (Stripe Connect derives from\n * the provider), and the list falls back to calling `getStatus()`. Use\n * {@link connectionStatus} to write both from one function.\n */\n statusFromConnection?(connection: ConnectionConfig | null): ConnectorStatus\n /** Which account at the provider `statusFromConnection` reads. Default `''`. */\n instanceKey?: string\n /** Validate credentials without persisting (api-key / mtls). */\n testConnection?(values: Record<string, string>): Promise<TestConnectionResult>\n /** Persist the connection (api-key / mtls). */\n saveConnection?(values: Record<string, string>): Promise<void>\n /**\n * Begin an OAuth connect flow — returns the consent URL to redirect to.\n *\n * `context` is optional so a connector written against an older core keeps\n * compiling; the hub always passes it. See {@link ConnectorConnectContext}.\n */\n startOAuth?(redirectTo?: string, context?: ConnectorConnectContext): Promise<string>\n /** Tear down the connection. */\n disconnect?(): Promise<void>\n /**\n * What unplugging costs, when the answer is not obvious.\n *\n * A mirror that freezes is not a catalogue that is deleted, and a merchant\n * who cannot tell the two apart never presses the button. Rendered beside\n * Desconectar, which is the only place the question is ever asked.\n */\n disconnectNote?: string\n /**\n * When this connector syncs, in the merchant's words.\n *\n * The first question anyone has after connecting, and one the screen could\n * not answer: the cadence lives in `plg_connector_schedules`, the on-write\n * wake in a database trigger, the manual run in a button — three places, none\n * of them a sentence. Rendered on Visão geral.\n */\n howItSyncs?: string[]\n /** Move an existing connection between planes (only when `planes` has both). */\n setPlane?(plane: ConnectorPlane): Promise<void>\n /**\n * What this connection has DONE — newest first.\n *\n * The surface renders one history for every connector, so a connector\n * declares where its runs are and nothing about how they look. Store-backed\n * connectors get it from {@link connectionRuns}; a connector whose runs live\n * in its own table maps them onto {@link SyncRun} here, which is the only\n * shape the view knows.\n *\n * Absent means \"I keep no history\", and the surface offers no history tab —\n * never an empty one, which would read as \"nothing ever ran\".\n */\n listRuns?(options?: { limit?: number }): Promise<SyncRun[]>\n /**\n * The connector's own actions that PRODUCE a run — sync now, import a\n * statement, pull events from a date.\n *\n * Deliberately separate from {@link ExtraPanel}: an import is something\n * somebody does once, and it was rendered beside a settings form, which is\n * where the two got confused. Rendered above the history, because the history\n * is where its effect shows up.\n *\n * `onRan` reloads that history — call it after the action lands.\n */\n RunActions?: React.ComponentType<{ onRan?: () => void }>\n /**\n * The same thing as {@link RunActions}, DECLARED instead of drawn.\n *\n * What differs between two connectors is the sentence on the button and the\n * call behind it. Spinner, toast, reloading the history, confirming a write at\n * the provider — the surface owns all of it, or two integrations end up\n * feeling like two products.\n *\n * Both seams render, so a connector can migrate one action at a time.\n */\n syncActions?: ConnectorSyncAction[]\n /**\n * Record by record: what reached the provider, what is queued, what was\n * refused. The seam the automations screen reads — a managed automation could\n * only show that the fact FIRED, never that anything arrived.\n */\n listDeliveries?(options?: { eventKey?: string; limit?: number }): Promise<ConnectorDelivery[]>\n /**\n * The connector's own CONFIGURATION UI, rendered on the Settings tab.\n *\n * Configuration only: which Google calendar maps to which professional, which\n * warehouse a stream belongs to. Status belongs in {@link ConnectorStatus},\n * history in {@link listRuns}, and a one-off action in {@link RunActions} —\n * this panel used to hold all four at once.\n */\n ExtraPanel?: React.ComponentType\n}\n\n/**\n * One RECORD's journey to the provider — \"cadastrei aqui, foi?\".\n *\n * A {@link SyncRun} says \"12 of 20 filed\", which is the number that does not\n * help the person holding order 1042 in their hand.\n */\nexport interface ConnectorDelivery {\n /** The record, as the merchant would name it: \"Pedido 1042\", \"Costela defumada\". */\n subject: string\n /** This app's id for it, when a screen wants to link back to the record. */\n subjectId?: string\n /** `pending` is a real answer: queued, not yet sent — what the merchant sees\n * in the fifteen minutes between a sale and the clock. */\n status: 'delivered' | 'pending' | 'failed'\n /** Why it is pending or failed, in the merchant's language. */\n detail?: string\n /** The id or number it got at the provider. The one thing checkable on both screens. */\n externalId?: string\n /** When it was delivered, or when the record was created if it was not. */\n at?: string\n}\n\n/** What a run actually did. `partial` and `error` are toasted as such. */\nexport interface ConnectorSyncOutcome {\n message: string\n status?: 'success' | 'partial' | 'error'\n}\n\n/** One thing a merchant can make a connection DO. Declared rather than drawn —\n * see {@link ConnectorDefinition.syncActions}. */\nexport interface ConnectorSyncAction {\n /** Stable, for React keys and for telling two actions apart in a test. */\n key: string\n /** The button. A verb, in the merchant's language: \"Trazer catálogo do Bling\". */\n label: string\n /** Which way the data moves. Outbound is a write the merchant cannot undo\n * from this screen, which is what `confirm` is for. */\n direction: 'inbound' | 'outbound'\n /** One line under the label, when the label alone cannot carry it. */\n description?: string\n /**\n * The action somebody came here to press — the one that is right on an\n * ordinary day. At most one. The rest fold under \"Outras ações\": five equal\n * buttons is a menu the reader has to grade themselves.\n */\n primary?: boolean\n /** Ask first. Only for what cannot be taken back. */\n confirm?: { title: string; body: string; cta: string }\n /**\n * Do it. What it returns becomes the toast — use the run's own sentence, so\n * the merchant reads the same words here and in the history below.\n *\n * Return a {@link ConnectorSyncOutcome} when the run can partly fail: a push\n * that filed 0 of 9 answers 200 with a sentence, and a surface that reads only\n * \"it did not throw\" celebrates it. The verdict is already computed where the\n * work happened; this is how it reaches the screen.\n */\n run(): Promise<string | ConnectorSyncOutcome | void>\n /**\n * How many records are still waiting for this action.\n *\n * `0` means there is nothing left to send, and the surface says so — a check\n * and a spent button, not a live control that would file nothing. A push whose\n * queue is an absence can always answer this; a pull cannot know what changed\n * at the provider, so it declares nothing and stays enabled.\n */\n pending?(): Promise<number | null> | number | null\n /** Whether to offer it at all. Hidden, not disabled: a greyed button reads as\n * something the reader failed to do. Absent means always. */\n available?(): Promise<boolean> | boolean\n /** A screen of its own, for an action that needs choices first. Replaces the\n * row while open; call `onRan` when something actually happened. */\n Component?: React.ComponentType<{ onClose(): void; onRan?(): void }>\n}\n\n/** Planes a connector supports — the contract's default is the sync pipeline. */\nexport function connectorPlanes(def: Pick<ConnectorDefinition, 'planes'>): ConnectorPlane[] {\n return def.planes?.length ? def.planes : ['sync']\n}\n\n/**\n * The plane to offer first. Agentic wins whenever the connector supports it:\n * handing the assistant a tool costs nothing to keep in agreement, while a sync\n * pipeline is a copy that has to be maintained for as long as it exists.\n */\nexport function preferredPlane(def: Pick<ConnectorDefinition, 'planes'>): ConnectorPlane {\n const planes = connectorPlanes(def)\n // Not `: 'sync'` — a request-only connector has no sync plane to fall back to.\n return planes.includes('agentic') ? 'agentic' : planes[0]\n}\n\n/** Direction a connector moves data in — outbound unless it says otherwise. */\nexport function connectorDirection(def: Pick<ConnectorDefinition, 'direction'>): ConnectorDirection {\n return def.direction ?? 'outbound'\n}\n\n/**\n * Wrap an object's async methods so a handler fires AFTER selected methods\n * resolve — the non-invasive hook for outbound, event-driven sync when there's\n * no app-level event bus. Example: wrap an AgendaDataProvider so that after\n * createBooking/updateBooking/deleteBooking succeed, an external calendar is\n * updated. The handler runs fire-and-forget; its failures never break the call.\n */\nexport function withAfterHooks<T extends object>(\n target: T,\n methods: Partial<Record<keyof T, (args: unknown[], result: unknown) => void | Promise<void>>>,\n): T {\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const orig = Reflect.get(obj, prop, receiver)\n const hook = (methods as Record<string | symbol, ((args: unknown[], result: unknown) => void | Promise<void>) | undefined>)[prop]\n if (typeof orig !== 'function' || !hook) return orig\n return (...args: unknown[]) => {\n const result = (orig as (...a: unknown[]) => unknown).apply(obj, args)\n if (result instanceof Promise) {\n return result.then((value) => {\n try { void Promise.resolve(hook(args, value)).catch(() => {}) } catch { /* never break the call */ }\n return value\n })\n }\n try { void Promise.resolve(hook(args, result)).catch(() => {}) } catch { /* noop */ }\n return result\n }\n },\n })\n}\n"]}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// src/integrations/runtime.ts
|
|
2
|
+
function cursorCompleteness(cursor) {
|
|
3
|
+
return cursor.kind === "delta" ? "authoritative" : "additive";
|
|
4
|
+
}
|
|
5
|
+
var CONNECTOR_RUNTIME_TOKEN_HEADER = "x-fayz-runtime-token";
|
|
6
|
+
var ConnectorCredentialUnavailableError = class extends Error {
|
|
7
|
+
constructor(message, status = null) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ConnectorCredentialUnavailableError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var CONNECTOR_REDEMPTION_ENV_KEYS = [
|
|
14
|
+
"FAYZ_API_URL",
|
|
15
|
+
"FAYZ_PROJECT_ID",
|
|
16
|
+
"CONNECTOR_REDEMPTION_SECRET"
|
|
17
|
+
];
|
|
18
|
+
function ambientEnv(name) {
|
|
19
|
+
const scope = globalThis;
|
|
20
|
+
try {
|
|
21
|
+
return scope.Deno?.env?.get(name) ?? scope.process?.env?.[name] ?? void 0;
|
|
22
|
+
} catch {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function redemptionApiRoot(value) {
|
|
27
|
+
const trimmed = value.trim().replace(/\/+$/, "");
|
|
28
|
+
return /\/api$/.test(trimmed) ? trimmed : `${trimmed}/api`;
|
|
29
|
+
}
|
|
30
|
+
function looksLikeHtml(body) {
|
|
31
|
+
return /^\s*(<!doctype html|<html)/i.test(body ?? "");
|
|
32
|
+
}
|
|
33
|
+
function readRefusal(body) {
|
|
34
|
+
const text = (body ?? "").trim();
|
|
35
|
+
if (!text || !text.startsWith("{")) return "";
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(text);
|
|
38
|
+
if (parsed.status !== "error" || typeof parsed.message !== "string") return "";
|
|
39
|
+
return parsed.message.slice(0, 300);
|
|
40
|
+
} catch {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function redemptionHost(url) {
|
|
45
|
+
try {
|
|
46
|
+
return new URL(url).host;
|
|
47
|
+
} catch {
|
|
48
|
+
return "the platform";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function redemptionFailure(connectorSlug, status, url, door, said = "") {
|
|
52
|
+
const where = redemptionHost(url);
|
|
53
|
+
const who = door === "session" ? "a sess\xE3o do operador" : `tenantKey ${door}`;
|
|
54
|
+
if (looksLikeHtml(said)) {
|
|
55
|
+
return `The platform did not answer at ${where} \u2014 a proxy or tunnel page came back instead of the ${connectorSlug} credential (${status}). Check FAYZ_API_URL on this project's functions: it points somewhere that is not serving the Fayz API right now`;
|
|
56
|
+
}
|
|
57
|
+
const detail = readRefusal(said);
|
|
58
|
+
const base = `The platform did not return the ${connectorSlug} credential (${status}) \u2014 ${where}, ${who}` + (detail ? `: ${detail}` : "");
|
|
59
|
+
if (status !== 404) return base;
|
|
60
|
+
return `${base}. 404 means that platform has no connected ${connectorSlug} credential for that tenant \u2014 most often the connection was authorised against a different environment, or the tenant proving this call is not the one that authorised`;
|
|
61
|
+
}
|
|
62
|
+
async function redeemConnectorCredential(options) {
|
|
63
|
+
const env = options.env ?? ambientEnv;
|
|
64
|
+
const connectorSlug = (options.connectorSlug ?? "").trim();
|
|
65
|
+
const runtimeToken = (options.runtimeToken ?? "").trim();
|
|
66
|
+
const apiUrl = (options.apiUrl ?? env("FAYZ_API_URL") ?? "").trim();
|
|
67
|
+
const projectId = (options.projectId ?? env("FAYZ_PROJECT_ID") ?? "").trim();
|
|
68
|
+
const secret = (options.redemptionSecret ?? env("CONNECTOR_REDEMPTION_SECRET") ?? "").trim();
|
|
69
|
+
if (!connectorSlug) throw new ConnectorCredentialUnavailableError("connectorSlug is required to redeem a credential");
|
|
70
|
+
if (!apiUrl || !projectId || !secret) {
|
|
71
|
+
throw new ConnectorCredentialUnavailableError(
|
|
72
|
+
`Connector redemption is not configured \u2014 expected ${CONNECTOR_REDEMPTION_ENV_KEYS.join(", ")} in the environment`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const tenantKey = (options.tenantKey ?? "").trim();
|
|
76
|
+
if (!runtimeToken && !tenantKey) {
|
|
77
|
+
throw new ConnectorCredentialUnavailableError(
|
|
78
|
+
"No runtime token and no tenantKey on this call, so there is no tenant to redeem a credential for"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const fetchImpl = options.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
82
|
+
const url = `${redemptionApiRoot(apiUrl)}/public/projects/${encodeURIComponent(projectId)}/connectors/connections/redeem?connectorSlug=${encodeURIComponent(connectorSlug)}`;
|
|
83
|
+
const response = await fetchImpl(url, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
// Two keys: the secret says "not a browser", the token says "this tenant".
|
|
87
|
+
"x-fayz-connector-secret": secret,
|
|
88
|
+
...runtimeToken ? { Authorization: `Bearer ${runtimeToken}` } : {},
|
|
89
|
+
...runtimeToken ? {} : { "Content-Type": "application/json" }
|
|
90
|
+
},
|
|
91
|
+
// Machine mode: no Authorization, tenant named in the body. The platform
|
|
92
|
+
// bounds it to MACHINE_REDEEMABLE_CONNECTORS and audits every use.
|
|
93
|
+
...runtimeToken ? {} : { body: JSON.stringify({ tenantKey }) }
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const said = await response.text().catch(() => "");
|
|
97
|
+
throw new ConnectorCredentialUnavailableError(
|
|
98
|
+
redemptionFailure(connectorSlug, response.status, url, runtimeToken ? "session" : tenantKey, said),
|
|
99
|
+
response.status
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(await response.text());
|
|
105
|
+
} catch {
|
|
106
|
+
throw new ConnectorCredentialUnavailableError(
|
|
107
|
+
`The platform answered ${response.status} with something that is not a credential`,
|
|
108
|
+
response.status
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const secrets = parsed?.secrets;
|
|
112
|
+
if (!secrets || typeof secrets !== "object" || Object.keys(secrets).length === 0) {
|
|
113
|
+
throw new ConnectorCredentialUnavailableError(
|
|
114
|
+
`The ${connectorSlug} connection carries no credential`,
|
|
115
|
+
response.status
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
connectionId: String(parsed?.connectionId ?? ""),
|
|
120
|
+
connectorSlug: String(parsed?.connectorSlug ?? connectorSlug),
|
|
121
|
+
tenantKey: String(parsed?.tenantKey ?? ""),
|
|
122
|
+
authKind: String(parsed?.authKind ?? "api-key"),
|
|
123
|
+
secrets,
|
|
124
|
+
...typeof parsed?.expiresAt === "string" ? { expiresAt: parsed.expiresAt } : {}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var ConnectorHttpError = class extends Error {
|
|
128
|
+
constructor(status, message, options = {}) {
|
|
129
|
+
super(message);
|
|
130
|
+
this.name = "ConnectorHttpError";
|
|
131
|
+
this.status = status;
|
|
132
|
+
this.retryAfterMs = options.retryAfterMs ?? null;
|
|
133
|
+
this.body = options.body ?? null;
|
|
134
|
+
this.details = options.details ?? null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
138
|
+
if (!value) return null;
|
|
139
|
+
const seconds = Number(value);
|
|
140
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1e3);
|
|
141
|
+
const at = Date.parse(value);
|
|
142
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : null;
|
|
143
|
+
}
|
|
144
|
+
function backoffDelay(attempt, options = {}) {
|
|
145
|
+
const baseDelayMs = options.baseDelayMs ?? 250;
|
|
146
|
+
const maxDelayMs = options.maxDelayMs ?? 5e3;
|
|
147
|
+
const random = options.random ?? Math.random;
|
|
148
|
+
const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** Math.max(0, attempt - 1));
|
|
149
|
+
return Math.round(exponential * (0.75 + random() * 0.5));
|
|
150
|
+
}
|
|
151
|
+
var DEFAULT_RETRYABLE = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
152
|
+
function statusNeedsBody(status, options = {}) {
|
|
153
|
+
if (status === null) return false;
|
|
154
|
+
return (options.needsBody ?? [403]).includes(status);
|
|
155
|
+
}
|
|
156
|
+
function classify(outcome, options = {}) {
|
|
157
|
+
const { status, attempt, maxAttempts } = outcome;
|
|
158
|
+
const exhausted = attempt >= maxAttempts;
|
|
159
|
+
const now = options.now?.() ?? Date.now();
|
|
160
|
+
const readRetryAfter = options.readRetryAfter ?? ((h, at) => parseRetryAfter(h?.get("retry-after"), at));
|
|
161
|
+
const wait = () => readRetryAfter(outcome.headers, now) ?? backoffDelay(attempt, options);
|
|
162
|
+
if (status === null) {
|
|
163
|
+
if (exhausted) return { action: "fail", reason: String(outcome.error?.message ?? outcome.error ?? "request failed") };
|
|
164
|
+
return { action: "retry", delayMs: backoffDelay(attempt, options) };
|
|
165
|
+
}
|
|
166
|
+
if (status < 400) return { action: "return" };
|
|
167
|
+
if ((options.reauthStatuses ?? [401]).includes(status)) {
|
|
168
|
+
return exhausted ? { action: "return" } : { action: "reauth" };
|
|
169
|
+
}
|
|
170
|
+
if ((options.resetCursorStatuses ?? []).includes(status)) return { action: "reset-cursor" };
|
|
171
|
+
const retryable = DEFAULT_RETRYABLE.has(status) || status >= 500 && status <= 599 || (options.retryableStatuses ?? []).includes(status) || (options.isQuotaBody?.(outcome.body, status) ?? false);
|
|
172
|
+
if (!retryable) return { action: "return" };
|
|
173
|
+
return exhausted ? { action: "return" } : { action: "retry", delayMs: wait() };
|
|
174
|
+
}
|
|
175
|
+
async function peekBody(response) {
|
|
176
|
+
try {
|
|
177
|
+
const copy = response.clone?.();
|
|
178
|
+
if (copy) return await copy.text();
|
|
179
|
+
return null;
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function fetchWithRetry(fetchImpl, input, init, options = {}) {
|
|
185
|
+
const maxAttempts = options.maxAttempts ?? 4;
|
|
186
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
187
|
+
let currentInit = init;
|
|
188
|
+
let reauthed = false;
|
|
189
|
+
let lastError;
|
|
190
|
+
let lastResponse;
|
|
191
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
192
|
+
let response;
|
|
193
|
+
let error;
|
|
194
|
+
try {
|
|
195
|
+
response = await fetchImpl(input, currentInit);
|
|
196
|
+
lastResponse = response;
|
|
197
|
+
} catch (caught) {
|
|
198
|
+
error = caught;
|
|
199
|
+
lastError = caught;
|
|
200
|
+
}
|
|
201
|
+
const status = response ? response.status : null;
|
|
202
|
+
const body = response && statusNeedsBody(status, options) ? await peekBody(response) : null;
|
|
203
|
+
const verdict = classify(
|
|
204
|
+
{ status, headers: response?.headers ?? null, body, error, attempt, maxAttempts },
|
|
205
|
+
options
|
|
206
|
+
);
|
|
207
|
+
if (verdict.action === "return" || verdict.action === "reset-cursor") {
|
|
208
|
+
if (response) return response;
|
|
209
|
+
throw lastError ?? new Error("connector request failed");
|
|
210
|
+
}
|
|
211
|
+
if (verdict.action === "fail") {
|
|
212
|
+
throw lastError ?? new ConnectorHttpError(status ?? 0, verdict.reason);
|
|
213
|
+
}
|
|
214
|
+
if (verdict.action === "reauth") {
|
|
215
|
+
if (reauthed || !options.onReauth) return response;
|
|
216
|
+
reauthed = true;
|
|
217
|
+
const next = await options.onReauth();
|
|
218
|
+
if (next) currentInit = next;
|
|
219
|
+
options.onRetry?.({ attempt, delayMs: 0, status, error: error ?? null });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
options.onRetry?.({ attempt, delayMs: verdict.delayMs, status, error: error ?? null });
|
|
223
|
+
await sleep(verdict.delayMs);
|
|
224
|
+
}
|
|
225
|
+
if (lastResponse) return lastResponse;
|
|
226
|
+
throw lastError ?? new Error("connector request failed");
|
|
227
|
+
}
|
|
228
|
+
function isExpiring(expiresAt, options = {}) {
|
|
229
|
+
if (!expiresAt) return true;
|
|
230
|
+
const expiresAtMs = expiresAt instanceof Date ? expiresAt.getTime() : new Date(expiresAt).getTime();
|
|
231
|
+
if (!Number.isFinite(expiresAtMs)) return true;
|
|
232
|
+
const now = options.now ?? Date.now();
|
|
233
|
+
const leadMs = options.leadMs ?? 24 * 60 * 6e4;
|
|
234
|
+
return expiresAtMs <= now + leadMs;
|
|
235
|
+
}
|
|
236
|
+
async function paginate(options) {
|
|
237
|
+
const maxPages = options.maxPages ?? 100;
|
|
238
|
+
const classifyError = options.classifyError ?? ((error) => {
|
|
239
|
+
const status = error?.status;
|
|
240
|
+
return status === 410 ? { action: "reset-cursor" } : { action: "fail", reason: String(status ?? error) };
|
|
241
|
+
});
|
|
242
|
+
let request = options.first;
|
|
243
|
+
let items = [];
|
|
244
|
+
let pages = 0;
|
|
245
|
+
let cursorReset = false;
|
|
246
|
+
while (request !== null && request !== void 0) {
|
|
247
|
+
if (pages >= maxPages) throw new Error(`connector pagination limit exceeded (${maxPages} pages)`);
|
|
248
|
+
let page;
|
|
249
|
+
try {
|
|
250
|
+
page = await options.fetchPage(request);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const verdict = classifyError(error);
|
|
253
|
+
const restart = verdict.action === "reset-cursor" && !cursorReset ? options.resetTo?.(request) : null;
|
|
254
|
+
if (!restart) throw error;
|
|
255
|
+
request = restart;
|
|
256
|
+
cursorReset = true;
|
|
257
|
+
items = [];
|
|
258
|
+
pages = 0;
|
|
259
|
+
options.onCursorReset?.();
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
pages += 1;
|
|
263
|
+
if (page.items?.length) items.push(...page.items);
|
|
264
|
+
request = page.next ?? null;
|
|
265
|
+
}
|
|
266
|
+
return { items, pages, cursorReset };
|
|
267
|
+
}
|
|
268
|
+
var RateLimitExceededError = class extends Error {
|
|
269
|
+
constructor(perDay) {
|
|
270
|
+
super(`daily call budget of ${perDay} exhausted; the run must resume tomorrow from its cursor`);
|
|
271
|
+
this.kind = "daily-budget";
|
|
272
|
+
this.name = "RateLimitExceededError";
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
function createRateLimiter(budget, options = {}) {
|
|
276
|
+
const now = options.now ?? Date.now;
|
|
277
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
278
|
+
const perSecond = budget.perSecond && budget.perSecond > 0 ? budget.perSecond : null;
|
|
279
|
+
const perDay = budget.perDay && budget.perDay > 0 ? budget.perDay : null;
|
|
280
|
+
const minGapMs = perSecond ? Math.ceil(1e3 / perSecond) : 0;
|
|
281
|
+
let spent = options.spentToday ?? 0;
|
|
282
|
+
let nextAt = 0;
|
|
283
|
+
return {
|
|
284
|
+
get spent() {
|
|
285
|
+
return spent;
|
|
286
|
+
},
|
|
287
|
+
get exhausted() {
|
|
288
|
+
return perDay !== null && spent >= perDay;
|
|
289
|
+
},
|
|
290
|
+
async take() {
|
|
291
|
+
if (perDay !== null && spent >= perDay) throw new RateLimitExceededError(perDay);
|
|
292
|
+
const at = now();
|
|
293
|
+
const departAt = Math.max(at, nextAt);
|
|
294
|
+
nextAt = departAt + minGapMs;
|
|
295
|
+
spent += 1;
|
|
296
|
+
const waitMs = departAt - at;
|
|
297
|
+
if (waitMs > 0) await sleep(waitMs);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function sliceWindow(from, to, maxSpanMs) {
|
|
302
|
+
const start = Date.parse(from);
|
|
303
|
+
const end = Date.parse(to);
|
|
304
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) throw new Error(`sliceWindow: unparseable range ${from}..${to}`);
|
|
305
|
+
if (end <= start) return [{ from, to }];
|
|
306
|
+
if (!Number.isFinite(maxSpanMs) || maxSpanMs <= 0) return [{ from, to }];
|
|
307
|
+
const slices = [];
|
|
308
|
+
let cursor = start;
|
|
309
|
+
while (cursor < end) {
|
|
310
|
+
const next = Math.min(end, cursor + maxSpanMs);
|
|
311
|
+
slices.push({ from: new Date(cursor).toISOString(), to: new Date(next).toISOString() });
|
|
312
|
+
cursor = next;
|
|
313
|
+
}
|
|
314
|
+
return slices;
|
|
315
|
+
}
|
|
316
|
+
function advanceWindow(cursor, now = Date.now()) {
|
|
317
|
+
const previousTo = Date.parse(cursor.to);
|
|
318
|
+
const base = Number.isFinite(previousTo) ? previousTo : now;
|
|
319
|
+
const from = Math.max(0, base - Math.max(0, cursor.overlapMs));
|
|
320
|
+
return { kind: "window", from: new Date(from).toISOString(), to: new Date(now).toISOString(), overlapMs: cursor.overlapMs };
|
|
321
|
+
}
|
|
322
|
+
function serializeCursor(cursor) {
|
|
323
|
+
if (!cursor) return null;
|
|
324
|
+
if (cursor.kind === "delta") return cursor.token;
|
|
325
|
+
if (cursor.kind === "window") return `${cursor.from}..${cursor.to}`;
|
|
326
|
+
return cursor.period;
|
|
327
|
+
}
|
|
328
|
+
function reconcileMirror(input) {
|
|
329
|
+
const observed = new Set(input.observed);
|
|
330
|
+
const tombstones = new Set(input.tombstones ?? []);
|
|
331
|
+
const authoritative = input.completeness === "authoritative";
|
|
332
|
+
const cancel = [];
|
|
333
|
+
const keptOutOfRange = [];
|
|
334
|
+
for (const record of input.linked) {
|
|
335
|
+
if (tombstones.has(record.externalId)) {
|
|
336
|
+
cancel.push(record.id);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (observed.has(record.externalId)) continue;
|
|
340
|
+
if (authoritative) cancel.push(record.id);
|
|
341
|
+
else keptOutOfRange.push(record.id);
|
|
342
|
+
}
|
|
343
|
+
return { cancel, keptOutOfRange, reason: authoritative ? "absence-is-deletion" : "absence-means-nothing" };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { CONNECTOR_REDEMPTION_ENV_KEYS, CONNECTOR_RUNTIME_TOKEN_HEADER, ConnectorCredentialUnavailableError, ConnectorHttpError, RateLimitExceededError, advanceWindow, backoffDelay, classify, createRateLimiter, cursorCompleteness, fetchWithRetry, isExpiring, paginate, parseRetryAfter, reconcileMirror, redeemConnectorCredential, redemptionFailure, redemptionHost, serializeCursor, sliceWindow, statusNeedsBody };
|
|
347
|
+
//# sourceMappingURL=chunk-ED6NBH6U.js.map
|
|
348
|
+
//# sourceMappingURL=chunk-ED6NBH6U.js.map
|