@fayz-ai/core 0.15.0 → 0.16.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 (47) hide show
  1. package/dist/app/app-config.d.ts +101 -0
  2. package/dist/app/app-config.d.ts.map +1 -0
  3. package/dist/app/service-client.d.ts +69 -0
  4. package/dist/app/service-client.d.ts.map +1 -0
  5. package/dist/{chunk-NBKFYGVD.js → chunk-2ZAUJVYP.js} +18 -3
  6. package/dist/chunk-2ZAUJVYP.js.map +1 -0
  7. package/dist/{chunk-ZYHS7T5T.js → chunk-ZIB6Y7TE.js} +31 -4
  8. package/dist/chunk-ZIB6Y7TE.js.map +1 -0
  9. package/dist/data/index.d.ts +1 -0
  10. package/dist/data/index.d.ts.map +1 -1
  11. package/dist/data/index.js +1 -1
  12. package/dist/data/missing-objects.d.ts +14 -0
  13. package/dist/data/missing-objects.d.ts.map +1 -0
  14. package/dist/events/envelope.d.ts.map +1 -1
  15. package/dist/i18n/index.d.ts.map +1 -1
  16. package/dist/i18n/index.js +1 -1
  17. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  18. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  19. package/dist/index.d.ts +8 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +468 -12
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib/debug.d.ts +12 -0
  24. package/dist/lib/debug.d.ts.map +1 -0
  25. package/dist/messaging/index.d.ts +5 -2
  26. package/dist/messaging/index.d.ts.map +1 -1
  27. package/dist/messaging/phone.d.ts +32 -0
  28. package/dist/messaging/phone.d.ts.map +1 -0
  29. package/dist/messaging/provider-template.d.ts +122 -0
  30. package/dist/messaging/provider-template.d.ts.map +1 -0
  31. package/dist/messaging/registry.d.ts +12 -1
  32. package/dist/messaging/registry.d.ts.map +1 -1
  33. package/dist/messaging/types.d.ts +32 -0
  34. package/dist/messaging/types.d.ts.map +1 -1
  35. package/dist/search/engine.d.ts.map +1 -1
  36. package/dist/{shell-translations.pt-br-MHKHD3IX.js → shell-translations.pt-br-HD4F3QKU.js} +20 -3
  37. package/dist/shell-translations.pt-br-HD4F3QKU.js.map +1 -0
  38. package/dist/types/analytics.d.ts +15 -0
  39. package/dist/types/analytics.d.ts.map +1 -1
  40. package/dist/types/crud.d.ts +16 -0
  41. package/dist/types/crud.d.ts.map +1 -1
  42. package/dist/types/plugins.d.ts +73 -0
  43. package/dist/types/plugins.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-NBKFYGVD.js.map +0 -1
  46. package/dist/chunk-ZYHS7T5T.js.map +0 -1
  47. package/dist/shell-translations.pt-br-MHKHD3IX.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  export { PluginContractError, assertConnectorContract, assertPluginManifestContract, bespokeConnectorTables } from './chunk-KJUD6JA5.js';
2
2
  export { CONNECTIONS_TABLE, CONNECTOR_CATEGORIES, ConnectionSettingsError, ConnectorRuntimeTokenUnavailableError, RequestBlockedError, RequestPlaneError, SYNC_RUNS_TABLE, assertSettingsAllowed, buyerIdempotencyKey, connectionKey, connectionRuns, connectionSettings, connectionStatus, connectorDirection, connectorEntities, connectorGrantScopes, connectorPermissions, connectorPlanes, connectorRuntimeToken, connectorSettings, connectorSubscribes, connectorSubscribesTo, createConnectionStore, createRpcEffectLedger, eventKeyMatches, getConnectorRuntimeTokenSource, grants, groupedPermissions, hasRequestPlane, hasSensitivePermission, invocationPlane, isConnectorCategory, isDataPermission, isEventPermission, isSettingSet, listConnections, missingRequiredSettings, permissionActions, permissionsWrite, preferredPlane, requestPlaneSpec, runEffect, runRequest, setConnectorRuntimeTokenSource, settingsStatus, setupStatusDetail, sortedPermissions, withAfterHooks } from './chunk-OHH26S4N.js';
3
3
  export { CONNECTOR_RUNTIME_TOKEN_HEADER, ConnectorHttpError, RateLimitExceededError, advanceWindow, backoffDelay, classify, createRateLimiter, cursorCompleteness, fetchWithRetry, isExpiring, paginate, parseRetryAfter, reconcileMirror, serializeCursor, sliceWindow, statusNeedsBody } from './chunk-ED6NBH6U.js';
4
- import { eventBus, DATA_CHANGED_EVENT, resolveDataProvider } from './chunk-NBKFYGVD.js';
5
- export { CustomFieldsError, DATA_CHANGED_EVENT, DataError, FIELDS_TABLE, attachPersistentCache, bulkCreate, bulkRemove, cacheKeyFor, cachePrefixFor, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createEventBus, createFayzApiProvider, createMockProvider, dataCall, dataQuery, emitDataChanged, eventBus, getActiveUnitId, getPersistentCache, invalidateCount, isUnitMode, localList, matchesDataChange, mergeCustomFields, registerDataTelemetry, replicaKeyFor, resetActiveUnit, resolveCustomFields, resolveDataProvider, setActiveUnitId, setUnitMode, subscribeActiveUnit, syncReplica, toDataError, useActiveUnitId, useDataChanged, useUnitMode, validateCustomFields, withCache, withOffline } from './chunk-NBKFYGVD.js';
4
+ import { eventBus, DATA_CHANGED_EVENT, resolveDataProvider } from './chunk-2ZAUJVYP.js';
5
+ export { CustomFieldsError, DATA_CHANGED_EVENT, DataError, FIELDS_TABLE, attachPersistentCache, bulkCreate, bulkRemove, cacheKeyFor, cachePrefixFor, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createEventBus, createFayzApiProvider, createMockProvider, dataCall, dataQuery, emitDataChanged, eventBus, getActiveUnitId, getPersistentCache, invalidateCount, isKnownMissing, isUnitMode, localList, matchesDataChange, mergeCustomFields, registerDataTelemetry, rememberIfMissing, replicaKeyFor, resetActiveUnit, resetMissingObjects, resolveCustomFields, resolveDataProvider, setActiveUnitId, setUnitMode, subscribeActiveUnit, syncReplica, toDataError, useActiveUnitId, useDataChanged, useUnitMode, validateCustomFields, withCache, withOffline } from './chunk-2ZAUJVYP.js';
6
6
  import { getActiveTenantId, getSupabaseClientOptional } from './chunk-BCI743RH.js';
7
7
  export { DEFAULT_CACHE_TTL, applyRangeToQuery, clearGlobalCache, createCacheStore, createSupabaseProvider, getActiveTenantId, getScopedResource, getSupabaseClientOptional, globalCache, isFilterObject, matchesRange, parseRangeFilter, setActiveTenantId, setGlobalSupabaseClient, setScopedResources, stableKey, subscribeActiveTenant, useActiveTenantId } from './chunk-BCI743RH.js';
8
8
  import { getBlock, checkNodePlacement, getScaffold } from './chunk-UMIZDILP.js';
9
9
  export { BLOCK_SETTING_TYPES, PLUGIN_API_VERSION, PluginRuntimeProvider, auditBlocks, blockRegistry, buildEntityDomainEvents, checkAgainstSchema, checkBlockConstraints, checkBlockContext, checkNodePlacement, clearExtensionPoints, clearHandles, clearLoaders, componentRegistry, dataNeedKey, defaultPropsFromSettings, defineBlock, defineExtensionPoint, defineLoader, definePlugin, describeBlock, describeBlocks, describeExtensionPoints, describeLoaders, entityEventKey, extensionAfterHooks, extensionPointRegistry, formatSchemaIssues, getAnalyticsCards, getBlock, getBlockMeta, getComponent, getDashboardWidgets, getHandle, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, handleNameFor, handleRegistry, hasExtensionPoint, hasHandle, hasLoader, isBlockEnabledOn, isLoaderParamRef, isLoaderPropRef, listBlocks, listExtensionHandlers, listExtensionPoints, listHandles, listLoaders, listMetrics, listPluginFactories, listPlugins, loaderRegistry, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerExtensionHandler, registerHandle, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, repairBlockTree, resolveHandle, resolveLoader, resolvePluginComponent, resolvePluginRuntime, runEscapeValve, runExtensionPoint, scaffoldRegistry, settingsToJsonSchema, summarizeBlockAudit, unregisterExtensionHandler, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional } from './chunk-UMIZDILP.js';
10
10
  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 } from './chunk-EWKX4VUM.js';
11
11
  export { clearEntityRegistry, deriveEntityKey, getAllEntities, getEntityByKey, registerEntity } from './chunk-CU64QI4A.js';
12
- import { getCurrentLocale } from './chunk-ZYHS7T5T.js';
13
- export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, preloadLocale, registerTranslations, setCurrentLocale, setLocaleStore, tl, useI18nConfig, useTranslation } from './chunk-ZYHS7T5T.js';
12
+ import { getCurrentLocale } from './chunk-ZIB6Y7TE.js';
13
+ export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, preloadLocale, registerTranslations, setCurrentLocale, setLocaleStore, tl, useI18nConfig, useTranslation } from './chunk-ZIB6Y7TE.js';
14
14
  export { defaultRouterAdapter, hashRouterAdapter, historyRouterAdapter, hostRouterAdapter, staticRouterAdapter, windowRouterAdapter } from './chunk-OXQGRJZT.js';
15
15
  export { FayzRuntimeError, createFayzRuntimeClient } from './chunk-7RTUHSKY.js';
16
16
  export { AGENT_ROLES } from './chunk-RCMFU3RZ.js';
@@ -18,6 +18,55 @@ import * as React3 from 'react';
18
18
  import { useEffect } from 'react';
19
19
  import { create } from 'zustand';
20
20
 
21
+ // src/lib/debug.ts
22
+ function isDevBuild() {
23
+ try {
24
+ return import.meta.env?.DEV === true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function flagValue() {
30
+ try {
31
+ return typeof localStorage !== "undefined" ? localStorage.getItem("debug") : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function scopeEnabled(scope) {
37
+ const flag = flagValue();
38
+ if (flag === "off") return false;
39
+ if (flag) {
40
+ return flag.split(",").some((raw) => {
41
+ const pattern = raw.trim();
42
+ if (!pattern) return false;
43
+ if (pattern === "*" || pattern === scope) return true;
44
+ if (pattern.endsWith(".*")) return scope.startsWith(pattern.slice(0, -1));
45
+ return false;
46
+ });
47
+ }
48
+ return isDevBuild();
49
+ }
50
+ function scopeColor(scope) {
51
+ let hash = 0;
52
+ for (let i = 0; i < scope.length; i++) hash = hash * 31 + scope.charCodeAt(i) | 0;
53
+ return `hsl(${Math.abs(hash) % 360} 60% 45%)`;
54
+ }
55
+ var LEVEL_STYLE = { log: "", warn: "color:#b45309", error: "color:#dc2626" };
56
+ function createDebug(scope) {
57
+ const badge = `%c${scope}%c`;
58
+ const badgeStyle = `color:${scopeColor(scope)};font-weight:600`;
59
+ const emit = (level) => (message, ...data) => {
60
+ if (!scopeEnabled(scope)) return;
61
+ console[level](`${badge} ${message}`, badgeStyle, LEVEL_STYLE[level], ...data);
62
+ };
63
+ const logger = emit("log");
64
+ logger.warn = emit("warn");
65
+ logger.error = emit("error");
66
+ Object.defineProperty(logger, "enabled", { get: () => scopeEnabled(scope) });
67
+ return logger;
68
+ }
69
+
21
70
  // src/types/plugins.ts
22
71
  var WidgetZone = {
23
72
  SIDEBAR_BEFORE_NAV: "shell.sidebar.before-nav",
@@ -379,6 +428,7 @@ function exportCSV(columns, rows, filename) {
379
428
  }
380
429
 
381
430
  // src/events/envelope.ts
431
+ var debug = createDebug("events");
382
432
  function emitDomainEvent(key, payload, meta) {
383
433
  const event = {
384
434
  key,
@@ -388,6 +438,7 @@ function emitDomainEvent(key, payload, meta) {
388
438
  subject: meta?.subject ?? null,
389
439
  occurredAt: meta?.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString()
390
440
  };
441
+ debug(`emit ${key}`, { tenantId: event.tenantId, subject: event.subject, sinks: sinks.size, payload });
391
442
  eventBus.emit(key, event);
392
443
  for (const sink of sinks) {
393
444
  try {
@@ -711,7 +762,8 @@ var SCAN_CONCURRENCY = 16;
711
762
  var TARGET_TIMEOUT_MS = 3e3;
712
763
  var CACHE_TTL_MS = 3e4;
713
764
  var CACHE_MAX_ENTRIES = 40;
714
- var RPC_NAME = "fayz_global_search";
765
+ var RPC_NAMES = ["global_search", "fayz_global_search"];
766
+ var rpcName = RPC_NAMES[0];
715
767
  var indexAvailable = null;
716
768
  function setSearchIndexAvailable(value) {
717
769
  indexAvailable = value;
@@ -903,20 +955,34 @@ function isMissingFunction(error) {
903
955
  if (!error) return false;
904
956
  if (error.code === "PGRST202") return true;
905
957
  const message = (error.message ?? "").toLowerCase();
906
- return message.includes(RPC_NAME) && (message.includes("does not exist") || message.includes("not find"));
958
+ return RPC_NAMES.some((n) => message.includes(n)) && (message.includes("does not exist") || message.includes("not find"));
907
959
  }
908
960
  async function searchViaIndex(query, targets, perTarget, limit) {
909
961
  const client2 = getSupabaseClientOptional();
910
962
  const tenantId = getActiveTenantId();
911
963
  if (!client2 || !tenantId) return { hits: [], ok: false };
912
964
  const byKey = new Map(targets.map((t) => [t.key, t]));
913
- const { data, error } = await client2.rpc(RPC_NAME, {
965
+ const args = {
914
966
  p_query: query.raw,
915
967
  p_tenant_id: tenantId,
916
968
  p_entity_keys: targets.map((t) => t.key),
917
969
  p_per_source: perTarget * 3,
918
970
  p_limit: limit * 3
919
- });
971
+ };
972
+ let { data, error } = await client2.rpc(rpcName, args);
973
+ if (error && isMissingFunction(error)) {
974
+ const other = RPC_NAMES.find((n) => n !== rpcName);
975
+ if (other) {
976
+ const retry = await client2.rpc(other, args);
977
+ if (!retry.error) {
978
+ rpcName = other;
979
+ data = retry.data;
980
+ error = null;
981
+ } else if (isMissingFunction(retry.error)) {
982
+ error = retry.error;
983
+ } else throw retry.error;
984
+ }
985
+ }
920
986
  if (error) {
921
987
  if (isMissingFunction(error)) {
922
988
  indexAvailable = false;
@@ -1569,10 +1635,10 @@ function surfaceAllowsWrites(surface) {
1569
1635
  function sortValue(value) {
1570
1636
  if (Array.isArray(value)) return value.map(sortValue);
1571
1637
  if (value !== null && typeof value === "object") {
1572
- const record = value;
1638
+ const record2 = value;
1573
1639
  const out = {};
1574
- for (const key of Object.keys(record).sort()) {
1575
- const v = record[key];
1640
+ for (const key of Object.keys(record2).sort()) {
1641
+ const v = record2[key];
1576
1642
  if (v !== void 0) out[key] = sortValue(v);
1577
1643
  }
1578
1644
  return out;
@@ -2381,6 +2447,195 @@ function renderApp(input, opts = {}) {
2381
2447
  React3.createElement(Scaffold, { manifest, surface: surfaceName })
2382
2448
  );
2383
2449
  }
2450
+
2451
+ // src/app/app-config.ts
2452
+ var EMPTY_APP_CONFIG = Object.freeze({
2453
+ tenant_id: null,
2454
+ plugins: [],
2455
+ nav: [],
2456
+ permissions: []
2457
+ });
2458
+ function isRpcCapable(client2) {
2459
+ return typeof client2?.rpc === "function";
2460
+ }
2461
+ var cache2 = /* @__PURE__ */ new Map();
2462
+ async function fetchAppConfig(client2, options = {}) {
2463
+ const key = options.tenantId ?? "current";
2464
+ if (!options.force) {
2465
+ const hit = cache2.get(key);
2466
+ if (hit) return hit;
2467
+ }
2468
+ if (!isRpcCapable(client2)) return EMPTY_APP_CONFIG;
2469
+ const { data, error } = await client2.rpc("app_config");
2470
+ if (error || !data || typeof data !== "object") return EMPTY_APP_CONFIG;
2471
+ const config = normalizeAppConfig(data);
2472
+ cache2.set(key, config);
2473
+ return config;
2474
+ }
2475
+ function invalidateAppConfig(tenantId) {
2476
+ if (tenantId) cache2.delete(tenantId);
2477
+ else cache2.clear();
2478
+ }
2479
+ function normalizeAppConfig(raw) {
2480
+ return {
2481
+ tenant_id: raw.tenant_id ?? null,
2482
+ branding: raw.branding ?? {},
2483
+ settings: raw.settings ?? {},
2484
+ plugins: Array.isArray(raw.plugins) ? raw.plugins : [],
2485
+ nav: Array.isArray(raw.nav) ? raw.nav : [],
2486
+ permissions: Array.isArray(raw.permissions) ? raw.permissions : [],
2487
+ generated_at: raw.generated_at
2488
+ };
2489
+ }
2490
+ function toTenantPluginBindings(config) {
2491
+ const byPlugin = /* @__PURE__ */ new Map();
2492
+ for (const row of config.plugins) {
2493
+ if (row.facet) continue;
2494
+ byPlugin.set(row.plugin, {
2495
+ pluginId: row.plugin,
2496
+ status: row.status,
2497
+ tenantId: config.tenant_id ?? void 0,
2498
+ config: { ...row.config }
2499
+ });
2500
+ }
2501
+ for (const row of config.plugins) {
2502
+ if (!row.facet) continue;
2503
+ const parent = byPlugin.get(row.plugin);
2504
+ if (!parent) continue;
2505
+ const facets = parent.config?.facets ?? {};
2506
+ parent.config = {
2507
+ ...parent.config,
2508
+ facets: { ...facets, [row.facet]: row.status === "active" }
2509
+ };
2510
+ }
2511
+ return [...byPlugin.values()];
2512
+ }
2513
+ function applyNavComposition(entries, nav) {
2514
+ const overrides = new Map(nav.map((n) => [n.key, n]));
2515
+ return entries.filter((entry) => !overrides.get(entry.key)?.hidden).map((entry) => {
2516
+ const override = overrides.get(entry.key);
2517
+ if (!override) return entry;
2518
+ return {
2519
+ ...entry,
2520
+ label: override.label ?? entry.label,
2521
+ section: override.section ?? entry.section,
2522
+ position: override.position ?? entry.position
2523
+ };
2524
+ }).sort((a, b) => (a.position ?? 999) - (b.position ?? 999));
2525
+ }
2526
+ var TENANT_OWNED_MANIFEST_KEYS = ["theme", "branding", "locale"];
2527
+ function applyAppConfigToManifest(manifest, config) {
2528
+ const branding = config.branding ?? {};
2529
+ const settings = config.settings ?? {};
2530
+ const next = { ...manifest };
2531
+ if (Object.keys(branding).length > 0) {
2532
+ next.theme = { ...manifest.theme ?? {}, ...branding.theme ?? {} };
2533
+ next.branding = { ...manifest.branding ?? {}, ...branding };
2534
+ }
2535
+ if (settings.locale && typeof settings.locale === "object") {
2536
+ next.locale = { ...manifest.locale ?? {}, ...settings.locale };
2537
+ }
2538
+ if (manifest.surfaces && config.plugins.length > 0) {
2539
+ const active = new Map(
2540
+ config.plugins.filter((p) => p.status === "active" && !p.facet).map((p) => [p.plugin, p])
2541
+ );
2542
+ const surfaces = {};
2543
+ for (const [name, surface] of Object.entries(manifest.surfaces)) {
2544
+ const plugins = surface.plugins;
2545
+ surfaces[name] = plugins ? {
2546
+ ...surface,
2547
+ // A plugin the bundle carries but this tenant has not turned on is
2548
+ // dropped here rather than rendered and hidden — a screen that
2549
+ // exists and refuses is worse than one that was never offered.
2550
+ plugins: plugins.filter((p) => active.has(p.id)).map((p) => ({ ...p, config: { ...p.config ?? {}, ...active.get(p.id)?.config ?? {} } }))
2551
+ } : surface;
2552
+ }
2553
+ next.surfaces = surfaces;
2554
+ }
2555
+ return next;
2556
+ }
2557
+
2558
+ // src/app/service-client.ts
2559
+ function isRpcCapable2(client2) {
2560
+ return typeof client2?.rpc === "function";
2561
+ }
2562
+ async function callService(client2, name, payload = {}, options = {}) {
2563
+ if (!isRpcCapable2(client2)) {
2564
+ return { ok: false, reason: "unavailable", message: "no client to call through" };
2565
+ }
2566
+ const started2 = Date.now();
2567
+ const forTenant = options.tenantId;
2568
+ const { data: authData, error: authError } = forTenant ? await client2.rpc("service_authorize_for", { p_name: name, p_tenant: forTenant }) : await client2.rpc("service_authorize", { p_name: name });
2569
+ if (authError || !authData || typeof authData !== "object") {
2570
+ return { ok: false, reason: "unavailable", message: "the authorization call itself failed" };
2571
+ }
2572
+ const auth = authData;
2573
+ if (auth.allowed !== true) {
2574
+ return {
2575
+ ok: false,
2576
+ reason: auth.reason ?? "failed",
2577
+ manualFallback: auth.manual_fallback,
2578
+ entitlement: auth.entitlement,
2579
+ plugin: auth.plugin,
2580
+ permission: auth.permission
2581
+ };
2582
+ }
2583
+ const manualFallback = auth.manual_fallback;
2584
+ const timeoutMs = options.timeoutMs ?? auth.timeout_ms ?? 8e3;
2585
+ if (!client2.functions) {
2586
+ return { ok: false, reason: "unavailable", manualFallback, message: "no function transport" };
2587
+ }
2588
+ try {
2589
+ const result = await withTimeout2(
2590
+ client2.functions.invoke(options.router ?? "service-router", {
2591
+ body: forTenant ? { service: name, payload, tenant_id: forTenant } : { service: name, payload }
2592
+ }),
2593
+ timeoutMs
2594
+ );
2595
+ const durationMs = Date.now() - started2;
2596
+ if (result === TIMED_OUT) {
2597
+ void record(client2, name, "timeout", "client timeout", durationMs, forTenant);
2598
+ return { ok: false, reason: "timeout", manualFallback };
2599
+ }
2600
+ if (result.error) {
2601
+ void record(client2, name, "failed", String(result.error), durationMs, forTenant);
2602
+ return { ok: false, reason: "failed", manualFallback, message: String(result.error) };
2603
+ }
2604
+ void record(client2, name, "ok", null, durationMs, forTenant);
2605
+ return { ok: true, data: result.data, durationMs };
2606
+ } catch (err) {
2607
+ const durationMs = Date.now() - started2;
2608
+ void record(client2, name, "failed", String(err), durationMs, forTenant);
2609
+ return { ok: false, reason: "failed", manualFallback, message: String(err) };
2610
+ }
2611
+ }
2612
+ var TIMED_OUT = /* @__PURE__ */ Symbol("timed-out");
2613
+ function withTimeout2(promise, ms) {
2614
+ return Promise.race([
2615
+ promise,
2616
+ new Promise((resolve) => setTimeout(() => resolve(TIMED_OUT), ms))
2617
+ ]);
2618
+ }
2619
+ async function record(client2, service, status, reason, durationMs, tenantId) {
2620
+ try {
2621
+ await (tenantId ? client2.rpc("service_call_record_for", {
2622
+ p_service: service,
2623
+ p_tenant: tenantId,
2624
+ p_status: status,
2625
+ p_reason: reason,
2626
+ p_duration_ms: durationMs
2627
+ }) : client2.rpc("service_call_record", {
2628
+ p_service: service,
2629
+ p_status: status,
2630
+ p_reason: reason,
2631
+ p_duration_ms: durationMs
2632
+ }));
2633
+ } catch {
2634
+ }
2635
+ }
2636
+ function callServiceForTenant(client2, tenantId, name, payload = {}, options = {}) {
2637
+ return callService(client2, name, payload, { ...options, tenantId });
2638
+ }
2384
2639
  var MAX_ENTRIES = 20;
2385
2640
  var useNavHistoryStore = create((set) => ({
2386
2641
  stack: [],
@@ -2432,6 +2687,23 @@ function getMessageDefaults() {
2432
2687
  function clearMessageDefaults() {
2433
2688
  defaultsRegistry.clear();
2434
2689
  }
2690
+ var ACTION_IDS = ["confirm", "cancel", "pay"];
2691
+ var ACTION_KINDS = ["reply", "payment"];
2692
+ function normalizeMessageActions(value) {
2693
+ if (value == null) return null;
2694
+ if (!Array.isArray(value)) return [];
2695
+ const out = [];
2696
+ for (const raw of value) {
2697
+ const item = raw;
2698
+ const id = item?.id;
2699
+ const kind = item?.kind;
2700
+ const label = String(item?.label ?? "").trim();
2701
+ if (!id || !ACTION_IDS.includes(id) || !kind || !ACTION_KINDS.includes(kind) || !label) continue;
2702
+ if (out.some((a) => a.id === id)) continue;
2703
+ out.push({ id, label, kind });
2704
+ }
2705
+ return out;
2706
+ }
2435
2707
  function renderMessageTemplate(text, values) {
2436
2708
  return text.replace(/\{\{\s*(\w+)\s*\}\}/g, (whole, key) => values[key] ?? whole);
2437
2709
  }
@@ -2455,6 +2727,7 @@ function resolveMessages(overrides, events) {
2455
2727
  enabled: override?.enabled ?? def.defaultEnabled?.[channel] ?? false,
2456
2728
  subject: override?.subject ?? fallback.subject ?? "",
2457
2729
  body: override?.body ?? fallback.body,
2730
+ actions: override?.actions ?? fallback.actions ?? [],
2458
2731
  customized: Boolean(override?.subject || override?.body),
2459
2732
  placeholders
2460
2733
  });
@@ -2486,6 +2759,7 @@ async function loadMessageOverrides(tenantId) {
2486
2759
  subject: row.subject || null,
2487
2760
  body: row.body || null,
2488
2761
  name: row.name || null,
2762
+ actions: normalizeMessageActions(row.actions),
2489
2763
  updatedAt: row.updated_at ?? row.created_at
2490
2764
  }));
2491
2765
  } catch (err) {
@@ -2494,6 +2768,188 @@ async function loadMessageOverrides(tenantId) {
2494
2768
  }
2495
2769
  }
2496
2770
 
2771
+ // src/messaging/provider-template.ts
2772
+ var PROVIDER_TEMPLATE_LANGUAGE = "pt_BR";
2773
+ var MAX_QUICK_REPLY_BUTTONS = 3;
2774
+ var DEFAULT_LEAD_PADDING = "Ol\xE1! ";
2775
+ var DEFAULT_TRAIL_PADDING = " At\xE9 breve!";
2776
+ var MAX_TEMPLATE_VARIABLES = 10;
2777
+ var PLACEHOLDER = /\{\{\s*([\w]+)\s*\}\}/g;
2778
+ function providerTemplateName(eventKey, tenantId) {
2779
+ const slug = String(eventKey ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2780
+ const base = `t_${slug || "message"}`;
2781
+ const owner = String(tenantId ?? "").trim();
2782
+ return owner ? `${base}_${shortHash(owner)}` : base;
2783
+ }
2784
+ function shortHash(value) {
2785
+ let hash = 2166136261;
2786
+ for (let i = 0; i < value.length; i += 1) {
2787
+ hash ^= value.charCodeAt(i);
2788
+ hash = Math.imul(hash, 16777619) >>> 0;
2789
+ }
2790
+ return hash.toString(36).padStart(7, "0");
2791
+ }
2792
+ function placementRuleApplies(category) {
2793
+ return category !== "authentication";
2794
+ }
2795
+ function compileProviderTemplate(input) {
2796
+ const source = String(input.body ?? "");
2797
+ if (!source.trim()) {
2798
+ return { ok: false, code: "empty_body", message: "A mensagem est\xE1 vazia" };
2799
+ }
2800
+ const fields = input.fields ?? [];
2801
+ const known = new Set(fields.map((f) => f.name));
2802
+ const sample = sampleEventPayload(fields);
2803
+ const examples = new Map(
2804
+ fields.map((f) => {
2805
+ const value = String(sample[f.name] ?? "");
2806
+ return [f.name, value && !value.startsWith("{{") ? value : f.label || f.name];
2807
+ })
2808
+ );
2809
+ const variableMap = [];
2810
+ let unknown = null;
2811
+ const positional = source.replace(PLACEHOLDER, (whole, name) => {
2812
+ if (!known.has(name)) {
2813
+ unknown ?? (unknown = name);
2814
+ return whole;
2815
+ }
2816
+ let index = variableMap.indexOf(name);
2817
+ if (index < 0) {
2818
+ variableMap.push(name);
2819
+ index = variableMap.length - 1;
2820
+ }
2821
+ return `{{${index + 1}}}`;
2822
+ });
2823
+ if (unknown) {
2824
+ return {
2825
+ ok: false,
2826
+ code: "unknown_variable",
2827
+ // Loud, because the alternative is a template Meta approves and a customer
2828
+ // reads with `{{cupom}}` printed in it.
2829
+ message: `A mensagem usa {{${unknown}}}, que este evento n\xE3o envia`,
2830
+ detail: unknown
2831
+ };
2832
+ }
2833
+ if (variableMap.length > MAX_TEMPLATE_VARIABLES) {
2834
+ return {
2835
+ ok: false,
2836
+ code: "too_many_variables",
2837
+ message: `A mensagem tem ${variableMap.length} campos e o WhatsApp aceita ${MAX_TEMPLATE_VARIABLES}`
2838
+ };
2839
+ }
2840
+ const category = input.kind === "marketing" ? "marketing" : "utility";
2841
+ let text = positional;
2842
+ const padded = { lead: false, trail: false };
2843
+ if (placementRuleApplies(category)) {
2844
+ if (/^[^\p{L}\p{N}]*\{\{\d+\}\}/u.test(text.trim())) {
2845
+ text = `${input.leadPadding ?? DEFAULT_LEAD_PADDING}${text.trimStart()}`;
2846
+ padded.lead = true;
2847
+ }
2848
+ if (/\{\{\d+\}\}[^\p{L}\p{N}]*$/u.test(text.trim())) {
2849
+ text = `${text.trimEnd()}${input.trailPadding ?? DEFAULT_TRAIL_PADDING}`;
2850
+ padded.trail = true;
2851
+ }
2852
+ }
2853
+ const leftover = /\{\{\s*(?!\d+\s*\}\})[^}]*\}\}/.exec(text);
2854
+ if (leftover) {
2855
+ return {
2856
+ ok: false,
2857
+ code: "named_placeholder_left",
2858
+ message: "A mensagem ainda tem um campo com nome, e o WhatsApp s\xF3 aceita numerados",
2859
+ detail: leftover[0]
2860
+ };
2861
+ }
2862
+ const components = [{
2863
+ type: "BODY",
2864
+ text,
2865
+ // One row of examples, one cell per variable, in the same order as the map.
2866
+ // Meta requires an example for each; Tyxter refuses the create without them.
2867
+ ...variableMap.length > 0 ? { example: { body_text: [variableMap.map((name) => examples.get(name) ?? name)] } } : {}
2868
+ }];
2869
+ const replies = (input.actions ?? []).filter((action) => action.kind === "reply").slice(0, MAX_QUICK_REPLY_BUTTONS);
2870
+ const actionsMap = replies.map((action, index) => ({
2871
+ index,
2872
+ id: action.id,
2873
+ kind: action.kind
2874
+ }));
2875
+ if (replies.length > 0) {
2876
+ components.push({
2877
+ type: "BUTTONS",
2878
+ buttons: replies.map((action) => ({
2879
+ type: "QUICK_REPLY",
2880
+ // 20 is Meta's button-text ceiling. Truncating beats a rejection that
2881
+ // arrives a day later naming a component index.
2882
+ text: action.label.slice(0, 20)
2883
+ }))
2884
+ });
2885
+ }
2886
+ return {
2887
+ ok: true,
2888
+ template: {
2889
+ name: providerTemplateName(input.eventKey, input.tenantId),
2890
+ language: PROVIDER_TEMPLATE_LANGUAGE,
2891
+ category,
2892
+ components,
2893
+ variableMap,
2894
+ actionsMap,
2895
+ bodySnapshot: text,
2896
+ padded
2897
+ }
2898
+ };
2899
+ }
2900
+ function positionalVariables(variableMap, values) {
2901
+ const out = {};
2902
+ variableMap.forEach((name, index) => {
2903
+ const value = values[name];
2904
+ out[String(index + 1)] = value == null ? "" : String(value);
2905
+ });
2906
+ return out;
2907
+ }
2908
+
2909
+ // src/messaging/phone.ts
2910
+ var BR_COUNTRY_CALLING_CODE = "55";
2911
+ var BR_MATCH_SUFFIX_LENGTH = 8;
2912
+ function isPlausibleDdd(ddd) {
2913
+ const n = Number(ddd);
2914
+ return n >= 11 && n <= 99 && !ddd.endsWith("0");
2915
+ }
2916
+ function isLegacyMobile(national) {
2917
+ return national.length === 10 && /^[6-9]/.test(national.slice(2));
2918
+ }
2919
+ function normalizeBrPhoneToE164(input) {
2920
+ const raw = String(input ?? "").trim();
2921
+ if (!raw) return null;
2922
+ const hadPlus = raw.startsWith("+");
2923
+ const digits = raw.replace(/\D/g, "");
2924
+ if (!digits) return null;
2925
+ if (hadPlus && !digits.startsWith(BR_COUNTRY_CALLING_CODE)) {
2926
+ return digits.length >= 8 && digits.length <= 15 ? `+${digits}` : null;
2927
+ }
2928
+ let national = digits;
2929
+ if (national.startsWith(BR_COUNTRY_CALLING_CODE) && national.length > 11) {
2930
+ national = national.slice(2);
2931
+ }
2932
+ if (national.length === 10 || national.length === 11) {
2933
+ if (!isPlausibleDdd(national.slice(0, 2))) return null;
2934
+ if (national.length === 11 && !/^9/.test(national.slice(2))) {
2935
+ return null;
2936
+ }
2937
+ const repaired = isLegacyMobile(national) ? `${national.slice(0, 2)}9${national.slice(2)}` : national;
2938
+ return `+${BR_COUNTRY_CALLING_CODE}${repaired}`;
2939
+ }
2940
+ return null;
2941
+ }
2942
+ function brNationalNumber(e164) {
2943
+ const value = String(e164 ?? "").trim();
2944
+ if (!value.startsWith(`+${BR_COUNTRY_CALLING_CODE}`)) return null;
2945
+ const national = value.slice(3);
2946
+ return /^\d{10,11}$/.test(national) ? national : null;
2947
+ }
2948
+ function brPhoneSuffixKey(input) {
2949
+ const digits = String(input ?? "").replace(/\D/g, "");
2950
+ return digits.length >= BR_MATCH_SUFFIX_LENGTH ? digits.slice(-BR_MATCH_SUFFIX_LENGTH) : null;
2951
+ }
2952
+
2497
2953
  // src/telemetry/errors.ts
2498
2954
  var APP_ERROR_EVENT = "app.error";
2499
2955
  var reporters = /* @__PURE__ */ new Set();
@@ -2625,6 +3081,6 @@ function createVendorReporter(client2) {
2625
3081
  };
2626
3082
  }
2627
3083
 
2628
- export { APP_ERROR_EVENT, BLOCK_PATH_ATTRIBUTE, BLOCK_TYPE_ATTRIBUTE, BlockChildren, BlockErrorBoundary, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DEFAULT_COUNTRY, EVENT_LOG_TABLE, MESSAGE_TEMPLATES_TABLE, MIN_QUERY_LENGTH, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applySeo, blockEditorMode, breadcrumbJsonLd, buildCSV, buildSeoTags, clearErrorReporters, clearEventSinks, clearMessageDefaults, clearPostalCache, clearSearchCache, computeContractHash, createSafeDataProvider, createVendorReporter, createViaCepProvider, defineApp, digitsOf, downloadCSV, emitDomainEvent, exportCSV, foldText, foldWithMap, formatCurrency, formatDate, formatDateTime, formatPostalCode, getActiveLocale, getBlockErrorVisibility, getCountry, getDefaultCurrency, getEventsWithoutMessages, getMessageDefaults, getPostalLookupProvider, highlightRanges, installGlobalErrorHandlers, isSearchIndexAvailable, isValidPostalCode, loadEventRuns, loadMessageOverrides, lookupPostalCode, maskDigitCount, maskPhone, migrateManifest, navHistoryBack, normalizePostalCode, normalizeQuery, onDomainEvent, onDomainEvents, organizationJsonLd, recordEventRun, recordNavigation, registerErrorReporter, registerEventSink, registerManifestMigration, registerMessageDefaults, renderApp, renderBlocks, renderMessageTemplate, reportAppError, resetErrorDedupe, resolveBackendEnv, resolveIntegrationSurface, resolveMessage, resolveMessages, routeModule, sampleEventPayload, scoreCandidate, searchEverything, seoOrigin, serializeManifest, serializeManifestWithHash, setBlockEditorMode, setBlockErrorVisibility, setDefaultCurrency, setPostalLookupProvider, setSearchIndexAvailable, similarity, startEventLog, surfaceAllowsWrites, unmaskPhone, useBlockPath, useDataVersion, useManifest, useNavHistoryStore, useNavReferrer, useOnDataChange, useOnEvent, useSeo, validateManifest };
3084
+ export { APP_ERROR_EVENT, BLOCK_PATH_ATTRIBUTE, BLOCK_TYPE_ATTRIBUTE, BR_COUNTRY_CALLING_CODE, BR_MATCH_SUFFIX_LENGTH, BlockChildren, BlockErrorBoundary, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DEFAULT_COUNTRY, DEFAULT_LEAD_PADDING, DEFAULT_TRAIL_PADDING, EMPTY_APP_CONFIG, EVENT_LOG_TABLE, MAX_QUICK_REPLY_BUTTONS, MAX_TEMPLATE_VARIABLES, MESSAGE_TEMPLATES_TABLE, MIN_QUERY_LENGTH, PROVIDER_TEMPLATE_LANGUAGE, TENANT_OWNED_MANIFEST_KEYS, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applyAppConfigToManifest, applyNavComposition, applySeo, blockEditorMode, brNationalNumber, brPhoneSuffixKey, breadcrumbJsonLd, buildCSV, buildSeoTags, callService, callServiceForTenant, clearErrorReporters, clearEventSinks, clearMessageDefaults, clearPostalCache, clearSearchCache, compileProviderTemplate, computeContractHash, createDebug, createSafeDataProvider, createVendorReporter, createViaCepProvider, defineApp, digitsOf, downloadCSV, emitDomainEvent, exportCSV, fetchAppConfig, foldText, foldWithMap, formatCurrency, formatDate, formatDateTime, formatPostalCode, getActiveLocale, getBlockErrorVisibility, getCountry, getDefaultCurrency, getEventsWithoutMessages, getMessageDefaults, getPostalLookupProvider, highlightRanges, installGlobalErrorHandlers, invalidateAppConfig, isSearchIndexAvailable, isValidPostalCode, loadEventRuns, loadMessageOverrides, lookupPostalCode, maskDigitCount, maskPhone, migrateManifest, navHistoryBack, normalizeAppConfig, normalizeBrPhoneToE164, normalizeMessageActions, normalizePostalCode, normalizeQuery, onDomainEvent, onDomainEvents, organizationJsonLd, placementRuleApplies, positionalVariables, providerTemplateName, recordEventRun, recordNavigation, registerErrorReporter, registerEventSink, registerManifestMigration, registerMessageDefaults, renderApp, renderBlocks, renderMessageTemplate, reportAppError, resetErrorDedupe, resolveBackendEnv, resolveIntegrationSurface, resolveMessage, resolveMessages, routeModule, sampleEventPayload, scoreCandidate, searchEverything, seoOrigin, serializeManifest, serializeManifestWithHash, setBlockEditorMode, setBlockErrorVisibility, setDefaultCurrency, setPostalLookupProvider, setSearchIndexAvailable, shortHash, similarity, startEventLog, surfaceAllowsWrites, toTenantPluginBindings, unmaskPhone, useBlockPath, useDataVersion, useManifest, useNavHistoryStore, useNavReferrer, useOnDataChange, useOnEvent, useSeo, validateManifest };
2629
3085
  //# sourceMappingURL=index.js.map
2630
3086
  //# sourceMappingURL=index.js.map