@orion-studios/cms 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/analytics/react.d.ts +53 -0
- package/dist/analytics/react.js +195 -0
- package/dist/blocks/index.d.ts +222 -0
- package/dist/blocks/index.js +338 -0
- package/dist/chunk-HVJCF2IZ.js +76 -0
- package/dist/chunk-VPUODCNH.js +448 -0
- package/dist/chunk-WQDHEQDE.js +527 -0
- package/dist/content/index.d.ts +51 -0
- package/dist/content/index.js +8 -0
- package/dist/forms/index.d.ts +70 -0
- package/dist/forms/index.js +38 -0
- package/dist/forms/react.d.ts +45 -0
- package/dist/forms/react.js +8 -0
- package/dist/server/index.d.ts +403 -0
- package/dist/server/index.js +2280 -0
- package/dist/studio/index.d.ts +534 -0
- package/dist/studio/index.js +3824 -0
- package/dist/studio/styles.css +444 -0
- package/dist/submission-BKdBedOe.d.ts +61 -0
- package/dist/submission-CzrfXu17.d.ts +30 -0
- package/package.json +97 -0
- package/sql/bootstrap.sql +458 -0
- package/sql/migrations/0001_atomic_scheduled_publish.sql +68 -0
- package/sql/migrations/0002_rate_limits.sql +62 -0
- package/sql/migrations/0003_atomic_global_update.sql +46 -0
- package/sql/migrations/0004_analytics_visitor_tracking.sql +8 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { F as FormConfig } from '../submission-CzrfXu17.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The shared, config-driven form renderer: the same component renders a form
|
|
7
|
+
* in the Studio's form editor preview and on the public site, from the same
|
|
8
|
+
* `cms_forms.config`. Sites theme it through the `classNames` map — the
|
|
9
|
+
* markup is plain semantic HTML.
|
|
10
|
+
*/
|
|
11
|
+
type FormRendererClassNames = {
|
|
12
|
+
form?: string;
|
|
13
|
+
field?: string;
|
|
14
|
+
fieldError?: string;
|
|
15
|
+
errorText?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
input?: string;
|
|
18
|
+
textarea?: string;
|
|
19
|
+
select?: string;
|
|
20
|
+
checkbox?: string;
|
|
21
|
+
radioGroup?: string;
|
|
22
|
+
button?: string;
|
|
23
|
+
success?: string;
|
|
24
|
+
formError?: string;
|
|
25
|
+
stepTitle?: string;
|
|
26
|
+
};
|
|
27
|
+
type FormRendererProps = {
|
|
28
|
+
/** Form slug — submissions POST to `${basePath}/forms/${slug}/submit`. */
|
|
29
|
+
slug: string;
|
|
30
|
+
config: FormConfig;
|
|
31
|
+
successMessage?: string;
|
|
32
|
+
/** Defaults to /api/cms. */
|
|
33
|
+
basePath?: string;
|
|
34
|
+
classNames?: FormRendererClassNames;
|
|
35
|
+
/** Optional heading + intro rendered above the fields. */
|
|
36
|
+
title?: ReactNode;
|
|
37
|
+
intro?: ReactNode;
|
|
38
|
+
submitLabel?: string;
|
|
39
|
+
/** Preview mode (Studio): renders fields but never submits. */
|
|
40
|
+
preview?: boolean;
|
|
41
|
+
onSuccess?: () => void;
|
|
42
|
+
};
|
|
43
|
+
declare function FormRenderer({ slug, config, successMessage, basePath, classNames, title, intro, submitLabel, preview, onSuccess, }: FormRendererProps): react_jsx_runtime.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { FormRenderer, type FormRendererClassNames, type FormRendererProps };
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { BlockRegistry, PageLayout } from '../blocks/index.js';
|
|
3
|
+
import { F as FormConfig, R as RateLimitStore } from '../submission-BKdBedOe.js';
|
|
4
|
+
export { CONTENT_CACHE_TAG } from '../content/index.js';
|
|
5
|
+
import 'react';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Submission email notifications. Vendor-thin: one fetch to Resend's REST API
|
|
10
|
+
* (no SDK), swappable via the `sendEmail` route option for tests or other
|
|
11
|
+
* providers. Failures are logged and never break the submission.
|
|
12
|
+
*/
|
|
13
|
+
type EmailMessage = {
|
|
14
|
+
to: string[];
|
|
15
|
+
subject: string;
|
|
16
|
+
text: string;
|
|
17
|
+
replyTo?: string;
|
|
18
|
+
};
|
|
19
|
+
type EmailSender = (message: EmailMessage) => Promise<void>;
|
|
20
|
+
type ResendSenderOptions = {
|
|
21
|
+
/** Defaults to process.env.RESEND_API_KEY. */
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
/** Defaults to process.env.CMS_EMAIL_FROM, then onboarding@resend.dev. */
|
|
24
|
+
from?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Returns a Resend-backed sender, or null when no API key is configured. */
|
|
27
|
+
declare function createResendSender(options?: ResendSenderOptions): EmailSender | null;
|
|
28
|
+
/** Human-readable plain-text rendering of a submission's data. */
|
|
29
|
+
declare function formatSubmissionText(data: Record<string, unknown>): string;
|
|
30
|
+
type NotifySubmissionArgs = {
|
|
31
|
+
sendEmail: EmailSender;
|
|
32
|
+
formTitle: string;
|
|
33
|
+
config: FormConfig;
|
|
34
|
+
successMessage?: string;
|
|
35
|
+
data: Record<string, unknown>;
|
|
36
|
+
siteName?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Sends the notification email(s) for one accepted submission: a copy to each
|
|
40
|
+
* configured notify address, and (optionally) an auto-reply to the submitter.
|
|
41
|
+
* Never throws — a mail failure must not break the submit.
|
|
42
|
+
*/
|
|
43
|
+
declare function notifySubmission(args: NotifySubmissionArgs): Promise<void>;
|
|
44
|
+
|
|
45
|
+
type CmsRoutesOptions = {
|
|
46
|
+
registry: BlockRegistry;
|
|
47
|
+
/** Overrides the service client (tests). */
|
|
48
|
+
client?: SupabaseClient;
|
|
49
|
+
/** Origins allowed for public form submissions. Defaults to same-origin. */
|
|
50
|
+
allowedOrigins?: string[];
|
|
51
|
+
/** Rate limiter for public submissions. Defaults to in-memory (5/min/IP). */
|
|
52
|
+
rateLimitStore?: RateLimitStore | null;
|
|
53
|
+
/** Token accepted by POST /sync and POST /cron/* in addition to admin bearer auth. */
|
|
54
|
+
syncToken?: string;
|
|
55
|
+
/** Extra domains never flagged as email typos. */
|
|
56
|
+
knownGoodEmailDomains?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Email sender for submission notifications. Defaults to Resend when
|
|
59
|
+
* RESEND_API_KEY is set; pass null to disable.
|
|
60
|
+
*/
|
|
61
|
+
sendEmail?: EmailSender | null;
|
|
62
|
+
/** Site name used in notification emails. */
|
|
63
|
+
siteName?: string;
|
|
64
|
+
/** Secret for draft preview tokens. Defaults to the service-role key. */
|
|
65
|
+
previewSecret?: string;
|
|
66
|
+
/** Cloudflare Turnstile secret — when set, public submits must include a valid `_turnstileToken`. */
|
|
67
|
+
turnstileSecret?: string;
|
|
68
|
+
/** Max upload size in bytes (default 15 MB). */
|
|
69
|
+
maxUploadBytes?: number;
|
|
70
|
+
/** Days of raw analytics events to keep (default 90). */
|
|
71
|
+
analyticsRetentionDays?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Dev-only: uploads are stored as data URLs instead of Supabase Storage so
|
|
74
|
+
* the memory backend can serve them. Never enable in production.
|
|
75
|
+
*/
|
|
76
|
+
memoryMode?: boolean;
|
|
77
|
+
};
|
|
78
|
+
type RouteContext = {
|
|
79
|
+
params: Promise<{
|
|
80
|
+
path?: string[];
|
|
81
|
+
}> | {
|
|
82
|
+
path?: string[];
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Durable rate limiting for multi-instance serverless. Backed by an atomic
|
|
87
|
+
* cms_rate_limit_consume() upsert so concurrent requests across lambdas share
|
|
88
|
+
* one counter with no count-then-insert race. Fails open (allow) if the DB /
|
|
89
|
+
* migration is unavailable, so a transient error never blocks real users.
|
|
90
|
+
*/
|
|
91
|
+
declare function createDurableRateLimitStore(getClient: () => SupabaseClient, options?: {
|
|
92
|
+
max?: number;
|
|
93
|
+
windowMs?: number;
|
|
94
|
+
bucket?: string;
|
|
95
|
+
}): RateLimitStore;
|
|
96
|
+
declare function createCmsRoutes(options: CmsRoutesOptions): {
|
|
97
|
+
GET: (request: Request, context: RouteContext) => Promise<Response>;
|
|
98
|
+
POST: (request: Request, context: RouteContext) => Promise<Response>;
|
|
99
|
+
PATCH: (request: Request, context: RouteContext) => Promise<Response>;
|
|
100
|
+
DELETE: (request: Request, context: RouteContext) => Promise<Response>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
declare const PREVIEW_TOKEN_TTL_MS: number;
|
|
104
|
+
declare function createPreviewToken(pageId: string, secret: string, ttlMs?: number): string;
|
|
105
|
+
/** Returns the page id when the token is valid and unexpired, else null. */
|
|
106
|
+
declare function verifyPreviewToken(token: string, secret: string): string | null;
|
|
107
|
+
type PreviewPage = {
|
|
108
|
+
id: string;
|
|
109
|
+
slug: string;
|
|
110
|
+
path: string;
|
|
111
|
+
title: string;
|
|
112
|
+
seo: Record<string, unknown>;
|
|
113
|
+
layout: PageLayout;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Site-side helper: verifies a preview token and loads the page's DRAFT
|
|
117
|
+
* layout with the given (service or memory) client.
|
|
118
|
+
*/
|
|
119
|
+
declare function getPreviewPage(client: SupabaseClient, token: string, secret: string): Promise<PreviewPage | null>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Pure analytics aggregation: raw event rows in, the full dashboard payload
|
|
123
|
+
* out. Isomorphic and dependency-free so the same code serves the Supabase
|
|
124
|
+
* and memory backends and is trivially testable.
|
|
125
|
+
*
|
|
126
|
+
* Conversion = a call tap, a form submission, or an email click. A session
|
|
127
|
+
* "converted" when it contains at least one conversion event.
|
|
128
|
+
*/
|
|
129
|
+
type StoredEvent = {
|
|
130
|
+
id?: number | string;
|
|
131
|
+
session_key: string;
|
|
132
|
+
visitor_key?: string;
|
|
133
|
+
type: string;
|
|
134
|
+
name: string;
|
|
135
|
+
path: string;
|
|
136
|
+
referrer: string;
|
|
137
|
+
utm: Record<string, string> | null;
|
|
138
|
+
device: string;
|
|
139
|
+
region: string;
|
|
140
|
+
city: string;
|
|
141
|
+
meta: Record<string, unknown> | null;
|
|
142
|
+
created_at: string;
|
|
143
|
+
};
|
|
144
|
+
type AnalyticsKpis = {
|
|
145
|
+
visitors: number;
|
|
146
|
+
identifiedVisitors: number;
|
|
147
|
+
returningVisitors: number;
|
|
148
|
+
sessions: number;
|
|
149
|
+
pageviews: number;
|
|
150
|
+
pagesPerVisitor: number;
|
|
151
|
+
calls: number;
|
|
152
|
+
formSubmits: number;
|
|
153
|
+
emails: number;
|
|
154
|
+
portalClicks: number;
|
|
155
|
+
conversions: number;
|
|
156
|
+
conversionRate: number;
|
|
157
|
+
};
|
|
158
|
+
type AnalyticsSummary = {
|
|
159
|
+
range: {
|
|
160
|
+
from: string;
|
|
161
|
+
to: string;
|
|
162
|
+
};
|
|
163
|
+
kpis: AnalyticsKpis;
|
|
164
|
+
previous: AnalyticsKpis;
|
|
165
|
+
trend: Array<{
|
|
166
|
+
day: string;
|
|
167
|
+
visitors: number;
|
|
168
|
+
conversions: number;
|
|
169
|
+
}>;
|
|
170
|
+
pages: Array<{
|
|
171
|
+
path: string;
|
|
172
|
+
views: number;
|
|
173
|
+
entries: number;
|
|
174
|
+
conversions: number;
|
|
175
|
+
}>;
|
|
176
|
+
sources: Array<{
|
|
177
|
+
source: string;
|
|
178
|
+
sessions: number;
|
|
179
|
+
conversions: number;
|
|
180
|
+
}>;
|
|
181
|
+
locations: Array<{
|
|
182
|
+
location: string;
|
|
183
|
+
sessions: number;
|
|
184
|
+
}>;
|
|
185
|
+
devices: Array<{
|
|
186
|
+
device: string;
|
|
187
|
+
sessions: number;
|
|
188
|
+
}>;
|
|
189
|
+
hours: number[];
|
|
190
|
+
paths: Array<{
|
|
191
|
+
path: string[];
|
|
192
|
+
count: number;
|
|
193
|
+
converted: number;
|
|
194
|
+
}>;
|
|
195
|
+
forms: Array<{
|
|
196
|
+
form: string;
|
|
197
|
+
views: number;
|
|
198
|
+
starts: number;
|
|
199
|
+
submits: number;
|
|
200
|
+
}>;
|
|
201
|
+
notFound: Array<{
|
|
202
|
+
path: string;
|
|
203
|
+
count: number;
|
|
204
|
+
}>;
|
|
205
|
+
};
|
|
206
|
+
declare function aggregateAnalytics(events: StoredEvent[], previousEvents: StoredEvent[], range: {
|
|
207
|
+
from: string;
|
|
208
|
+
to: string;
|
|
209
|
+
}): AnalyticsSummary;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Server-side analytics ingest: validation, bot filtering, and the
|
|
213
|
+
* cookieless session key. The client payload stays dumb — everything
|
|
214
|
+
* sensitive (session, device, geo) derives from the request server-side.
|
|
215
|
+
*/
|
|
216
|
+
declare const EVENT_TYPES: readonly ["pageview", "click", "form", "not_found"];
|
|
217
|
+
type AnalyticsEventType = (typeof EVENT_TYPES)[number];
|
|
218
|
+
type IncomingEvent = {
|
|
219
|
+
type: AnalyticsEventType;
|
|
220
|
+
name?: string;
|
|
221
|
+
path?: string;
|
|
222
|
+
referrer?: string;
|
|
223
|
+
utm?: Record<string, string>;
|
|
224
|
+
meta?: Record<string, unknown>;
|
|
225
|
+
};
|
|
226
|
+
type AnalyticsEventRow = {
|
|
227
|
+
session_key: string;
|
|
228
|
+
visitor_key: string;
|
|
229
|
+
type: AnalyticsEventType;
|
|
230
|
+
name: string;
|
|
231
|
+
path: string;
|
|
232
|
+
referrer: string;
|
|
233
|
+
utm: Record<string, string>;
|
|
234
|
+
device: string;
|
|
235
|
+
region: string;
|
|
236
|
+
city: string;
|
|
237
|
+
meta: Record<string, unknown>;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Daily-rotating visitor hash: same visitor+day → same key, next day → a new
|
|
241
|
+
* unrelated key. Orders one visit into a path; can't track anyone over time.
|
|
242
|
+
*/
|
|
243
|
+
declare function sessionKeyFor(ip: string, userAgent: string, secret: string, now?: Date): string;
|
|
244
|
+
/** One-way server hash for a consented first-party visitor cookie. */
|
|
245
|
+
declare function visitorKeyFor(visitorId: unknown, secret: string): string;
|
|
246
|
+
/** True when the request looks like an automated client, not a visitor. */
|
|
247
|
+
declare function isBotRequest(userAgent: string): boolean;
|
|
248
|
+
/** Coarse device class — enough for a mobile/desktop split, nothing more. */
|
|
249
|
+
declare function deviceFrom(userAgent: string): string;
|
|
250
|
+
type ParseEventsResult = {
|
|
251
|
+
rows: AnalyticsEventRow[];
|
|
252
|
+
dropped: number;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Validates and normalizes a client batch into insertable rows. Unknown
|
|
256
|
+
* types, oversized fields, and junk are dropped silently — analytics ingest
|
|
257
|
+
* never errors at a visitor.
|
|
258
|
+
*/
|
|
259
|
+
declare function parseEventBatch(body: unknown, context: {
|
|
260
|
+
sessionKey: string;
|
|
261
|
+
visitorKey?: string;
|
|
262
|
+
device: string;
|
|
263
|
+
region: string;
|
|
264
|
+
city: string;
|
|
265
|
+
}): ParseEventsResult;
|
|
266
|
+
/** Reads Vercel's geo headers (present in production; empty elsewhere). */
|
|
267
|
+
declare function geoFrom(request: Request): {
|
|
268
|
+
region: string;
|
|
269
|
+
city: string;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Server-side Supabase clients.
|
|
274
|
+
*
|
|
275
|
+
* - The service client (service-role key) is the API layer's data access path.
|
|
276
|
+
* It bypasses RLS; authorization is enforced by the permission matrix in
|
|
277
|
+
* routes.ts — a single, testable enforcement point.
|
|
278
|
+
* - User identity comes from the Studio's bearer token (its Supabase session
|
|
279
|
+
* access token), verified via auth.getUser(). Stateless: no cookie plumbing.
|
|
280
|
+
*/
|
|
281
|
+
type CmsEnv = {
|
|
282
|
+
supabaseUrl: string;
|
|
283
|
+
serviceRoleKey: string;
|
|
284
|
+
};
|
|
285
|
+
declare function readCmsEnv(): CmsEnv;
|
|
286
|
+
declare function getServiceClient(env?: CmsEnv): SupabaseClient;
|
|
287
|
+
/** Test seam: inject a fake client. */
|
|
288
|
+
declare function setServiceClientForTesting(client: SupabaseClient | null): void;
|
|
289
|
+
type CmsUser = {
|
|
290
|
+
id: string;
|
|
291
|
+
email: string | null;
|
|
292
|
+
role: 'admin' | 'developer' | 'editor' | 'content';
|
|
293
|
+
name: string;
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Resolves the requesting user from the Authorization bearer token and loads
|
|
297
|
+
* their CMS role. Returns null for anonymous/invalid tokens.
|
|
298
|
+
*/
|
|
299
|
+
declare function resolveUser(request: Request, client?: SupabaseClient): Promise<CmsUser | null>;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The capability-tier permission matrix. Enforced in the API layer regardless
|
|
303
|
+
* of what the Studio UI shows.
|
|
304
|
+
*/
|
|
305
|
+
type CmsRole = CmsUser['role'];
|
|
306
|
+
type CmsAction = 'pages.read' | 'pages.saveDraft' | 'pages.changeStructure' | 'pages.publish' | 'pages.create' | 'pages.delete' | 'pages.restore' | 'globals.read' | 'globals.write' | 'media.read' | 'media.upload' | 'media.update' | 'media.delete' | 'forms.read' | 'forms.write' | 'forms.delete' | 'submissions.read' | 'submissions.manage' | 'redirects.manage' | 'activity.read' | 'analytics.read' | 'sync.run' | 'users.manage';
|
|
307
|
+
declare function can(user: CmsUser | null, action: CmsAction): boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Structural change detection for the content tier: `content` users may edit
|
|
310
|
+
* block data in place but not add, remove, retype, or reorder blocks.
|
|
311
|
+
*/
|
|
312
|
+
declare function isStructuralChange(previous: PageLayout, next: PageLayout): boolean;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Content-as-code sync: the single implementation used by both the
|
|
316
|
+
* POST /api/cms/sync route and local `npm run sync` scripts.
|
|
317
|
+
*
|
|
318
|
+
* Contract (proven on Currin): create missing pages, refresh metadata always,
|
|
319
|
+
* write layouts only where the page is not builder-owned, upsert globals and
|
|
320
|
+
* forms. Idempotent.
|
|
321
|
+
*/
|
|
322
|
+
type SyncPageInput = {
|
|
323
|
+
slug: string;
|
|
324
|
+
path?: string;
|
|
325
|
+
title?: string;
|
|
326
|
+
seo?: Record<string, unknown>;
|
|
327
|
+
layout?: unknown;
|
|
328
|
+
};
|
|
329
|
+
type SyncGlobalInput = {
|
|
330
|
+
key: string;
|
|
331
|
+
data: Record<string, unknown>;
|
|
332
|
+
};
|
|
333
|
+
type SyncFormInput = {
|
|
334
|
+
slug: string;
|
|
335
|
+
title?: string;
|
|
336
|
+
config?: Record<string, unknown>;
|
|
337
|
+
successMessage?: string;
|
|
338
|
+
};
|
|
339
|
+
type SyncMediaInput = {
|
|
340
|
+
storagePath: string;
|
|
341
|
+
filename?: string;
|
|
342
|
+
alt?: string;
|
|
343
|
+
caption?: string;
|
|
344
|
+
mimeType?: string;
|
|
345
|
+
width?: number | null;
|
|
346
|
+
height?: number | null;
|
|
347
|
+
filesize?: number | null;
|
|
348
|
+
};
|
|
349
|
+
type SyncInput = {
|
|
350
|
+
pages?: SyncPageInput[];
|
|
351
|
+
globals?: SyncGlobalInput[];
|
|
352
|
+
forms?: SyncFormInput[];
|
|
353
|
+
media?: SyncMediaInput[];
|
|
354
|
+
};
|
|
355
|
+
type SyncResult = {
|
|
356
|
+
synced: string[];
|
|
357
|
+
skipped: Array<{
|
|
358
|
+
slug: string;
|
|
359
|
+
issues: unknown;
|
|
360
|
+
}>;
|
|
361
|
+
globals: number;
|
|
362
|
+
forms: number;
|
|
363
|
+
media: number;
|
|
364
|
+
/** Non-page resources that failed to write (globals/forms). */
|
|
365
|
+
failed: Array<{
|
|
366
|
+
kind: 'global' | 'form';
|
|
367
|
+
key: string;
|
|
368
|
+
error: string;
|
|
369
|
+
}>;
|
|
370
|
+
};
|
|
371
|
+
declare function runContentSync(client: SupabaseClient, registry: BlockRegistry, input: SyncInput): Promise<SyncResult>;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* In-memory CMS backend for local development and end-to-end tests.
|
|
375
|
+
*
|
|
376
|
+
* Lets a freshly scaffolded site run `npm run dev` and use the full Studio
|
|
377
|
+
* with ZERO Supabase setup: data lives in process memory, auth accepts the
|
|
378
|
+
* dev token, and uploads become data URLs. Never use in production — the
|
|
379
|
+
* route factory only enables it when explicitly passed.
|
|
380
|
+
*
|
|
381
|
+
* Emulates exactly the supabase-js surface the CMS uses: filtered selects,
|
|
382
|
+
* insert/upsert/delete, the four cms_* RPC functions, storage, and
|
|
383
|
+
* auth.getUser.
|
|
384
|
+
*/
|
|
385
|
+
type Row = Record<string, unknown>;
|
|
386
|
+
declare const MEMORY_DEV_TOKEN = "orion-dev-token";
|
|
387
|
+
declare class MemoryStore {
|
|
388
|
+
tables: Map<string, Row[]>;
|
|
389
|
+
private serial;
|
|
390
|
+
table(name: string): Row[];
|
|
391
|
+
setTable(name: string, rows: Row[]): void;
|
|
392
|
+
nextSerial(): number;
|
|
393
|
+
}
|
|
394
|
+
type MemoryCms = {
|
|
395
|
+
client: SupabaseClient;
|
|
396
|
+
store: MemoryStore;
|
|
397
|
+
devToken: string;
|
|
398
|
+
};
|
|
399
|
+
declare function createMemoryCms(): MemoryCms;
|
|
400
|
+
/** Process-wide singleton for Next.js dev servers (module state survives HMR via globalThis). */
|
|
401
|
+
declare function getMemoryCms(): MemoryCms;
|
|
402
|
+
|
|
403
|
+
export { type AnalyticsEventType, type AnalyticsKpis, type AnalyticsSummary, type CmsAction, type CmsEnv, type CmsRole, type CmsRoutesOptions, type CmsUser, type EmailMessage, type EmailSender, type IncomingEvent, MEMORY_DEV_TOKEN, type MemoryCms, PREVIEW_TOKEN_TTL_MS, type PreviewPage, type ResendSenderOptions, type StoredEvent, type SyncFormInput, type SyncGlobalInput, type SyncInput, type SyncMediaInput, type SyncPageInput, type SyncResult, aggregateAnalytics, can, createCmsRoutes, createDurableRateLimitStore, createMemoryCms, createPreviewToken, createResendSender, deviceFrom, formatSubmissionText, geoFrom, getMemoryCms, getPreviewPage, getServiceClient, isBotRequest, isStructuralChange, notifySubmission, parseEventBatch, readCmsEnv, resolveUser, runContentSync, sessionKeyFor, setServiceClientForTesting, verifyPreviewToken, visitorKeyFor };
|