@maravilla-labs/platform 0.2.5 → 0.3.1
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/dist/config.d.ts +12 -1
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +45 -2
- package/dist/events.js +5 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.js +169 -1
- package/dist/index.js.map +1 -1
- package/dist/transforms-KzpoYW43.d.ts +156 -0
- package/package.json +8 -3
- package/src/config.ts +10 -0
- package/src/events.ts +52 -0
- package/src/index.ts +1 -0
- package/src/remote-client.ts +139 -1
- package/src/ren.ts +6 -0
- package/src/transforms.ts +251 -0
- package/src/types.ts +99 -0
- package/tests/derive-key.test.ts +68 -0
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { T as TransformsConfig } from './transforms-KzpoYW43.js';
|
|
2
|
+
export { a as TransformsPatternSpec } from './transforms-KzpoYW43.js';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.
|
|
3
6
|
*
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
type SecretRef = string | {
|
|
39
42
|
env: string;
|
|
40
43
|
};
|
|
44
|
+
|
|
41
45
|
interface ResourceDefinition {
|
|
42
46
|
/** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */
|
|
43
47
|
name: string;
|
|
@@ -172,6 +176,13 @@ interface MaravillaConfig {
|
|
|
172
176
|
auth?: AuthConfigBlock;
|
|
173
177
|
/** Declarative database indexes (regular + vector). Reconciled upsert-only on deploy. */
|
|
174
178
|
database?: DatabaseConfigBlock;
|
|
179
|
+
/**
|
|
180
|
+
* Declarative media transforms — each entry compiles into a synthetic
|
|
181
|
+
* `storage.put` event handler that fans out the declared
|
|
182
|
+
* `platform.media.transforms.*` calls (via `Promise.all`) for every
|
|
183
|
+
* matching upload. See {@link TransformsConfig}.
|
|
184
|
+
*/
|
|
185
|
+
transforms?: TransformsConfig;
|
|
175
186
|
}
|
|
176
187
|
/** MongoDB-style key direction: `1` ascending, `-1` descending. */
|
|
177
188
|
type IndexDirectionConfig = 1 | -1;
|
|
@@ -233,4 +244,4 @@ interface DatabaseConfigBlock {
|
|
|
233
244
|
*/
|
|
234
245
|
declare function defineConfig(config: MaravillaConfig): MaravillaConfig;
|
|
235
246
|
|
|
236
|
-
export { type AuthConfigBlock, type BrandingConfig, type DatabaseConfigBlock, type DocumentIndexDeclaration, type GroupDefinition, type GroupPermissionDefinition, type IndexDirectionConfig, type MaravillaConfig, type OAuthProviderDefinition, type OAuthProvidersConfig, type PasswordPolicyDefinition, type RegistrationConfig, type RegistrationFieldDefinition, type RelationTypeDefinition, type ResourceDefinition, type SecretRef, type SecurityConfig, type SessionConfigDefinition, type VectorIndexDeclaration, type VectorMetricConfig, type VectorStorageConfig, defineConfig };
|
|
247
|
+
export { type AuthConfigBlock, type BrandingConfig, type DatabaseConfigBlock, type DocumentIndexDeclaration, type GroupDefinition, type GroupPermissionDefinition, type IndexDirectionConfig, type MaravillaConfig, type OAuthProviderDefinition, type OAuthProvidersConfig, type PasswordPolicyDefinition, type RegistrationConfig, type RegistrationFieldDefinition, type RelationTypeDefinition, type ResourceDefinition, type SecretRef, type SecurityConfig, type SessionConfigDefinition, TransformsConfig, type VectorIndexDeclaration, type VectorMetricConfig, type VectorStorageConfig, defineConfig };
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["/**\n * @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.\n *\n * Declares your project's auth settings (resources, groups, relations,\n * registration fields, OAuth providers, security policy, branding) alongside\n * your code. The Maravilla adapter reads this at build time and reconciles\n * the settings into delivery on deploy.\n *\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [\n * { name: 'todos', title: 'Todos', actions: ['read', 'write'],\n * policy: 'auth.user_id == node.owner' },\n * ],\n * },\n * });\n * ```\n *\n * Omitted sections leave the DB alone — partial adoption is explicitly\n * supported. List-based sections (`resources`, `groups`, `relations`,\n * `oauth`) are upserted and never auto-delete DB-only entries. Singleton\n * sections (`registration`, `security`, `branding`) are replaced wholesale\n * when declared.\n */\n\n/**\n * String value that may either be a literal secret or a reference to an\n * environment variable on the **tenant** (resolved server-side at\n * reconcile time, never shipped plaintext in the manifest).\n *\n * Accepted forms:\n * - `\"literal-value\"` — inline (not recommended for real secrets)\n * - `\"${env.VAR_NAME}\"` — string-template form\n * - `{ env: \"VAR_NAME\" }` — object form\n */\nexport type SecretRef = string | { env: string };\n\n// ── Resources + policies ──\n\nexport interface ResourceDefinition {\n /** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */\n name: string;\n /** Human-readable title for the admin UI. */\n title: string;\n /** Optional longer description. */\n description?: string;\n /** Actions this resource supports, e.g. `['read', 'write', 'delete']`. */\n actions: string[];\n /**\n * Optional raisin-rel policy expression. Evaluated on every KV/DB/\n * realtime/media op that targets this resource. Leave empty to skip\n * Layer 2 for this resource — tenant + owner isolation still applies.\n */\n policy?: string;\n}\n\n// ── Groups ──\n\nexport interface GroupPermissionDefinition {\n /** Must match a `ResourceDefinition.name`. */\n resource_name: string;\n /** Actions this group is granted on the resource. */\n actions: string[];\n}\n\nexport interface GroupDefinition {\n /** Unique group name per tenant. */\n name: string;\n /** Optional description for the admin UI. */\n description?: string;\n /** Resource permissions granted to the group. Replaces the group's current permissions when declared. */\n permissions?: GroupPermissionDefinition[];\n}\n\n// ── Relations ──\n\nexport interface RelationTypeDefinition {\n /** Uppercase identifier used in policies (`... VIA 'STEWARDS'`). */\n relation_name: string;\n /** Human-readable title. */\n title: string;\n description?: string;\n /** Grouping for the admin UI (e.g. `\"family\"`, `\"work\"`). */\n category?: string;\n icon?: string;\n color?: string;\n /** Name of the inverse relation type, if one exists. */\n inverse_relation_name?: string;\n /** When true, membership in this relation implies stewardship rights. */\n implies_stewardship?: boolean;\n /** When true, the relation can only target users flagged as minors. */\n requires_minor?: boolean;\n /** When true, the relation is symmetric (A→B implies B→A). */\n bidirectional?: boolean;\n}\n\n// ── Registration fields ──\n\nexport interface RegistrationFieldDefinition {\n /** Field key used as the form field name + in profile data. */\n key: string;\n /** Display label. */\n label: string;\n /** One of: text, email, phone, date, number, select, boolean, url, textarea. */\n field_type: string;\n required: boolean;\n show_on_register: boolean;\n /** Optional validation metadata — passed through to the UI. */\n validation?: Record<string, unknown>;\n}\n\nexport interface RegistrationConfig {\n /** Ordered list of custom registration fields. Declaring this replaces the full list. */\n fields: RegistrationFieldDefinition[];\n}\n\n// ── OAuth providers ──\n\nexport interface OAuthProviderDefinition {\n enabled: boolean;\n client_id: string;\n /** Prefer `{ env: \"VAR_NAME\" }` or `\"${env.VAR_NAME}\"`. */\n client_secret: SecretRef;\n scopes: string[];\n /** Only for `custom_oidc`. */\n discovery_url?: string;\n}\n\nexport interface OAuthProvidersConfig {\n google?: OAuthProviderDefinition;\n github?: OAuthProviderDefinition;\n okta?: OAuthProviderDefinition;\n custom_oidc?: OAuthProviderDefinition;\n}\n\n// ── Security ──\n\nexport interface PasswordPolicyDefinition {\n min_length: number;\n require_uppercase: boolean;\n require_number: boolean;\n require_special: boolean;\n}\n\nexport interface SessionConfigDefinition {\n access_token_ttl_secs: number;\n refresh_token_ttl_secs: number;\n max_sessions_per_user: number;\n require_email_verification: boolean;\n}\n\nexport interface SecurityConfig {\n password_policy?: PasswordPolicyDefinition;\n session?: SessionConfigDefinition;\n}\n\n// ── Branding ──\n\nexport interface BrandingConfig {\n app_name?: string;\n logo_url?: string;\n primary_color?: string;\n secondary_color?: string;\n welcome_message?: string;\n welcome_subtitle?: string;\n /** `\"centered\"`, `\"split-left\"`, `\"split-right\"`, or `\"fullscreen\"`. */\n layout?: string;\n background_image_url?: string;\n /** 0–100 percentage. */\n background_focal_point?: { x: number; y: number };\n background_gradient?: string;\n /** `\"light\"`, `\"dark\"`, or `\"auto\"`. */\n color_mode?: string;\n font_family?: string;\n terms_url?: string;\n privacy_url?: string;\n /** Raw CSS merged into the hosted auth pages. */\n custom_css?: string;\n}\n\n// ── Top-level shape ──\n\nexport interface AuthConfigBlock {\n resources?: ResourceDefinition[];\n groups?: GroupDefinition[];\n relations?: RelationTypeDefinition[];\n registration?: RegistrationConfig;\n oauth?: OAuthProvidersConfig;\n security?: SecurityConfig;\n branding?: BrandingConfig;\n}\n\nexport interface MaravillaConfig {\n /** All project-level auth settings. Every field is optional — partial adoption is supported. */\n auth?: AuthConfigBlock;\n /** Declarative database indexes (regular + vector). Reconciled upsert-only on deploy. */\n database?: DatabaseConfigBlock;\n}\n\n// ── Database block ──\n//\n// Regular indexes speed up document reads on frequently-queried fields.\n// Vector indexes back hybrid semantic search via sqlite-vec. Both are\n// upsert-only — declaring an index in config creates it if missing,\n// updates metadata when safe, and never auto-deletes DB-only indexes.\n\n/** MongoDB-style key direction: `1` ascending, `-1` descending. */\nexport type IndexDirectionConfig = 1 | -1;\n\nexport interface DocumentIndexDeclaration {\n /** Collection the index lives on. */\n collection: string;\n /** Optional name; falls back to an auto-derived name. */\n name?: string;\n /**\n * Compound-index key shape. Array of `[field, direction]` tuples\n * preserves ordering, which matters for compound indexes.\n */\n keys: Array<[string, IndexDirectionConfig]> | Record<string, IndexDirectionConfig>;\n unique?: boolean;\n sparse?: boolean;\n /**\n * Partial-index predicate — restricted to inline-literal operators\n * (`$eq`, `$ne`, `$gt`/`$gte`/`$lt`/`$lte`, `$in`/`$nin`, `$exists`,\n * `$and`, `$or`). No `$regex` / `$where` / `$text`.\n */\n partial?: Record<string, unknown>;\n /** TTL in seconds. Requires a single-field index on a unix-seconds field. */\n expireAfterSeconds?: number;\n}\n\n/** Distance metric used by a vector index. */\nexport type VectorMetricConfig = 'cosine' | 'l2' | 'hamming';\n\n/** Storage precision for a vector index. */\nexport type VectorStorageConfig = 'float32' | 'int8' | 'bit';\n\nexport interface VectorIndexDeclaration {\n collection: string;\n field: string;\n dimensions: number;\n metric?: VectorMetricConfig;\n storage?: VectorStorageConfig;\n matryoshka?: boolean;\n multiVector?: boolean;\n}\n\nexport interface DatabaseConfigBlock {\n /** MongoDB-style secondary indexes. */\n indexes?: DocumentIndexDeclaration[];\n /** sqlite-vec-backed vector indexes. */\n vectorIndexes?: VectorIndexDeclaration[];\n}\n\n/**\n * Identity function that returns the config unchanged — exists purely so the\n * TypeScript compiler can infer `MaravillaConfig` and give you IntelliSense\n * on every field.\n *\n * @example\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [{ name: 'todos', title: 'Todos', actions: ['read', 'write'] }],\n * },\n * });\n * ```\n */\nexport function defineConfig(config: MaravillaConfig): MaravillaConfig {\n return config;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["/**\n * @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.\n *\n * Declares your project's auth settings (resources, groups, relations,\n * registration fields, OAuth providers, security policy, branding) alongside\n * your code. The Maravilla adapter reads this at build time and reconciles\n * the settings into delivery on deploy.\n *\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [\n * { name: 'todos', title: 'Todos', actions: ['read', 'write'],\n * policy: 'auth.user_id == node.owner' },\n * ],\n * },\n * });\n * ```\n *\n * Omitted sections leave the DB alone — partial adoption is explicitly\n * supported. List-based sections (`resources`, `groups`, `relations`,\n * `oauth`) are upserted and never auto-delete DB-only entries. Singleton\n * sections (`registration`, `security`, `branding`) are replaced wholesale\n * when declared.\n */\n\n/**\n * String value that may either be a literal secret or a reference to an\n * environment variable on the **tenant** (resolved server-side at\n * reconcile time, never shipped plaintext in the manifest).\n *\n * Accepted forms:\n * - `\"literal-value\"` — inline (not recommended for real secrets)\n * - `\"${env.VAR_NAME}\"` — string-template form\n * - `{ env: \"VAR_NAME\" }` — object form\n */\nexport type SecretRef = string | { env: string };\n\nimport type { TransformsConfig } from './transforms.js';\nexport type { TransformsConfig, TransformsPatternSpec } from './transforms.js';\n\n// ── Resources + policies ──\n\nexport interface ResourceDefinition {\n /** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */\n name: string;\n /** Human-readable title for the admin UI. */\n title: string;\n /** Optional longer description. */\n description?: string;\n /** Actions this resource supports, e.g. `['read', 'write', 'delete']`. */\n actions: string[];\n /**\n * Optional raisin-rel policy expression. Evaluated on every KV/DB/\n * realtime/media op that targets this resource. Leave empty to skip\n * Layer 2 for this resource — tenant + owner isolation still applies.\n */\n policy?: string;\n}\n\n// ── Groups ──\n\nexport interface GroupPermissionDefinition {\n /** Must match a `ResourceDefinition.name`. */\n resource_name: string;\n /** Actions this group is granted on the resource. */\n actions: string[];\n}\n\nexport interface GroupDefinition {\n /** Unique group name per tenant. */\n name: string;\n /** Optional description for the admin UI. */\n description?: string;\n /** Resource permissions granted to the group. Replaces the group's current permissions when declared. */\n permissions?: GroupPermissionDefinition[];\n}\n\n// ── Relations ──\n\nexport interface RelationTypeDefinition {\n /** Uppercase identifier used in policies (`... VIA 'STEWARDS'`). */\n relation_name: string;\n /** Human-readable title. */\n title: string;\n description?: string;\n /** Grouping for the admin UI (e.g. `\"family\"`, `\"work\"`). */\n category?: string;\n icon?: string;\n color?: string;\n /** Name of the inverse relation type, if one exists. */\n inverse_relation_name?: string;\n /** When true, membership in this relation implies stewardship rights. */\n implies_stewardship?: boolean;\n /** When true, the relation can only target users flagged as minors. */\n requires_minor?: boolean;\n /** When true, the relation is symmetric (A→B implies B→A). */\n bidirectional?: boolean;\n}\n\n// ── Registration fields ──\n\nexport interface RegistrationFieldDefinition {\n /** Field key used as the form field name + in profile data. */\n key: string;\n /** Display label. */\n label: string;\n /** One of: text, email, phone, date, number, select, boolean, url, textarea. */\n field_type: string;\n required: boolean;\n show_on_register: boolean;\n /** Optional validation metadata — passed through to the UI. */\n validation?: Record<string, unknown>;\n}\n\nexport interface RegistrationConfig {\n /** Ordered list of custom registration fields. Declaring this replaces the full list. */\n fields: RegistrationFieldDefinition[];\n}\n\n// ── OAuth providers ──\n\nexport interface OAuthProviderDefinition {\n enabled: boolean;\n client_id: string;\n /** Prefer `{ env: \"VAR_NAME\" }` or `\"${env.VAR_NAME}\"`. */\n client_secret: SecretRef;\n scopes: string[];\n /** Only for `custom_oidc`. */\n discovery_url?: string;\n}\n\nexport interface OAuthProvidersConfig {\n google?: OAuthProviderDefinition;\n github?: OAuthProviderDefinition;\n okta?: OAuthProviderDefinition;\n custom_oidc?: OAuthProviderDefinition;\n}\n\n// ── Security ──\n\nexport interface PasswordPolicyDefinition {\n min_length: number;\n require_uppercase: boolean;\n require_number: boolean;\n require_special: boolean;\n}\n\nexport interface SessionConfigDefinition {\n access_token_ttl_secs: number;\n refresh_token_ttl_secs: number;\n max_sessions_per_user: number;\n require_email_verification: boolean;\n}\n\nexport interface SecurityConfig {\n password_policy?: PasswordPolicyDefinition;\n session?: SessionConfigDefinition;\n}\n\n// ── Branding ──\n\nexport interface BrandingConfig {\n app_name?: string;\n logo_url?: string;\n primary_color?: string;\n secondary_color?: string;\n welcome_message?: string;\n welcome_subtitle?: string;\n /** `\"centered\"`, `\"split-left\"`, `\"split-right\"`, or `\"fullscreen\"`. */\n layout?: string;\n background_image_url?: string;\n /** 0–100 percentage. */\n background_focal_point?: { x: number; y: number };\n background_gradient?: string;\n /** `\"light\"`, `\"dark\"`, or `\"auto\"`. */\n color_mode?: string;\n font_family?: string;\n terms_url?: string;\n privacy_url?: string;\n /** Raw CSS merged into the hosted auth pages. */\n custom_css?: string;\n}\n\n// ── Top-level shape ──\n\nexport interface AuthConfigBlock {\n resources?: ResourceDefinition[];\n groups?: GroupDefinition[];\n relations?: RelationTypeDefinition[];\n registration?: RegistrationConfig;\n oauth?: OAuthProvidersConfig;\n security?: SecurityConfig;\n branding?: BrandingConfig;\n}\n\nexport interface MaravillaConfig {\n /** All project-level auth settings. Every field is optional — partial adoption is supported. */\n auth?: AuthConfigBlock;\n /** Declarative database indexes (regular + vector). Reconciled upsert-only on deploy. */\n database?: DatabaseConfigBlock;\n /**\n * Declarative media transforms — each entry compiles into a synthetic\n * `storage.put` event handler that fans out the declared\n * `platform.media.transforms.*` calls (via `Promise.all`) for every\n * matching upload. See {@link TransformsConfig}.\n */\n transforms?: TransformsConfig;\n}\n\n// ── Database block ──\n//\n// Regular indexes speed up document reads on frequently-queried fields.\n// Vector indexes back hybrid semantic search via sqlite-vec. Both are\n// upsert-only — declaring an index in config creates it if missing,\n// updates metadata when safe, and never auto-deletes DB-only indexes.\n\n/** MongoDB-style key direction: `1` ascending, `-1` descending. */\nexport type IndexDirectionConfig = 1 | -1;\n\nexport interface DocumentIndexDeclaration {\n /** Collection the index lives on. */\n collection: string;\n /** Optional name; falls back to an auto-derived name. */\n name?: string;\n /**\n * Compound-index key shape. Array of `[field, direction]` tuples\n * preserves ordering, which matters for compound indexes.\n */\n keys: Array<[string, IndexDirectionConfig]> | Record<string, IndexDirectionConfig>;\n unique?: boolean;\n sparse?: boolean;\n /**\n * Partial-index predicate — restricted to inline-literal operators\n * (`$eq`, `$ne`, `$gt`/`$gte`/`$lt`/`$lte`, `$in`/`$nin`, `$exists`,\n * `$and`, `$or`). No `$regex` / `$where` / `$text`.\n */\n partial?: Record<string, unknown>;\n /** TTL in seconds. Requires a single-field index on a unix-seconds field. */\n expireAfterSeconds?: number;\n}\n\n/** Distance metric used by a vector index. */\nexport type VectorMetricConfig = 'cosine' | 'l2' | 'hamming';\n\n/** Storage precision for a vector index. */\nexport type VectorStorageConfig = 'float32' | 'int8' | 'bit';\n\nexport interface VectorIndexDeclaration {\n collection: string;\n field: string;\n dimensions: number;\n metric?: VectorMetricConfig;\n storage?: VectorStorageConfig;\n matryoshka?: boolean;\n multiVector?: boolean;\n}\n\nexport interface DatabaseConfigBlock {\n /** MongoDB-style secondary indexes. */\n indexes?: DocumentIndexDeclaration[];\n /** sqlite-vec-backed vector indexes. */\n vectorIndexes?: VectorIndexDeclaration[];\n}\n\n/**\n * Identity function that returns the config unchanged — exists purely so the\n * TypeScript compiler can infer `MaravillaConfig` and give you IntelliSense\n * on every field.\n *\n * @example\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [{ name: 'todos', title: 'Todos', actions: ['read', 'write'] }],\n * },\n * });\n * ```\n */\nexport function defineConfig(config: MaravillaConfig): MaravillaConfig {\n return config;\n}\n"],"mappings":";AA2RO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;","names":[]}
|
package/dist/events.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { R as RenEvent } from './ren-D0DCQ0Fs.js';
|
|
|
21
21
|
* and invokes the bundled handler via `globalThis.handleEvent(id, event)`.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
type EventTrigger = EventTriggerKv | EventTriggerDb | EventTriggerAuth | EventTriggerSchedule | EventTriggerQueue | EventTriggerChannel | EventTriggerDeploy | EventTriggerRen;
|
|
24
|
+
type EventTrigger = EventTriggerKv | EventTriggerDb | EventTriggerAuth | EventTriggerSchedule | EventTriggerQueue | EventTriggerChannel | EventTriggerDeploy | EventTriggerStorage | EventTriggerRen;
|
|
25
25
|
interface EventTriggerKv {
|
|
26
26
|
kind: 'kv';
|
|
27
27
|
namespace?: string;
|
|
@@ -57,6 +57,19 @@ interface EventTriggerDeploy {
|
|
|
57
57
|
kind: 'deploy';
|
|
58
58
|
phase: 'ready' | 'draining' | 'stopped';
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Object storage trigger — fires when an object is created or removed
|
|
62
|
+
* under the matching `keyPattern`. Used by both hand-written `onStorage`
|
|
63
|
+
* handlers and by the synthesized handlers that the adapter compiles
|
|
64
|
+
* from a `transforms` block in `maravilla.config.ts`.
|
|
65
|
+
*/
|
|
66
|
+
interface EventTriggerStorage {
|
|
67
|
+
kind: 'storage';
|
|
68
|
+
/** Glob-style storage key pattern (e.g. `"uploads/videos/**"`). Omit to match all objects. */
|
|
69
|
+
keyPattern?: string;
|
|
70
|
+
/** Operation filter; omit to match both `put` and `delete`. */
|
|
71
|
+
op?: 'put' | 'delete';
|
|
72
|
+
}
|
|
60
73
|
interface EventTriggerRen {
|
|
61
74
|
kind: 'ren';
|
|
62
75
|
match: {
|
|
@@ -124,6 +137,20 @@ interface DeployEvent {
|
|
|
124
137
|
phase: 'ready' | 'draining' | 'stopped';
|
|
125
138
|
ts: number;
|
|
126
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Storage event payload — emitted by the dev-server / runtime when an
|
|
142
|
+
* object is put or deleted. The synthesized transforms handlers consume
|
|
143
|
+
* the `key` field to look up the source object.
|
|
144
|
+
*/
|
|
145
|
+
interface StorageEvent {
|
|
146
|
+
op: 'put' | 'delete';
|
|
147
|
+
key: string;
|
|
148
|
+
/** Present on `put` — content type from the upload metadata, when known. */
|
|
149
|
+
contentType?: string;
|
|
150
|
+
/** Present on `put` — size in bytes, when known. */
|
|
151
|
+
size?: number;
|
|
152
|
+
ts: number;
|
|
153
|
+
}
|
|
127
154
|
interface EventCtx {
|
|
128
155
|
/** Per-tenant env vars. */
|
|
129
156
|
env: Record<string, string>;
|
|
@@ -174,9 +201,25 @@ declare function onSchedule(cron: string, handler: (event: ScheduleEvent, ctx: E
|
|
|
174
201
|
declare function onQueue<T = unknown>(name: string, config: Omit<EventTriggerQueue, 'kind' | 'name'>, handler: (messages: QueueMessage<T>[], ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<QueueMessage<T>[]>;
|
|
175
202
|
declare function onChannel<T = unknown>(config: Omit<EventTriggerChannel, 'kind'>, handler: (event: ChannelEvent<T>, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<ChannelEvent<T>>;
|
|
176
203
|
declare function onDeploy(phase: EventTriggerDeploy['phase'], handler: (event: DeployEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<DeployEvent>;
|
|
204
|
+
/**
|
|
205
|
+
* React to object-storage `put` / `delete` events. `keyPattern` is a
|
|
206
|
+
* glob (e.g. `"uploads/videos/**"`). Omit `op` to match both put and
|
|
207
|
+
* delete; omit `keyPattern` to match every object in the tenant's
|
|
208
|
+
* bucket.
|
|
209
|
+
*
|
|
210
|
+
* ```ts
|
|
211
|
+
* export const onUpload = onStorage(
|
|
212
|
+
* { keyPattern: 'uploads/photos/**', op: 'put' },
|
|
213
|
+
* async (event, ctx) => {
|
|
214
|
+
* await ctx.platform.media.transforms.resize(event.key, { width: 1600, format: 'webp' });
|
|
215
|
+
* },
|
|
216
|
+
* );
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
declare function onStorage(config: Omit<EventTriggerStorage, 'kind'>, handler: (event: StorageEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<StorageEvent>;
|
|
177
220
|
/** Escape hatch for arbitrary `RenEvent` matches. */
|
|
178
221
|
declare function defineEvent(config: Omit<EventTriggerRen, 'kind'>, handler: (event: RenEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<RenEvent>;
|
|
179
222
|
/** Type guard used by the build-time discoverer and the runtime registry. */
|
|
180
223
|
declare function isRegisteredHandler(value: unknown): value is RegisteredHandler;
|
|
181
224
|
|
|
182
|
-
export { type AuthEvent, type AuthOp, type ChannelEvent, type DbChangeEvent, type DeployEvent, type EventCtx, type EventTrigger, type EventTriggerAuth, type EventTriggerChannel, type EventTriggerDb, type EventTriggerDeploy, type EventTriggerKv, type EventTriggerQueue, type EventTriggerRen, type EventTriggerSchedule, type KvChangeEvent, type QueueMessage, type RegisteredHandler, type ScheduleEvent, TRIGGER_SYMBOL, defineEvent, isRegisteredHandler, onAuth, onChannel, onDbChange, onDeploy, onKvChange, onQueue, onSchedule };
|
|
225
|
+
export { type AuthEvent, type AuthOp, type ChannelEvent, type DbChangeEvent, type DeployEvent, type EventCtx, type EventTrigger, type EventTriggerAuth, type EventTriggerChannel, type EventTriggerDb, type EventTriggerDeploy, type EventTriggerKv, type EventTriggerQueue, type EventTriggerRen, type EventTriggerSchedule, type EventTriggerStorage, type KvChangeEvent, type QueueMessage, type RegisteredHandler, type ScheduleEvent, type StorageEvent, TRIGGER_SYMBOL, defineEvent, isRegisteredHandler, onAuth, onChannel, onDbChange, onDeploy, onKvChange, onQueue, onSchedule, onStorage };
|
package/dist/events.js
CHANGED
|
@@ -24,6 +24,9 @@ function onChannel(config, handler) {
|
|
|
24
24
|
function onDeploy(phase, handler) {
|
|
25
25
|
return register({ kind: "deploy", phase }, handler);
|
|
26
26
|
}
|
|
27
|
+
function onStorage(config, handler) {
|
|
28
|
+
return register({ kind: "storage", ...config }, handler);
|
|
29
|
+
}
|
|
27
30
|
function defineEvent(config, handler) {
|
|
28
31
|
return register({ kind: "ren", ...config }, handler);
|
|
29
32
|
}
|
|
@@ -40,6 +43,7 @@ export {
|
|
|
40
43
|
onDeploy,
|
|
41
44
|
onKvChange,
|
|
42
45
|
onQueue,
|
|
43
|
-
onSchedule
|
|
46
|
+
onSchedule,
|
|
47
|
+
onStorage
|
|
44
48
|
};
|
|
45
49
|
//# sourceMappingURL=events.js.map
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/events.ts"],"sourcesContent":["/**\n * @fileoverview Event handler registration helpers for Maravilla.\n *\n * User apps declare event handlers in `events.ts` or `events/*.ts`:\n *\n * ```ts\n * import { onKvChange, defineEvent } from '@maravilla-labs/platform/events';\n *\n * export const invalidateCache = onKvChange(\n * { namespace: 'config', keyPattern: 'feature:*' },\n * async (event, ctx) => { await ctx.kv.delete('cache', event.key!); },\n * );\n * ```\n *\n * These helpers are pure factories. They produce a `RegisteredHandler`\n * marker object that the build pipeline (`@maravilla-labs/functions`\n * `discoverEvents`) detects by its `__maravilla_trigger` property.\n * The Rust event dispatcher uses the trigger config from the manifest\n * and invokes the bundled handler via `globalThis.handleEvent(id, event)`.\n */\n\nimport type { RenEvent } from './ren.js';\n\n// ============ Trigger descriptors ============\n// Kept structurally identical to adapter-core's `EventTrigger` — duplicated\n// here so the runtime SDK has no build-time dep on adapter-core. Keep in sync.\n\nexport type EventTrigger =\n | EventTriggerKv\n | EventTriggerDb\n | EventTriggerAuth\n | EventTriggerSchedule\n | EventTriggerQueue\n | EventTriggerChannel\n | EventTriggerDeploy\n | EventTriggerRen;\n\nexport interface EventTriggerKv {\n kind: 'kv';\n namespace?: string;\n keyPattern?: string;\n op?: 'put' | 'delete' | 'expired';\n}\n\nexport interface EventTriggerDb {\n kind: 'db';\n collection: string;\n op?: 'insert' | 'update' | 'delete';\n}\n\nexport type AuthOp =\n | 'registered'\n | 'logged_in'\n | 'logged_out'\n | 'logged_out_all'\n | 'deleted'\n | 'updated';\n\nexport interface EventTriggerAuth {\n kind: 'auth';\n op?: AuthOp;\n}\n\nexport interface EventTriggerSchedule {\n kind: 'schedule';\n cron: string;\n}\n\nexport interface EventTriggerQueue {\n kind: 'queue';\n name: string;\n batch?: number;\n maxAttempts?: number;\n}\n\nexport interface EventTriggerChannel {\n kind: 'channel';\n channel: string;\n type?: string;\n}\n\nexport interface EventTriggerDeploy {\n kind: 'deploy';\n phase: 'ready' | 'draining' | 'stopped';\n}\n\nexport interface EventTriggerRen {\n kind: 'ren';\n match: { r?: string; t?: string; ns?: string };\n}\n\n// ============ Event payload shapes ============\n\nexport interface KvChangeEvent {\n op: 'put' | 'delete' | 'expired';\n namespace: string;\n key: string;\n /** Present on `put`. */\n value?: unknown;\n ts: number;\n}\n\nexport interface DbChangeEvent {\n op: 'insert' | 'update' | 'delete';\n collection: string;\n id: string;\n doc?: unknown;\n before?: unknown;\n after?: unknown;\n ts: number;\n}\n\nexport interface AuthEvent {\n op: AuthOp;\n userId: string;\n /**\n * Op-specific payload:\n * - `registered` → `{ email, provider, profile }` where `profile` is the\n * app's custom fields passed to `platform.auth.register({ profile: {...} })`.\n * - `logged_in` → `{ email }`\n * - `logged_out` → `{ sessionId }`\n * - `logged_out_all` / `deleted` / `updated` → null or empty.\n */\n data?: {\n email?: string;\n provider?: string;\n profile?: Record<string, unknown> | null;\n sessionId?: string;\n [k: string]: unknown;\n };\n ts: number;\n}\n\nexport interface ScheduleEvent {\n cron: string;\n scheduledAt: number;\n firedAt: number;\n}\n\nexport interface QueueMessage<T = unknown> {\n id: string;\n payload: T;\n attempt: number;\n enqueuedAt: number;\n}\n\nexport interface ChannelEvent<T = unknown> {\n channel: string;\n type: string;\n data?: T;\n uid?: string;\n ts: number;\n}\n\nexport interface DeployEvent {\n phase: 'ready' | 'draining' | 'stopped';\n ts: number;\n}\n\n// ============ Handler context ============\n\nexport interface EventCtx {\n /** Per-tenant env vars. */\n env: Record<string, string>;\n /** KV store — same shape as `getPlatform().env.KV` / `platform.kv`. */\n kv?: unknown;\n /** MongoDB-style database — same shape as `getPlatform().env.DB`. */\n database?: unknown;\n /** Object storage. */\n storage?: unknown;\n /** Durable queue producer (`.send(name, payload, opts?)`). */\n queue?: { send: (name: string, payload: unknown, opts?: unknown) => Promise<string> };\n /** Auth service — register/login/logout/user CRUD/etc. */\n auth?: unknown;\n /** Web Push service. */\n push?: unknown;\n /** Full platform object — escape hatch for services not surfaced above. */\n platform?: unknown;\n /** Trace correlation id — propagate through logs. */\n traceId: string;\n tenant: string;\n handlerId: string;\n}\n\n// ============ Registered handler marker ============\n\nexport const TRIGGER_SYMBOL = '__maravilla_trigger' as const;\n\nexport interface RegisteredHandler<Event = unknown, Ctx = EventCtx> {\n readonly [TRIGGER_SYMBOL]: EventTrigger;\n readonly handler: (event: Event, ctx: Ctx) => unknown | Promise<unknown>;\n}\n\nfunction register<Event, Ctx = EventCtx>(\n trigger: EventTrigger,\n handler: (event: Event, ctx: Ctx) => unknown | Promise<unknown>,\n): RegisteredHandler<Event, Ctx> {\n return { [TRIGGER_SYMBOL]: trigger, handler };\n}\n\n// ============ Public factory helpers ============\n\nexport function onKvChange(\n config: Omit<EventTriggerKv, 'kind'>,\n handler: (event: KvChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<KvChangeEvent> {\n return register({ kind: 'kv', ...config }, handler);\n}\n\nexport function onDbChange(\n config: Omit<EventTriggerDb, 'kind'>,\n handler: (event: DbChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<DbChangeEvent> {\n return register({ kind: 'db', ...config }, handler);\n}\n\n/**\n * React to user authentication events: registration, login, logout,\n * deletion, update. Omit `op` to match all auth events; set it to\n * narrow to a specific lifecycle transition.\n *\n * ```ts\n * export const welcomeEmail = onAuth(\n * { op: 'registered' },\n * async (event, ctx) => {\n * await sendWelcomeEmail(event.data?.email);\n * },\n * );\n * ```\n */\nexport function onAuth(\n config: Omit<EventTriggerAuth, 'kind'>,\n handler: (event: AuthEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<AuthEvent> {\n return register({ kind: 'auth', ...config }, handler);\n}\n\nexport function onSchedule(\n cron: string,\n handler: (event: ScheduleEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<ScheduleEvent> {\n return register({ kind: 'schedule', cron }, handler);\n}\n\nexport function onQueue<T = unknown>(\n name: string,\n config: Omit<EventTriggerQueue, 'kind' | 'name'>,\n handler: (messages: QueueMessage<T>[], ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<QueueMessage<T>[]> {\n return register({ kind: 'queue', name, ...config }, handler);\n}\n\nexport function onChannel<T = unknown>(\n config: Omit<EventTriggerChannel, 'kind'>,\n handler: (event: ChannelEvent<T>, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<ChannelEvent<T>> {\n return register({ kind: 'channel', ...config }, handler);\n}\n\nexport function onDeploy(\n phase: EventTriggerDeploy['phase'],\n handler: (event: DeployEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<DeployEvent> {\n return register({ kind: 'deploy', phase }, handler);\n}\n\n/** Escape hatch for arbitrary `RenEvent` matches. */\nexport function defineEvent(\n config: Omit<EventTriggerRen, 'kind'>,\n handler: (event: RenEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<RenEvent> {\n return register({ kind: 'ren', ...config }, handler);\n}\n\n/** Type guard used by the build-time discoverer and the runtime registry. */\nexport function isRegisteredHandler(value: unknown): value is RegisteredHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n TRIGGER_SYMBOL in value &&\n typeof (value as Record<string, unknown>).handler === 'function'\n );\n}\n"],"mappings":";AA0LO,IAAM,iBAAiB;AAO9B,SAAS,SACP,SACA,SAC+B;AAC/B,SAAO,EAAE,CAAC,cAAc,GAAG,SAAS,QAAQ;AAC9C;AAIO,SAAS,WACd,QACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO;AACpD;AAEO,SAAS,WACd,QACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO;AACpD;AAgBO,SAAS,OACd,QACA,SAC8B;AAC9B,SAAO,SAAS,EAAE,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO;AACtD;AAEO,SAAS,WACd,MACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,YAAY,KAAK,GAAG,OAAO;AACrD;AAEO,SAAS,QACd,MACA,QACA,SACsC;AACtC,SAAO,SAAS,EAAE,MAAM,SAAS,MAAM,GAAG,OAAO,GAAG,OAAO;AAC7D;AAEO,SAAS,UACd,QACA,SACoC;AACpC,SAAO,SAAS,EAAE,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO;AACzD;AAEO,SAAS,SACd,OACA,SACgC;AAChC,SAAO,SAAS,EAAE,MAAM,UAAU,MAAM,GAAG,OAAO;AACpD;AAGO,SAAS,YACd,QACA,SAC6B;AAC7B,SAAO,SAAS,EAAE,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO;AACrD;AAGO,SAAS,oBAAoB,OAA4C;AAC9E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,kBAAkB,SAClB,OAAQ,MAAkC,YAAY;AAE1D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/events.ts"],"sourcesContent":["/**\n * @fileoverview Event handler registration helpers for Maravilla.\n *\n * User apps declare event handlers in `events.ts` or `events/*.ts`:\n *\n * ```ts\n * import { onKvChange, defineEvent } from '@maravilla-labs/platform/events';\n *\n * export const invalidateCache = onKvChange(\n * { namespace: 'config', keyPattern: 'feature:*' },\n * async (event, ctx) => { await ctx.kv.delete('cache', event.key!); },\n * );\n * ```\n *\n * These helpers are pure factories. They produce a `RegisteredHandler`\n * marker object that the build pipeline (`@maravilla-labs/functions`\n * `discoverEvents`) detects by its `__maravilla_trigger` property.\n * The Rust event dispatcher uses the trigger config from the manifest\n * and invokes the bundled handler via `globalThis.handleEvent(id, event)`.\n */\n\nimport type { RenEvent } from './ren.js';\n\n// ============ Trigger descriptors ============\n// Kept structurally identical to adapter-core's `EventTrigger` — duplicated\n// here so the runtime SDK has no build-time dep on adapter-core. Keep in sync.\n\nexport type EventTrigger =\n | EventTriggerKv\n | EventTriggerDb\n | EventTriggerAuth\n | EventTriggerSchedule\n | EventTriggerQueue\n | EventTriggerChannel\n | EventTriggerDeploy\n | EventTriggerStorage\n | EventTriggerRen;\n\nexport interface EventTriggerKv {\n kind: 'kv';\n namespace?: string;\n keyPattern?: string;\n op?: 'put' | 'delete' | 'expired';\n}\n\nexport interface EventTriggerDb {\n kind: 'db';\n collection: string;\n op?: 'insert' | 'update' | 'delete';\n}\n\nexport type AuthOp =\n | 'registered'\n | 'logged_in'\n | 'logged_out'\n | 'logged_out_all'\n | 'deleted'\n | 'updated';\n\nexport interface EventTriggerAuth {\n kind: 'auth';\n op?: AuthOp;\n}\n\nexport interface EventTriggerSchedule {\n kind: 'schedule';\n cron: string;\n}\n\nexport interface EventTriggerQueue {\n kind: 'queue';\n name: string;\n batch?: number;\n maxAttempts?: number;\n}\n\nexport interface EventTriggerChannel {\n kind: 'channel';\n channel: string;\n type?: string;\n}\n\nexport interface EventTriggerDeploy {\n kind: 'deploy';\n phase: 'ready' | 'draining' | 'stopped';\n}\n\n/**\n * Object storage trigger — fires when an object is created or removed\n * under the matching `keyPattern`. Used by both hand-written `onStorage`\n * handlers and by the synthesized handlers that the adapter compiles\n * from a `transforms` block in `maravilla.config.ts`.\n */\nexport interface EventTriggerStorage {\n kind: 'storage';\n /** Glob-style storage key pattern (e.g. `\"uploads/videos/**\"`). Omit to match all objects. */\n keyPattern?: string;\n /** Operation filter; omit to match both `put` and `delete`. */\n op?: 'put' | 'delete';\n}\n\nexport interface EventTriggerRen {\n kind: 'ren';\n match: { r?: string; t?: string; ns?: string };\n}\n\n// ============ Event payload shapes ============\n\nexport interface KvChangeEvent {\n op: 'put' | 'delete' | 'expired';\n namespace: string;\n key: string;\n /** Present on `put`. */\n value?: unknown;\n ts: number;\n}\n\nexport interface DbChangeEvent {\n op: 'insert' | 'update' | 'delete';\n collection: string;\n id: string;\n doc?: unknown;\n before?: unknown;\n after?: unknown;\n ts: number;\n}\n\nexport interface AuthEvent {\n op: AuthOp;\n userId: string;\n /**\n * Op-specific payload:\n * - `registered` → `{ email, provider, profile }` where `profile` is the\n * app's custom fields passed to `platform.auth.register({ profile: {...} })`.\n * - `logged_in` → `{ email }`\n * - `logged_out` → `{ sessionId }`\n * - `logged_out_all` / `deleted` / `updated` → null or empty.\n */\n data?: {\n email?: string;\n provider?: string;\n profile?: Record<string, unknown> | null;\n sessionId?: string;\n [k: string]: unknown;\n };\n ts: number;\n}\n\nexport interface ScheduleEvent {\n cron: string;\n scheduledAt: number;\n firedAt: number;\n}\n\nexport interface QueueMessage<T = unknown> {\n id: string;\n payload: T;\n attempt: number;\n enqueuedAt: number;\n}\n\nexport interface ChannelEvent<T = unknown> {\n channel: string;\n type: string;\n data?: T;\n uid?: string;\n ts: number;\n}\n\nexport interface DeployEvent {\n phase: 'ready' | 'draining' | 'stopped';\n ts: number;\n}\n\n/**\n * Storage event payload — emitted by the dev-server / runtime when an\n * object is put or deleted. The synthesized transforms handlers consume\n * the `key` field to look up the source object.\n */\nexport interface StorageEvent {\n op: 'put' | 'delete';\n key: string;\n /** Present on `put` — content type from the upload metadata, when known. */\n contentType?: string;\n /** Present on `put` — size in bytes, when known. */\n size?: number;\n ts: number;\n}\n\n// ============ Handler context ============\n\nexport interface EventCtx {\n /** Per-tenant env vars. */\n env: Record<string, string>;\n /** KV store — same shape as `getPlatform().env.KV` / `platform.kv`. */\n kv?: unknown;\n /** MongoDB-style database — same shape as `getPlatform().env.DB`. */\n database?: unknown;\n /** Object storage. */\n storage?: unknown;\n /** Durable queue producer (`.send(name, payload, opts?)`). */\n queue?: { send: (name: string, payload: unknown, opts?: unknown) => Promise<string> };\n /** Auth service — register/login/logout/user CRUD/etc. */\n auth?: unknown;\n /** Web Push service. */\n push?: unknown;\n /** Full platform object — escape hatch for services not surfaced above. */\n platform?: unknown;\n /** Trace correlation id — propagate through logs. */\n traceId: string;\n tenant: string;\n handlerId: string;\n}\n\n// ============ Registered handler marker ============\n\nexport const TRIGGER_SYMBOL = '__maravilla_trigger' as const;\n\nexport interface RegisteredHandler<Event = unknown, Ctx = EventCtx> {\n readonly [TRIGGER_SYMBOL]: EventTrigger;\n readonly handler: (event: Event, ctx: Ctx) => unknown | Promise<unknown>;\n}\n\nfunction register<Event, Ctx = EventCtx>(\n trigger: EventTrigger,\n handler: (event: Event, ctx: Ctx) => unknown | Promise<unknown>,\n): RegisteredHandler<Event, Ctx> {\n return { [TRIGGER_SYMBOL]: trigger, handler };\n}\n\n// ============ Public factory helpers ============\n\nexport function onKvChange(\n config: Omit<EventTriggerKv, 'kind'>,\n handler: (event: KvChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<KvChangeEvent> {\n return register({ kind: 'kv', ...config }, handler);\n}\n\nexport function onDbChange(\n config: Omit<EventTriggerDb, 'kind'>,\n handler: (event: DbChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<DbChangeEvent> {\n return register({ kind: 'db', ...config }, handler);\n}\n\n/**\n * React to user authentication events: registration, login, logout,\n * deletion, update. Omit `op` to match all auth events; set it to\n * narrow to a specific lifecycle transition.\n *\n * ```ts\n * export const welcomeEmail = onAuth(\n * { op: 'registered' },\n * async (event, ctx) => {\n * await sendWelcomeEmail(event.data?.email);\n * },\n * );\n * ```\n */\nexport function onAuth(\n config: Omit<EventTriggerAuth, 'kind'>,\n handler: (event: AuthEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<AuthEvent> {\n return register({ kind: 'auth', ...config }, handler);\n}\n\nexport function onSchedule(\n cron: string,\n handler: (event: ScheduleEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<ScheduleEvent> {\n return register({ kind: 'schedule', cron }, handler);\n}\n\nexport function onQueue<T = unknown>(\n name: string,\n config: Omit<EventTriggerQueue, 'kind' | 'name'>,\n handler: (messages: QueueMessage<T>[], ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<QueueMessage<T>[]> {\n return register({ kind: 'queue', name, ...config }, handler);\n}\n\nexport function onChannel<T = unknown>(\n config: Omit<EventTriggerChannel, 'kind'>,\n handler: (event: ChannelEvent<T>, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<ChannelEvent<T>> {\n return register({ kind: 'channel', ...config }, handler);\n}\n\nexport function onDeploy(\n phase: EventTriggerDeploy['phase'],\n handler: (event: DeployEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<DeployEvent> {\n return register({ kind: 'deploy', phase }, handler);\n}\n\n/**\n * React to object-storage `put` / `delete` events. `keyPattern` is a\n * glob (e.g. `\"uploads/videos/**\"`). Omit `op` to match both put and\n * delete; omit `keyPattern` to match every object in the tenant's\n * bucket.\n *\n * ```ts\n * export const onUpload = onStorage(\n * { keyPattern: 'uploads/photos/**', op: 'put' },\n * async (event, ctx) => {\n * await ctx.platform.media.transforms.resize(event.key, { width: 1600, format: 'webp' });\n * },\n * );\n * ```\n */\nexport function onStorage(\n config: Omit<EventTriggerStorage, 'kind'>,\n handler: (event: StorageEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<StorageEvent> {\n return register({ kind: 'storage', ...config }, handler);\n}\n\n/** Escape hatch for arbitrary `RenEvent` matches. */\nexport function defineEvent(\n config: Omit<EventTriggerRen, 'kind'>,\n handler: (event: RenEvent, ctx: EventCtx) => unknown | Promise<unknown>,\n): RegisteredHandler<RenEvent> {\n return register({ kind: 'ren', ...config }, handler);\n}\n\n/** Type guard used by the build-time discoverer and the runtime registry. */\nexport function isRegisteredHandler(value: unknown): value is RegisteredHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n TRIGGER_SYMBOL in value &&\n typeof (value as Record<string, unknown>).handler === 'function'\n );\n}\n"],"mappings":";AAwNO,IAAM,iBAAiB;AAO9B,SAAS,SACP,SACA,SAC+B;AAC/B,SAAO,EAAE,CAAC,cAAc,GAAG,SAAS,QAAQ;AAC9C;AAIO,SAAS,WACd,QACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO;AACpD;AAEO,SAAS,WACd,QACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,OAAO;AACpD;AAgBO,SAAS,OACd,QACA,SAC8B;AAC9B,SAAO,SAAS,EAAE,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO;AACtD;AAEO,SAAS,WACd,MACA,SACkC;AAClC,SAAO,SAAS,EAAE,MAAM,YAAY,KAAK,GAAG,OAAO;AACrD;AAEO,SAAS,QACd,MACA,QACA,SACsC;AACtC,SAAO,SAAS,EAAE,MAAM,SAAS,MAAM,GAAG,OAAO,GAAG,OAAO;AAC7D;AAEO,SAAS,UACd,QACA,SACoC;AACpC,SAAO,SAAS,EAAE,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO;AACzD;AAEO,SAAS,SACd,OACA,SACgC;AAChC,SAAO,SAAS,EAAE,MAAM,UAAU,MAAM,GAAG,OAAO;AACpD;AAiBO,SAAS,UACd,QACA,SACiC;AACjC,SAAO,SAAS,EAAE,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO;AACzD;AAGO,SAAS,YACd,QACA,SAC6B;AAC7B,SAAO,SAAS,EAAE,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO;AACrD;AAGO,SAAS,oBAAoB,OAA4C;AAC9E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,kBAAkB,SAClB,OAAQ,MAAkC,YAAY;AAE1D;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { a as RenClient, b as RenClientOptions, R as RenEvent, g as getOrCreateClientId, r as renFetch, s as storageDelete, c as storageUpload } from './ren-D0DCQ0Fs.js';
|
|
2
2
|
import { LocalParticipant } from 'livekit-client';
|
|
3
3
|
export { RegisterPushOptions, RegisterPushResult, offsetBefore, registerPush, unregisterPush } from './push.js';
|
|
4
|
+
export { I as ImageFormat, J as JobHandle, b as JobStatus, c as JobStatusResponse, M as MediaInfo, O as OcrOpts, R as ResizeOpts, d as ThumbnailOpts, e as TranscodeOpts, f as TransformSpec, T as TransformsConfig, a as TransformsPatternSpec, g as TransformsService, V as VideoFormat, k as keyFor, t as transforms } from './transforms-KzpoYW43.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Media service for video/audio room management.
|
|
@@ -710,6 +711,25 @@ interface Storage {
|
|
|
710
711
|
lastModified: string;
|
|
711
712
|
metadata?: any;
|
|
712
713
|
} | null>;
|
|
714
|
+
/**
|
|
715
|
+
* Notify the platform that a presigned-PUT upload completed at the given
|
|
716
|
+
* key. The bytes never crossed the server, so no `storage.put` event would
|
|
717
|
+
* fire automatically — call this from your form action right after the
|
|
718
|
+
* client's direct PUT succeeds. The platform looks up the object's
|
|
719
|
+
* metadata and publishes the matching `storage.put` event so any registered
|
|
720
|
+
* event handlers (including the synthesized handlers from a `transforms`
|
|
721
|
+
* config block) fire as if the upload had streamed through the server.
|
|
722
|
+
*
|
|
723
|
+
* Idempotent — safe to call more than once with the same key (the server
|
|
724
|
+
* dedupes by emitting at most one event per (tenant, key, mtime) tuple).
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* ```ts
|
|
728
|
+
* await fetch(presignedUrl, { method: 'PUT', body: blob });
|
|
729
|
+
* await platform.env.STORAGE.confirm(key);
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
confirm(key: string): Promise<void>;
|
|
713
733
|
}
|
|
714
734
|
/**
|
|
715
735
|
* Platform environment containing all available services.
|
|
@@ -1327,6 +1347,72 @@ interface PushService {
|
|
|
1327
1347
|
*/
|
|
1328
1348
|
rotateVapidKeys(): Promise<PublicPushConfig>;
|
|
1329
1349
|
}
|
|
1350
|
+
/** State of a workflow run. */
|
|
1351
|
+
type WorkflowRunStatus = 'queued' | 'running' | 'sleeping' | 'waiting_event' | 'completed' | 'failed' | 'cancelled';
|
|
1352
|
+
/** Kind of a recorded step. */
|
|
1353
|
+
type WorkflowStepKind = 'run' | 'sleep' | 'wait_event' | 'ai' | 'mcp' | 'invoke';
|
|
1354
|
+
/** A durable workflow run as stored in the ledger. */
|
|
1355
|
+
interface WorkflowRun {
|
|
1356
|
+
runId: string;
|
|
1357
|
+
workflowId: string;
|
|
1358
|
+
projectId?: string;
|
|
1359
|
+
deploymentId: string;
|
|
1360
|
+
input: unknown;
|
|
1361
|
+
status: WorkflowRunStatus;
|
|
1362
|
+
attempt: number;
|
|
1363
|
+
createdAt: number;
|
|
1364
|
+
updatedAt: number;
|
|
1365
|
+
completedAt?: number;
|
|
1366
|
+
output?: unknown;
|
|
1367
|
+
error?: unknown;
|
|
1368
|
+
}
|
|
1369
|
+
/** A single step in a run's history. */
|
|
1370
|
+
interface WorkflowStepRecord {
|
|
1371
|
+
name: string;
|
|
1372
|
+
kind: WorkflowStepKind;
|
|
1373
|
+
status: 'pending' | 'completed' | 'failed';
|
|
1374
|
+
output?: unknown;
|
|
1375
|
+
error?: unknown;
|
|
1376
|
+
startedAt: number;
|
|
1377
|
+
completedAt?: number;
|
|
1378
|
+
wakeAt?: number;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Handle returned by `platform.workflows.start()` — poll status, await
|
|
1382
|
+
* the final result, cancel, or inspect step history.
|
|
1383
|
+
*/
|
|
1384
|
+
interface WorkflowHandle {
|
|
1385
|
+
/** The run's unique id. */
|
|
1386
|
+
readonly runId: string;
|
|
1387
|
+
/** Current run record, or `null` if the run does not exist. */
|
|
1388
|
+
status(): Promise<WorkflowRun | null>;
|
|
1389
|
+
/** Full step history — useful for debugging. */
|
|
1390
|
+
history(): Promise<WorkflowStepRecord[]>;
|
|
1391
|
+
/**
|
|
1392
|
+
* Wait for the run to reach a terminal state and return its output.
|
|
1393
|
+
* Throws if the run ended `failed` or `cancelled`; the thrown error
|
|
1394
|
+
* carries the run record on the `run` property.
|
|
1395
|
+
*/
|
|
1396
|
+
result(options?: {
|
|
1397
|
+
timeoutMs?: number;
|
|
1398
|
+
}): Promise<unknown>;
|
|
1399
|
+
/** Best-effort cancellation. Returns `true` if the run transitioned. */
|
|
1400
|
+
cancel(): Promise<boolean>;
|
|
1401
|
+
}
|
|
1402
|
+
/** Durable multi-step workflow service. */
|
|
1403
|
+
interface Workflows {
|
|
1404
|
+
/** Start a new run of the workflow with the given input. */
|
|
1405
|
+
start(workflowId: string, input?: unknown): Promise<WorkflowHandle>;
|
|
1406
|
+
/** Get a handle to an existing run without starting one. */
|
|
1407
|
+
handle(runId: string): WorkflowHandle;
|
|
1408
|
+
/**
|
|
1409
|
+
* Signal an event to any runs paused on `step.waitForEvent`. Returns
|
|
1410
|
+
* the number of runs resolved. Matching rules: `eventType` must equal
|
|
1411
|
+
* the waiter's filter `type`; each key in the filter's `match` must
|
|
1412
|
+
* be present in `payload` with an equal value.
|
|
1413
|
+
*/
|
|
1414
|
+
sendEvent(eventType: string, payload?: unknown): Promise<number>;
|
|
1415
|
+
}
|
|
1330
1416
|
interface Platform {
|
|
1331
1417
|
/** Environment containing all available platform services */
|
|
1332
1418
|
env: PlatformEnv;
|
|
@@ -1345,6 +1431,8 @@ interface Platform {
|
|
|
1345
1431
|
* fallback that doesn't proxy to delivery.
|
|
1346
1432
|
*/
|
|
1347
1433
|
push?: PushService;
|
|
1434
|
+
/** Durable multi-step workflows — `start / handle / sendEvent`. */
|
|
1435
|
+
workflows: Workflows;
|
|
1348
1436
|
}
|
|
1349
1437
|
|
|
1350
1438
|
interface RealtimeEvent {
|
|
@@ -1660,4 +1748,4 @@ declare function getPlatform(options?: {
|
|
|
1660
1748
|
*/
|
|
1661
1749
|
declare function clearPlatformCache(): void;
|
|
1662
1750
|
|
|
1663
|
-
export { type AuthCaller, type AuthField, type AuthService, type AuthSession, type AuthUser, type Database, type DbFindOptions, type IndexDescriptor, type IndexDirection, type IndexKind, type IndexSpec, type KvListResult, type KvNamespace, type ListScheduledFilter, type LoginOptions, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type NotificationPayload, type Platform, type PlatformEnv, type PolicyService, type PresenceMember, type PresenceService, type PublicPushConfig, type PushService, type PushTarget, type QueueStats, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, type RegisterOptions, RemoteMediaService, type ScheduleOptions, type ScheduledJob, type SendReport, type Storage, type StoragePutStreamSource, type StoredPushSubscription, type SubscriptionCounts, type TrackKind, type TrackSource, type UpdateUserOptions, type UserListFilter, type UserListResponse, type VectorAggregation, type VectorIndexDescriptor, type VectorIndexSpec, type VectorMetric, type VectorQuery, type VectorQueryMode, type VectorQueryWithFilter, type VectorSearchHit, type VectorStorage, type VideoResolution, attachTrack, clearPlatformCache, detachTrack, getPlatform };
|
|
1751
|
+
export { type AuthCaller, type AuthField, type AuthService, type AuthSession, type AuthUser, type Database, type DbFindOptions, type IndexDescriptor, type IndexDirection, type IndexKind, type IndexSpec, type KvListResult, type KvNamespace, type ListScheduledFilter, type LoginOptions, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type NotificationPayload, type Platform, type PlatformEnv, type PolicyService, type PresenceMember, type PresenceService, type PublicPushConfig, type PushService, type PushTarget, type QueueStats, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, type RegisterOptions, RemoteMediaService, type ScheduleOptions, type ScheduledJob, type SendReport, type Storage, type StoragePutStreamSource, type StoredPushSubscription, type SubscriptionCounts, type TrackKind, type TrackSource, type UpdateUserOptions, type UserListFilter, type UserListResponse, type VectorAggregation, type VectorIndexDescriptor, type VectorIndexSpec, type VectorMetric, type VectorQuery, type VectorQueryMode, type VectorQueryWithFilter, type VectorSearchHit, type VectorStorage, type VideoResolution, type WorkflowHandle, type WorkflowRun, type WorkflowRunStatus, type WorkflowStepKind, type WorkflowStepRecord, type Workflows, attachTrack, clearPlatformCache, detachTrack, getPlatform };
|
package/dist/index.js
CHANGED
|
@@ -368,6 +368,13 @@ var RemoteStorage = class _RemoteStorage {
|
|
|
368
368
|
if (response.status === 404) return null;
|
|
369
369
|
return response.json();
|
|
370
370
|
}
|
|
371
|
+
async confirm(key) {
|
|
372
|
+
await this.fetch(`${this.baseUrl}/api/storage/confirm`, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers: { "content-type": "application/json" },
|
|
375
|
+
body: JSON.stringify({ key })
|
|
376
|
+
});
|
|
377
|
+
}
|
|
371
378
|
};
|
|
372
379
|
var RemotePresenceService = class {
|
|
373
380
|
constructor(baseUrl, headers) {
|
|
@@ -560,6 +567,108 @@ var RemotePolicyService = class {
|
|
|
560
567
|
);
|
|
561
568
|
}
|
|
562
569
|
};
|
|
570
|
+
var RemoteWorkflows = class {
|
|
571
|
+
constructor(baseUrl, headers) {
|
|
572
|
+
this.baseUrl = baseUrl;
|
|
573
|
+
this.headers = headers;
|
|
574
|
+
}
|
|
575
|
+
baseUrl;
|
|
576
|
+
headers;
|
|
577
|
+
async request(path, options = {}) {
|
|
578
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
579
|
+
...options,
|
|
580
|
+
headers: { ...this.headers, ...options.headers }
|
|
581
|
+
});
|
|
582
|
+
if (!response.ok && response.status !== 404) {
|
|
583
|
+
const error = await response.text();
|
|
584
|
+
throw new Error(`Platform API error: ${response.status} - ${error}`);
|
|
585
|
+
}
|
|
586
|
+
return response;
|
|
587
|
+
}
|
|
588
|
+
async start(workflowId, input = {}) {
|
|
589
|
+
if (typeof workflowId !== "string" || !workflowId) {
|
|
590
|
+
throw new Error("workflows.start: workflowId must be a non-empty string");
|
|
591
|
+
}
|
|
592
|
+
const res = await this.request(
|
|
593
|
+
`/api/workflows/${encodeURIComponent(workflowId)}/start`,
|
|
594
|
+
{ method: "POST", body: JSON.stringify(input ?? {}) }
|
|
595
|
+
);
|
|
596
|
+
const { runId } = await res.json();
|
|
597
|
+
return makeRemoteWorkflowHandle(this.baseUrl, this.headers, runId);
|
|
598
|
+
}
|
|
599
|
+
handle(runId) {
|
|
600
|
+
if (typeof runId !== "string" || !runId) {
|
|
601
|
+
throw new Error("workflows.handle: runId must be a non-empty string");
|
|
602
|
+
}
|
|
603
|
+
return makeRemoteWorkflowHandle(this.baseUrl, this.headers, runId);
|
|
604
|
+
}
|
|
605
|
+
async sendEvent(eventType, payload = null) {
|
|
606
|
+
if (typeof eventType !== "string" || !eventType) {
|
|
607
|
+
throw new Error("workflows.sendEvent: eventType must be a non-empty string");
|
|
608
|
+
}
|
|
609
|
+
const res = await this.request(`/api/workflows/signal`, {
|
|
610
|
+
method: "POST",
|
|
611
|
+
body: JSON.stringify({ eventType, payload })
|
|
612
|
+
});
|
|
613
|
+
const data = await res.json();
|
|
614
|
+
return data.count;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
function makeRemoteWorkflowHandle(baseUrl, headers, runId) {
|
|
618
|
+
const doFetch = async (path, init = {}) => {
|
|
619
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
620
|
+
...init,
|
|
621
|
+
headers: { ...headers, ...init.headers }
|
|
622
|
+
});
|
|
623
|
+
if (!res.ok && res.status !== 404) {
|
|
624
|
+
throw new Error(`Platform API error: ${res.status} - ${await res.text()}`);
|
|
625
|
+
}
|
|
626
|
+
return res;
|
|
627
|
+
};
|
|
628
|
+
const handle = {
|
|
629
|
+
runId,
|
|
630
|
+
async status() {
|
|
631
|
+
const res = await doFetch(`/api/workflows/runs/${encodeURIComponent(runId)}`);
|
|
632
|
+
if (res.status === 404) return null;
|
|
633
|
+
return await res.json();
|
|
634
|
+
},
|
|
635
|
+
async history() {
|
|
636
|
+
const res = await doFetch(
|
|
637
|
+
`/api/workflows/runs/${encodeURIComponent(runId)}/history`
|
|
638
|
+
);
|
|
639
|
+
return await res.json();
|
|
640
|
+
},
|
|
641
|
+
async cancel() {
|
|
642
|
+
const res = await doFetch(
|
|
643
|
+
`/api/workflows/runs/${encodeURIComponent(runId)}/cancel`,
|
|
644
|
+
{ method: "POST" }
|
|
645
|
+
);
|
|
646
|
+
const data = await res.json();
|
|
647
|
+
return data.cancelled;
|
|
648
|
+
},
|
|
649
|
+
async result(options) {
|
|
650
|
+
const timeoutMs = options?.timeoutMs;
|
|
651
|
+
const deadline = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? Date.now() + timeoutMs : null;
|
|
652
|
+
let delay = 100;
|
|
653
|
+
while (true) {
|
|
654
|
+
const run = await handle.status();
|
|
655
|
+
if (!run) throw new Error(`workflow run not found: ${runId}`);
|
|
656
|
+
if (run.status === "completed") return run.output;
|
|
657
|
+
if (run.status === "failed" || run.status === "cancelled") {
|
|
658
|
+
const err = new Error(`workflow ${run.status}`);
|
|
659
|
+
err.run = run;
|
|
660
|
+
throw err;
|
|
661
|
+
}
|
|
662
|
+
if (deadline !== null && Date.now() >= deadline) {
|
|
663
|
+
throw new Error("workflow result timeout");
|
|
664
|
+
}
|
|
665
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
666
|
+
delay = Math.min(delay * 2, 5e3);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
return handle;
|
|
671
|
+
}
|
|
563
672
|
function createRemoteClient(baseUrl, tenant) {
|
|
564
673
|
const headers = {
|
|
565
674
|
"Content-Type": "application/json",
|
|
@@ -576,6 +685,7 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
576
685
|
const realtime = new RemoteRealtimeService(baseUrl, headers);
|
|
577
686
|
const auth = new RemoteAuthService(baseUrl, headers);
|
|
578
687
|
const policy = new RemotePolicyService();
|
|
688
|
+
const workflows = new RemoteWorkflows(baseUrl, headers);
|
|
579
689
|
return {
|
|
580
690
|
env: {
|
|
581
691
|
KV: kvProxy,
|
|
@@ -585,7 +695,8 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
585
695
|
media,
|
|
586
696
|
realtime,
|
|
587
697
|
auth,
|
|
588
|
-
policy
|
|
698
|
+
policy,
|
|
699
|
+
workflows
|
|
589
700
|
};
|
|
590
701
|
}
|
|
591
702
|
|
|
@@ -669,6 +780,12 @@ var RenClient = class {
|
|
|
669
780
|
"presence.join",
|
|
670
781
|
"presence.leave",
|
|
671
782
|
"presence.update",
|
|
783
|
+
// Media transform lifecycle (emitted by media-transforms-worker)
|
|
784
|
+
"transform.queued",
|
|
785
|
+
"transform.started",
|
|
786
|
+
"transform.progress",
|
|
787
|
+
"transform.complete",
|
|
788
|
+
"transform.failed",
|
|
672
789
|
// Meta events
|
|
673
790
|
"ren.meta"
|
|
674
791
|
];
|
|
@@ -1421,6 +1538,55 @@ function offsetBefore(anchor, offset) {
|
|
|
1421
1538
|
return new Date(anchorDate.getTime() - amount * UNIT_MS[unit]);
|
|
1422
1539
|
}
|
|
1423
1540
|
|
|
1541
|
+
// src/transforms.ts
|
|
1542
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
1543
|
+
function sortKeys(value) {
|
|
1544
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
1545
|
+
if (value && typeof value === "object") {
|
|
1546
|
+
const out = {};
|
|
1547
|
+
for (const k of Object.keys(value).sort()) {
|
|
1548
|
+
out[k] = sortKeys(value[k]);
|
|
1549
|
+
}
|
|
1550
|
+
return out;
|
|
1551
|
+
}
|
|
1552
|
+
return value;
|
|
1553
|
+
}
|
|
1554
|
+
function canonicalJson(value) {
|
|
1555
|
+
return JSON.stringify(sortKeys(value));
|
|
1556
|
+
}
|
|
1557
|
+
function shortHexSha256(input) {
|
|
1558
|
+
const bytes = new TextEncoder().encode(input);
|
|
1559
|
+
const hash = sha256(bytes);
|
|
1560
|
+
return Array.from(hash.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1561
|
+
}
|
|
1562
|
+
function outputExtension(spec) {
|
|
1563
|
+
switch (spec.kind) {
|
|
1564
|
+
case "transcode":
|
|
1565
|
+
return spec.format;
|
|
1566
|
+
// 'mp4' | 'webm'
|
|
1567
|
+
case "thumbnail":
|
|
1568
|
+
case "resize": {
|
|
1569
|
+
const fmt = spec.format ?? "jpg";
|
|
1570
|
+
return fmt;
|
|
1571
|
+
}
|
|
1572
|
+
case "ocr":
|
|
1573
|
+
return "txt";
|
|
1574
|
+
default: {
|
|
1575
|
+
const _exhaust = spec;
|
|
1576
|
+
throw new Error(`unknown transform kind: ${_exhaust.kind}`);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function keyFor(srcKey, spec) {
|
|
1581
|
+
const srcHash = shortHexSha256(srcKey);
|
|
1582
|
+
const variantHash = shortHexSha256(canonicalJson(spec));
|
|
1583
|
+
const ext = outputExtension(spec);
|
|
1584
|
+
return `__derived/${srcHash}/${variantHash}.${ext}`;
|
|
1585
|
+
}
|
|
1586
|
+
var transforms = {
|
|
1587
|
+
keyFor
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1424
1590
|
// src/index.ts
|
|
1425
1591
|
var cachedPlatform = void 0;
|
|
1426
1592
|
function getPlatform(options) {
|
|
@@ -1466,11 +1632,13 @@ export {
|
|
|
1466
1632
|
detachTrack,
|
|
1467
1633
|
getOrCreateClientId,
|
|
1468
1634
|
getPlatform,
|
|
1635
|
+
keyFor,
|
|
1469
1636
|
offsetBefore,
|
|
1470
1637
|
registerPush,
|
|
1471
1638
|
renFetch,
|
|
1472
1639
|
storageDelete,
|
|
1473
1640
|
storageUpload,
|
|
1641
|
+
transforms,
|
|
1474
1642
|
unregisterPush
|
|
1475
1643
|
};
|
|
1476
1644
|
//# sourceMappingURL=index.js.map
|