spine-framework 0.3.16 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.framework/cli/commands/create-app.ts +1 -1
- package/.framework/cli/commands/dev.ts +215 -33
- package/.framework/cli/index.ts +37 -75
- package/.framework/functions/_shared/app-manifest.ts +8 -15
- package/.framework/functions/_shared/db.ts +11 -15
- package/.framework/functions/_shared/index.ts +7 -16
- package/.framework/functions/_shared/principal.ts +4 -62
- package/.framework/functions/_shared/resolve-ids.ts +6 -6
- package/.framework/functions/_shared/webhook-registry.ts +43 -1
- package/.framework/functions/admin-funnel.ts +104 -0
- package/.framework/functions/admin-install-funnel.ts +225 -0
- package/.framework/functions/admin-signals.ts +241 -0
- package/.framework/functions/integration-routes.ts +3 -7
- package/.framework/migrations/001_schema.sql +406 -0
- package/.framework/migrations/002_rls_and_functions.sql +710 -0
- package/.framework/migrations/003_system_data.sql +520 -0
- package/.framework/migrations/004_enhancements.sql +43 -0
- package/.framework/public/_redirects +3 -0
- package/.framework/public/spine-logo.jpg +0 -0
- package/.framework/public/spine-logo.svg +3 -0
- package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
- package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
- package/{scripts → .framework/scripts}/assemble.sh +2 -8
- package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
- package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
- package/.framework/seeds/link_types/account_cli_instances.json +9 -0
- package/.framework/seeds/types/funnel_signal_use.json +98 -0
- package/.framework/seeds/types/spine_instance.json +109 -0
- package/.framework/src/App.tsx +11 -21
- package/.framework/src/components/CustomAppLoader.tsx +2 -19
- package/.framework/src/components/ui/accordion.tsx +1 -1
- package/.framework/src/components/ui/alert-dialog.tsx +1 -1
- package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
- package/.framework/src/components/ui/avatar.tsx +1 -1
- package/.framework/src/components/ui/badge.tsx +2 -2
- package/.framework/src/components/ui/breadcrumb.tsx +2 -2
- package/.framework/src/components/ui/button-group.tsx +2 -2
- package/.framework/src/components/ui/button.tsx +2 -2
- package/.framework/src/components/ui/chart.tsx +6 -1
- package/.framework/src/components/ui/checkbox.tsx +1 -1
- package/.framework/src/components/ui/collapsible.tsx +1 -1
- package/.framework/src/components/ui/context-menu.tsx +1 -1
- package/.framework/src/components/ui/dialog.tsx +1 -1
- package/.framework/src/components/ui/direction.tsx +17 -1
- package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
- package/.framework/src/components/ui/hover-card.tsx +1 -1
- package/.framework/src/components/ui/item.tsx +2 -2
- package/.framework/src/components/ui/label.tsx +1 -1
- package/.framework/src/components/ui/menubar.tsx +1 -1
- package/.framework/src/components/ui/navigation-menu.tsx +1 -1
- package/.framework/src/components/ui/popover.tsx +1 -1
- package/.framework/src/components/ui/progress.tsx +1 -1
- package/.framework/src/components/ui/radio-group.tsx +1 -1
- package/.framework/src/components/ui/scroll-area.tsx +1 -1
- package/.framework/src/components/ui/select.tsx +1 -1
- package/.framework/src/components/ui/separator.tsx +1 -1
- package/.framework/src/components/ui/sheet.tsx +1 -1
- package/.framework/src/components/ui/sidebar.tsx +6 -6
- package/.framework/src/components/ui/slider.tsx +1 -1
- package/.framework/src/components/ui/switch.tsx +1 -1
- package/.framework/src/components/ui/tabs.tsx +1 -1
- package/.framework/src/components/ui/toggle-group.tsx +1 -1
- package/.framework/src/components/ui/toggle.tsx +1 -1
- package/.framework/src/components/ui/tooltip.tsx +1 -1
- package/.framework/src/pages/auth/LoginPage.tsx +10 -9
- package/LICENSE.md +63 -93
- package/README.md +254 -129
- package/custom/.devin/skills/install-app/SKILL.md +39 -0
- package/custom/.devin/workflows/app-install.yaml +83 -0
- package/custom/README.md +79 -0
- package/docs/APPS.md +232 -0
- package/docs/ARCHITECTURE.md +246 -0
- package/docs/INSTALL.md +155 -0
- package/docs/README.md +170 -0
- package/docs/design-schema-spec.md +1837 -0
- package/docs/dev-guides/01-create-an-app.md +183 -0
- package/docs/dev-guides/02-create-a-function.md +178 -0
- package/docs/dev-guides/03-create-a-component.md +178 -0
- package/docs/dev-guides/04-webhook-handlers.md +129 -0
- package/docs/dev-guides/05-testing.md +168 -0
- package/docs/dev-guides/README.md +40 -0
- package/docs/npm-packages-plan.md +288 -0
- package/docs/spine-core--api-reference.md +822 -0
- package/package.json +78 -150
- package/.framework/cli/bin.cjs +0 -14
- package/.framework/cli/commands/claim.ts +0 -430
- package/.framework/cli/commands/init.ts +0 -219
- package/.framework/cli/commands/install-app.ts +0 -685
- package/.framework/cli/commands/migrate.ts +0 -178
- package/.framework/cli/commands/publish.ts +0 -191
- package/.framework/cli/commands/status.ts +0 -268
- package/.framework/cli/commands/uninstall-app.ts +0 -166
- package/.framework/cli/commands/update-app.ts +0 -397
- package/.framework/cli/welcome.cjs +0 -45
- package/.framework/config/signal-client.ts +0 -147
- package/.framework/config/signals.ts +0 -70
- package/.framework/docs/STABILITY.md +0 -52
- package/.framework/docs/examples/custom-app/README.md +0 -77
- package/.framework/docs/examples/custom-function/README.md +0 -27
- package/.framework/docs/examples/custom-function/handler.ts +0 -48
- package/.framework/docs/examples/custom-webhook/README.md +0 -68
- package/.framework/functions/handler-registry.ts +0 -22
- package/.framework/functions/signal.ts +0 -168
- package/.framework/migrations/000_foundation.sql +0 -1302
- package/.framework/migrations/001_seed.sql +0 -163
- package/.framework/migrations/002_seed_constraints.sql +0 -19
- package/.framework/migrations/003_auth_user_trigger.sql +0 -67
- package/.framework/migrations/004_app_version_tracking.sql +0 -8
- package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
- package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
- package/CHANGELOG.md +0 -68
- package/STRUCTURE.md +0 -150
- package/bin/spine-framework.cjs +0 -62
- package/bin/welcome.cjs +0 -58
- package/bin/ws-shim.ts +0 -10
- package/config/components.json +0 -25
- package/config/deno.lock +0 -108
- package/config/package-lock.json +0 -17183
- package/config/postcss.config.cjs +0 -10
- package/config/tailwind.config.cjs +0 -78
- package/config/tsconfig.build.json +0 -32
- package/config/tsconfig.cli.json +0 -18
- package/config/tsconfig.json +0 -41
- package/config/tsconfig.node.json +0 -17
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/typedoc.json +0 -16
- package/config/vite.config.d.ts +0 -2
- package/config/vite.config.ts +0 -71
- package/dist/cli/commands/agents.d.ts +0 -39
- package/dist/cli/commands/agents.d.ts.map +0 -1
- package/dist/cli/commands/auth.d.ts +0 -36
- package/dist/cli/commands/auth.d.ts.map +0 -1
- package/dist/cli/commands/claim.d.ts +0 -38
- package/dist/cli/commands/claim.d.ts.map +0 -1
- package/dist/cli/commands/create-app.d.ts +0 -23
- package/dist/cli/commands/create-app.d.ts.map +0 -1
- package/dist/cli/commands/dev.d.ts +0 -24
- package/dist/cli/commands/dev.d.ts.map +0 -1
- package/dist/cli/commands/doctor.d.ts +0 -42
- package/dist/cli/commands/doctor.d.ts.map +0 -1
- package/dist/cli/commands/generate.d.ts +0 -36
- package/dist/cli/commands/generate.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts +0 -20
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/install-app.d.ts +0 -30
- package/dist/cli/commands/install-app.d.ts.map +0 -1
- package/dist/cli/commands/items.d.ts +0 -45
- package/dist/cli/commands/items.d.ts.map +0 -1
- package/dist/cli/commands/migrate.d.ts +0 -21
- package/dist/cli/commands/migrate.d.ts.map +0 -1
- package/dist/cli/commands/migrations.d.ts +0 -41
- package/dist/cli/commands/migrations.d.ts.map +0 -1
- package/dist/cli/commands/pipelines.d.ts +0 -40
- package/dist/cli/commands/pipelines.d.ts.map +0 -1
- package/dist/cli/commands/publish.d.ts +0 -23
- package/dist/cli/commands/publish.d.ts.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -23
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/system.d.ts +0 -29
- package/dist/cli/commands/system.d.ts.map +0 -1
- package/dist/cli/commands/test.d.ts +0 -46
- package/dist/cli/commands/test.d.ts.map +0 -1
- package/dist/cli/commands/uninstall-app.d.ts +0 -23
- package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
- package/dist/cli/commands/update-app.d.ts +0 -24
- package/dist/cli/commands/update-app.d.ts.map +0 -1
- package/dist/cli/context.d.ts +0 -88
- package/dist/cli/context.d.ts.map +0 -1
- package/dist/cli/env-loader.d.ts +0 -14
- package/dist/cli/env-loader.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -44
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/config/signals.d.ts +0 -82
- package/dist/config/signals.d.ts.map +0 -1
- package/dist/functions/_shared/agent-runner.d.ts +0 -156
- package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
- package/dist/functions/_shared/app-manifest.d.ts +0 -68
- package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
- package/dist/functions/_shared/audit.d.ts +0 -91
- package/dist/functions/_shared/audit.d.ts.map +0 -1
- package/dist/functions/_shared/db.d.ts +0 -125
- package/dist/functions/_shared/db.d.ts.map +0 -1
- package/dist/functions/_shared/index.d.ts +0 -299
- package/dist/functions/_shared/index.d.ts.map +0 -1
- package/dist/functions/_shared/middleware.d.ts +0 -315
- package/dist/functions/_shared/middleware.d.ts.map +0 -1
- package/dist/functions/_shared/permissions.d.ts +0 -626
- package/dist/functions/_shared/permissions.d.ts.map +0 -1
- package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
- package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
- package/dist/functions/_shared/principal.d.ts +0 -284
- package/dist/functions/_shared/principal.d.ts.map +0 -1
- package/dist/functions/_shared/resolve-ids.d.ts +0 -10
- package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
- package/dist/functions/_shared/schema-utils.d.ts +0 -181
- package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
- package/dist/functions/_shared/testing.d.ts +0 -172
- package/dist/functions/_shared/testing.d.ts.map +0 -1
- package/dist/functions/_shared/trigger-engine.d.ts +0 -140
- package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registration.d.ts +0 -81
- package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registry.d.ts +0 -41
- package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
- package/dist/functions/account-nodes.d.ts +0 -48
- package/dist/functions/account-nodes.d.ts.map +0 -1
- package/dist/functions/admin-data.d.ts +0 -178
- package/dist/functions/admin-data.d.ts.map +0 -1
- package/dist/functions/ai-agents.d.ts +0 -125
- package/dist/functions/ai-agents.d.ts.map +0 -1
- package/dist/functions/api-keys.d.ts +0 -140
- package/dist/functions/api-keys.d.ts.map +0 -1
- package/dist/functions/apps.d.ts +0 -163
- package/dist/functions/apps.d.ts.map +0 -1
- package/dist/functions/auth.d.ts +0 -74
- package/dist/functions/auth.d.ts.map +0 -1
- package/dist/functions/debug-auth.d.ts +0 -33
- package/dist/functions/debug-auth.d.ts.map +0 -1
- package/dist/functions/embeddings.d.ts +0 -205
- package/dist/functions/embeddings.d.ts.map +0 -1
- package/dist/functions/handler-registry.d.ts +0 -22
- package/dist/functions/handler-registry.d.ts.map +0 -1
- package/dist/functions/integration-routes.d.ts +0 -45
- package/dist/functions/integration-routes.d.ts.map +0 -1
- package/dist/functions/integrations.d.ts +0 -124
- package/dist/functions/integrations.d.ts.map +0 -1
- package/dist/functions/item-progress.d.ts +0 -41
- package/dist/functions/item-progress.d.ts.map +0 -1
- package/dist/functions/logs.d.ts +0 -162
- package/dist/functions/logs.d.ts.map +0 -1
- package/dist/functions/observability.d.ts +0 -123
- package/dist/functions/observability.d.ts.map +0 -1
- package/dist/functions/pipeline-executions.d.ts +0 -190
- package/dist/functions/pipeline-executions.d.ts.map +0 -1
- package/dist/functions/pipelines.d.ts +0 -171
- package/dist/functions/pipelines.d.ts.map +0 -1
- package/dist/functions/prompt-configs.d.ts +0 -125
- package/dist/functions/prompt-configs.d.ts.map +0 -1
- package/dist/functions/roles.d.ts +0 -118
- package/dist/functions/roles.d.ts.map +0 -1
- package/dist/functions/signal.d.ts +0 -73
- package/dist/functions/signal.d.ts.map +0 -1
- package/dist/functions/system-cron.d.ts +0 -65
- package/dist/functions/system-cron.d.ts.map +0 -1
- package/dist/functions/system.d.ts +0 -29
- package/dist/functions/system.d.ts.map +0 -1
- package/dist/functions/tests.d.ts +0 -28
- package/dist/functions/tests.d.ts.map +0 -1
- package/dist/functions/timers.d.ts +0 -139
- package/dist/functions/timers.d.ts.map +0 -1
- package/dist/functions/triggers.d.ts +0 -203
- package/dist/functions/triggers.d.ts.map +0 -1
- package/dist/functions/types.d.ts +0 -151
- package/dist/functions/types.d.ts.map +0 -1
- package/dist/src/types/types.d.ts +0 -364
- package/dist/src/types/types.d.ts.map +0 -1
- package/index.html +0 -13
- package/netlify.toml +0 -36
- package/package-project.json +0 -71
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/netlify-dev-wrapper.sh +0 -22
- package/vitest.config.ts +0 -45
- /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
- /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
- /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module config/signal-client
|
|
3
|
-
* @audience installer
|
|
4
|
-
* @layer cli
|
|
5
|
-
* @stability stable
|
|
6
|
-
*
|
|
7
|
-
* Universal funnel signal client — browser, Node.js, and Deno compatible.
|
|
8
|
-
* Sends signals to the public `/api/signal` endpoint (no auth required).
|
|
9
|
-
*
|
|
10
|
-
* Usage (marketing site):
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { SpineSignal } from 'spine-framework/config/signal-client'
|
|
13
|
-
* SpineSignal.init({ endpoint: 'https://cortex.yourcompany.com' })
|
|
14
|
-
* SpineSignal.send({ stage: 'anonymous', source: 'mar', action_type: 'page_view', action_value: 1, session_id: sid })
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* Usage (pixel — email tracking):
|
|
18
|
-
* ```html
|
|
19
|
-
* <img src="https://cortex.yourcompany.com/api/signal?format=pixel&stage=identified&source=mar&action_type=email_open&action_value=1&person_id=UUID" width="1" height="1" />
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* Usage (CLI / Node.js):
|
|
23
|
-
* ```ts
|
|
24
|
-
* import { SpineSignal } from 'spine-framework/config/signal-client'
|
|
25
|
-
* await SpineSignal.send({ stage: 'installed', source: 'use', action_type: 'framework_init', action_value: 2, instance_id: serial })
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
export interface SignalPayload {
|
|
30
|
-
stage: 'anonymous' | 'identified' | 'installed'
|
|
31
|
-
source: 'mar' | 'int' | 'use' | 'manual'
|
|
32
|
-
action_type: string
|
|
33
|
-
action_value: 1 | 2 | 5
|
|
34
|
-
pipeline_slug?: string
|
|
35
|
-
anonymous_id?: string
|
|
36
|
-
person_id?: string
|
|
37
|
-
account_id?: string
|
|
38
|
-
session_id?: string
|
|
39
|
-
action_description?: string
|
|
40
|
-
occurred_at?: string
|
|
41
|
-
url?: string
|
|
42
|
-
path?: string
|
|
43
|
-
referrer?: string
|
|
44
|
-
user_agent?: string
|
|
45
|
-
utm_source?: string
|
|
46
|
-
utm_medium?: string
|
|
47
|
-
utm_campaign?: string
|
|
48
|
-
instance_id?: string
|
|
49
|
-
environment?: 'dev' | 'staging' | 'production'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface SignalClientConfig {
|
|
53
|
-
endpoint: string
|
|
54
|
-
timeout?: number
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface SignalResult {
|
|
58
|
-
ok: boolean
|
|
59
|
-
signal_id?: string
|
|
60
|
-
rating?: number
|
|
61
|
-
error?: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let _config: SignalClientConfig = {
|
|
65
|
-
endpoint: 'https://cortex.spine-framework.com',
|
|
66
|
-
timeout: 5000,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Initialize the signal client with a custom endpoint.
|
|
71
|
-
* Call once at app startup before any `send()` calls.
|
|
72
|
-
*/
|
|
73
|
-
function init(config: SignalClientConfig): void {
|
|
74
|
-
_config = { ..._config, ...config }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Send a funnel signal. Fire-and-forget safe — never throws.
|
|
79
|
-
* Returns a result object indicating success or failure.
|
|
80
|
-
*/
|
|
81
|
-
async function send(payload: SignalPayload): Promise<SignalResult> {
|
|
82
|
-
try {
|
|
83
|
-
const controller = new AbortController()
|
|
84
|
-
const timeout = setTimeout(() => controller.abort(), _config.timeout ?? 5000)
|
|
85
|
-
|
|
86
|
-
const response = await fetch(`${_config.endpoint}/api/signal`, {
|
|
87
|
-
method: 'POST',
|
|
88
|
-
headers: { 'Content-Type': 'application/json' },
|
|
89
|
-
body: JSON.stringify(payload),
|
|
90
|
-
signal: controller.signal,
|
|
91
|
-
}).finally(() => clearTimeout(timeout))
|
|
92
|
-
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
const body = await response.json().catch(() => ({}))
|
|
95
|
-
return { ok: false, error: body?.error || `HTTP ${response.status}` }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const data = await response.json()
|
|
99
|
-
return {
|
|
100
|
-
ok: true,
|
|
101
|
-
signal_id: data?.data?.signal_id,
|
|
102
|
-
rating: data?.data?.rating,
|
|
103
|
-
}
|
|
104
|
-
} catch {
|
|
105
|
-
return { ok: false, error: 'Signal send failed (network or timeout)' }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Build a tracking pixel URL for email templates.
|
|
111
|
-
* Embed as: `<img src={SpineSignal.pixelUrl({...})} width="1" height="1" />`
|
|
112
|
-
*/
|
|
113
|
-
function pixelUrl(payload: Omit<SignalPayload, 'session_id'>): string {
|
|
114
|
-
const params = new URLSearchParams({ format: 'pixel' })
|
|
115
|
-
for (const [key, value] of Object.entries(payload)) {
|
|
116
|
-
if (value !== undefined && value !== null) {
|
|
117
|
-
params.set(key, String(value))
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return `${_config.endpoint}/api/signal?${params.toString()}`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Minimal browser session ID — stable for the tab session.
|
|
125
|
-
* Suitable for `session_id` on marketing source signals.
|
|
126
|
-
* Returns a consistent ID stored in sessionStorage (browser only).
|
|
127
|
-
*/
|
|
128
|
-
function getSessionId(): string {
|
|
129
|
-
if (typeof sessionStorage !== 'undefined') {
|
|
130
|
-
const key = '_spine_sid'
|
|
131
|
-
let sid = sessionStorage.getItem(key)
|
|
132
|
-
if (!sid) {
|
|
133
|
-
sid = `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`
|
|
134
|
-
sessionStorage.setItem(key, sid)
|
|
135
|
-
}
|
|
136
|
-
return sid
|
|
137
|
-
}
|
|
138
|
-
// Node.js / non-browser: generate ephemeral ID
|
|
139
|
-
return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export const SpineSignal = {
|
|
143
|
-
init,
|
|
144
|
-
send,
|
|
145
|
-
pixelUrl,
|
|
146
|
-
getSessionId,
|
|
147
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module config/signals
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* Signal endpoint configuration for framework install tracking and engagement.
|
|
9
|
-
*
|
|
10
|
-
* The public framework sends anonymous signals to a configurable endpoint.
|
|
11
|
-
* Private Cortex instances receive and process these signals.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export interface SignalConfig {
|
|
15
|
-
endpoint: string
|
|
16
|
-
timeout: number
|
|
17
|
-
retryAttempts: number
|
|
18
|
-
batchSize: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const signalConfig: SignalConfig = {
|
|
22
|
-
endpoint: process.env.SPINE_SIGNAL_ENDPOINT || 'https://cortex.spine-framework.com',
|
|
23
|
-
timeout: 5000,
|
|
24
|
-
retryAttempts: 3,
|
|
25
|
-
batchSize: 10,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Engagement signal types with scores
|
|
29
|
-
export const engagementSignals = {
|
|
30
|
-
framework_installed: { score: 1, description: 'Framework npm install completion' },
|
|
31
|
-
framework_initialized: { score: 3, description: 'First successful startup' },
|
|
32
|
-
framework_connected_db: { score: 5, description: 'Database connection success' },
|
|
33
|
-
app_install: { score: 4, description: 'App installation' },
|
|
34
|
-
app_initialized: { score: 4, description: 'App first load' },
|
|
35
|
-
app_first_action: { score: 6, description: 'First feature usage' },
|
|
36
|
-
account_created: { score: 8, description: 'Anonymous → identified migration' },
|
|
37
|
-
first_deploy: { score: 10, description: 'Production deployment' },
|
|
38
|
-
team_collaboration: { score: 12, description: 'Multi-user usage' },
|
|
39
|
-
} as const
|
|
40
|
-
|
|
41
|
-
export type EngagementSignalType = keyof typeof engagementSignals
|
|
42
|
-
|
|
43
|
-
// Claim trigger thresholds
|
|
44
|
-
export const claimTriggers = {
|
|
45
|
-
immediate: 0, // User explicitly runs `spine-framework claim`
|
|
46
|
-
gentle_nudge: 3, // After first successful startup
|
|
47
|
-
value_realized: 5, // After database connection
|
|
48
|
-
ecosystem_engaged: 8, // After app installation
|
|
49
|
-
power_user: 10, // After regular usage patterns
|
|
50
|
-
} as const
|
|
51
|
-
|
|
52
|
-
export interface EngagementEntry {
|
|
53
|
-
type: EngagementSignalType
|
|
54
|
-
timestamp: string
|
|
55
|
-
score: number
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface EngagementData {
|
|
59
|
-
framework_install_serial: string
|
|
60
|
-
engagement_score: number
|
|
61
|
-
signals: EngagementEntry[]
|
|
62
|
-
claim_status: 'anonymous' | 'claimed' | 'pending'
|
|
63
|
-
last_claim_prompt?: string
|
|
64
|
-
account_id?: string
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ClaimPrompt {
|
|
68
|
-
timing: keyof typeof claimTriggers
|
|
69
|
-
message: string
|
|
70
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Stability Policy
|
|
2
|
-
|
|
3
|
-
> Version target: **0.x** — breaking changes are allowed until 1.0.
|
|
4
|
-
|
|
5
|
-
## Stability Levels
|
|
6
|
-
|
|
7
|
-
Every exported symbol in `_shared/index.ts` is annotated with one of these levels:
|
|
8
|
-
|
|
9
|
-
| Level | Meaning | Breaking change policy |
|
|
10
|
-
|-------|---------|----------------------|
|
|
11
|
-
| **stable** | Safe for custom code to depend on | Minimum 1 minor version deprecation notice before removal |
|
|
12
|
-
| **evolving** | API may change between minor versions | No deprecation period required, but changelog entry mandatory |
|
|
13
|
-
| **internal** | Not exported; core-contributor only | May change without notice |
|
|
14
|
-
|
|
15
|
-
## Versioning
|
|
16
|
-
|
|
17
|
-
Spine follows [semver](https://semver.org/) with these conventions:
|
|
18
|
-
|
|
19
|
-
- **0.x.y** — Pre-stable. Breaking changes increment the minor version.
|
|
20
|
-
- **1.0.0+** — Stable. Breaking changes increment the major version.
|
|
21
|
-
|
|
22
|
-
## Deprecation Process
|
|
23
|
-
|
|
24
|
-
1. Mark the symbol with `@deprecated` JSDoc tag and a migration hint
|
|
25
|
-
2. Add a console warning on first use (once per process)
|
|
26
|
-
3. Remove after the next minor version bump
|
|
27
|
-
|
|
28
|
-
## What Counts as Breaking
|
|
29
|
-
|
|
30
|
-
- Removing or renaming an exported function/type from `_shared/index.ts`
|
|
31
|
-
- Changing function signatures (required params added, return type changed)
|
|
32
|
-
- Removing a CLI command or changing its required arguments
|
|
33
|
-
- Changing database table/column names in migration files
|
|
34
|
-
- Removing seed data keys from `seed/*.json` schemas
|
|
35
|
-
|
|
36
|
-
## What Does NOT Count as Breaking
|
|
37
|
-
|
|
38
|
-
- Adding new optional parameters to existing functions
|
|
39
|
-
- Adding new exports to `_shared/index.ts`
|
|
40
|
-
- Adding new CLI commands
|
|
41
|
-
- Adding new database tables or columns
|
|
42
|
-
- Performance improvements to existing functions
|
|
43
|
-
- Bug fixes (even if they change observable behavior that was clearly wrong)
|
|
44
|
-
|
|
45
|
-
## Release Checklist
|
|
46
|
-
|
|
47
|
-
1. `npm run test:boundary` — architectural boundaries intact
|
|
48
|
-
2. `npm run test:unit` — unit tests pass
|
|
49
|
-
3. Update `CHANGELOG.md` with breaking changes highlighted
|
|
50
|
-
4. Bump version in `package.json`
|
|
51
|
-
5. `npm run prepublishOnly` — build + test gate
|
|
52
|
-
6. `npm publish`
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Example: Custom App
|
|
2
|
-
|
|
3
|
-
A minimal React app with manifest and routing, installable via `spine-framework install-app`.
|
|
4
|
-
|
|
5
|
-
## Directory Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
custom/apps/my-app/
|
|
9
|
-
├── manifest.json # App metadata, routes, nav items
|
|
10
|
-
├── index.tsx # React entry point (default export)
|
|
11
|
-
├── seed/
|
|
12
|
-
│ ├── types.json # Item types this app provides
|
|
13
|
-
│ └── triggers.json # Automation triggers
|
|
14
|
-
├── components/
|
|
15
|
-
│ └── Dashboard.tsx # App-specific components
|
|
16
|
-
└── package.json # npm package metadata (optional)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## manifest.json
|
|
20
|
-
|
|
21
|
-
```json
|
|
22
|
-
{
|
|
23
|
-
"name": "My App",
|
|
24
|
-
"slug": "my-app",
|
|
25
|
-
"version": "0.1.0",
|
|
26
|
-
"description": "A custom app for demonstration",
|
|
27
|
-
"routes": [
|
|
28
|
-
{ "path": "/", "redirect": "/dashboard" },
|
|
29
|
-
{ "path": "/dashboard", "component": "Dashboard" }
|
|
30
|
-
],
|
|
31
|
-
"nav_items": [
|
|
32
|
-
{ "label": "Dashboard", "path": "/dashboard", "icon": "LayoutDashboard" }
|
|
33
|
-
],
|
|
34
|
-
"required_roles": ["member"]
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## index.tsx
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
import * as React from 'react'
|
|
42
|
-
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
43
|
-
|
|
44
|
-
function Dashboard() {
|
|
45
|
-
return (
|
|
46
|
-
<div className="p-6">
|
|
47
|
-
<h1 className="text-2xl font-bold">My App</h1>
|
|
48
|
-
</div>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default function MyApp() {
|
|
53
|
-
return (
|
|
54
|
-
<Routes>
|
|
55
|
-
<Route path="/" element={<Navigate to="dashboard" replace />} />
|
|
56
|
-
<Route path="dashboard" element={<Dashboard />} />
|
|
57
|
-
</Routes>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
# Create the app scaffold
|
|
66
|
-
spine-framework create-app my-app
|
|
67
|
-
|
|
68
|
-
# Or install an existing app package
|
|
69
|
-
spine-framework install-app my-app
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## How It Works
|
|
73
|
-
|
|
74
|
-
1. `CustomAppLoader` discovers apps via `import.meta.glob('custom/apps/*/index.tsx')`
|
|
75
|
-
2. The app's `manifest.json` provides routing and navigation metadata
|
|
76
|
-
3. Seed data is applied via `spine-framework install-app` (idempotent upserts)
|
|
77
|
-
4. The app is isolated — it cannot import from other apps or core internals
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Example: Custom Function
|
|
2
|
-
|
|
3
|
-
A minimal Netlify Function using Spine's core context system.
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
1. Create `custom/functions/custom_my-handler.ts`
|
|
8
|
-
2. Import `createHandler` and `CoreContext` from `spine-framework/_shared`
|
|
9
|
-
3. Your handler receives a fully-resolved `CoreContext` with:
|
|
10
|
-
- `ctx.principal` — authenticated user/machine identity
|
|
11
|
-
- `ctx.db` — Supabase client (scoped to principal's permissions)
|
|
12
|
-
- `ctx.accountId` — resolved account UUID
|
|
13
|
-
- `ctx.requestId` — unique request trace ID
|
|
14
|
-
|
|
15
|
-
## Key Points
|
|
16
|
-
|
|
17
|
-
- **Naming convention:** Custom functions MUST be prefixed with `custom_`
|
|
18
|
-
- **No static imports from core:** Use the `spine-framework` package exports
|
|
19
|
-
- **Authentication is automatic:** `createHandler` validates the JWT/API key before your code runs
|
|
20
|
-
- **Database access:** Use `ctx.db` for RLS-scoped queries, or import `adminDb` for service-role operations
|
|
21
|
-
|
|
22
|
-
## API
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
GET/POST /.netlify/functions/custom_my-handler?action=list
|
|
26
|
-
Authorization: Bearer <jwt>
|
|
27
|
-
```
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: Custom Netlify Function using Spine core context
|
|
3
|
-
*
|
|
4
|
-
* This demonstrates the minimal pattern for creating a custom function
|
|
5
|
-
* that leverages Spine's authentication, database, and principal system.
|
|
6
|
-
*
|
|
7
|
-
* File: custom/functions/custom_my-handler.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createHandler, adminDb } from 'spine-framework/_shared'
|
|
11
|
-
import type { CoreContext } from 'spine-framework/_shared'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A simple handler that lists items for the current account.
|
|
15
|
-
*/
|
|
16
|
-
async function listAccountItems(ctx: CoreContext) {
|
|
17
|
-
const { data, error } = await ctx.db
|
|
18
|
-
.from('items')
|
|
19
|
-
.select('id, title, type_id, created_at')
|
|
20
|
-
.eq('account_id', ctx.accountId)
|
|
21
|
-
.eq('is_active', true)
|
|
22
|
-
.order('created_at', { ascending: false })
|
|
23
|
-
.limit(50)
|
|
24
|
-
|
|
25
|
-
if (error) {
|
|
26
|
-
return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
statusCode: 200,
|
|
31
|
-
body: JSON.stringify({ items: data }),
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Netlify Function entry point.
|
|
37
|
-
* `createHandler` resolves the principal, validates auth, and provides CoreContext.
|
|
38
|
-
*/
|
|
39
|
-
export const handler = createHandler(async (ctx) => {
|
|
40
|
-
const { action } = ctx.params
|
|
41
|
-
|
|
42
|
-
switch (action) {
|
|
43
|
-
case 'list':
|
|
44
|
-
return listAccountItems(ctx)
|
|
45
|
-
default:
|
|
46
|
-
return { statusCode: 400, body: JSON.stringify({ error: 'Unknown action' }) }
|
|
47
|
-
}
|
|
48
|
-
})
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Example: Custom Webhook Handler
|
|
2
|
-
|
|
3
|
-
Demonstrates the self-registration pattern for webhook handlers.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
1. Create a handler function in `custom/functions/custom_my-webhook.ts`
|
|
8
|
-
2. Register it by calling `registerWebhookHandler()` in `custom/functions/custom_webhook-handlers.ts`
|
|
9
|
-
3. Core resolves handlers at runtime via the `webhook_handlers` DB table — no static imports needed
|
|
10
|
-
|
|
11
|
-
## Handler Function
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
// custom/functions/custom_my-webhook.ts
|
|
15
|
-
|
|
16
|
-
import { createHandler } from 'spine-framework/_shared'
|
|
17
|
-
import type { CoreContext } from 'spine-framework/_shared'
|
|
18
|
-
|
|
19
|
-
export const handler = createHandler(async (ctx: CoreContext) => {
|
|
20
|
-
const payload = ctx.body
|
|
21
|
-
|
|
22
|
-
// Process the webhook payload
|
|
23
|
-
console.log('Received webhook:', payload.event_type)
|
|
24
|
-
|
|
25
|
-
// Do something with the data
|
|
26
|
-
const { error } = await ctx.db
|
|
27
|
-
.from('items')
|
|
28
|
-
.insert({
|
|
29
|
-
title: `Webhook: ${payload.event_type}`,
|
|
30
|
-
type_id: await resolveTypeId('webhook_event'),
|
|
31
|
-
account_id: ctx.accountId,
|
|
32
|
-
data: payload,
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
if (error) {
|
|
36
|
-
return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { statusCode: 200, body: JSON.stringify({ ok: true }) }
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Registration
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
// In custom/functions/custom_webhook-handlers.ts
|
|
47
|
-
|
|
48
|
-
import { registerWebhookHandler } from 'spine-framework/_shared/webhook-registration'
|
|
49
|
-
|
|
50
|
-
registerWebhookHandler({
|
|
51
|
-
name: 'my-webhook',
|
|
52
|
-
functionName: 'custom_my-webhook',
|
|
53
|
-
description: 'Handles incoming webhooks from MyService',
|
|
54
|
-
events: ['integration.webhook'],
|
|
55
|
-
}).catch(console.error)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Integration Config
|
|
59
|
-
|
|
60
|
-
When configuring an integration in the admin panel, set:
|
|
61
|
-
- `handler.path` = `my-webhook` (matches the `name` in registration)
|
|
62
|
-
|
|
63
|
-
## Key Points
|
|
64
|
-
|
|
65
|
-
- **Self-registration is idempotent** — safe to call on every module load
|
|
66
|
-
- **No core changes needed** — just add your handler and register it
|
|
67
|
-
- **Runtime resolution** — core looks up handlers from `webhook_handlers` table via `resolveHandler()`
|
|
68
|
-
- **Events array** — currently `['integration.webhook']` is the standard event type
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module handler-registry
|
|
3
|
-
* @audience core-contributor
|
|
4
|
-
* @layer shared-service
|
|
5
|
-
* @stability stable
|
|
6
|
-
*
|
|
7
|
-
* Static handler registry for esbuild-compatible dynamic dispatch.
|
|
8
|
-
*
|
|
9
|
-
* **THIS FILE IS OVERWRITTEN AT ASSEMBLY TIME.**
|
|
10
|
-
* The stub here (empty registry) is replaced by `assemble-functions.sh`
|
|
11
|
-
* with static imports of every `custom_*.ts` handler found in the assembled
|
|
12
|
-
* functions directory. esbuild can then bundle all handlers into the
|
|
13
|
-
* `integration-routes` function at build time — no runtime dynamic imports.
|
|
14
|
-
*
|
|
15
|
-
* Keys are the handler path (e.g. "funnel-signal") AND the full file name
|
|
16
|
-
* (e.g. "custom_funnel-signal") so lookups work either way.
|
|
17
|
-
*
|
|
18
|
-
* @seeAlso scripts/assemble-functions.sh (generates this file)
|
|
19
|
-
* @seeAlso integration-routes.ts (consumes HANDLER_REGISTRY)
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export const HANDLER_REGISTRY: Record<string, any> = {}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module signal
|
|
3
|
-
* @audience both
|
|
4
|
-
* @layer api-handler
|
|
5
|
-
* @stability stable
|
|
6
|
-
*
|
|
7
|
-
* Public funnel signal ingestion endpoint. Accepts signals from any source
|
|
8
|
-
* (browser JS, CLI, email pixel) without authentication. Rate limiting is
|
|
9
|
-
* enforced at the Netlify edge layer via netlify.toml.
|
|
10
|
-
*
|
|
11
|
-
* **Routed by:** `POST /.netlify/functions/signal`
|
|
12
|
-
* `GET /.netlify/functions/signal?format=pixel`
|
|
13
|
-
*
|
|
14
|
-
* **Request formats:**
|
|
15
|
-
* - `POST` with JSON body — standard signal (returns JSON response)
|
|
16
|
-
* - `GET ?format=pixel` — tracking pixel (returns 1×1 transparent GIF)
|
|
17
|
-
*
|
|
18
|
-
* **Required fields (POST body / GET query params):**
|
|
19
|
-
* - `stage` — 'anonymous' | 'identified' | 'installed'
|
|
20
|
-
* - `source` — 'mar' | 'int' | 'use' | 'manual'
|
|
21
|
-
* - `action_type` — string (e.g. 'page_view', 'form_submit')
|
|
22
|
-
* - `action_value` — 1 | 2 | 5
|
|
23
|
-
* - at least one of: `anonymous_id`, `person_id`, `account_id`
|
|
24
|
-
* - `session_id` — required when source = 'mar'
|
|
25
|
-
*
|
|
26
|
-
* **Optional fields:**
|
|
27
|
-
* - `pipeline_slug` — routes signal to a specific funnel pipeline
|
|
28
|
-
* - `path`, `referrer`, `url`, `utm_*`, `instance_id`, `environment`
|
|
29
|
-
*
|
|
30
|
-
* **Authorization:** None — intentionally public. Signals are best-effort and
|
|
31
|
-
* validated by `processSignal` in `custom_funnel-signal.ts`.
|
|
32
|
-
*
|
|
33
|
-
* **CORS:** Allows all origins for cross-origin browser calls.
|
|
34
|
-
*
|
|
35
|
-
* @seeAlso custom/apps/cortex/functions/custom_funnel-signal.ts (processSignal)
|
|
36
|
-
* @seeAlso config/signal-client.ts (browser/Node client for senders)
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
// 1×1 transparent GIF (base64)
|
|
40
|
-
const PIXEL_GIF_BASE64 = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
|
|
41
|
-
const PIXEL_GIF = Buffer.from(PIXEL_GIF_BASE64, 'base64')
|
|
42
|
-
|
|
43
|
-
const CORS_HEADERS = {
|
|
44
|
-
'Access-Control-Allow-Origin': '*',
|
|
45
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
46
|
-
'Access-Control-Allow-Headers': 'Content-Type, X-Session-Id',
|
|
47
|
-
'Access-Control-Max-Age': '86400',
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const handler = async (event: any) => {
|
|
51
|
-
// Handle CORS preflight
|
|
52
|
-
if (event.httpMethod === 'OPTIONS') {
|
|
53
|
-
return { statusCode: 204, headers: CORS_HEADERS, body: '' }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const requestId = crypto.randomUUID()
|
|
57
|
-
const isPixel = event.queryStringParameters?.format === 'pixel'
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// Build signal payload from body (POST) or query params (GET pixel)
|
|
61
|
-
let signalData: any
|
|
62
|
-
|
|
63
|
-
if (event.httpMethod === 'GET' && isPixel) {
|
|
64
|
-
signalData = { ...event.queryStringParameters }
|
|
65
|
-
// action_value comes as string in query params — coerce to number
|
|
66
|
-
if (signalData.action_value) {
|
|
67
|
-
signalData.action_value = parseInt(signalData.action_value, 10)
|
|
68
|
-
}
|
|
69
|
-
} else if (event.httpMethod === 'POST') {
|
|
70
|
-
if (!event.body) {
|
|
71
|
-
return {
|
|
72
|
-
statusCode: 400,
|
|
73
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
74
|
-
body: JSON.stringify({ error: 'Missing request body' }),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
signalData = JSON.parse(event.body)
|
|
79
|
-
} catch {
|
|
80
|
-
return {
|
|
81
|
-
statusCode: 400,
|
|
82
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
83
|
-
body: JSON.stringify({ error: 'Invalid JSON body' }),
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
return {
|
|
88
|
-
statusCode: 405,
|
|
89
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
90
|
-
body: JSON.stringify({ error: 'Method not allowed' }),
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Resolve processSignal dynamically — core must not statically import custom handlers.
|
|
95
|
-
// After assembly, custom_funnel-signal is a sibling in the same functions directory.
|
|
96
|
-
let processSignal: Function
|
|
97
|
-
try {
|
|
98
|
-
// @ts-ignore — custom_funnel-signal is injected at runtime by the assembly step
|
|
99
|
-
const mod = await import('./custom_funnel-signal')
|
|
100
|
-
processSignal = mod.processSignal
|
|
101
|
-
if (typeof processSignal !== 'function') throw new Error('processSignal not exported')
|
|
102
|
-
} catch {
|
|
103
|
-
return {
|
|
104
|
-
statusCode: 503,
|
|
105
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
106
|
-
body: JSON.stringify({ error: 'Signal handler not available' }),
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Minimal script context for processSignal
|
|
111
|
-
const scriptContext = {
|
|
112
|
-
requestId,
|
|
113
|
-
accountId: null,
|
|
114
|
-
slug: 'public-signal',
|
|
115
|
-
principal: { id: 'system', type: 'machine', accountId: null },
|
|
116
|
-
headers: event.headers || {},
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const scriptEvent = {
|
|
120
|
-
httpMethod: event.httpMethod,
|
|
121
|
-
headers: event.headers || {},
|
|
122
|
-
body: signalData,
|
|
123
|
-
path: event.path || '/signal',
|
|
124
|
-
queryStringParameters: event.queryStringParameters || {},
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Fire-and-forget for pixel requests; await for JSON
|
|
128
|
-
if (isPixel) {
|
|
129
|
-
processSignal(signalData, scriptContext, scriptEvent).catch(() => {
|
|
130
|
-
// Silent fail for pixel — response is always the GIF
|
|
131
|
-
})
|
|
132
|
-
return {
|
|
133
|
-
statusCode: 200,
|
|
134
|
-
headers: {
|
|
135
|
-
...CORS_HEADERS,
|
|
136
|
-
'Content-Type': 'image/gif',
|
|
137
|
-
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
138
|
-
'Pragma': 'no-cache',
|
|
139
|
-
},
|
|
140
|
-
body: PIXEL_GIF.toString('base64'),
|
|
141
|
-
isBase64Encoded: true,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const result = await processSignal(signalData, scriptContext, scriptEvent)
|
|
146
|
-
|
|
147
|
-
if (result.status === 'error') {
|
|
148
|
-
return {
|
|
149
|
-
statusCode: 400,
|
|
150
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
151
|
-
body: JSON.stringify({ error: result.error }),
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
statusCode: 200,
|
|
157
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
158
|
-
body: JSON.stringify({ data: result }),
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
} catch (err: any) {
|
|
162
|
-
return {
|
|
163
|
-
statusCode: 500,
|
|
164
|
-
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
165
|
-
body: JSON.stringify({ error: 'Internal error' }),
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|