@fayz-ai/core 0.10.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.
Files changed (93) hide show
  1. package/dist/chunk-BEGXYHS4.js +566 -0
  2. package/dist/chunk-BEGXYHS4.js.map +1 -0
  3. package/dist/chunk-ED6NBH6U.js +348 -0
  4. package/dist/chunk-ED6NBH6U.js.map +1 -0
  5. package/dist/chunk-EWKX4VUM.js +429 -0
  6. package/dist/chunk-EWKX4VUM.js.map +1 -0
  7. package/dist/chunk-LARZWDHA.js +319 -0
  8. package/dist/chunk-LARZWDHA.js.map +1 -0
  9. package/dist/{chunk-732GZEUI.js → chunk-RRYFXUQX.js} +22 -367
  10. package/dist/chunk-RRYFXUQX.js.map +1 -0
  11. package/dist/{chunk-M27JPX3K.js → chunk-TDI6F6PT.js} +363 -24
  12. package/dist/chunk-TDI6F6PT.js.map +1 -0
  13. package/dist/{chunk-JO7Z2AOE.js → chunk-UVKH6RDX.js} +211 -335
  14. package/dist/chunk-UVKH6RDX.js.map +1 -0
  15. package/dist/chunk-V53GR4UT.js +289 -0
  16. package/dist/chunk-V53GR4UT.js.map +1 -0
  17. package/dist/connector-runtime/index.d.ts +15 -0
  18. package/dist/connector-runtime/index.d.ts.map +1 -0
  19. package/dist/connector-runtime/index.js +3 -0
  20. package/dist/connector-runtime/index.js.map +1 -0
  21. package/dist/data/archetype.d.ts.map +1 -1
  22. package/dist/data/bulk.d.ts +24 -0
  23. package/dist/data/bulk.d.ts.map +1 -1
  24. package/dist/data/cached.d.ts.map +1 -1
  25. package/dist/data/custom-fields.d.ts +5 -0
  26. package/dist/data/custom-fields.d.ts.map +1 -0
  27. package/dist/data/index.d.ts +2 -1
  28. package/dist/data/index.d.ts.map +1 -1
  29. package/dist/data/index.js +3 -1
  30. package/dist/data/mock.d.ts.map +1 -1
  31. package/dist/data/platform-api.d.ts.map +1 -1
  32. package/dist/data/resolve.d.ts.map +1 -1
  33. package/dist/data/supabase.d.ts.map +1 -1
  34. package/dist/data/types.d.ts +8 -0
  35. package/dist/data/types.d.ts.map +1 -1
  36. package/dist/events/log.d.ts.map +1 -1
  37. package/dist/fields/store.d.ts +45 -0
  38. package/dist/fields/store.d.ts.map +1 -0
  39. package/dist/i18n/index.d.ts.map +1 -1
  40. package/dist/i18n/index.js +1 -1
  41. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  42. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  43. package/dist/index.d.ts +22 -8
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +180 -29
  46. package/dist/index.js.map +1 -1
  47. package/dist/integrations/events.d.ts +7 -0
  48. package/dist/integrations/events.d.ts.map +1 -0
  49. package/dist/integrations/index.d.ts +663 -0
  50. package/dist/integrations/index.d.ts.map +1 -0
  51. package/dist/integrations/index.js +3 -1
  52. package/dist/integrations/permissions.d.ts +132 -0
  53. package/dist/integrations/permissions.d.ts.map +1 -0
  54. package/dist/integrations/request.d.ts +170 -0
  55. package/dist/integrations/request.d.ts.map +1 -0
  56. package/dist/integrations/runtime-token.d.ts +33 -0
  57. package/dist/integrations/runtime-token.d.ts.map +1 -0
  58. package/dist/integrations/runtime.d.ts +398 -0
  59. package/dist/integrations/runtime.d.ts.map +1 -0
  60. package/dist/integrations/settings.d.ts +102 -0
  61. package/dist/integrations/settings.d.ts.map +1 -0
  62. package/dist/integrations/store.d.ts +145 -0
  63. package/dist/integrations/store.d.ts.map +1 -0
  64. package/dist/plugin/index.js +2 -1
  65. package/dist/plugin/runtime.d.ts.map +1 -1
  66. package/dist/registry/entities.d.ts +41 -15
  67. package/dist/registry/entities.d.ts.map +1 -1
  68. package/dist/registry/index.d.ts +2 -2
  69. package/dist/registry/index.d.ts.map +1 -1
  70. package/dist/{shell-translations.pt-br-2GWRQRRP.js → shell-translations.pt-br-BM5ZFSKJ.js} +72 -3
  71. package/dist/shell-translations.pt-br-BM5ZFSKJ.js.map +1 -0
  72. package/dist/telemetry/errors.d.ts +87 -0
  73. package/dist/telemetry/errors.d.ts.map +1 -0
  74. package/dist/testing/index.js +4 -1
  75. package/dist/testing.d.ts +2 -0
  76. package/dist/testing.d.ts.map +1 -1
  77. package/dist/types/analytics.d.ts +17 -0
  78. package/dist/types/analytics.d.ts.map +1 -1
  79. package/dist/types/crud.d.ts +15 -1
  80. package/dist/types/crud.d.ts.map +1 -1
  81. package/dist/types/plugins.d.ts +83 -0
  82. package/dist/types/plugins.d.ts.map +1 -1
  83. package/package.json +7 -3
  84. package/dist/chunk-6RBW2Y5K.js +0 -42
  85. package/dist/chunk-6RBW2Y5K.js.map +0 -1
  86. package/dist/chunk-732GZEUI.js.map +0 -1
  87. package/dist/chunk-CPMHCTX3.js +0 -77
  88. package/dist/chunk-CPMHCTX3.js.map +0 -1
  89. package/dist/chunk-JO7Z2AOE.js.map +0 -1
  90. package/dist/chunk-M27JPX3K.js.map +0 -1
  91. package/dist/integrations.d.ts +0 -159
  92. package/dist/integrations.d.ts.map +0 -1
  93. package/dist/shell-translations.pt-br-2GWRQRRP.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/integrations/runtime.ts"],"names":[],"mappings":"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","file":"chunk-ED6NBH6U.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Connector runtime — the mechanics every connector's data plane repeats.\n// ---------------------------------------------------------------------------\n// Lifted out of plugins/plugin-agenda/functions/_shared/google-calendar-runtime.js\n// (FAY-1376): that module was far more generic than its name admitted. Retry,\n// backoff, Retry-After parsing, expiry-with-lead and pagination are HTTP\n// mechanics, not Google's. Only `listAllEventChanges` was genuinely Google's,\n// and it stays there — rewritten on top of `paginate`.\n//\n// This module has NO runtime imports, on purpose. It is built to its own entry\n// (`@fayz-ai/core/connector-runtime`) so a Supabase Edge Function can load it\n// under Deno without dragging React, supabase-js or the plugin registry along.\n// Everything here is pure and transport-injected: `fetch`, the clock and the\n// sleep are parameters, so every decision below is provable under node:test.\n\n// ---------------------------------------------------------------------------\n// Resuming between runs — three things, not one\n// ---------------------------------------------------------------------------\n// A single `cursor: string` collapses a distinction that decides whether data\n// is lost. What separates these is not the field's name, it is whether ABSENCE\n// carries meaning:\n//\n// delta — the SERVER tracks what changed and reports tombstones (Google's\n// syncToken). A linked record that stops appearing was deleted.\n// window — the CLIENT computes a date range; the server has no memory (Bling).\n// A record that stops appearing merely left the window.\n// period — a closed, often sealed statement period (PlugBank). Deletion does\n// not apply.\n//\n// Flatten them and the engine cancels local records that were only out of range.\nexport type Cursor =\n | { kind: 'delta'; token: string | null; restartFrom?: string }\n | { kind: 'window'; from: string; to: string; overlapMs: number }\n | { kind: 'period'; period: string; sealed: boolean }\n\n/** `authoritative` = absence means deleted. `additive` = absence means nothing. */\nexport type Completeness = 'authoritative' | 'additive'\n\n/**\n * What a cursor kind can promise about the records it did NOT return.\n *\n * Only a delta cursor can promise anything: the server counted the changes and\n * named the tombstones. A window is computed by the client against a server\n * with no memory, so the records outside it are not absent — they were never\n * asked for.\n */\nexport function cursorCompleteness(cursor: Cursor): Completeness {\n return cursor.kind === 'delta' ? 'authoritative' : 'additive'\n}\n\n// ---------------------------------------------------------------------------\n// Reading the credential back — the other half of the sink\n// ---------------------------------------------------------------------------\n// The browser writes a tenant's credential to the platform and gets a handle\n// (see `ConnectorCredentialSink` in @fayz-ai/sdk). Something has to read it at\n// run time, and until now nothing did: `POST …/connections/redeem` shipped with\n// no caller at all, so every connector still had to find its provider key\n// somewhere else — which is how three of them ended up with a plaintext column\n// in a pool database.\n//\n// It lives HERE, in the one module already written to be loaded by a Supabase\n// Edge Function under Deno, and not in a per-plugin `_shared/` copy. FAY-1390\n// is about the copies that already exist; adding a third alongside them would\n// be adding the problem to the fix.\n//\n// Redemption takes TWO keys and neither is sufficient alone:\n//\n// the data-plane secret proves the caller is not a browser\n// the runtime-data token proves WHICH TENANT it is acting for\n//\n// The tenant therefore comes off verified claims and is not nameable in the\n// request — a caller holding the secret still cannot reach another tenant's\n// credential, which is the FAY-1375 bug class closed by construction. The\n// consequence for a caller: the runtime token arrives WITH the request being\n// served; an edge function cannot mint one, and must not try.\n\n/**\n * The header a runtime-data token rides in, on its way to a data plane.\n *\n * It lives beside `redeemConnectorCredential` because the two only make sense\n * together: the function cannot mint a token and must forward the one the\n * request carried, so the caller and the callee have to agree on a spelling.\n * Two connectors that each picked their own would fail at exactly the moment a\n * merchant clicks Sincronizar, and would look like a credential problem.\n *\n * Lower-case: an edge function reads `request.headers.get(...)`, which is\n * case-insensitive, but a CORS `Access-Control-Allow-Headers` list is compared\n * against the lower-cased name the browser sends.\n */\nexport const CONNECTOR_RUNTIME_TOKEN_HEADER = 'x-fayz-runtime-token'\n\n/** What redemption returns. `secrets` is the credential — it goes to the provider and nowhere else. */\nexport interface RedeemedConnectorCredential {\n connectionId: string\n connectorSlug: string\n tenantKey: string\n authKind: string\n /**\n * For `api-key` and `mtls`, the fields the merchant typed. For `oauth`, a\n * freshly minted access token — `refresh_token` is removed on the platform\n * side, so the field that could mint further tokens has no route out.\n */\n secrets: Record<string, string>\n /** Present for `oauth`: when the returned access token stops working. */\n expiresAt?: string\n}\n\n/** The credential could not be read. Never carries the secret, the token or the body. */\nexport class ConnectorCredentialUnavailableError extends Error {\n readonly status: number | null\n constructor(message: string, status: number | null = null) {\n super(message)\n this.name = 'ConnectorCredentialUnavailableError'\n this.status = status\n }\n}\n\nexport interface RedeemConnectorCredentialOptions {\n /** Which connector's credential, e.g. 'appmax'. */\n connectorSlug: string\n /**\n * The runtime-data token that came in with the request being served.\n *\n * It is what says which tenant this run is for, and the function has no way\n * to mint one — so it is required for anything serving a signed-in operator.\n * The one alternative is {@link tenantKey}; exactly one of the two must be set.\n */\n runtimeToken?: string\n /**\n * MACHINE mode: name the tenant instead of proving it with a token.\n *\n * For callers a session never reaches — a shopper with no account, a cron job\n * with no user. The redemption secret alone then stands for \"not a browser\",\n * so the platform bounds this path to `MACHINE_REDEEMABLE_CONNECTORS` and\n * audits every use as `machine`.\n *\n * Consequence worth stating plainly: whoever holds the secret can reach any\n * tenant of that project through this door. That is why it only ever lives in\n * an edge function's environment, never in anything a browser loads.\n */\n tenantKey?: string\n /** Defaults to `FAYZ_API_URL` from the environment. */\n apiUrl?: string\n /** Defaults to `FAYZ_PROJECT_ID`. */\n projectId?: string\n /** Defaults to `CONNECTOR_REDEMPTION_SECRET`. */\n redemptionSecret?: string\n /** Defaults to the ambient `fetch`. Injected so the call is provable under node:test. */\n fetchImpl?: (input: string, init: Record<string, unknown>) => Promise<{\n ok: boolean\n status: number\n text(): Promise<string>\n }>\n /** Defaults to `Deno.env.get`, falling back to `process.env`. */\n env?: (name: string) => string | undefined\n}\n\n/**\n * The header a data-plane call carries its runtime token in.\n *\n * A header and not the body: `Authorization` already belongs to the platform\n * the function is deployed on (Supabase checks the caller's JWT there), and a\n * token in a body is a token in whatever logs that body.\n *\n * It is declared ONCE, above, in the module that loads under Deno, because both\n * halves have to name it and only one of them can import the other's: the\n * browser side gets it through `@fayz-ai/core` and the function side through\n * `@fayz-ai/core/connector-runtime`. Written down once, or the second caller\n * spells it differently and neither is wrong at review time — which is exactly\n * what two branches did here, each adding its own `const` with the same value.\n */\n\n/** The three names `provisionConnectorRuntimeEnv` puts into a project's functions. */\nexport const CONNECTOR_REDEMPTION_ENV_KEYS = [\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n] as const\n\nfunction ambientEnv(name: string): string | undefined {\n const scope = globalThis as {\n Deno?: { env?: { get(key: string): string | undefined } }\n process?: { env?: Record<string, string | undefined> }\n }\n try {\n return scope.Deno?.env?.get(name) ?? scope.process?.env?.[name] ?? undefined\n } catch {\n // Deno throws rather than returning undefined when the permission is absent.\n return undefined\n }\n}\n\n/**\n * `FAYZ_API_URL` is provisioned as `https://host/api` — already one segment in.\n * Accept both spellings so a hand-set variable without it cannot 404 at the one\n * moment a sync needs its key.\n */\nfunction redemptionApiRoot(value: string): string {\n const trimmed = value.trim().replace(/\\/+$/, '')\n return /\\/api$/.test(trimmed) ? trimmed : `${trimmed}/api`\n}\n\n/** Whether something other than the API answered — a tunnel page, a proxy, a login. */\nfunction looksLikeHtml(body: string): boolean {\n return /^\\s*(<!doctype html|<html)/i.test(body ?? '')\n}\n\n/**\n * The platform's own sentence — and ONLY the platform's.\n *\n * A failure body is quoted here, which is a rule the rest of this module spends\n * paragraphs refusing to break: the run row this ends up on is rendered in the\n * merchant's UI, and an error message is the easiest place for a secret to end\n * up. So the quote is bounded to the envelope the Fayz API itself returns\n * (`{ status: 'error', message }`) — a provider's body, a proxy page or any\n * other shape is never repeated, only classified.\n *\n * That narrowness is what the diagnosis needed anyway: \"Runtime data token is\n * for project X, but this call asks about Y\" is our own sentence, and it is the\n * one that turned an unreadable 403 into a one-line fix.\n */\nfunction readRefusal(body: string): string {\n const text = (body ?? '').trim()\n if (!text || !text.startsWith('{')) return ''\n try {\n const parsed = JSON.parse(text) as { status?: unknown; message?: unknown }\n if (parsed.status !== 'error' || typeof parsed.message !== 'string') return ''\n return parsed.message.slice(0, 300)\n } catch {\n return ''\n }\n}\n\n/** The host a redemption was addressed to. Never the path — it carries the ids. */\nexport function redemptionHost(url: string): string {\n try {\n return new URL(url).host\n } catch {\n return 'the platform'\n }\n}\n\n/**\n * What a failed redemption should say. Exported so it can be graded — the value\n * is entirely in what it names.\n *\n * `door` is `'session'` for an operator's token, or the tenant key a machine\n * call named. Both are safe to print; neither the secret nor the token is.\n */\nexport function redemptionFailure(\n connectorSlug: string,\n status: number,\n url: string,\n door: 'session' | string,\n said = '',\n): string {\n const where = redemptionHost(url)\n const who = door === 'session' ? 'a sessão do operador' : `tenantKey ${door}`\n // An HTML body means something ANSWERED FOR the platform — a dead tunnel, a\n // proxy, a login page. Reading that as \"no credential\" sends someone auditing\n // a connection that was never asked about.\n if (looksLikeHtml(said)) {\n return (\n `The platform did not answer at ${where} — a proxy or tunnel page came back instead of the ` +\n `${connectorSlug} credential (${status}). Check FAYZ_API_URL on this project's functions: it ` +\n `points somewhere that is not serving the Fayz API right now`\n )\n }\n const detail = readRefusal(said)\n const base =\n `The platform did not return the ${connectorSlug} credential (${status}) — ${where}, ${who}` +\n (detail ? `: ${detail}` : '')\n if (status !== 404) return base\n // The one status that reads as a bug in the connector and almost never is.\n return (\n `${base}. 404 means that platform has no connected ${connectorSlug} credential for that tenant — ` +\n `most often the connection was authorised against a different environment, or the tenant proving ` +\n `this call is not the one that authorised`\n )\n}\n\n/**\n * Read a tenant's connector credential from the platform, server-side.\n *\n * Everything that could leak is deliberately absent from what this can throw:\n * the response body is never quoted, because the only route that returns a\n * credential is this one and an error message is the easiest place for it to\n * end up.\n */\nexport async function redeemConnectorCredential(\n options: RedeemConnectorCredentialOptions,\n): Promise<RedeemedConnectorCredential> {\n const env = options.env ?? ambientEnv\n const connectorSlug = (options.connectorSlug ?? '').trim()\n const runtimeToken = (options.runtimeToken ?? '').trim()\n const apiUrl = (options.apiUrl ?? env('FAYZ_API_URL') ?? '').trim()\n const projectId = (options.projectId ?? env('FAYZ_PROJECT_ID') ?? '').trim()\n const secret = (options.redemptionSecret ?? env('CONNECTOR_REDEMPTION_SECRET') ?? '').trim()\n\n if (!connectorSlug) throw new ConnectorCredentialUnavailableError('connectorSlug is required to redeem a credential')\n // Unconfigured is a refusal, in every environment. A data plane that answers\n // without its secret is one that was never wired, reporting success.\n if (!apiUrl || !projectId || !secret) {\n throw new ConnectorCredentialUnavailableError(\n `Connector redemption is not configured — expected ${CONNECTOR_REDEMPTION_ENV_KEYS.join(', ')} in the environment`,\n )\n }\n const tenantKey = (options.tenantKey ?? '').trim()\n if (!runtimeToken && !tenantKey) {\n throw new ConnectorCredentialUnavailableError(\n 'No runtime token and no tenantKey on this call, so there is no tenant to redeem a credential for',\n )\n }\n\n const fetchImpl =\n options.fetchImpl ??\n ((input: string, init: Record<string, unknown>) =>\n (globalThis as { fetch: (i: string, n: unknown) => Promise<any> }).fetch(input, init))\n\n const url =\n `${redemptionApiRoot(apiUrl)}/public/projects/${encodeURIComponent(projectId)}` +\n `/connectors/connections/redeem?connectorSlug=${encodeURIComponent(connectorSlug)}`\n\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: {\n // Two keys: the secret says \"not a browser\", the token says \"this tenant\".\n 'x-fayz-connector-secret': secret,\n ...(runtimeToken ? { Authorization: `Bearer ${runtimeToken}` } : {}),\n ...(runtimeToken ? {} : { 'Content-Type': 'application/json' }),\n },\n // Machine mode: no Authorization, tenant named in the body. The platform\n // bounds it to MACHINE_REDEEMABLE_CONNECTORS and audits every use.\n ...(runtimeToken ? {} : { body: JSON.stringify({ tenantKey }) }),\n })\n\n if (!response.ok) {\n // WHICH platform, through WHICH door, and what it actually said. Quoting the\n // body is safe by construction here: the credential only ever comes back on\n // a 200, so a failure body is an error and never a secret.\n const said = await response.text().catch(() => '')\n throw new ConnectorCredentialUnavailableError(\n redemptionFailure(connectorSlug, response.status, url, runtimeToken ? 'session' : tenantKey, said),\n response.status,\n )\n }\n\n let parsed: Partial<RedeemedConnectorCredential> | undefined\n try {\n parsed = JSON.parse(await response.text()) as Partial<RedeemedConnectorCredential>\n } catch {\n throw new ConnectorCredentialUnavailableError(\n `The platform answered ${response.status} with something that is not a credential`,\n response.status,\n )\n }\n\n const secrets = parsed?.secrets\n if (!secrets || typeof secrets !== 'object' || Object.keys(secrets).length === 0) {\n throw new ConnectorCredentialUnavailableError(\n `The ${connectorSlug} connection carries no credential`,\n response.status,\n )\n }\n\n return {\n connectionId: String(parsed?.connectionId ?? ''),\n connectorSlug: String(parsed?.connectorSlug ?? connectorSlug),\n tenantKey: String(parsed?.tenantKey ?? ''),\n authKind: String(parsed?.authKind ?? 'api-key'),\n secrets: secrets as Record<string, string>,\n ...(typeof parsed?.expiresAt === 'string' ? { expiresAt: parsed.expiresAt } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * A provider answered with a status the caller could not use. `body` is carried\n * because some providers put the real reason there and only there — Google\n * signals quota exhaustion as 403 with `rateLimitExceeded` in the payload, and a\n * classifier that only sees the status cannot tell it from a permission denial.\n */\nexport class ConnectorHttpError extends Error {\n readonly status: number\n readonly retryAfterMs: number | null\n readonly body: string | null\n readonly details: unknown\n\n constructor(\n status: number,\n message: string,\n options: { retryAfterMs?: number | null; body?: string | null; details?: unknown } = {},\n ) {\n super(message)\n this.name = 'ConnectorHttpError'\n this.status = status\n this.retryAfterMs = options.retryAfterMs ?? null\n this.body = options.body ?? null\n this.details = options.details ?? null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Retry-After and backoff\n// ---------------------------------------------------------------------------\n\n/**\n * RFC 7231 `Retry-After`: either delta-seconds or an HTTP-date. Returns\n * milliseconds, or null when the header is absent or unparseable.\n */\nexport function parseRetryAfter(value: string | null | undefined, now: number = Date.now()): number | null {\n if (!value) return null\n const seconds = Number(value)\n if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1_000)\n const at = Date.parse(value)\n return Number.isFinite(at) ? Math.max(0, at - now) : null\n}\n\nexport interface BackoffOptions {\n baseDelayMs?: number\n maxDelayMs?: number\n random?: () => number\n}\n\n/** Exponential backoff with ±25% jitter, capped. Attempt is 1-based. */\nexport function backoffDelay(attempt: number, options: BackoffOptions = {}): number {\n const baseDelayMs = options.baseDelayMs ?? 250\n const maxDelayMs = options.maxDelayMs ?? 5_000\n const random = options.random ?? Math.random\n const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** Math.max(0, attempt - 1))\n return Math.round(exponential * (0.75 + random() * 0.5))\n}\n\n// ---------------------------------------------------------------------------\n// The verdict — retry is not a boolean\n// ---------------------------------------------------------------------------\n// `isRetryable(status)` cannot express that 401 means retry AFTER RE-AUTHENTICATING,\n// 429 means retry AFTER SLEEPING, and Google's 410 means retry AFTER DISCARDING\n// THE CURSOR. Three different recoveries, which is why they lived in three\n// unrelated places and why the 403 case was simply missing.\n//\n// `classify` owns `delayMs` as well as the action. That is what lets one\n// connector honour `Retry-After` while another reads `X-RateLimit-Reset`,\n// without either of them re-implementing the loop.\n\nexport type Verdict =\n /** Hand the response back to the caller — success, or a client error it must see. */\n | { action: 'return' }\n /** Wait, then repeat the same request. */\n | { action: 'retry'; delayMs: number }\n /** The credential is stale: renew it, then repeat the request once. */\n | { action: 'reauth' }\n /** The cursor is gone: discard it and restart the run from scratch. */\n | { action: 'reset-cursor' }\n /** Nothing left to try. */\n | { action: 'fail'; reason: string }\n\n/** What `classify` is shown about one attempt. */\nexport interface AttemptOutcome {\n /** null when the transport itself failed and there is no response. */\n status: number | null\n headers?: { get(name: string): string | null } | null\n /** Response body, when the caller peeked at it. See {@link ClassifyOptions.needsBody}. */\n body?: string | null\n error?: unknown\n /** 1-based. */\n attempt: number\n maxAttempts: number\n}\n\nexport interface ClassifyOptions extends BackoffOptions {\n now?: () => number\n /** Statuses to retry beyond the defaults (408, 425, 429, 5xx). */\n retryableStatuses?: number[]\n /** Statuses meaning \"the cursor is stale, start over\". Google: 410. */\n resetCursorStatuses?: number[]\n /** Statuses meaning \"renew the credential and repeat\". Default: 401. */\n reauthStatuses?: number[]\n /** Where this provider states its wait. Default: `Retry-After` (RFC 7231). */\n readRetryAfter?(headers: { get(name: string): string | null } | null | undefined, now: number): number | null\n /**\n * Whether a 403 is quota exhaustion rather than a permission denial.\n *\n * This is the defect the extraction fixes. `isRetryableGoogleStatus` returned\n * false for 403 — and 403 with `rateLimitExceeded` in the body is exactly how\n * the Google Calendar API reports the quota being blown, the single most\n * characteristic failure of the provider it was written for.\n */\n isQuotaBody?(body: string | null | undefined, status: number): boolean\n /** Statuses whose body `classify` needs. Default: [403], for the case above. */\n needsBody?: number[]\n}\n\nconst DEFAULT_RETRYABLE = new Set([408, 425, 429])\n\n/** Statuses whose body must be read before `classify` can decide. */\nexport function statusNeedsBody(status: number | null, options: ClassifyOptions = {}): boolean {\n if (status === null) return false\n return (options.needsBody ?? [403]).includes(status)\n}\n\n/** The one retry policy. See {@link Verdict}. */\nexport function classify(outcome: AttemptOutcome, options: ClassifyOptions = {}): Verdict {\n const { status, attempt, maxAttempts } = outcome\n const exhausted = attempt >= maxAttempts\n const now = options.now?.() ?? Date.now()\n const readRetryAfter = options.readRetryAfter ?? ((h, at) => parseRetryAfter(h?.get('retry-after'), at))\n const wait = () => readRetryAfter(outcome.headers, now) ?? backoffDelay(attempt, options)\n\n // No response at all. Retrying is the only recovery, and once the attempts are\n // spent there is nothing to hand back — unlike a status, which the caller can\n // still read.\n if (status === null) {\n if (exhausted) return { action: 'fail', reason: String((outcome.error as Error)?.message ?? outcome.error ?? 'request failed') }\n return { action: 'retry', delayMs: backoffDelay(attempt, options) }\n }\n\n if (status < 400) return { action: 'return' }\n\n if ((options.reauthStatuses ?? [401]).includes(status)) {\n return exhausted ? { action: 'return' } : { action: 'reauth' }\n }\n\n if ((options.resetCursorStatuses ?? []).includes(status)) return { action: 'reset-cursor' }\n\n const retryable =\n DEFAULT_RETRYABLE.has(status) ||\n (status >= 500 && status <= 599) ||\n (options.retryableStatuses ?? []).includes(status) ||\n (options.isQuotaBody?.(outcome.body, status) ?? false)\n\n if (!retryable) return { action: 'return' }\n // Out of attempts on a retryable status: hand the response back rather than\n // inventing an error. The caller owns what a spent 429 means for its run.\n return exhausted ? { action: 'return' } : { action: 'retry', delayMs: wait() }\n}\n\n// ---------------------------------------------------------------------------\n// fetchWithRetry\n// ---------------------------------------------------------------------------\n\n/** The slice of `Response` this module touches. Keeps the module runtime-free. */\nexport interface MinimalResponse {\n status: number\n headers: { get(name: string): string | null }\n clone?(): { text(): Promise<string> }\n text?(): Promise<string>\n}\n\nexport interface FetchWithRetryOptions extends ClassifyOptions {\n maxAttempts?: number\n sleep?: (ms: number) => Promise<void>\n onRetry?(info: { attempt: number; delayMs: number; status: number | null; error: unknown }): void\n /**\n * Renew the credential and return the init for the repeat. Called at most\n * once per request — a refresh loop against a provider that rate-limits its\n * own token endpoint is how an IP gets blocked for an hour.\n */\n onReauth?(): Promise<Record<string, unknown> | void> | Record<string, unknown> | void\n}\n\ntype FetchLike = (input: unknown, init?: unknown) => Promise<MinimalResponse>\n\nasync function peekBody(response: MinimalResponse): Promise<string | null> {\n try {\n const copy = response.clone?.()\n if (copy) return await copy.text()\n return null\n } catch {\n return null\n }\n}\n\n/**\n * One request, retried according to {@link classify}. Returns the last response;\n * it never converts a status into an exception, because which 4xx is fatal is\n * the caller's domain question, not the transport's.\n */\nexport async function fetchWithRetry(\n fetchImpl: FetchLike,\n input: unknown,\n init: unknown,\n options: FetchWithRetryOptions = {},\n): Promise<MinimalResponse> {\n const maxAttempts = options.maxAttempts ?? 4\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))\n let currentInit = init\n let reauthed = false\n let lastError: unknown\n let lastResponse: MinimalResponse | undefined\n\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n let response: MinimalResponse | undefined\n let error: unknown\n try {\n response = await fetchImpl(input, currentInit)\n lastResponse = response\n } catch (caught) {\n error = caught\n lastError = caught\n }\n\n const status = response ? response.status : null\n const body = response && statusNeedsBody(status, options) ? await peekBody(response) : null\n const verdict = classify(\n { status, headers: response?.headers ?? null, body, error, attempt, maxAttempts },\n options,\n )\n\n if (verdict.action === 'return' || verdict.action === 'reset-cursor') {\n if (response) return response\n throw lastError ?? new Error('connector request failed')\n }\n\n if (verdict.action === 'fail') {\n throw lastError ?? new ConnectorHttpError(status ?? 0, verdict.reason)\n }\n\n if (verdict.action === 'reauth') {\n // \"Retry after re-authenticating\" is not available to a caller that gave\n // no way to re-authenticate; repeating the request would only spend a\n // second call to be told 401 again.\n if (reauthed || !options.onReauth) return response as MinimalResponse\n reauthed = true\n const next = await options.onReauth()\n if (next) currentInit = next\n options.onRetry?.({ attempt, delayMs: 0, status, error: error ?? null })\n continue\n }\n\n options.onRetry?.({ attempt, delayMs: verdict.delayMs, status, error: error ?? null })\n await sleep(verdict.delayMs)\n }\n\n if (lastResponse) return lastResponse\n throw lastError ?? new Error('connector request failed')\n}\n\n// ---------------------------------------------------------------------------\n// Expiry\n// ---------------------------------------------------------------------------\n\n/**\n * Whether something dated expires within the lead time — a watch channel, an\n * OAuth access token, a refresh token that rotates. Renewing with slack is a\n * requirement and not a nicety for a provider like Bling, whose token endpoint\n * blocks the IP for an hour after 20 calls in a minute: the only safe refresh\n * is the one made days early, never the one made in a loop at the deadline.\n */\nexport function isExpiring(\n expiresAt: string | number | Date | null | undefined,\n options: { now?: number; leadMs?: number } = {},\n): boolean {\n if (!expiresAt) return true\n const expiresAtMs = expiresAt instanceof Date ? expiresAt.getTime() : new Date(expiresAt).getTime()\n if (!Number.isFinite(expiresAtMs)) return true\n const now = options.now ?? Date.now()\n const leadMs = options.leadMs ?? 24 * 60 * 60_000\n return expiresAtMs <= now + leadMs\n}\n\n// ---------------------------------------------------------------------------\n// Pagination — axis A, genuinely one abstraction\n// ---------------------------------------------------------------------------\n// \"This response is a page.\" Google `pageToken`, Bling `pagina=N`, Stripe\n// `starting_after`, GitHub `Link`. A de-para over one hook.\n//\n// The failure this replaces is not hypothetical: FAY-1377 was one page silently\n// dropped, and the pull had been reporting success the whole time.\n\nexport interface PageResult<TItem, TRequest> {\n items?: TItem[]\n /** The request for the next page, or null/undefined when this was the last. */\n next?: TRequest | null\n}\n\nexport interface PaginateOptions<TItem, TRequest> {\n /** The request for page one. */\n first: TRequest\n fetchPage(request: TRequest): Promise<PageResult<TItem, TRequest>>\n /** Hard stop, so a provider that always returns a next token cannot spin. */\n maxPages?: number\n /**\n * Where to restart when a page fails with a `reset-cursor` verdict. Absent =\n * the reset is not available and the error propagates.\n */\n resetTo?(request: TRequest): TRequest | null\n /** Classify a thrown page error. Defaults to reading `error.status`. */\n classifyError?(error: unknown): Verdict\n onCursorReset?(): void\n}\n\nexport interface PaginateResult<TItem> {\n items: TItem[]\n pages: number\n /** Whether the run restarted after the provider rejected the cursor. */\n cursorReset: boolean\n}\n\n/**\n * Walk every page. The contract is that `items` is complete when this returns —\n * a caller that stops at the first page is the FAY-1377 bug, and no connector\n * should have to re-derive the loop to avoid it.\n */\nexport async function paginate<TItem, TRequest>(\n options: PaginateOptions<TItem, TRequest>,\n): Promise<PaginateResult<TItem>> {\n const maxPages = options.maxPages ?? 100\n const classifyError =\n options.classifyError ??\n ((error: unknown): Verdict => {\n const status = (error as { status?: number })?.status\n return status === 410 ? { action: 'reset-cursor' } : { action: 'fail', reason: String(status ?? error) }\n })\n\n let request: TRequest | null = options.first\n let items: TItem[] = []\n let pages = 0\n let cursorReset = false\n\n while (request !== null && request !== undefined) {\n if (pages >= maxPages) throw new Error(`connector pagination limit exceeded (${maxPages} pages)`)\n let page: PageResult<TItem, TRequest>\n try {\n page = await options.fetchPage(request)\n } catch (error) {\n // A stale cursor is not a failure, it is an instruction: drop it and\n // re-list. Once only, so a broken stream cannot spin.\n const verdict = classifyError(error)\n const restart: TRequest | null | undefined =\n verdict.action === 'reset-cursor' && !cursorReset ? options.resetTo?.(request) : null\n if (!restart) throw error\n request = restart\n cursorReset = true\n items = []\n pages = 0\n options.onCursorReset?.()\n continue\n }\n\n pages += 1\n if (page.items?.length) items.push(...page.items)\n request = page.next ?? null\n }\n\n return { items, pages, cursorReset }\n}\n\n// ---------------------------------------------------------------------------\n// The call budget — honoured by the scheduler, not by a sleep in each connector\n// ---------------------------------------------------------------------------\n// Bling allows 3 req/s and 120k/day PER ACCOUNT, counted across every module.\n// That is not an implementation detail; it is what forbids one request per\n// product and therefore what forces `sample` and `backfill` apart.\n//\n// A `sleep(350)` inside the fetch helper cannot express a daily ceiling and\n// cannot be shared between two streams pulling at once. A token bucket the\n// caller must pass through can.\n\nexport interface RateLimitBudget {\n /** Sustained requests per second the provider allows for one account. */\n perSecond?: number\n /** Requests per rolling day for one account, summed across every module. */\n perDay?: number\n}\n\nexport interface RateLimiterOptions {\n now?: () => number\n sleep?: (ms: number) => Promise<void>\n /** Calls already spent today, read from the run history on resume. */\n spentToday?: number\n}\n\nexport class RateLimitExceededError extends Error {\n readonly kind = 'daily-budget'\n constructor(perDay: number) {\n super(`daily call budget of ${perDay} exhausted; the run must resume tomorrow from its cursor`)\n this.name = 'RateLimitExceededError'\n }\n}\n\nexport interface RateLimiter {\n /** Wait until one call fits inside the budget. Throws when the day is spent. */\n take(): Promise<void>\n /** Calls taken since the limiter was created, plus what it resumed with. */\n readonly spent: number\n /** Whether another call fits today at all. */\n readonly exhausted: boolean\n}\n\n/**\n * A token bucket over the declared budget. The clock is injected, so \"3000 SKUs\n * enter without blowing 3 req/s\" is a proof and not an impression.\n */\nexport function createRateLimiter(budget: RateLimitBudget, options: RateLimiterOptions = {}): RateLimiter {\n const now = options.now ?? Date.now\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)))\n const perSecond = budget.perSecond && budget.perSecond > 0 ? budget.perSecond : null\n const perDay = budget.perDay && budget.perDay > 0 ? budget.perDay : null\n // Rounded UP to a whole millisecond. 1000/3 = 333.33 spaces three calls in\n // 999.99ms, which is four inside some one-second window the provider is\n // counting — the budget has to be honoured on the provider's side of the\n // rounding, not on ours.\n const minGapMs = perSecond ? Math.ceil(1_000 / perSecond) : 0\n\n let spent = options.spentToday ?? 0\n // Last permitted departure time. Spacing departures rather than counting\n // arrivals is what makes N concurrent streams share one budget correctly.\n let nextAt = 0\n\n return {\n get spent() { return spent },\n get exhausted() { return perDay !== null && spent >= perDay },\n async take() {\n if (perDay !== null && spent >= perDay) throw new RateLimitExceededError(perDay)\n const at = now()\n const departAt = Math.max(at, nextAt)\n nextAt = departAt + minGapMs\n spent += 1\n const waitMs = departAt - at\n if (waitMs > 0) await sleep(waitMs)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Window cursors — the client computes the range, the server has no memory\n// ---------------------------------------------------------------------------\n\n/**\n * Split a range into slices no longer than `maxSpanMs`.\n *\n * Bling answers a date filter wider than one year with a 400, so the window is\n * always sliced. Generic because the constraint is not Bling's alone — a\n * provider that caps a range is the normal case, and a connector that discovers\n * the cap in production discovers it as a failed backfill.\n */\nexport function sliceWindow(from: string, to: string, maxSpanMs: number): Array<{ from: string; to: string }> {\n const start = Date.parse(from)\n const end = Date.parse(to)\n if (!Number.isFinite(start) || !Number.isFinite(end)) throw new Error(`sliceWindow: unparseable range ${from}..${to}`)\n if (end <= start) return [{ from, to }]\n if (!Number.isFinite(maxSpanMs) || maxSpanMs <= 0) return [{ from, to }]\n\n const slices: Array<{ from: string; to: string }> = []\n let cursor = start\n while (cursor < end) {\n const next = Math.min(end, cursor + maxSpanMs)\n slices.push({ from: new Date(cursor).toISOString(), to: new Date(next).toISOString() })\n cursor = next\n }\n return slices\n}\n\n/**\n * The next window to ask for, overlapping the one just finished.\n *\n * The overlap is not belt-and-braces: a provider that stamps `updatedAt` at\n * second precision, or writes it a moment before the row is visible, drops\n * whatever landed on the boundary. Re-reading it costs an idempotent upsert.\n */\nexport function advanceWindow(\n cursor: Extract<Cursor, { kind: 'window' }>,\n now: number = Date.now(),\n): Extract<Cursor, { kind: 'window' }> {\n const previousTo = Date.parse(cursor.to)\n const base = Number.isFinite(previousTo) ? previousTo : now\n const from = Math.max(0, base - Math.max(0, cursor.overlapMs))\n return { kind: 'window', from: new Date(from).toISOString(), to: new Date(now).toISOString(), overlapMs: cursor.overlapMs }\n}\n\n/** Serialise a cursor for `plg_sync_runs.cursor`, which is one text column. */\nexport function serializeCursor(cursor: Cursor | null | undefined): string | null {\n if (!cursor) return null\n if (cursor.kind === 'delta') return cursor.token\n if (cursor.kind === 'window') return `${cursor.from}..${cursor.to}`\n return cursor.period\n}\n\n// ---------------------------------------------------------------------------\n// Reconciliation — the whole reason `completeness` exists\n// ---------------------------------------------------------------------------\n\nexport interface MirrorRecord {\n /** The local row's id. */\n id: string\n /** The provider's id it is linked to. */\n externalId: string\n}\n\nexport interface ReconcileInput {\n completeness: Completeness\n /** Provider ids this run actually saw. */\n observed: Iterable<string>\n /** Local rows currently linked to this stream. */\n linked: Iterable<MirrorRecord>\n /** Provider ids the provider explicitly reported as gone. Always honoured. */\n tombstones?: Iterable<string>\n}\n\nexport interface ReconcileResult {\n /** Local ids to cancel. */\n cancel: string[]\n /** Local ids absent from this run that must NOT be touched. */\n keptOutOfRange: string[]\n reason: 'absence-is-deletion' | 'absence-means-nothing'\n}\n\n/**\n * Decide what a run's silence means.\n *\n * Under `authoritative` the provider counted the changes and named the\n * tombstones, so a linked row that did not appear was deleted. Under `additive`\n * the run only ever asked about part of the world — a window, a statement\n * period — and a row that did not appear was simply not asked about. Cancelling\n * it is silent data loss, and it is the failure this whole cursor design exists\n * to make impossible.\n *\n * Explicit tombstones are obeyed under both: a provider that bothers to say\n * \"this one is gone\" is authoritative about that one record regardless of how\n * the run was scoped.\n */\nexport function reconcileMirror(input: ReconcileInput): ReconcileResult {\n const observed = new Set(input.observed)\n const tombstones = new Set(input.tombstones ?? [])\n const authoritative = input.completeness === 'authoritative'\n\n const cancel: string[] = []\n const keptOutOfRange: string[] = []\n\n for (const record of input.linked) {\n if (tombstones.has(record.externalId)) { cancel.push(record.id); continue }\n if (observed.has(record.externalId)) continue\n if (authoritative) cancel.push(record.id)\n else keptOutOfRange.push(record.id)\n }\n\n return { cancel, keptOutOfRange, reason: authoritative ? 'absence-is-deletion' : 'absence-means-nothing' }\n}\n"]}
@@ -0,0 +1,429 @@
1
+ // src/registry/registry-class.ts
2
+ var Registry = class {
3
+ constructor(kind) {
4
+ this.kind = kind;
5
+ this.entries = /* @__PURE__ */ new Map();
6
+ }
7
+ register(id, value, meta = {}) {
8
+ const title = meta.title ?? meta.label;
9
+ this.entries.set(id, { id, value, meta: title === void 0 ? meta : { ...meta, title, label: title } });
10
+ }
11
+ get(id) {
12
+ return this.entries.get(id)?.value;
13
+ }
14
+ getEntry(id) {
15
+ return this.entries.get(id);
16
+ }
17
+ has(id) {
18
+ return this.entries.has(id);
19
+ }
20
+ list() {
21
+ return Array.from(this.entries.values());
22
+ }
23
+ ids() {
24
+ return Array.from(this.entries.keys());
25
+ }
26
+ /**
27
+ * Every entry reduced to the uniform meta envelope, sorted by id.
28
+ *
29
+ * Sorted for the same reason as `describeBlocks`: this feeds prompts,
30
+ * documents and generated catalogs, and registration order is an
31
+ * implementation detail that would make every diff noisy.
32
+ */
33
+ describe() {
34
+ return this.list().map(({ id, meta }) => ({
35
+ id,
36
+ kind: this.kind,
37
+ title: meta.title ?? meta.label,
38
+ description: meta.description,
39
+ schema: meta.propsSchema,
40
+ stability: meta.stability ?? "stable",
41
+ since: meta.since,
42
+ source: meta.source,
43
+ pluginId: meta.pluginId
44
+ })).sort((a, b) => a.id.localeCompare(b.id));
45
+ }
46
+ clear() {
47
+ this.entries.clear();
48
+ }
49
+ };
50
+ function isCustomId(id) {
51
+ return id.startsWith("custom:");
52
+ }
53
+
54
+ // src/registry/entities.ts
55
+ var ENTITY_FIELD_TYPES = [
56
+ "text",
57
+ "richtext",
58
+ "number",
59
+ "boolean",
60
+ "json",
61
+ "image",
62
+ "url",
63
+ "ref",
64
+ "point",
65
+ "date",
66
+ "datetime",
67
+ "select"
68
+ ];
69
+ var GENERIC_ENTITY_TABLE = "plg_entity_records";
70
+ var ENTITY_KEY_RE = /^[a-z][a-z0-9_]*$/;
71
+ var FIELD_NAME_RE = /^[a-z][a-z0-9_]*$/;
72
+ var RESERVED_ENTITY_FIELDS = ["id", "slug", "entity", "tenant_id", "created_at", "updated_at"];
73
+ var entityDeclarationRegistry = new Registry("entity");
74
+ function fail(message, valid) {
75
+ throw new Error(valid?.length ? `${message} Valid: ${valid.join(", ")}.` : message);
76
+ }
77
+ function normalizeDeclaration(key, input) {
78
+ if (!ENTITY_KEY_RE.test(key)) {
79
+ fail(`defineEntity: "${key}" is not a valid entity key \u2014 keys are snake_case (${ENTITY_KEY_RE}).`);
80
+ }
81
+ const fieldNames = Object.keys(input.fields ?? {});
82
+ if (fieldNames.length === 0) {
83
+ fail(`defineEntity("${key}"): an entity declares at least one field.`);
84
+ }
85
+ for (const [name, field] of Object.entries(input.fields)) {
86
+ if (!FIELD_NAME_RE.test(name)) {
87
+ fail(`defineEntity("${key}"): "${name}" is not a valid field name \u2014 fields are snake_case (${FIELD_NAME_RE}).`);
88
+ }
89
+ if (RESERVED_ENTITY_FIELDS.includes(name)) {
90
+ fail(`defineEntity("${key}"): "${name}" is a structural record column and cannot be a declared field.`, RESERVED_ENTITY_FIELDS);
91
+ }
92
+ if (!ENTITY_FIELD_TYPES.includes(field?.type)) {
93
+ fail(`defineEntity("${key}"): field "${name}" has unknown type "${String(field?.type)}".`, ENTITY_FIELD_TYPES);
94
+ }
95
+ if (field.type === "ref" && !field.entity) {
96
+ fail(`defineEntity("${key}"): ref field "${name}" must name the entity it points to ({ type: 'ref', entity: '<key>' }).`);
97
+ }
98
+ }
99
+ const knows = (name, where) => {
100
+ if (!fieldNames.includes(name)) {
101
+ fail(`defineEntity("${key}"): ${where} references undeclared field "${name}".`, fieldNames);
102
+ }
103
+ };
104
+ for (const index of input.indexes ?? []) {
105
+ for (const name of index) knows(name, "an index");
106
+ }
107
+ if (input.titleField) knows(input.titleField, "titleField");
108
+ for (const name of input.displayFields ?? []) knows(name, "displayFields");
109
+ const titleField = input.titleField ?? fieldNames.find((name) => input.fields[name].type === "text");
110
+ const displayFields = input.displayFields ?? fieldNames.filter((name) => input.fields[name].type !== "json");
111
+ return {
112
+ key,
113
+ title: input.title ?? key,
114
+ description: input.description,
115
+ fields: input.fields,
116
+ scope: input.scope ?? "tenant",
117
+ access: { read: input.access?.read ?? "staff", write: "staff" },
118
+ indexes: input.indexes ?? [],
119
+ titleField,
120
+ displayFields,
121
+ storage: { mode: "generic", table: GENERIC_ENTITY_TABLE }
122
+ };
123
+ }
124
+ function defineEntity(key, input, meta = {}) {
125
+ const declaration = normalizeDeclaration(key, input);
126
+ entityDeclarationRegistry.register(key, declaration, {
127
+ title: declaration.title,
128
+ description: declaration.description,
129
+ propsSchema: entityRecordSchema(declaration),
130
+ source: meta.source ?? "app",
131
+ pluginId: meta.pluginId,
132
+ since: meta.since
133
+ });
134
+ return declaration;
135
+ }
136
+ function formatValidEntities() {
137
+ const ids = entityDeclarationRegistry.ids().sort();
138
+ return ids.length ? `Declared entities: ${ids.join(", ")}` : "No entities are declared \u2014 call defineEntity(key, declaration) first.";
139
+ }
140
+ function resolveEntityDeclaration(key) {
141
+ const declaration = entityDeclarationRegistry.get(key);
142
+ if (!declaration) throw new Error(`Unknown entity "${key}". ${formatValidEntities()}`);
143
+ return declaration;
144
+ }
145
+ function hasEntityDeclaration(key) {
146
+ return entityDeclarationRegistry.has(key);
147
+ }
148
+ function listEntityDeclarations() {
149
+ return entityDeclarationRegistry.ids().sort();
150
+ }
151
+ function clearEntityDeclarations() {
152
+ entityDeclarationRegistry.clear();
153
+ }
154
+ var FIELD_FORMAT = {
155
+ image: "uri",
156
+ url: "uri",
157
+ date: "date",
158
+ datetime: "date-time"
159
+ };
160
+ function applyValidation(node, field) {
161
+ const v = field.validation;
162
+ if (!v) return;
163
+ if (field.type === "number") {
164
+ if (v.min !== void 0) node.minimum = v.min;
165
+ if (v.max !== void 0) node.maximum = v.max;
166
+ return;
167
+ }
168
+ if (v.minLength !== void 0) node.minLength = v.minLength;
169
+ if (v.maxLength !== void 0) node.maxLength = v.maxLength;
170
+ if (v.pattern !== void 0) node.pattern = v.pattern;
171
+ }
172
+ function fieldToJsonSchema(name, field) {
173
+ const node = {
174
+ title: field.label ?? name,
175
+ // The control survives the compile, exactly as in block settings: a `ref`
176
+ // is a string in JSON Schema terms, but an editor rendering it as a text
177
+ // input (instead of a record picker) is a bug.
178
+ "x-fayz-control": field.type
179
+ };
180
+ if (field.help) node.description = field.help;
181
+ if (field.default !== void 0) node.default = field.default;
182
+ const format = FIELD_FORMAT[field.type];
183
+ if (format) node.format = format;
184
+ switch (field.type) {
185
+ case "number":
186
+ node.type = "number";
187
+ applyValidation(node, field);
188
+ return node;
189
+ case "boolean":
190
+ node.type = "boolean";
191
+ return node;
192
+ case "json":
193
+ return node;
194
+ case "point":
195
+ node.type = "object";
196
+ node.properties = { lat: { type: "number" }, lng: { type: "number" } };
197
+ node.required = ["lat", "lng"];
198
+ node.additionalProperties = false;
199
+ return node;
200
+ case "ref":
201
+ node.type = "string";
202
+ node["x-fayz-ref-entity"] = field.entity;
203
+ return node;
204
+ case "select":
205
+ node.type = "string";
206
+ node.enum = (field.options ?? []).map((option) => option.value);
207
+ return node;
208
+ default:
209
+ node.type = "string";
210
+ applyValidation(node, field);
211
+ return node;
212
+ }
213
+ }
214
+ function entityRecordSchema(declaration) {
215
+ const properties = {};
216
+ const required = [];
217
+ for (const [name, field] of Object.entries(declaration.fields)) {
218
+ properties[name] = fieldToJsonSchema(name, field);
219
+ if (field.required) required.push(name);
220
+ }
221
+ const schema = {
222
+ type: "object",
223
+ title: declaration.title,
224
+ properties,
225
+ additionalProperties: false
226
+ };
227
+ if (declaration.description) schema.description = declaration.description;
228
+ if (required.length > 0) schema.required = required;
229
+ return schema;
230
+ }
231
+ function typeOf(value) {
232
+ return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
233
+ }
234
+ function checkBounds(issues, name, field, value) {
235
+ const v = field.validation;
236
+ if (!v) return;
237
+ const invalid = (message) => issues.push({ path: name, code: "invalid-value", message });
238
+ if (typeof value === "number") {
239
+ if (v.min !== void 0 && value < v.min) invalid(`"${name}" is below the minimum of ${v.min}`);
240
+ if (v.max !== void 0 && value > v.max) invalid(`"${name}" is above the maximum of ${v.max}`);
241
+ return;
242
+ }
243
+ if (v.minLength !== void 0 && value.length < v.minLength) {
244
+ invalid(`"${name}" is shorter than ${v.minLength} character(s)`);
245
+ }
246
+ if (v.maxLength !== void 0 && value.length > v.maxLength) {
247
+ invalid(`"${name}" is longer than ${v.maxLength} character(s)`);
248
+ }
249
+ if (v.pattern !== void 0) {
250
+ let re = null;
251
+ try {
252
+ re = new RegExp(v.pattern);
253
+ } catch {
254
+ invalid(`"${name}" has an unreadable pattern in its definition`);
255
+ }
256
+ if (re && !re.test(value)) invalid(`"${name}" does not match the expected format`);
257
+ }
258
+ }
259
+ function validateEntityRecordData(declaration, data) {
260
+ const issues = [];
261
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
262
+ return [{ path: "", code: "invalid-value", message: `record data is an object, got ${typeOf(data)}` }];
263
+ }
264
+ const record = data;
265
+ const fieldNames = Object.keys(declaration.fields);
266
+ for (const [name, value] of Object.entries(record)) {
267
+ const field = declaration.fields[name];
268
+ if (!field) {
269
+ issues.push({
270
+ path: name,
271
+ code: "unknown-field",
272
+ message: `field "${name}" is not declared by this entity`,
273
+ valid: fieldNames
274
+ });
275
+ continue;
276
+ }
277
+ if (value === null || value === void 0) continue;
278
+ switch (field.type) {
279
+ case "number":
280
+ if (typeof value !== "number" || !Number.isFinite(value)) {
281
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a number, got ${typeOf(value)}` });
282
+ } else {
283
+ checkBounds(issues, name, field, value);
284
+ }
285
+ break;
286
+ case "boolean":
287
+ if (typeof value !== "boolean") {
288
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a boolean, got ${typeOf(value)}` });
289
+ }
290
+ break;
291
+ case "json":
292
+ break;
293
+ // any JSON value
294
+ case "point": {
295
+ const point = value;
296
+ if (typeOf(value) !== "object" || typeof point.lat !== "number" || typeof point.lng !== "number" || Object.keys(point).length !== 2) {
297
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects {lat: number, lng: number}` });
298
+ }
299
+ break;
300
+ }
301
+ case "select": {
302
+ const allowed = (field.options ?? []).map((option) => option.value);
303
+ if (typeof value !== "string") {
304
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a string, got ${typeOf(value)}` });
305
+ } else if (!allowed.includes(value)) {
306
+ issues.push({
307
+ path: name,
308
+ code: "invalid-value",
309
+ message: `"${name}" does not accept "${value}"`,
310
+ valid: allowed
311
+ });
312
+ }
313
+ break;
314
+ }
315
+ default:
316
+ if (typeof value !== "string") {
317
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a string, got ${typeOf(value)}` });
318
+ } else {
319
+ checkBounds(issues, name, field, value);
320
+ }
321
+ }
322
+ }
323
+ for (const [name, field] of Object.entries(declaration.fields)) {
324
+ if (field.required && (record[name] === void 0 || record[name] === null || record[name] === "")) {
325
+ issues.push({ path: name, code: "missing-field", message: `required field "${name}" is missing` });
326
+ }
327
+ }
328
+ return issues;
329
+ }
330
+ function describeEntities() {
331
+ return entityDeclarationRegistry.describe().map((base) => {
332
+ const declaration = entityDeclarationRegistry.get(base.id);
333
+ return {
334
+ ...base,
335
+ recordSchema: base.schema,
336
+ access: declaration.access,
337
+ scope: declaration.scope,
338
+ storage: declaration.storage,
339
+ indexes: declaration.indexes,
340
+ titleField: declaration.titleField,
341
+ displayFields: declaration.displayFields
342
+ };
343
+ });
344
+ }
345
+ var FIELD_TYPE_TO_INPUT = {
346
+ text: "text",
347
+ richtext: "markdown",
348
+ number: "number",
349
+ boolean: "boolean",
350
+ // JSON and point have no dedicated control yet; they degrade to text inputs
351
+ // with a hint, which is honest — the descriptor still names the real type in
352
+ // the record schema, so a richer control can adopt them without a migration.
353
+ json: "textarea",
354
+ point: "text",
355
+ image: "image",
356
+ url: "url",
357
+ ref: "relation",
358
+ date: "date",
359
+ datetime: "datetime",
360
+ select: "select"
361
+ };
362
+ function entityAdminForm(declaration) {
363
+ const fields = Object.entries(declaration.fields).map(([name, field]) => {
364
+ const def = {
365
+ key: name,
366
+ label: field.label ?? name,
367
+ type: FIELD_TYPE_TO_INPUT[field.type],
368
+ showInForm: true,
369
+ showInTable: declaration.displayFields.includes(name)
370
+ };
371
+ if (field.required) def.required = true;
372
+ if (field.help) def.hint = field.help;
373
+ if (field.default !== void 0) def.defaultValue = field.default;
374
+ if (field.type === "json") def.hint = field.help ?? "JSON value";
375
+ if (field.type === "point") def.hint = field.help ?? "lat,lng";
376
+ if (field.type === "select") {
377
+ def.options = (field.options ?? []).map((option) => ({ label: option.label ?? option.value, value: option.value }));
378
+ }
379
+ if (field.type === "ref" && field.entity) {
380
+ def.relation = {
381
+ table: declaration.storage.table,
382
+ valueField: "id",
383
+ labelField: "slug",
384
+ filter: { entity: field.entity }
385
+ };
386
+ }
387
+ return def;
388
+ });
389
+ return { entity: declaration.key, title: declaration.title, fields };
390
+ }
391
+ var REFERENCE_RE = /^[a-z][a-z0-9_]*$/;
392
+ var customFieldsRegistry = new Registry("customFields");
393
+ function defineCustomFields(reference, fields, meta = {}) {
394
+ if (!REFERENCE_RE.test(reference)) {
395
+ fail(`defineCustomFields: "${reference}" is not a valid record kind (${REFERENCE_RE}).`);
396
+ }
397
+ for (const [name, field] of Object.entries(fields)) {
398
+ if (!FIELD_NAME_RE.test(name)) {
399
+ fail(`defineCustomFields("${reference}"): "${name}" is not a valid field name (${FIELD_NAME_RE}).`);
400
+ }
401
+ if (!ENTITY_FIELD_TYPES.includes(field?.type)) {
402
+ fail(`defineCustomFields("${reference}"): field "${name}" has unknown type "${String(field?.type)}".`, ENTITY_FIELD_TYPES);
403
+ }
404
+ if (field.type === "select" && !(field.options ?? []).length) {
405
+ fail(`defineCustomFields("${reference}"): select field "${name}" declares no options.`);
406
+ }
407
+ }
408
+ const declaration = { reference, fields };
409
+ customFieldsRegistry.register(reference, declaration, {
410
+ title: `Custom fields: ${reference}`,
411
+ propsSchema: entityRecordSchema({ title: `${reference} custom fields`, fields }),
412
+ source: meta.source ?? "app",
413
+ pluginId: meta.pluginId
414
+ });
415
+ return declaration;
416
+ }
417
+ function declaredCustomFields(reference) {
418
+ return customFieldsRegistry.get(reference)?.fields ?? {};
419
+ }
420
+ function hasCustomFieldsDeclaration(reference) {
421
+ return customFieldsRegistry.has(reference);
422
+ }
423
+ function clearCustomFieldsDeclarations() {
424
+ customFieldsRegistry.clear();
425
+ }
426
+
427
+ export { ENTITY_FIELD_TYPES, GENERIC_ENTITY_TABLE, RESERVED_ENTITY_FIELDS, Registry, clearCustomFieldsDeclarations, clearEntityDeclarations, customFieldsRegistry, declaredCustomFields, defineCustomFields, defineEntity, describeEntities, entityAdminForm, entityDeclarationRegistry, entityRecordSchema, hasCustomFieldsDeclaration, hasEntityDeclaration, isCustomId, listEntityDeclarations, resolveEntityDeclaration, validateEntityRecordData };
428
+ //# sourceMappingURL=chunk-EWKX4VUM.js.map
429
+ //# sourceMappingURL=chunk-EWKX4VUM.js.map