@zveltio/sdk 2.0.7 → 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.
- package/dist/build/index.d.ts +77 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +157 -0
- package/dist/client/QueryBuilder.d.ts.map +1 -1
- package/dist/client/QueryBuilder.js +12 -3
- package/dist/client/RealtimeClient.d.ts.map +1 -1
- package/dist/client/RealtimeClient.js +3 -1
- package/dist/client/ZveltioClient.d.ts.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -5
- package/dist/core.d.ts.map +1 -1
- package/dist/ddl/index.d.ts +153 -0
- package/dist/ddl/index.d.ts.map +1 -0
- package/dist/ddl/index.js +33 -0
- package/dist/extension/index.d.ts +522 -10
- package/dist/extension/index.d.ts.map +1 -1
- package/dist/extension/index.js +1 -1
- package/dist/extension/permission-gate.d.ts +37 -0
- package/dist/extension/permission-gate.d.ts.map +1 -0
- package/dist/extension/permission-gate.js +93 -0
- package/dist/generate-types.d.ts +37 -0
- package/dist/generate-types.d.ts.map +1 -0
- package/dist/generate-types.js +157 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/local-store.d.ts.map +1 -1
- package/dist/local-store.js +1 -3
- package/dist/offline/index.d.ts +105 -0
- package/dist/offline/index.d.ts.map +1 -0
- package/dist/offline/index.js +235 -0
- package/dist/publish/index.d.ts +94 -0
- package/dist/publish/index.d.ts.map +1 -0
- package/dist/publish/index.js +148 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +6 -2
- package/dist/rpc/index.d.ts +68 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +61 -0
- package/dist/schema-codegen.d.ts +60 -0
- package/dist/schema-codegen.d.ts.map +1 -0
- package/dist/schema-codegen.js +369 -0
- package/dist/schema-watcher.d.ts.map +1 -1
- package/dist/schema-watcher.js +1 -3
- package/dist/studio/index.d.ts +84 -0
- package/dist/studio/index.d.ts.map +1 -0
- package/dist/studio/index.js +180 -0
- package/dist/svelte.d.ts.map +1 -1
- package/dist/sync-manager.d.ts.map +1 -1
- package/dist/sync-manager.js +4 -11
- package/dist/testing/index.d.ts +121 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +283 -0
- package/dist/testing/with-test-db.d.ts +119 -0
- package/dist/testing/with-test-db.d.ts.map +1 -0
- package/dist/testing/with-test-db.js +313 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -1
- package/dist/validate/index.d.ts +95 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +201 -0
- package/dist/validate/migration-parse.d.ts +17 -0
- package/dist/validate/migration-parse.d.ts.map +1 -0
- package/dist/validate/migration-parse.js +11 -0
- package/package.json +53 -1
|
@@ -1,34 +1,538 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extension/index.ts"],"names":[],"mappings":"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"}
|
package/dist/extension/index.js
CHANGED
|
@@ -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"}
|