@maravilla-labs/platform 0.2.1 → 0.2.2
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 +236 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -0
- package/dist/events.d.ts +167 -0
- package/dist/events.js +45 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +470 -51
- package/dist/index.js +253 -2
- package/dist/index.js.map +1 -1
- package/dist/push.d.ts +67 -0
- package/dist/push.js +173 -0
- package/dist/push.js.map +1 -0
- package/dist/ren-D0DCQ0Fs.d.ts +48 -0
- package/package.json +13 -1
- package/src/config.ts +276 -0
- package/src/events.ts +271 -0
- package/src/index.ts +1 -0
- package/src/push.ts +280 -0
- package/src/remote-client.ts +101 -1
- package/src/types.ts +514 -1
- package/tsup.config.ts +1 -1
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.
|
|
3
|
+
*
|
|
4
|
+
* Declares your project's auth settings (resources, groups, relations,
|
|
5
|
+
* registration fields, OAuth providers, security policy, branding) alongside
|
|
6
|
+
* your code. The Maravilla adapter reads this at build time and reconciles
|
|
7
|
+
* the settings into delivery on deploy.
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { defineConfig } from '@maravilla-labs/platform/config';
|
|
11
|
+
*
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* auth: {
|
|
14
|
+
* resources: [
|
|
15
|
+
* { name: 'todos', title: 'Todos', actions: ['read', 'write'],
|
|
16
|
+
* policy: 'auth.user_id == node.owner' },
|
|
17
|
+
* ],
|
|
18
|
+
* },
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Omitted sections leave the DB alone — partial adoption is explicitly
|
|
23
|
+
* supported. List-based sections (`resources`, `groups`, `relations`,
|
|
24
|
+
* `oauth`) are upserted and never auto-delete DB-only entries. Singleton
|
|
25
|
+
* sections (`registration`, `security`, `branding`) are replaced wholesale
|
|
26
|
+
* when declared.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* String value that may either be a literal secret or a reference to an
|
|
30
|
+
* environment variable on the **tenant** (resolved server-side at
|
|
31
|
+
* reconcile time, never shipped plaintext in the manifest).
|
|
32
|
+
*
|
|
33
|
+
* Accepted forms:
|
|
34
|
+
* - `"literal-value"` — inline (not recommended for real secrets)
|
|
35
|
+
* - `"${env.VAR_NAME}"` — string-template form
|
|
36
|
+
* - `{ env: "VAR_NAME" }` — object form
|
|
37
|
+
*/
|
|
38
|
+
type SecretRef = string | {
|
|
39
|
+
env: string;
|
|
40
|
+
};
|
|
41
|
+
interface ResourceDefinition {
|
|
42
|
+
/** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Human-readable title for the admin UI. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** Optional longer description. */
|
|
47
|
+
description?: string;
|
|
48
|
+
/** Actions this resource supports, e.g. `['read', 'write', 'delete']`. */
|
|
49
|
+
actions: string[];
|
|
50
|
+
/**
|
|
51
|
+
* Optional raisin-rel policy expression. Evaluated on every KV/DB/
|
|
52
|
+
* realtime/media op that targets this resource. Leave empty to skip
|
|
53
|
+
* Layer 2 for this resource — tenant + owner isolation still applies.
|
|
54
|
+
*/
|
|
55
|
+
policy?: string;
|
|
56
|
+
}
|
|
57
|
+
interface GroupPermissionDefinition {
|
|
58
|
+
/** Must match a `ResourceDefinition.name`. */
|
|
59
|
+
resource_name: string;
|
|
60
|
+
/** Actions this group is granted on the resource. */
|
|
61
|
+
actions: string[];
|
|
62
|
+
}
|
|
63
|
+
interface GroupDefinition {
|
|
64
|
+
/** Unique group name per tenant. */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Optional description for the admin UI. */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** Resource permissions granted to the group. Replaces the group's current permissions when declared. */
|
|
69
|
+
permissions?: GroupPermissionDefinition[];
|
|
70
|
+
}
|
|
71
|
+
interface RelationTypeDefinition {
|
|
72
|
+
/** Uppercase identifier used in policies (`... VIA 'STEWARDS'`). */
|
|
73
|
+
relation_name: string;
|
|
74
|
+
/** Human-readable title. */
|
|
75
|
+
title: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
/** Grouping for the admin UI (e.g. `"family"`, `"work"`). */
|
|
78
|
+
category?: string;
|
|
79
|
+
icon?: string;
|
|
80
|
+
color?: string;
|
|
81
|
+
/** Name of the inverse relation type, if one exists. */
|
|
82
|
+
inverse_relation_name?: string;
|
|
83
|
+
/** When true, membership in this relation implies stewardship rights. */
|
|
84
|
+
implies_stewardship?: boolean;
|
|
85
|
+
/** When true, the relation can only target users flagged as minors. */
|
|
86
|
+
requires_minor?: boolean;
|
|
87
|
+
/** When true, the relation is symmetric (A→B implies B→A). */
|
|
88
|
+
bidirectional?: boolean;
|
|
89
|
+
}
|
|
90
|
+
interface RegistrationFieldDefinition {
|
|
91
|
+
/** Field key used as the form field name + in profile data. */
|
|
92
|
+
key: string;
|
|
93
|
+
/** Display label. */
|
|
94
|
+
label: string;
|
|
95
|
+
/** One of: text, email, phone, date, number, select, boolean, url, textarea. */
|
|
96
|
+
field_type: string;
|
|
97
|
+
required: boolean;
|
|
98
|
+
show_on_register: boolean;
|
|
99
|
+
/** Optional validation metadata — passed through to the UI. */
|
|
100
|
+
validation?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
interface RegistrationConfig {
|
|
103
|
+
/** Ordered list of custom registration fields. Declaring this replaces the full list. */
|
|
104
|
+
fields: RegistrationFieldDefinition[];
|
|
105
|
+
}
|
|
106
|
+
interface OAuthProviderDefinition {
|
|
107
|
+
enabled: boolean;
|
|
108
|
+
client_id: string;
|
|
109
|
+
/** Prefer `{ env: "VAR_NAME" }` or `"${env.VAR_NAME}"`. */
|
|
110
|
+
client_secret: SecretRef;
|
|
111
|
+
scopes: string[];
|
|
112
|
+
/** Only for `custom_oidc`. */
|
|
113
|
+
discovery_url?: string;
|
|
114
|
+
}
|
|
115
|
+
interface OAuthProvidersConfig {
|
|
116
|
+
google?: OAuthProviderDefinition;
|
|
117
|
+
github?: OAuthProviderDefinition;
|
|
118
|
+
okta?: OAuthProviderDefinition;
|
|
119
|
+
custom_oidc?: OAuthProviderDefinition;
|
|
120
|
+
}
|
|
121
|
+
interface PasswordPolicyDefinition {
|
|
122
|
+
min_length: number;
|
|
123
|
+
require_uppercase: boolean;
|
|
124
|
+
require_number: boolean;
|
|
125
|
+
require_special: boolean;
|
|
126
|
+
}
|
|
127
|
+
interface SessionConfigDefinition {
|
|
128
|
+
access_token_ttl_secs: number;
|
|
129
|
+
refresh_token_ttl_secs: number;
|
|
130
|
+
max_sessions_per_user: number;
|
|
131
|
+
require_email_verification: boolean;
|
|
132
|
+
}
|
|
133
|
+
interface SecurityConfig {
|
|
134
|
+
password_policy?: PasswordPolicyDefinition;
|
|
135
|
+
session?: SessionConfigDefinition;
|
|
136
|
+
}
|
|
137
|
+
interface BrandingConfig {
|
|
138
|
+
app_name?: string;
|
|
139
|
+
logo_url?: string;
|
|
140
|
+
primary_color?: string;
|
|
141
|
+
secondary_color?: string;
|
|
142
|
+
welcome_message?: string;
|
|
143
|
+
welcome_subtitle?: string;
|
|
144
|
+
/** `"centered"`, `"split-left"`, `"split-right"`, or `"fullscreen"`. */
|
|
145
|
+
layout?: string;
|
|
146
|
+
background_image_url?: string;
|
|
147
|
+
/** 0–100 percentage. */
|
|
148
|
+
background_focal_point?: {
|
|
149
|
+
x: number;
|
|
150
|
+
y: number;
|
|
151
|
+
};
|
|
152
|
+
background_gradient?: string;
|
|
153
|
+
/** `"light"`, `"dark"`, or `"auto"`. */
|
|
154
|
+
color_mode?: string;
|
|
155
|
+
font_family?: string;
|
|
156
|
+
terms_url?: string;
|
|
157
|
+
privacy_url?: string;
|
|
158
|
+
/** Raw CSS merged into the hosted auth pages. */
|
|
159
|
+
custom_css?: string;
|
|
160
|
+
}
|
|
161
|
+
interface AuthConfigBlock {
|
|
162
|
+
resources?: ResourceDefinition[];
|
|
163
|
+
groups?: GroupDefinition[];
|
|
164
|
+
relations?: RelationTypeDefinition[];
|
|
165
|
+
registration?: RegistrationConfig;
|
|
166
|
+
oauth?: OAuthProvidersConfig;
|
|
167
|
+
security?: SecurityConfig;
|
|
168
|
+
branding?: BrandingConfig;
|
|
169
|
+
}
|
|
170
|
+
interface MaravillaConfig {
|
|
171
|
+
/** All project-level auth settings. Every field is optional — partial adoption is supported. */
|
|
172
|
+
auth?: AuthConfigBlock;
|
|
173
|
+
/** Declarative database indexes (regular + vector). Reconciled upsert-only on deploy. */
|
|
174
|
+
database?: DatabaseConfigBlock;
|
|
175
|
+
}
|
|
176
|
+
/** MongoDB-style key direction: `1` ascending, `-1` descending. */
|
|
177
|
+
type IndexDirectionConfig = 1 | -1;
|
|
178
|
+
interface DocumentIndexDeclaration {
|
|
179
|
+
/** Collection the index lives on. */
|
|
180
|
+
collection: string;
|
|
181
|
+
/** Optional name; falls back to an auto-derived name. */
|
|
182
|
+
name?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Compound-index key shape. Array of `[field, direction]` tuples
|
|
185
|
+
* preserves ordering, which matters for compound indexes.
|
|
186
|
+
*/
|
|
187
|
+
keys: Array<[string, IndexDirectionConfig]> | Record<string, IndexDirectionConfig>;
|
|
188
|
+
unique?: boolean;
|
|
189
|
+
sparse?: boolean;
|
|
190
|
+
/**
|
|
191
|
+
* Partial-index predicate — restricted to inline-literal operators
|
|
192
|
+
* (`$eq`, `$ne`, `$gt`/`$gte`/`$lt`/`$lte`, `$in`/`$nin`, `$exists`,
|
|
193
|
+
* `$and`, `$or`). No `$regex` / `$where` / `$text`.
|
|
194
|
+
*/
|
|
195
|
+
partial?: Record<string, unknown>;
|
|
196
|
+
/** TTL in seconds. Requires a single-field index on a unix-seconds field. */
|
|
197
|
+
expireAfterSeconds?: number;
|
|
198
|
+
}
|
|
199
|
+
/** Distance metric used by a vector index. */
|
|
200
|
+
type VectorMetricConfig = 'cosine' | 'l2' | 'hamming';
|
|
201
|
+
/** Storage precision for a vector index. */
|
|
202
|
+
type VectorStorageConfig = 'float32' | 'int8' | 'bit';
|
|
203
|
+
interface VectorIndexDeclaration {
|
|
204
|
+
collection: string;
|
|
205
|
+
field: string;
|
|
206
|
+
dimensions: number;
|
|
207
|
+
metric?: VectorMetricConfig;
|
|
208
|
+
storage?: VectorStorageConfig;
|
|
209
|
+
matryoshka?: boolean;
|
|
210
|
+
multiVector?: boolean;
|
|
211
|
+
}
|
|
212
|
+
interface DatabaseConfigBlock {
|
|
213
|
+
/** MongoDB-style secondary indexes. */
|
|
214
|
+
indexes?: DocumentIndexDeclaration[];
|
|
215
|
+
/** sqlite-vec-backed vector indexes. */
|
|
216
|
+
vectorIndexes?: VectorIndexDeclaration[];
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Identity function that returns the config unchanged — exists purely so the
|
|
220
|
+
* TypeScript compiler can infer `MaravillaConfig` and give you IntelliSense
|
|
221
|
+
* on every field.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* import { defineConfig } from '@maravilla-labs/platform/config';
|
|
226
|
+
*
|
|
227
|
+
* export default defineConfig({
|
|
228
|
+
* auth: {
|
|
229
|
+
* resources: [{ name: 'todos', title: 'Todos', actions: ['read', 'write'] }],
|
|
230
|
+
* },
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
declare function defineConfig(config: MaravillaConfig): MaravillaConfig;
|
|
235
|
+
|
|
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 };
|
package/dist/config.js
ADDED
|
@@ -0,0 +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":";AAiRO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;","names":[]}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { R as RenEvent } from './ren-D0DCQ0Fs.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Event handler registration helpers for Maravilla.
|
|
5
|
+
*
|
|
6
|
+
* User apps declare event handlers in `events.ts` or `events/*.ts`:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { onKvChange, defineEvent } from '@maravilla-labs/platform/events';
|
|
10
|
+
*
|
|
11
|
+
* export const invalidateCache = onKvChange(
|
|
12
|
+
* { namespace: 'config', keyPattern: 'feature:*' },
|
|
13
|
+
* async (event, ctx) => { await ctx.kv.delete('cache', event.key!); },
|
|
14
|
+
* );
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* These helpers are pure factories. They produce a `RegisteredHandler`
|
|
18
|
+
* marker object that the build pipeline (`@maravilla-labs/functions`
|
|
19
|
+
* `discoverEvents`) detects by its `__maravilla_trigger` property.
|
|
20
|
+
* The Rust event dispatcher uses the trigger config from the manifest
|
|
21
|
+
* and invokes the bundled handler via `globalThis.handleEvent(id, event)`.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
type EventTrigger = EventTriggerKv | EventTriggerDb | EventTriggerAuth | EventTriggerSchedule | EventTriggerQueue | EventTriggerChannel | EventTriggerDeploy | EventTriggerRen;
|
|
25
|
+
interface EventTriggerKv {
|
|
26
|
+
kind: 'kv';
|
|
27
|
+
namespace?: string;
|
|
28
|
+
keyPattern?: string;
|
|
29
|
+
op?: 'put' | 'delete' | 'expired';
|
|
30
|
+
}
|
|
31
|
+
interface EventTriggerDb {
|
|
32
|
+
kind: 'db';
|
|
33
|
+
collection: string;
|
|
34
|
+
op?: 'insert' | 'update' | 'delete';
|
|
35
|
+
}
|
|
36
|
+
type AuthOp = 'registered' | 'logged_in' | 'logged_out' | 'logged_out_all' | 'deleted' | 'updated';
|
|
37
|
+
interface EventTriggerAuth {
|
|
38
|
+
kind: 'auth';
|
|
39
|
+
op?: AuthOp;
|
|
40
|
+
}
|
|
41
|
+
interface EventTriggerSchedule {
|
|
42
|
+
kind: 'schedule';
|
|
43
|
+
cron: string;
|
|
44
|
+
}
|
|
45
|
+
interface EventTriggerQueue {
|
|
46
|
+
kind: 'queue';
|
|
47
|
+
name: string;
|
|
48
|
+
batch?: number;
|
|
49
|
+
maxAttempts?: number;
|
|
50
|
+
}
|
|
51
|
+
interface EventTriggerChannel {
|
|
52
|
+
kind: 'channel';
|
|
53
|
+
channel: string;
|
|
54
|
+
type?: string;
|
|
55
|
+
}
|
|
56
|
+
interface EventTriggerDeploy {
|
|
57
|
+
kind: 'deploy';
|
|
58
|
+
phase: 'ready' | 'draining' | 'stopped';
|
|
59
|
+
}
|
|
60
|
+
interface EventTriggerRen {
|
|
61
|
+
kind: 'ren';
|
|
62
|
+
match: {
|
|
63
|
+
r?: string;
|
|
64
|
+
t?: string;
|
|
65
|
+
ns?: string;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
interface KvChangeEvent {
|
|
69
|
+
op: 'put' | 'delete' | 'expired';
|
|
70
|
+
namespace: string;
|
|
71
|
+
key: string;
|
|
72
|
+
/** Present on `put`. */
|
|
73
|
+
value?: unknown;
|
|
74
|
+
ts: number;
|
|
75
|
+
}
|
|
76
|
+
interface DbChangeEvent {
|
|
77
|
+
op: 'insert' | 'update' | 'delete';
|
|
78
|
+
collection: string;
|
|
79
|
+
id: string;
|
|
80
|
+
doc?: unknown;
|
|
81
|
+
before?: unknown;
|
|
82
|
+
after?: unknown;
|
|
83
|
+
ts: number;
|
|
84
|
+
}
|
|
85
|
+
interface AuthEvent {
|
|
86
|
+
op: AuthOp;
|
|
87
|
+
userId: string;
|
|
88
|
+
/**
|
|
89
|
+
* Op-specific payload:
|
|
90
|
+
* - `registered` → `{ email, provider, profile }` where `profile` is the
|
|
91
|
+
* app's custom fields passed to `platform.auth.register({ profile: {...} })`.
|
|
92
|
+
* - `logged_in` → `{ email }`
|
|
93
|
+
* - `logged_out` → `{ sessionId }`
|
|
94
|
+
* - `logged_out_all` / `deleted` / `updated` → null or empty.
|
|
95
|
+
*/
|
|
96
|
+
data?: {
|
|
97
|
+
email?: string;
|
|
98
|
+
provider?: string;
|
|
99
|
+
profile?: Record<string, unknown> | null;
|
|
100
|
+
sessionId?: string;
|
|
101
|
+
[k: string]: unknown;
|
|
102
|
+
};
|
|
103
|
+
ts: number;
|
|
104
|
+
}
|
|
105
|
+
interface ScheduleEvent {
|
|
106
|
+
cron: string;
|
|
107
|
+
scheduledAt: number;
|
|
108
|
+
firedAt: number;
|
|
109
|
+
}
|
|
110
|
+
interface QueueMessage<T = unknown> {
|
|
111
|
+
id: string;
|
|
112
|
+
payload: T;
|
|
113
|
+
attempt: number;
|
|
114
|
+
enqueuedAt: number;
|
|
115
|
+
}
|
|
116
|
+
interface ChannelEvent<T = unknown> {
|
|
117
|
+
channel: string;
|
|
118
|
+
type: string;
|
|
119
|
+
data?: T;
|
|
120
|
+
uid?: string;
|
|
121
|
+
ts: number;
|
|
122
|
+
}
|
|
123
|
+
interface DeployEvent {
|
|
124
|
+
phase: 'ready' | 'draining' | 'stopped';
|
|
125
|
+
ts: number;
|
|
126
|
+
}
|
|
127
|
+
interface EventCtx {
|
|
128
|
+
/** Per-tenant env vars. */
|
|
129
|
+
env: Record<string, string>;
|
|
130
|
+
/** Trace correlation id — propagate through logs. */
|
|
131
|
+
traceId: string;
|
|
132
|
+
tenant: string;
|
|
133
|
+
handlerId: string;
|
|
134
|
+
[extra: string]: unknown;
|
|
135
|
+
}
|
|
136
|
+
declare const TRIGGER_SYMBOL: "__maravilla_trigger";
|
|
137
|
+
interface RegisteredHandler<Event = unknown, Ctx = EventCtx> {
|
|
138
|
+
readonly [TRIGGER_SYMBOL]: EventTrigger;
|
|
139
|
+
readonly handler: (event: Event, ctx: Ctx) => unknown | Promise<unknown>;
|
|
140
|
+
}
|
|
141
|
+
declare function onKvChange(config: Omit<EventTriggerKv, 'kind'>, handler: (event: KvChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<KvChangeEvent>;
|
|
142
|
+
declare function onDbChange(config: Omit<EventTriggerDb, 'kind'>, handler: (event: DbChangeEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<DbChangeEvent>;
|
|
143
|
+
/**
|
|
144
|
+
* React to user authentication events: registration, login, logout,
|
|
145
|
+
* deletion, update. Omit `op` to match all auth events; set it to
|
|
146
|
+
* narrow to a specific lifecycle transition.
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* export const welcomeEmail = onAuth(
|
|
150
|
+
* { op: 'registered' },
|
|
151
|
+
* async (event, ctx) => {
|
|
152
|
+
* await sendWelcomeEmail(event.data?.email);
|
|
153
|
+
* },
|
|
154
|
+
* );
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
declare function onAuth(config: Omit<EventTriggerAuth, 'kind'>, handler: (event: AuthEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<AuthEvent>;
|
|
158
|
+
declare function onSchedule(cron: string, handler: (event: ScheduleEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<ScheduleEvent>;
|
|
159
|
+
declare function onQueue<T = unknown>(name: string, config: Omit<EventTriggerQueue, 'kind' | 'name'>, handler: (messages: QueueMessage<T>[], ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<QueueMessage<T>[]>;
|
|
160
|
+
declare function onChannel<T = unknown>(config: Omit<EventTriggerChannel, 'kind'>, handler: (event: ChannelEvent<T>, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<ChannelEvent<T>>;
|
|
161
|
+
declare function onDeploy(phase: EventTriggerDeploy['phase'], handler: (event: DeployEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<DeployEvent>;
|
|
162
|
+
/** Escape hatch for arbitrary `RenEvent` matches. */
|
|
163
|
+
declare function defineEvent(config: Omit<EventTriggerRen, 'kind'>, handler: (event: RenEvent, ctx: EventCtx) => unknown | Promise<unknown>): RegisteredHandler<RenEvent>;
|
|
164
|
+
/** Type guard used by the build-time discoverer and the runtime registry. */
|
|
165
|
+
declare function isRegisteredHandler(value: unknown): value is RegisteredHandler;
|
|
166
|
+
|
|
167
|
+
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 };
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var TRIGGER_SYMBOL = "__maravilla_trigger";
|
|
3
|
+
function register(trigger, handler) {
|
|
4
|
+
return { [TRIGGER_SYMBOL]: trigger, handler };
|
|
5
|
+
}
|
|
6
|
+
function onKvChange(config, handler) {
|
|
7
|
+
return register({ kind: "kv", ...config }, handler);
|
|
8
|
+
}
|
|
9
|
+
function onDbChange(config, handler) {
|
|
10
|
+
return register({ kind: "db", ...config }, handler);
|
|
11
|
+
}
|
|
12
|
+
function onAuth(config, handler) {
|
|
13
|
+
return register({ kind: "auth", ...config }, handler);
|
|
14
|
+
}
|
|
15
|
+
function onSchedule(cron, handler) {
|
|
16
|
+
return register({ kind: "schedule", cron }, handler);
|
|
17
|
+
}
|
|
18
|
+
function onQueue(name, config, handler) {
|
|
19
|
+
return register({ kind: "queue", name, ...config }, handler);
|
|
20
|
+
}
|
|
21
|
+
function onChannel(config, handler) {
|
|
22
|
+
return register({ kind: "channel", ...config }, handler);
|
|
23
|
+
}
|
|
24
|
+
function onDeploy(phase, handler) {
|
|
25
|
+
return register({ kind: "deploy", phase }, handler);
|
|
26
|
+
}
|
|
27
|
+
function defineEvent(config, handler) {
|
|
28
|
+
return register({ kind: "ren", ...config }, handler);
|
|
29
|
+
}
|
|
30
|
+
function isRegisteredHandler(value) {
|
|
31
|
+
return typeof value === "object" && value !== null && TRIGGER_SYMBOL in value && typeof value.handler === "function";
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
TRIGGER_SYMBOL,
|
|
35
|
+
defineEvent,
|
|
36
|
+
isRegisteredHandler,
|
|
37
|
+
onAuth,
|
|
38
|
+
onChannel,
|
|
39
|
+
onDbChange,
|
|
40
|
+
onDeploy,
|
|
41
|
+
onKvChange,
|
|
42
|
+
onQueue,
|
|
43
|
+
onSchedule
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +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 /** Trace correlation id — propagate through logs. */\n traceId: string;\n tenant: string;\n handlerId: string;\n // Platform services are attached by the runtime at invoke time.\n [extra: string]: unknown;\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":";AA8KO,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":[]}
|