@maravilla-labs/platform 0.8.0 → 0.8.3

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.
@@ -17,6 +17,7 @@
17
17
  * and the TS test (`packages/platform/tests/derive-key.test.ts`) to keep
18
18
  * the two in lockstep.
19
19
  */
20
+
20
21
  /** Video container targets supported by v1. */
21
22
  type VideoFormat = 'mp4' | 'webm';
22
23
  /** Image output formats supported by v1. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maravilla-labs/platform",
3
- "version": "0.8.0",
3
+ "version": "0.8.3",
4
4
  "description": "Universal platform client for Maravilla runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "test": "vitest run"
33
33
  },
34
34
  "dependencies": {
35
- "@maravilla-labs/types": "^0.6.0",
35
+ "@maravilla-labs/types": "^0.8.3",
36
36
  "@noble/hashes": "^1.5.0",
37
37
  "livekit-client": "^2.18.1"
38
38
  },
package/src/config.ts CHANGED
@@ -419,6 +419,45 @@ export interface BrandingConfig {
419
419
  custom_css?: string;
420
420
  }
421
421
 
422
+ // ── Email ──
423
+
424
+ /**
425
+ * Transactional email settings for the hosted `_auth` flows (currently the
426
+ * password-reset email). Reconciled server-side on deploy.
427
+ *
428
+ * The visible `From` *address* is always the platform's verified sender
429
+ * (Maravilla Cloud) and is not configurable. The `From` display *name* is safe
430
+ * to customise via `from_name`; surface your own address via `reply_to`.
431
+ */
432
+ export interface EmailConfig {
433
+ /** Master switch. When `false`, the reset flow still mints a token but no email is sent. Defaults to `true`. */
434
+ enabled?: boolean;
435
+ /** `From` display name (e.g. `"Acme Support"`) → `Acme Support <noreply@maravilla.cloud>`. The address is fixed; only the name is yours. */
436
+ from_name?: string;
437
+ /** Reply-To address. The visible `From` is always the platform sender; replies go here. */
438
+ reply_to?: string;
439
+ /** Override the password-reset email subject line. */
440
+ password_reset_subject?: string;
441
+ /** Full custom HTML body. Use the literal `{{reset_url}}` placeholder for the link. */
442
+ password_reset_html?: string;
443
+ /** Full custom plain-text body (same `{{reset_url}}` placeholder). */
444
+ password_reset_text?: string;
445
+ /** Enable passwordless "magic link" email login on the hosted `/_auth/login` page. Off by default; requires `enabled` to send. */
446
+ magic_link_enabled?: boolean;
447
+ /** Override the magic-link email subject line. */
448
+ magic_link_subject?: string;
449
+ /** Full custom HTML body for the magic-link email. Use the literal `{{magic_link_url}}` placeholder. */
450
+ magic_link_html?: string;
451
+ /** Full custom plain-text body for the magic-link email (same `{{magic_link_url}}` placeholder). */
452
+ magic_link_text?: string;
453
+ /** Override the email-verification email subject line. */
454
+ verification_subject?: string;
455
+ /** Full custom HTML body for the verification email. Use the literal `{{verify_url}}` placeholder. */
456
+ verification_html?: string;
457
+ /** Full custom plain-text body for the verification email (same `{{verify_url}}` placeholder). */
458
+ verification_text?: string;
459
+ }
460
+
422
461
  // ── Top-level shape ──
423
462
 
424
463
  export interface AuthConfigBlock {
@@ -429,6 +468,8 @@ export interface AuthConfigBlock {
429
468
  oauth?: OAuthProvidersConfig;
430
469
  security?: SecurityConfig;
431
470
  branding?: BrandingConfig;
471
+ /** Transactional email settings for the hosted `_auth` flows. */
472
+ email?: EmailConfig;
432
473
  /**
433
474
  * Named, reusable policy fragments. Reference a fragment from any
434
475
  * resource policy with `fragment('name')`; {@link defineConfig} expands
@@ -1,4 +1,4 @@
1
- import type { KvNamespace, KvListResult, Database, DbDocument, DbFindOptions, Storage, RealtimeService, PresenceService, AuthService, AuthCaller, AuthUser, AuthSession, AuthField, RegisterOptions, LoginOptions, CreateManagedUserOptions, UserListFilter, UserListResponse, UpdateUserOptions, Relation, AddRelationOptions, ListRelationsOptions, PolicyExplain, CanCheck, PolicyService, VectorIndexSpec, VectorIndexDescriptor, VectorQueryWithFilter, VectorSearchHit, IndexSpec, IndexDescriptor, Workflows, WorkflowHandle, WorkflowRun, WorkflowStepRecord } from './types.js';
1
+ import type { KvNamespace, KvListResult, Database, DbDocument, DbFindOptions, Storage, RealtimeService, PresenceService, AuthService, AuthCaller, AuthUser, AuthSession, AuthField, RegisterOptions, LoginOptions, CreateManagedUserOptions, UserListFilter, UserListResponse, UpdateUserOptions, Relation, AddRelationOptions, ListRelationsOptions, PolicyExplain, CanCheck, PolicyService, VectorIndexSpec, VectorIndexDescriptor, VectorQueryWithFilter, VectorSearchHit, IndexSpec, IndexDescriptor, Workflows, WorkflowHandle, WorkflowRun, WorkflowStepRecord, BrowserClient, FramesClient, BrowserJobHandle, ScreenshotRequest, PdfRequest, FramesRenderRequest } from './types.js';
2
2
  import type { TransformsService, TranscodeOpts, ThumbnailOpts, ResizeOpts, OcrOpts, DocToPdfOpts, DocThumbnailOpts, DocConvertOpts, DocToMarkdownOpts, DocToHtmlOpts, DocReplaceImagesOpts, DocInsertQrCodeOpts, DocTemplateMergeOpts, JobHandle, JobStatusResponse, MediaInfo } from './transforms.js';
3
3
  import { RemoteMediaService } from './media.js';
4
4
  import { getRequestAuthHeader } from './request-scope.js';
@@ -1137,11 +1137,16 @@ export function createRemoteClient(baseUrl: string, tenant: string) {
1137
1137
  const policy = new RemotePolicyService();
1138
1138
  const workflows = new RemoteWorkflows(baseUrl, headers);
1139
1139
 
1140
+ const browser = new RemoteBrowserClient(baseUrl, headers);
1141
+ const frames = new RemoteFramesClient(baseUrl, headers);
1142
+
1140
1143
  return {
1141
1144
  env: {
1142
1145
  KV: kvProxy,
1143
1146
  DB: db,
1144
1147
  STORAGE: storage,
1148
+ BROWSER: browser,
1149
+ FRAMES: frames,
1145
1150
  },
1146
1151
  media,
1147
1152
  realtime,
@@ -1151,6 +1156,70 @@ export function createRemoteClient(baseUrl: string, tenant: string) {
1151
1156
  };
1152
1157
  }
1153
1158
 
1159
+ /**
1160
+ * Remote `platform.env.BROWSER` for development. Posts to the dev-server
1161
+ * `/api/media/browser/*` routes and returns the same {@link BrowserJobHandle}
1162
+ * shape the native runtime returns. Phase 1 plumbing — the worker marks
1163
+ * the job `failed("not implemented")` immediately; clients can still
1164
+ * exercise the full request → handle → poll cycle.
1165
+ *
1166
+ * @internal
1167
+ */
1168
+ class RemoteBrowserClient implements BrowserClient {
1169
+ constructor(
1170
+ private baseUrl: string,
1171
+ private headers: Record<string, string>,
1172
+ ) {}
1173
+
1174
+ private async post(path: string, body: unknown): Promise<BrowserJobHandle> {
1175
+ const res = await fetch(`${this.baseUrl}/api/media/browser${path}`, {
1176
+ method: 'POST',
1177
+ headers: { 'Content-Type': 'application/json', ...this.headers, ...getRequestAuthHeader() },
1178
+ body: JSON.stringify(body),
1179
+ });
1180
+ if (!res.ok) {
1181
+ const text = await res.text();
1182
+ throw new Error(`Browser error (${res.status}): ${text}`);
1183
+ }
1184
+ return res.json() as Promise<BrowserJobHandle>;
1185
+ }
1186
+
1187
+ screenshot(request: ScreenshotRequest): Promise<BrowserJobHandle> {
1188
+ return this.post('/screenshot', request);
1189
+ }
1190
+
1191
+ pdf(request: PdfRequest): Promise<BrowserJobHandle> {
1192
+ return this.post('/pdf', request);
1193
+ }
1194
+ }
1195
+
1196
+ /**
1197
+ * Remote `platform.env.FRAMES` for development. Posts to the dev-server
1198
+ * `/api/media/frames/render` route. Same plumbing stub story as
1199
+ * {@link RemoteBrowserClient}.
1200
+ *
1201
+ * @internal
1202
+ */
1203
+ class RemoteFramesClient implements FramesClient {
1204
+ constructor(
1205
+ private baseUrl: string,
1206
+ private headers: Record<string, string>,
1207
+ ) {}
1208
+
1209
+ async render(request: FramesRenderRequest): Promise<BrowserJobHandle> {
1210
+ const res = await fetch(`${this.baseUrl}/api/media/frames/render`, {
1211
+ method: 'POST',
1212
+ headers: { 'Content-Type': 'application/json', ...this.headers, ...getRequestAuthHeader() },
1213
+ body: JSON.stringify(request),
1214
+ });
1215
+ if (!res.ok) {
1216
+ const text = await res.text();
1217
+ throw new Error(`Frames render error (${res.status}): ${text}`);
1218
+ }
1219
+ return res.json() as Promise<BrowserJobHandle>;
1220
+ }
1221
+ }
1222
+
1154
1223
  /**
1155
1224
  * Remote `platform.media.transforms` for development. Each method posts
1156
1225
  * to the dev-server's `/api/media/transforms/*` route and returns the
package/src/transforms.ts CHANGED
@@ -24,6 +24,43 @@
24
24
  // consume this package client-side.
25
25
  import { sha256 } from '@noble/hashes/sha2';
26
26
 
27
+ // ── Headless-browser + frames-render re-exports ─────────────────────────
28
+ //
29
+ // The contract types live in `@maravilla-labs/types` so the runtime crate
30
+ // and downstream packages (adapter-core, app code) consume one canonical
31
+ // shape. We re-export them through this module so app code that already
32
+ // reaches for `@maravilla-labs/platform/transforms` for media-related
33
+ // helpers also gets the browser / frames request types without a second
34
+ // import.
35
+ //
36
+ // The methods themselves — `platform.env.BROWSER.screenshot(...)`,
37
+ // `.pdf(...)`, `platform.env.FRAMES.render(...)` — are injected by the
38
+ // runtime and typed by the `BrowserClient` / `FramesClient` interfaces
39
+ // (declared in `@maravilla-labs/types`, re-exported below). All three
40
+ // return a `BrowserJobHandle` whose `output_key` equals the
41
+ // caller-supplied `target` storage key; poll status with
42
+ // `platform.media.transforms.job(handle.id)`, then read the rendered
43
+ // bytes from `platform.env.STORAGE`. See `BrowserClient.screenshot` /
44
+ // `BrowserClient.pdf` / `FramesClient.render` for per-method JSDoc with
45
+ // runnable examples; see
46
+ // `docs/recipes/{screenshot-public-url,render-animated-title-card,og-image-from-own-page}.md`
47
+ // for full walkthroughs.
48
+ export type {
49
+ BrowserClient,
50
+ BrowserCookie,
51
+ BrowserJobHandle,
52
+ BrowserJobStatus,
53
+ BrowserViewport,
54
+ BrowserWaitFor,
55
+ FramesClient,
56
+ FramesCodec,
57
+ FramesRenderRequest,
58
+ PdfPageFormat,
59
+ PdfRequest,
60
+ ScreenshotFormat,
61
+ ScreenshotRequest,
62
+ } from '@maravilla-labs/types';
63
+
27
64
  // ── Types — mirror `crates/platform/src/media/transforms/types.rs` ──────
28
65
 
29
66
  /** Video container targets supported by v1. */
package/src/types.ts CHANGED
@@ -741,9 +741,9 @@ export interface Storage {
741
741
  * This is the main interface for accessing KV storage, database, and object storage operations.
742
742
  */
743
743
  export interface PlatformEnv {
744
- /**
744
+ /**
745
745
  * Key-Value storage namespaces. Access different KV namespaces by property name.
746
- *
746
+ *
747
747
  * @example
748
748
  * ```typescript
749
749
  * const userCache = platform.env.KV.users;
@@ -755,6 +755,19 @@ export interface PlatformEnv {
755
755
  DB: Database;
756
756
  /** Object/file storage interface */
757
757
  STORAGE: Storage;
758
+ /**
759
+ * Headless-browser screenshot / PDF render. Returns a job handle
760
+ * polled via the shared transforms job ledger. Phase 1 plumbing stub —
761
+ * the worker marks browser jobs `failed("not implemented")` until
762
+ * Task #2 lands the Chromium pipeline.
763
+ */
764
+ BROWSER: BrowserClient;
765
+ /**
766
+ * Deterministic HTML → MP4 / WebM render. Path-only: the renderer hits
767
+ * a route on the tenant's own deployment. Phase 1 plumbing stub —
768
+ * real implementation in Task #3.
769
+ */
770
+ FRAMES: FramesClient;
758
771
  }
759
772
 
760
773
  /**
@@ -805,7 +818,22 @@ export interface PresenceService {
805
818
  //
806
819
  // `AuthService` is also imported (not just re-exported) because the
807
820
  // `Platform` interface below references it within this module's scope.
808
- import type { AuthService } from '@maravilla-labs/types';
821
+ import type { AuthService, BrowserClient, FramesClient } from '@maravilla-labs/types';
822
+ export type {
823
+ BrowserClient,
824
+ FramesClient,
825
+ BrowserCookie,
826
+ BrowserJobHandle,
827
+ BrowserJobStatus,
828
+ BrowserViewport,
829
+ BrowserWaitFor,
830
+ FramesCodec,
831
+ FramesRenderRequest,
832
+ PdfPageFormat,
833
+ PdfRequest,
834
+ ScreenshotFormat,
835
+ ScreenshotRequest,
836
+ } from '@maravilla-labs/types';
809
837
  export type {
810
838
  AuthUser,
811
839
  AuthCaller,