@zveltio/sdk 2.0.8 → 3.0.0-beta.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.
Files changed (64) hide show
  1. package/dist/build/index.d.ts +77 -0
  2. package/dist/build/index.d.ts.map +1 -0
  3. package/dist/build/index.js +157 -0
  4. package/dist/client/QueryBuilder.d.ts.map +1 -1
  5. package/dist/client/QueryBuilder.js +12 -3
  6. package/dist/client/RealtimeClient.d.ts.map +1 -1
  7. package/dist/client/RealtimeClient.js +3 -1
  8. package/dist/client/ZveltioClient.d.ts.map +1 -1
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +16 -5
  11. package/dist/core.d.ts.map +1 -1
  12. package/dist/ddl/index.d.ts +153 -0
  13. package/dist/ddl/index.d.ts.map +1 -0
  14. package/dist/ddl/index.js +33 -0
  15. package/dist/extension/index.d.ts +522 -10
  16. package/dist/extension/index.d.ts.map +1 -1
  17. package/dist/extension/index.js +1 -1
  18. package/dist/extension/permission-gate.d.ts +37 -0
  19. package/dist/extension/permission-gate.d.ts.map +1 -0
  20. package/dist/extension/permission-gate.js +93 -0
  21. package/dist/generate-types.d.ts +37 -0
  22. package/dist/generate-types.d.ts.map +1 -0
  23. package/dist/generate-types.js +157 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/local-store.d.ts.map +1 -1
  27. package/dist/local-store.js +1 -3
  28. package/dist/offline/index.d.ts +105 -0
  29. package/dist/offline/index.d.ts.map +1 -0
  30. package/dist/offline/index.js +235 -0
  31. package/dist/publish/index.d.ts +94 -0
  32. package/dist/publish/index.d.ts.map +1 -0
  33. package/dist/publish/index.js +148 -0
  34. package/dist/realtime.d.ts.map +1 -1
  35. package/dist/realtime.js +6 -2
  36. package/dist/rpc/index.d.ts +68 -0
  37. package/dist/rpc/index.d.ts.map +1 -0
  38. package/dist/rpc/index.js +61 -0
  39. package/dist/schema-codegen.d.ts +60 -0
  40. package/dist/schema-codegen.d.ts.map +1 -0
  41. package/dist/schema-codegen.js +369 -0
  42. package/dist/schema-watcher.d.ts.map +1 -1
  43. package/dist/schema-watcher.js +1 -3
  44. package/dist/studio/index.d.ts +84 -0
  45. package/dist/studio/index.d.ts.map +1 -0
  46. package/dist/studio/index.js +180 -0
  47. package/dist/svelte.d.ts.map +1 -1
  48. package/dist/sync-manager.d.ts.map +1 -1
  49. package/dist/sync-manager.js +4 -11
  50. package/dist/testing/index.d.ts +121 -0
  51. package/dist/testing/index.d.ts.map +1 -0
  52. package/dist/testing/index.js +283 -0
  53. package/dist/testing/with-test-db.d.ts +119 -0
  54. package/dist/testing/with-test-db.d.ts.map +1 -0
  55. package/dist/testing/with-test-db.js +313 -0
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/utils.js +3 -1
  58. package/dist/validate/index.d.ts +95 -0
  59. package/dist/validate/index.d.ts.map +1 -0
  60. package/dist/validate/index.js +201 -0
  61. package/dist/validate/migration-parse.d.ts +17 -0
  62. package/dist/validate/migration-parse.d.ts.map +1 -0
  63. package/dist/validate/migration-parse.js +11 -0
  64. package/package.json +53 -1
@@ -1,34 +1,538 @@
1
1
  import type { Hono } from 'hono';
2
- export interface ExtensionContext {
3
- db: any;
4
- auth: any;
5
- fieldTypeRegistry: FieldTypeRegistryAPI;
6
- }
2
+ import type { Kysely } from 'kysely';
3
+ export { permissionGate } from './permission-gate.js';
7
4
  export interface FieldTypeRegistryAPI {
8
5
  register(definition: any): void;
9
6
  get(type: string): any;
10
7
  has(type: string): boolean;
11
8
  list(): string[];
9
+ /**
10
+ * Full list of registered field type definitions. Distinct from
11
+ * `list()` which returns just the type names — `getAll()` returns
12
+ * the registered metadata records (label, validators, default
13
+ * value, etc.). Used by AI/schema-generation tooling.
14
+ */
15
+ getAll(): Array<{
16
+ type: string;
17
+ [key: string]: any;
18
+ }>;
19
+ /** Coerce a value FROM the database representation INTO its TS shape. */
20
+ deserialize(type: string, value: any): any;
21
+ /** Coerce a value FROM its TS shape INTO the database representation. */
22
+ serialize(type: string, value: any): any;
23
+ }
24
+ /**
25
+ * Context injected into every extension's `register()` call.
26
+ *
27
+ * The `DB` generic threads the extension's codegen'd database schema
28
+ * (`@zveltio/sdk/codegen` → `<ext>/.zveltio/db.d.ts`) through to `ctx.db`.
29
+ * Default `any` keeps legacy extensions compiling untouched. Migrated
30
+ * extensions get full Kysely autocomplete + typo detection.
31
+ */
32
+ export interface ExtensionContext<DB = any> {
33
+ /**
34
+ * Kysely database instance (restricted — cannot query zv_* system tables).
35
+ * Typed via the `DB` generic; use `ZveltioExtension<MySchema>` to opt in.
36
+ */
37
+ db: Kysely<DB>;
38
+ /** Better-Auth instance — use `ctx.auth.api.getSession({ headers })` in route handlers. */
39
+ auth: any;
40
+ /** Field type registry — register custom field types here. */
41
+ fieldTypeRegistry: FieldTypeRegistryAPI;
42
+ /** Typed event bus — subscribe to record lifecycle events (insert/update/delete). */
43
+ events: any;
44
+ /** Check if a user has permission for a resource/action. */
45
+ checkPermission: (userId: string, resource: string, action: string) => Promise<boolean>;
46
+ /** Get all roles assigned to a user. */
47
+ getUserRoles: (userId: string) => Promise<string[]>;
48
+ /**
49
+ * DDLManager — schema-migration utilities for collections and fields
50
+ * (S4-08). See `@zveltio/sdk/ddl` for the interface. The engine owns the
51
+ * implementation; extensions use the instance handed in here. Typed as
52
+ * `any` at this layer (vs the SDK's DDLManager interface) so existing
53
+ * `ctx.DDLManager` call sites in the 50+ extensions don't break when
54
+ * upgrading; opt in to the typed surface with:
55
+ *
56
+ * import type { DDLManager } from '@zveltio/sdk/ddl';
57
+ * const ddl: DDLManager = ctx.DDLManager;
58
+ */
59
+ DDLManager: any;
60
+ /**
61
+ * Inter-extension service registry. Extensions publish services here for other
62
+ * extensions to consume. Stable public API — use this for cross-extension
63
+ * communication instead of direct imports.
64
+ */
65
+ services: ServiceRegistry;
66
+ /**
67
+ * Query-alter registry. Register a function per table that the data layer
68
+ * will call before executing SELECT queries on that table — typical uses
69
+ * are tenant isolation, soft-delete filtering, and column redaction.
70
+ *
71
+ * @example
72
+ * ctx.queryAlter.register({
73
+ * table: 'zvd_contacts',
74
+ * alter(qb, user) {
75
+ * if (user.isGod) return qb;
76
+ * return qb.where('tenant_id', '=', user.tenantId);
77
+ * },
78
+ * });
79
+ *
80
+ * Alters are scoped to the registering extension — removed automatically on
81
+ * unload / hot-reload. Today's scope: SELECT queries only.
82
+ */
83
+ queryAlter: QueryAlterScope;
84
+ /**
85
+ * Per-record authorization callbacks. Return `'deny'` to block access to
86
+ * a specific row; first deny across all extensions wins. See
87
+ * `EntityAccessScope` for an example.
88
+ */
89
+ entityAccess: EntityAccessScope;
90
+ /**
91
+ * Register a route on the engine's GLOBAL app, outside the extension's
92
+ * `/ext/<name>/` namespace. Use sparingly — most routes should live on
93
+ * the sub-app provided to `register()`. Valid cases:
94
+ *
95
+ * - Public CDN-style endpoints with stable user-facing URLs
96
+ * (e.g. `/share/:token` for shared file links).
97
+ * - User-deployed handlers whose path shape is dictated by the user
98
+ * (e.g. `/api/fn/:name` for edge functions).
99
+ *
100
+ * Extensions on `mountStrategy: 'subapp'` use this to keep specific
101
+ * routes at fixed root-relative paths even though the rest of the
102
+ * extension lives under `/ext/<name>/`. Public routes are re-registered
103
+ * on each engine rebuild — disabling the extension makes them disappear
104
+ * just like sub-app routes.
105
+ *
106
+ * @example
107
+ * ctx.registerPublicRoute({
108
+ * method: 'GET',
109
+ * path: '/share/:token',
110
+ * handler: async (c) => { ... },
111
+ * });
112
+ */
113
+ registerPublicRoute(spec: PublicRouteSpec): void;
114
+ /**
115
+ * Engine-internal helpers. Stable across patch versions but may break across
116
+ * minor versions. First-party extensions only.
117
+ */
118
+ internals: ExtensionInternals;
119
+ }
120
+ /**
121
+ * Engine-internal helpers passed via `ctx.internals.*`.
122
+ * Each field is the live engine singleton or function — extensions should never
123
+ * import these directly from `../../../packages/engine/src/...` paths.
124
+ */
125
+ export interface ExtensionInternals {
126
+ /** Type-safe insert into a dynamic (user-defined) collection table. */
127
+ dynamicInsert: (db: any, collection: string, values: Record<string, unknown>) => Promise<unknown>;
128
+ /** Introspect a Postgres schema — returns tables, columns, types, indexes, FKs. */
129
+ introspectSchema: (db: any, schemaName?: string, excludePatterns?: string[], dryRun?: boolean) => Promise<any[]>;
130
+ /** Run a data-quality scan over a collection. */
131
+ runQualityScan: (...args: any[]) => Promise<unknown>;
132
+ /** Invalidate the cached validation rules for a collection. */
133
+ invalidateRulesCache: (collection: string) => void;
134
+ /** Run an Edge Function in the sandbox (used by developer/edge-functions). */
135
+ runEdgeFunction: (...args: any[]) => Promise<unknown>;
136
+ /** Cross-extension hook registry (e.g. trash purge, scheduled cleanups). */
137
+ extensionRegistry: any;
138
+ /** Queue an HTML→PDF render via the worker pool. */
139
+ generatePDFAsync: (html: string, options?: Record<string, unknown>) => Promise<unknown>;
140
+ /** Synchronous template render with `{{var}}` interpolation. */
141
+ renderTemplate: (template: string, variables: Record<string, unknown>) => string;
142
+ /** Inline HTML→PDF render (blocking). */
143
+ generatePDF: (...args: any[]) => Promise<unknown>;
144
+ /** Move a file to the trash bucket (soft delete with TTL). */
145
+ moveToTrash: (...args: any[]) => Promise<unknown>;
146
+ /** Schedule async indexing for a newly uploaded file. */
147
+ scheduleFileIndexing: (...args: any[]) => Promise<unknown>;
148
+ /** GraphQL DataLoader registry — N+1 query batching. */
149
+ DataLoaderRegistry: any;
150
+ /** Validate GraphQL query depth. Returns an error message if too deep, null otherwise. */
151
+ checkQueryDepth: (query: string, maxDepth?: number) => string | null;
152
+ /** Enqueue an asynchronous DDL job (Ghost Tables, large alters). */
153
+ enqueueDDLJob: (...args: any[]) => Promise<unknown>;
154
+ /** Validate that a URL targets a public, non-internal address (SSRF safety). */
155
+ validatePublicUrl: (url: string) => Promise<URL>;
156
+ /** Extract plain text from an uploaded file (PDF/DOCX/etc.) for AI indexing. */
157
+ extractTextFromFile: (buffer: ArrayBuffer | Buffer | Uint8Array, mimeType: string) => Promise<string>;
158
+ /** Send an in-app notification to a user (writes to zv_notifications system table). */
159
+ sendNotification: (db: any, input: {
160
+ user_id: string;
161
+ type?: string;
162
+ title: string;
163
+ message?: string;
164
+ data?: unknown;
165
+ }) => Promise<void>;
166
+ /**
167
+ * Create a Better-Auth session for an already-authenticated user (SSO bridge).
168
+ *
169
+ * SAML / LDAP / OIDC extensions verify the user out-of-band and need a
170
+ * Better-Auth session that `auth.api.getSession({ headers })` will accept.
171
+ * This helper handles the exact column shape (camelCase) of the `session`
172
+ * table AND the signed-cookie format Better-Auth expects, so SSO providers
173
+ * don't have to inline Better-Auth internals.
174
+ *
175
+ * Returns the `Set-Cookie` header value — the route handler attaches it to
176
+ * its Response. Pass `crossDomain: true` to emit `SameSite=None; Secure`
177
+ * (needed when Studio runs on a different origin than the engine).
178
+ */
179
+ createBetterAuthSession: (db: any, userId: string, opts?: {
180
+ ipAddress?: string;
181
+ userAgent?: string;
182
+ ttlSeconds?: number;
183
+ crossDomain?: boolean;
184
+ }) => Promise<{
185
+ token: string;
186
+ setCookie: string;
187
+ }>;
188
+ /**
189
+ * Encrypt a secret (LDAP bind passwords, third-party API keys, …) with
190
+ * the engine's AES-256-GCM field encryption key (FIELD_ENCRYPTION_KEY).
191
+ * Output is prefixed with `enc:v1:`, so calling this on an already-
192
+ * encrypted value is a no-op. Throws if FIELD_ENCRYPTION_KEY is unset;
193
+ * extensions should refuse the write rather than persist plaintext.
194
+ */
195
+ encryptSecret: (plaintext: string) => Promise<string>;
196
+ /**
197
+ * Reverse of `encryptSecret`. Returns the plaintext if the value has
198
+ * the `enc:v1:` prefix; otherwise returns the value as-is so reads of
199
+ * legacy unencrypted rows still work during a rolling encryption rollout.
200
+ */
201
+ decryptSecret: (value: string) => Promise<string>;
202
+ }
203
+ /**
204
+ * Entity-access scope handed to each extension via `ctx.entityAccess`.
205
+ *
206
+ * Use this for row-level authorization that cannot be expressed via roles
207
+ * alone: "only the owner can view this", "drafts editable only by author",
208
+ * "manager can approve only their direct reports", etc.
209
+ *
210
+ * Semantics: first `deny` wins. If no checks are registered for a table,
211
+ * access is allowed (no extension cares about it).
212
+ *
213
+ * @example
214
+ * ctx.entityAccess.register({
215
+ * table: 'zvd_payroll',
216
+ * async check(record, user, op) {
217
+ * if (user.roles.includes('hr')) return 'allow';
218
+ * if (op === 'view' && record.user_id === user.id) return 'allow';
219
+ * return 'deny';
220
+ * },
221
+ * });
222
+ *
223
+ * Scoped to the registering extension — automatically cleaned up on unload.
224
+ */
225
+ export interface EntityAccessScope {
226
+ register(def: {
227
+ table: string;
228
+ check: (record: any, user: any, op: 'view' | 'update' | 'delete') => ('allow' | 'deny') | Promise<'allow' | 'deny'>;
229
+ }): void;
230
+ list(): Array<{
231
+ table: string;
232
+ }>;
233
+ unregisterAll(): void;
234
+ }
235
+ /**
236
+ * Query-alter scope handed to each extension via `ctx.queryAlter`.
237
+ *
238
+ * Each call to `register({ table, alter })` is automatically tagged with the
239
+ * registering extension's name so it can be cleaned up on unload — extensions
240
+ * don't manage that lifecycle themselves.
241
+ */
242
+ export interface QueryAlterScope {
243
+ register(def: {
244
+ /** Table name to attach the alter to (typically `zvd_<collection>`). */
245
+ table: string;
246
+ /**
247
+ * Mutates the Kysely query builder before execution. Must return a chained
248
+ * builder. `qb` and `user` are typed `any` in the SDK so extensions don't
249
+ * need to depend on engine internals; cast as needed.
250
+ */
251
+ alter: (qb: any, user: any) => any;
252
+ }): void;
253
+ list(): Array<{
254
+ table: string;
255
+ }>;
256
+ unregisterAll(): void;
257
+ }
258
+ /**
259
+ * Inter-extension service registry.
260
+ *
261
+ * Extensions publish services here for other extensions to consume — a Drupal-style
262
+ * services container. Services are keyed by string names (e.g. `'ai.providers'`,
263
+ * `'crm.contacts.lookup'`). Consumers should treat `get()` returning `null` as a
264
+ * recoverable signal (the providing extension is not active) and either skip the
265
+ * feature or surface a clear error to the user.
266
+ *
267
+ * Extensions MUST NOT directly import from other extensions — communication goes
268
+ * exclusively through this registry.
269
+ *
270
+ * The instance passed to each extension via `ctx.services` is **scoped to that
271
+ * extension** — anything `register()`-ed through it is automatically attributed
272
+ * to the extension and gets cleaned up on unload (disable / hot-reload).
273
+ * `register()` is idempotent within a scope (replaces on duplicate), so an
274
+ * extension's `register()` can safely run multiple times during hot-reload.
275
+ */
276
+ export interface ServiceRegistry {
277
+ /**
278
+ * Publish a service under a name.
279
+ * Idempotent: re-registering the same name from the same extension replaces.
280
+ * Throws if a different extension already owns that name.
281
+ */
282
+ register<T = unknown>(name: string, value: T): void;
283
+ /** Remove a service this extension previously registered. No-op if not owned. */
284
+ unregister(name: string): void;
285
+ /** Get a service. Returns `null` if not registered. */
286
+ get<T = unknown>(name: string): T | null;
287
+ /** Check if a service is registered. */
288
+ has(name: string): boolean;
289
+ /**
290
+ * Wait for a service to be registered. Resolves immediately if already there.
291
+ * Rejects after `timeoutMs` (default 30s) if never registered.
292
+ */
293
+ waitFor<T = unknown>(name: string, timeoutMs?: number): Promise<T>;
294
+ /** List all registered service names — useful for debugging. */
295
+ list(): string[];
296
+ }
297
+ /**
298
+ * Pre-write hook payloads.
299
+ *
300
+ * Subscribe via `ctx.events.onBefore('record.beforeInsert', handler)`. The
301
+ * handler is async and receives a mutable payload. Call:
302
+ * - `payload.mutate({ ... })` to merge fields into the in-flight write —
303
+ * subsequent handlers and the data layer see the patched values.
304
+ * - `payload.abort('reason')` to reject the write — the HTTP response
305
+ * becomes 422 with code `EXT_HOOK_ABORTED` and the reason in the body.
306
+ *
307
+ * Hooks run sequentially in registration order. Throwing a non-abort error
308
+ * surfaces as 500.
309
+ */
310
+ export interface BeforeInsertPayload {
311
+ collection: string;
312
+ data: Record<string, unknown>;
313
+ userId: string;
314
+ abort(reason: string): never;
315
+ mutate(patch: Record<string, unknown>): void;
316
+ }
317
+ export interface BeforeUpdatePayload {
318
+ collection: string;
319
+ id: string;
320
+ before: Record<string, unknown>;
321
+ patch: Record<string, unknown>;
322
+ userId: string;
323
+ abort(reason: string): never;
324
+ mutate(patch: Record<string, unknown>): void;
12
325
  }
13
- export interface ZveltioExtension {
326
+ export interface BeforeDeletePayload {
327
+ collection: string;
328
+ id: string;
329
+ record: Record<string, unknown>;
330
+ userId: string;
331
+ abort(reason: string): never;
332
+ }
333
+ /**
334
+ * Native scheduled task. Extensions declare these via `ZveltioExtension.schedules()`.
335
+ *
336
+ * Specify exactly ONE timing field:
337
+ * - `intervalMs`: re-run every N milliseconds.
338
+ * - `at`: run once a day at `{ hour, minute }` (server's local timezone).
339
+ * - `cron`: reserved for future cron-expression support; currently logged
340
+ * as unsupported and the schedule is skipped.
341
+ *
342
+ * Every invocation persists to the `zv_extension_schedule_runs` table with
343
+ * status `running` → (`ok` | `failed` | `dlq`). `failed` rows trigger another
344
+ * attempt up to `retry.maxAttempts`; the final failed attempt is recorded as
345
+ * `dlq` (dead letter — admin can replay manually).
346
+ *
347
+ * Cross-instance: NOT yet coordinated. Multiple engine replicas will each run
348
+ * the same schedule until distributed locking lands.
349
+ */
350
+ export interface ExtensionSchedule<DB = any> {
351
+ /** Stable name, unique within this extension. Used as the persistence key. */
352
+ name: string;
353
+ intervalMs?: number;
354
+ at?: {
355
+ hour: number;
356
+ minute: number;
357
+ };
358
+ cron?: string;
359
+ /** Async work to perform. `runId` is the row id in zv_extension_schedule_runs. */
360
+ handler: (ctx: ExtensionContext<DB>, runId: string) => Promise<void>;
361
+ retry?: {
362
+ maxAttempts?: number;
363
+ backoffMs?: number;
364
+ };
365
+ /** Reserved — single-engine assumption today. Documented behaviour. */
366
+ singleton?: boolean;
367
+ }
368
+ /**
369
+ * Spec for a route registered on the engine's global app via
370
+ * `ctx.registerPublicRoute()`. See the `registerPublicRoute` field on
371
+ * `ExtensionContext` for usage guidance.
372
+ */
373
+ export interface PublicRouteSpec {
374
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'ALL';
375
+ /** Absolute path on the global app, e.g. `'/share/:token'`. */
376
+ path: string;
377
+ /** Hono handler — receives the Context, returns a Response (or via c.json). */
378
+ handler: (c: any) => any;
379
+ }
380
+ /**
381
+ * Mount strategy for the extension's Hono routes (S3-01).
382
+ *
383
+ * - `'global'` (default, legacy): the `register(app, ctx)` callback receives
384
+ * the engine's global Hono app. Extension owns full URL paths and typically
385
+ * mounts under `/api/<feature>`. Routes cannot be cleanly de-registered at
386
+ * runtime — disabling the extension requires an app rebuild.
387
+ *
388
+ * - `'subapp'`: the `register(subApp, ctx)` callback receives a per-extension
389
+ * Hono instance. The engine mounts it at `/ext/<extension-name>` so the
390
+ * extension's routes appear under that prefix. Disable/enable is cheap
391
+ * (next rebuild drops the sub-app); no cross-extension URL collisions.
392
+ * New extensions should use this. Migrating an existing extension is
393
+ * lock-step with updating its Studio bundle URL calls.
394
+ */
395
+ export type MountStrategy = 'global' | 'subapp';
396
+ /** The interface every Zveltio extension must implement. */
397
+ /**
398
+ * The interface every Zveltio extension implements.
399
+ *
400
+ * @example Untyped (legacy / new extensions before codegen)
401
+ * const ext: ZveltioExtension = { ... }
402
+ *
403
+ * @example Typed via codegen (S4-01 + S4-02)
404
+ * import type { ExtensionSchema as DB } from './.zveltio/db.js';
405
+ * const ext: ZveltioExtension<DB> = {
406
+ * async register(app, ctx) {
407
+ * // ctx.db is Kysely<DB> — full autocomplete on table + column names
408
+ * const items = await ctx.db.selectFrom('zv_my_items').selectAll().execute();
409
+ * }
410
+ * };
411
+ */
412
+ export interface ZveltioExtension<DB = any> {
413
+ /** Unique name — must match manifest.json `name` exactly (e.g. `'hr/employees'`). */
14
414
  name: string;
415
+ /** Category shown in the marketplace (e.g. `'hr'`, `'finance'`, `'content'`). */
15
416
  category: string;
16
- register: (app: Hono, ctx: ExtensionContext) => Promise<void>;
417
+ /**
418
+ * How the engine mounts the extension's routes. Defaults to `'global'` for
419
+ * backward compatibility with existing extensions. New extensions should
420
+ * declare `'subapp'`. See `MountStrategy` for details.
421
+ */
422
+ mountStrategy?: MountStrategy;
423
+ /**
424
+ * Called once when the extension is activated.
425
+ * Register Hono routes, subscribe to events, etc.
426
+ */
427
+ register: (app: Hono, ctx: ExtensionContext<DB>) => Promise<void>;
428
+ /** Register custom Studio field types contributed by this extension. */
17
429
  registerFieldTypes?: (registry: FieldTypeRegistryAPI) => void;
430
+ /** Return absolute paths to SQL migration files, run in order on first activation. */
18
431
  getMigrations?: () => string[];
432
+ /**
433
+ * Return the list of scheduled tasks. Called once after `register()`. The
434
+ * engine's cron runner picks them up and starts polling.
435
+ */
436
+ schedules?: () => ExtensionSchedule<DB>[];
437
+ /**
438
+ * Called when the extension is disabled or the server shuts down.
439
+ * Close database connections, clear timers, etc.
440
+ * Note: Hono routes cannot be de-registered at runtime — they persist until restart.
441
+ */
442
+ cleanup?: () => Promise<void>;
19
443
  }
20
444
  export interface StudioExtensionAPI {
21
445
  registerRoute(route: StudioRoute): void;
22
446
  registerFieldType(ft: StudioFieldType): void;
447
+ registerAssetPreview(handler: AssetPreviewHandler): void;
448
+ /**
449
+ * Register a component into a named slot in core Studio (S3-03).
450
+ * Slot names are stable strings declared by core (e.g. `dashboard.widgets`,
451
+ * `collections.list.toolbar`). See the developer guide for the full list.
452
+ */
453
+ registerSlot(name: string, contribution: SlotContribution): void;
454
+ /**
455
+ * Drupal-style `hook_form_alter` for Studio forms (S3-02). The hook
456
+ * receives a `form` object with `addField`, `hideField`, `reorder`,
457
+ * `addValidator`. Form IDs are stable strings (e.g. `core:user-edit`,
458
+ * `collection:zvd_contacts:edit`).
459
+ */
460
+ registerFormAlter(formId: string, hook: FormAlterHook): void;
23
461
  engineUrl: string;
24
462
  }
463
+ /** S3-03: a single slot contribution. */
464
+ export interface SlotContribution {
465
+ /** Svelte component to render. */
466
+ component: any;
467
+ /**
468
+ * Lower runs first. Default 100. Two contributions with the same
469
+ * priority render in registration order.
470
+ */
471
+ priority?: number;
472
+ /**
473
+ * Optional predicate. If returns false, the contribution is skipped.
474
+ * `ctx` carries whatever the slot host passes (typically `{ user }`).
475
+ */
476
+ visible?: (ctx: Record<string, unknown>) => boolean;
477
+ /** Optional props passed to the component. */
478
+ props?: Record<string, unknown>;
479
+ }
480
+ /** S3-02: signature of a form-alter hook. */
481
+ export type FormAlterHook = (form: FormAlterAPI, ctx: Record<string, unknown>) => void;
482
+ /** S3-02: the surface form-alter hooks operate on. */
483
+ export interface FormAlterAPI {
484
+ addField(spec: {
485
+ after?: string;
486
+ before?: string;
487
+ field: FormField;
488
+ }): void;
489
+ hideField(name: string): void;
490
+ reorder(order: string[]): void;
491
+ addValidator(fieldName: string, validator: (value: unknown) => string | null): void;
492
+ readonly fields: ReadonlyArray<FormField>;
493
+ }
494
+ /** S3-02: minimal form schema shape consumed by alters + renderers. */
495
+ export interface FormSchema {
496
+ /** Stable form id; matched by `registerFormAlter`. */
497
+ id: string;
498
+ /** Fields in render order. */
499
+ fields: FormField[];
500
+ /** Free-form metadata renderers may inspect. */
501
+ meta?: Record<string, unknown>;
502
+ }
503
+ /** S3-02: a single form field as seen by alters. */
504
+ export interface FormField {
505
+ name: string;
506
+ type: string;
507
+ label?: string;
508
+ required?: boolean;
509
+ hidden?: boolean;
510
+ options?: Array<string | {
511
+ value: string;
512
+ label: string;
513
+ }>;
514
+ validators?: Array<(value: unknown) => string | null>;
515
+ /** Anything else the renderer needs. Renderers + alters share this loosely. */
516
+ [k: string]: unknown;
517
+ }
25
518
  export interface StudioRoute {
26
519
  path: string;
27
520
  component: any;
28
- label: string;
29
- icon: string;
30
- category: string;
521
+ /** Sidebar label. Optional — when omitted, the route exists for routing
522
+ * but doesn't surface in the nav (use together with `hidden` for purely
523
+ * programmatic child routes). */
524
+ label?: string;
525
+ icon?: string;
526
+ /** Sidebar group key. Optional; extensions that don't care about
527
+ * grouping can leave it off. */
528
+ category?: string;
31
529
  children?: StudioRoute[];
530
+ /** Display under a custom heading instead of the default category. Used
531
+ * by extensions that want a localized group label (e.g. "Trasabilitate"). */
532
+ parent?: string;
533
+ /** Hide from the sidebar even when label is set. Useful for detail
534
+ * pages (`/items/:id`) that are reachable via in-app navigation. */
535
+ hidden?: boolean;
32
536
  }
33
537
  export interface StudioFieldType {
34
538
  type: string;
@@ -42,5 +546,13 @@ export interface StudioFieldType {
42
546
  default: any;
43
547
  }>;
44
548
  }
549
+ export interface AssetPreviewHandler {
550
+ match: (asset: {
551
+ url: string;
552
+ name?: string;
553
+ mimeType?: string;
554
+ }) => boolean;
555
+ component: any;
556
+ }
45
557
  export type { Hono };
46
558
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extension/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAIjC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,GAAG,CAAC;IACR,IAAI,EAAE,GAAG,CAAC;IACV,iBAAiB,EAAE,oBAAoB,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,IAAI,IAAI,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,aAAa,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;CAChC;AAGD,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,GAAG,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,YAAY,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extension/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAgBtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,IAAI,IAAI,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,MAAM,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC,CAAC;IACtD,yEAAyE;IACzE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;IAC3C,yEAAyE;IACzE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB,CAAC,EAAE,GAAG,GAAG;IAGxC;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACf,2FAA2F;IAC3F,IAAI,EAAE,GAAG,CAAC;IACV,8DAA8D;IAC9D,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,qFAAqF;IACrF,MAAM,EAAE,GAAG,CAAC;IACZ,4DAA4D;IAC5D,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF,wCAAwC;IACxC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpD;;;;;;;;;;OAUG;IACH,UAAU,EAAE,GAAG,CAAC;IAEhB;;;;OAIG;IACH,QAAQ,EAAE,eAAe,CAAC;IAE1B;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,EAAE,eAAe,CAAC;IAE5B;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,CAAC;IAEhC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAMjD;;;OAGG;IACH,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,aAAa,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClG,mFAAmF;IACnF,gBAAgB,EAAE,CAChB,EAAE,EAAE,GAAG,EACP,UAAU,CAAC,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,EAAE,EAC1B,MAAM,CAAC,EAAE,OAAO,KACb,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpB,iDAAiD;IACjD,cAAc,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,+DAA+D;IAC/D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,8EAA8E;IAC9E,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,4EAA4E;IAC5E,iBAAiB,EAAE,GAAG,CAAC;IACvB,oDAAoD;IACpD,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF,gEAAgE;IAChE,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IACjF,yCAAyC;IACzC,WAAW,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,8DAA8D;IAC9D,WAAW,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,yDAAyD;IACzD,oBAAoB,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,wDAAwD;IACxD,kBAAkB,EAAE,GAAG,CAAC;IACxB,0FAA0F;IAC1F,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrE,oEAAoE;IACpE,aAAa,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,gFAAgF;IAChF,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACjD,gFAAgF;IAChF,mBAAmB,EAAE,CACnB,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,UAAU,EACzC,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,uFAAuF;IACvF,gBAAgB,EAAE,CAChB,EAAE,EAAE,GAAG,EACP,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KACvF,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB;;;;;;;;;;;;OAYG;IACH,uBAAuB,EAAE,CACvB,EAAE,EAAE,GAAG,EACP,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,KACE,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD;;;;;;OAMG;IACH,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD;;;;OAIG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,CACL,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,GAAG,EACT,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,KAC7B,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;KACrD,GAAG,IAAI,CAAC;IACT,IAAI,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC,aAAa,IAAI,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE;QACZ,wEAAwE;QACxE,KAAK,EAAE,MAAM,CAAC;QACd;;;;WAIG;QACH,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;KACpC,GAAG,IAAI,CAAC;IACT,IAAI,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC,aAAa,IAAI,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACpD,iFAAiF;IACjF,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uDAAuD;IACvD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IACzC,wCAAwC;IACxC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnE,gEAAgE;IAChE,IAAI,IAAI,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,iBAAiB,CAAC,EAAE,GAAG,GAAG;IACzC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,OAAO,EAAE,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,KAAK,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;IACjF,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhD,4DAA4D;AAC5D;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB,CAAC,EAAE,GAAG,GAAG;IACxC,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAID,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7C,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjE;;;;;OAKG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,SAAS,EAAE,GAAG,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IACpD,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEvF,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACpF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CAC3C;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,+EAA+E;IAC/E,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,GAAG,CAAC;IACf;;sCAEkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;qCACiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB;kFAC8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;yEACqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;IAC7E,SAAS,EAAE,GAAG,CAAC;CAChB;AAED,YAAY,EAAE,IAAI,EAAE,CAAC"}
@@ -1 +1 @@
1
- export {};
1
+ export { permissionGate } from './permission-gate.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `permissionGate` — minimal route-level RBAC for extensions.
3
+ *
4
+ * Default-deny: if no Casbin policy grants the resource/action, the request
5
+ * is rejected with 403. The god role bypasses the gate (handled by the
6
+ * engine's `checkPermission`).
7
+ *
8
+ * Action is derived from the HTTP method using the standard CRUD mapping
9
+ * (GET → read, POST → create, PATCH/PUT → update, DELETE → delete). The
10
+ * `resource` is the extension's logical name (e.g. `'crm'`, `'invoices'`);
11
+ * operators grant access by adding a Casbin policy:
12
+ *
13
+ * INSERT INTO zvd_permissions (ptype, v0, v1, v2)
14
+ * VALUES ('p', 'user_role', 'crm', 'read');
15
+ *
16
+ * Why this exists: many extensions historically only checked
17
+ * `auth.api.getSession(...)` — i.e. any authenticated user could
18
+ * read/write the entire extension. That's the right default for a
19
+ * single-tenant deployment but unacceptable in multi-tenant or
20
+ * role-segmented installs. The gate gives operators a single knob to
21
+ * tighten access without each extension having to roll its own RBAC.
22
+ *
23
+ * Usage in an extension's `routes.ts`:
24
+ *
25
+ * app.use('*', permissionGate(ctx, 'crm'));
26
+ *
27
+ * Place AFTER the auth-guard middleware that sets `c.set('user', ...)`.
28
+ */
29
+ type MaybePromise<T> = T | Promise<T>;
30
+ interface GateContext {
31
+ checkPermission: (userId: string, resource: string, action: string) => Promise<boolean>;
32
+ }
33
+ export declare function permissionGate(ctx: GateContext, resource: string, opts?: {
34
+ actionOverrides?: Record<string, string>;
35
+ }): (c: any, next: () => MaybePromise<unknown>) => Promise<any>;
36
+ export {};
37
+ //# sourceMappingURL=permission-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../../src/extension/permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,UAAU,WAAW;IACnB,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAuCD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,IAEzC,GAAG,GAAG,EAAE,MAAM,MAAM,YAAY,CAAC,OAAO,CAAC,kBAgCxD"}