@zveltio/sdk 3.0.0-beta.76 → 3.0.0-beta.78
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/README.md +5 -5
- package/dist/ddl/index.d.ts +16 -0
- package/dist/ddl/index.d.ts.map +1 -1
- package/dist/extension/capabilities.d.ts +70 -0
- package/dist/extension/capabilities.d.ts.map +1 -0
- package/dist/extension/capabilities.js +78 -0
- package/dist/extension/index.d.ts +262 -10
- package/dist/extension/index.d.ts.map +1 -1
- package/dist/extension/index.js +4 -0
- package/dist/offline/index.d.ts +20 -51
- package/dist/offline/index.d.ts.map +1 -1
- package/dist/offline/index.js +114 -140
- package/dist/schema-codegen.d.ts +22 -0
- package/dist/schema-codegen.d.ts.map +1 -1
- package/dist/schema-codegen.js +100 -0
- package/dist/sync-manager.js +1 -1
- package/dist/testing/with-test-db.d.ts +1 -1
- package/dist/testing/with-test-db.d.ts.map +1 -1
- package/dist/testing/with-test-db.js +1 -1
- package/dist/tests/validate-manifest-dependencies.test.d.ts +2 -0
- package/dist/tests/validate-manifest-dependencies.test.d.ts.map +1 -0
- package/dist/tests/validate-manifest-dependencies.test.js +27 -0
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +28 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -186,18 +186,18 @@ await sync.stop();
|
|
|
186
186
|
The engine exposes two endpoints for batch sync:
|
|
187
187
|
|
|
188
188
|
- `POST /api/sync/push` — send offline operations to server
|
|
189
|
-
- `POST /api/sync/pull` — pull changes
|
|
189
|
+
- `POST /api/sync/pull` — pull changes after your cursors
|
|
190
190
|
|
|
191
191
|
`SyncManager.pull()` uses `/api/sync/pull` and keeps its cursors in the local store. Both endpoints take a
|
|
192
192
|
session only: a client authenticated by an API key gets 403, and `pull()` then falls back to paging the
|
|
193
193
|
collections API, which cannot report deletes.
|
|
194
194
|
|
|
195
|
-
A pull returns at most 1000 rows per collection. Send `{ collections,
|
|
195
|
+
A pull returns at most 1000 rows per collection. Send `{ collections, cursors }`, merge the
|
|
196
196
|
response's `cursors` into yours, and pull again while `hasMore` is true. Keep `cursors` for the next
|
|
197
|
-
sync
|
|
198
|
-
sharing one `updated_at` (a bulk import).
|
|
197
|
+
sync; a collection without one is read from its start. Cursors are opaque: there is no timestamp
|
|
198
|
+
position, because a timestamp cannot page through rows sharing one `updated_at` (a bulk import).
|
|
199
199
|
|
|
200
200
|
A row deleted on the server comes back as `{ operation: 'delete', data: null }`; drop your copy. Deletes are
|
|
201
|
-
kept for 30 days: a cursor
|
|
201
|
+
kept for 30 days: a cursor last caught up longer ago than that gets `resync[collection] === true`, and the collection restarts
|
|
202
202
|
from the first row — drop your synced copies of it before applying the page. A first pull (no cursor) gets
|
|
203
203
|
no deletes, so start it from an empty copy.
|
package/dist/ddl/index.d.ts
CHANGED
|
@@ -84,6 +84,17 @@ export interface CollectionDefinition {
|
|
|
84
84
|
isSystem?: boolean;
|
|
85
85
|
schemaLocked?: boolean;
|
|
86
86
|
}
|
|
87
|
+
/** A row from `zvd_relations`, as `getRelations` returns it. */
|
|
88
|
+
export interface RelationRecord {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
type: string;
|
|
92
|
+
source_collection: string;
|
|
93
|
+
source_field: string;
|
|
94
|
+
target_collection: string;
|
|
95
|
+
target_field: string | null;
|
|
96
|
+
junction_table: string | null;
|
|
97
|
+
}
|
|
87
98
|
/** A row from `zv_collections` / `getCollection` / `getCollections`. The
|
|
88
99
|
* exact shape is engine-internal; this interface only fixes the fields
|
|
89
100
|
* extensions are documented to read. Treat unknown keys as best-effort. */
|
|
@@ -135,6 +146,8 @@ export interface DDLManager {
|
|
|
135
146
|
removeField(db: DDLManagerDb, collectionName: string, fieldName: string): Promise<void>;
|
|
136
147
|
/** List every collection registered in `zv_collections`. */
|
|
137
148
|
getCollections(db: DDLManagerDb): Promise<CollectionRecord[]>;
|
|
149
|
+
/** Every registered relation, or only those whose source or target is `collection`. */
|
|
150
|
+
getRelations(db: DDLManagerDb, collection?: string): Promise<RelationRecord[]>;
|
|
138
151
|
/** Read a single collection by name. Returns `null` when missing. */
|
|
139
152
|
getCollection(db: DDLManagerDb, name: string): Promise<CollectionRecord | null>;
|
|
140
153
|
/** Update display-only metadata (label, description, icon, sort, etc.). */
|
|
@@ -142,6 +155,9 @@ export interface DDLManager {
|
|
|
142
155
|
/** Introspect the live PG table and return its field configs. Useful for
|
|
143
156
|
* extensions that auto-discover schema changes. */
|
|
144
157
|
introspectTable(db: DDLManagerDb, collectionName: string): Promise<FieldDefinition[]>;
|
|
158
|
+
/** Every physical column of the collection's table, system columns included,
|
|
159
|
+
* in table order; `[]` when the table does not exist. */
|
|
160
|
+
columnNames(db: DDLManagerDb, collectionName: string): Promise<string[]>;
|
|
145
161
|
/** Re-read PG and write the result back to `zv_collections.fields`. Returns
|
|
146
162
|
* the number of changes detected. */
|
|
147
163
|
syncFieldsFromDB(db: DDLManagerDb, collectionName: string): Promise<number>;
|
package/dist/ddl/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ddl/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ddl/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capabilities as TYPES — roadmap R8: "an undeclared capability is a compile
|
|
3
|
+
* error, not a runtime refusal".
|
|
4
|
+
*
|
|
5
|
+
* The engine refuses a gated `ctx.internals` member whose capability the
|
|
6
|
+
* manifest does not declare (`CapabilityDeniedError`). That refusal happens the
|
|
7
|
+
* first time the code path runs — often in production, on the request that
|
|
8
|
+
* needed it. Declaring the capabilities to TypeScript as well moves the same
|
|
9
|
+
* answer to the editor:
|
|
10
|
+
*
|
|
11
|
+
* export default defineExtension(['ddl', 'secrets'], {
|
|
12
|
+
* name: 'my-ext',
|
|
13
|
+
* category: 'tools',
|
|
14
|
+
* async register(app, ctx) {
|
|
15
|
+
* await ctx.internals.encryptSecret('x'); // ok: 'secrets'
|
|
16
|
+
* await ctx.internals.deleteUser(db, 'u'); // compile error: needs 'auth:users'
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* The list passed to `defineExtension` is a statement to the compiler; the
|
|
21
|
+
* manifest's `permissions` remains what the engine enforces and what an
|
|
22
|
+
* administrator approves. `zveltio extension validate` compares the two.
|
|
23
|
+
*
|
|
24
|
+
* These two tables mirror the engine's `CAPABILITIES` and `INTERNALS_CAPABILITY`
|
|
25
|
+
* (`packages/engine/src/lib/extensions/capabilities.ts`); the engine's unit test
|
|
26
|
+
* `sdk-capability-types.test.ts` fails when they drift apart.
|
|
27
|
+
*/
|
|
28
|
+
/** Every capability an extension may declare, `net:<host>` aside. */
|
|
29
|
+
export declare const EXTENSION_CAPABILITIES: readonly ["db:admin", "tenant:enter", "ddl", "secrets", "auth:session", "auth:users", "identity:provision", "audit:read", "data:write", "data:system", "notifications", "files", "documents", "edge-functions", "introspection", "storage", "cron", "field-types"];
|
|
30
|
+
/** A capability name, including the parameterised `net:<host>`. */
|
|
31
|
+
export type Capability = (typeof EXTENSION_CAPABILITIES)[number] | `net:${string}`;
|
|
32
|
+
/** The `ctx.internals` members a capability unlocks. */
|
|
33
|
+
export declare const CAPABILITY_MEMBERS: {
|
|
34
|
+
readonly enqueueDDLJob: "ddl";
|
|
35
|
+
readonly introspectSchema: "introspection";
|
|
36
|
+
readonly extensionRegistry: "introspection";
|
|
37
|
+
readonly encryptSecret: "secrets";
|
|
38
|
+
readonly decryptSecret: "secrets";
|
|
39
|
+
readonly deriveTokenHash: "secrets";
|
|
40
|
+
readonly createBetterAuthSession: "auth:session";
|
|
41
|
+
readonly deleteUser: "auth:users";
|
|
42
|
+
readonly exportUserData: "auth:users";
|
|
43
|
+
readonly revokeUserSessions: "auth:users";
|
|
44
|
+
readonly setUserActive: "auth:users";
|
|
45
|
+
readonly liftOwnBan: "auth:users";
|
|
46
|
+
readonly provisionUser: "identity:provision";
|
|
47
|
+
readonly listTenantUsers: "identity:provision";
|
|
48
|
+
readonly updateUserProfile: "identity:provision";
|
|
49
|
+
readonly addTenantMember: "identity:provision";
|
|
50
|
+
readonly removeTenantMember: "identity:provision";
|
|
51
|
+
readonly setTenantMembershipEnd: "identity:provision";
|
|
52
|
+
readonly readAuditActivity: "audit:read";
|
|
53
|
+
readonly createRecord: "data:write";
|
|
54
|
+
readonly updateRecord: "data:write";
|
|
55
|
+
readonly deleteRecord: "data:write";
|
|
56
|
+
readonly asSystem: "data:system";
|
|
57
|
+
readonly sendNotification: "notifications";
|
|
58
|
+
readonly moveToTrash: "files";
|
|
59
|
+
readonly generatePDFAsync: "documents";
|
|
60
|
+
readonly runEdgeFunction: "edge-functions";
|
|
61
|
+
};
|
|
62
|
+
/** Members of `ctx.internals` that need a capability. */
|
|
63
|
+
export type GatedMember = keyof typeof CAPABILITY_MEMBERS;
|
|
64
|
+
/** The gated members the capabilities `C` unlock. */
|
|
65
|
+
export type MembersUnlockedBy<C extends string> = {
|
|
66
|
+
[K in GatedMember]: (typeof CAPABILITY_MEMBERS)[K] extends C ? K : never;
|
|
67
|
+
}[GatedMember];
|
|
68
|
+
/** The gated members `C` does NOT unlock — removed from the narrowed type. */
|
|
69
|
+
export type MembersLockedFor<C extends string> = Exclude<GatedMember, MembersUnlockedBy<C>>;
|
|
70
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/extension/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,oQAmBzB,CAAC;AAEX,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,MAAM,EAAE,CAAC;AAEnF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B6C,CAAC;AAE7E,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAE1D,qDAAqD;AACrD,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI;KAC/C,CAAC,IAAI,WAAW,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK;CACzE,CAAC,WAAW,CAAC,CAAC;AAEf,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capabilities as TYPES — roadmap R8: "an undeclared capability is a compile
|
|
3
|
+
* error, not a runtime refusal".
|
|
4
|
+
*
|
|
5
|
+
* The engine refuses a gated `ctx.internals` member whose capability the
|
|
6
|
+
* manifest does not declare (`CapabilityDeniedError`). That refusal happens the
|
|
7
|
+
* first time the code path runs — often in production, on the request that
|
|
8
|
+
* needed it. Declaring the capabilities to TypeScript as well moves the same
|
|
9
|
+
* answer to the editor:
|
|
10
|
+
*
|
|
11
|
+
* export default defineExtension(['ddl', 'secrets'], {
|
|
12
|
+
* name: 'my-ext',
|
|
13
|
+
* category: 'tools',
|
|
14
|
+
* async register(app, ctx) {
|
|
15
|
+
* await ctx.internals.encryptSecret('x'); // ok: 'secrets'
|
|
16
|
+
* await ctx.internals.deleteUser(db, 'u'); // compile error: needs 'auth:users'
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* The list passed to `defineExtension` is a statement to the compiler; the
|
|
21
|
+
* manifest's `permissions` remains what the engine enforces and what an
|
|
22
|
+
* administrator approves. `zveltio extension validate` compares the two.
|
|
23
|
+
*
|
|
24
|
+
* These two tables mirror the engine's `CAPABILITIES` and `INTERNALS_CAPABILITY`
|
|
25
|
+
* (`packages/engine/src/lib/extensions/capabilities.ts`); the engine's unit test
|
|
26
|
+
* `sdk-capability-types.test.ts` fails when they drift apart.
|
|
27
|
+
*/
|
|
28
|
+
/** Every capability an extension may declare, `net:<host>` aside. */
|
|
29
|
+
export const EXTENSION_CAPABILITIES = [
|
|
30
|
+
'db:admin',
|
|
31
|
+
'tenant:enter',
|
|
32
|
+
'ddl',
|
|
33
|
+
'secrets',
|
|
34
|
+
'auth:session',
|
|
35
|
+
'auth:users',
|
|
36
|
+
'identity:provision',
|
|
37
|
+
'audit:read',
|
|
38
|
+
'data:write',
|
|
39
|
+
'data:system',
|
|
40
|
+
'notifications',
|
|
41
|
+
'files',
|
|
42
|
+
'documents',
|
|
43
|
+
'edge-functions',
|
|
44
|
+
'introspection',
|
|
45
|
+
'storage',
|
|
46
|
+
'cron',
|
|
47
|
+
'field-types',
|
|
48
|
+
];
|
|
49
|
+
/** The `ctx.internals` members a capability unlocks. */
|
|
50
|
+
export const CAPABILITY_MEMBERS = {
|
|
51
|
+
enqueueDDLJob: 'ddl',
|
|
52
|
+
introspectSchema: 'introspection',
|
|
53
|
+
extensionRegistry: 'introspection',
|
|
54
|
+
encryptSecret: 'secrets',
|
|
55
|
+
decryptSecret: 'secrets',
|
|
56
|
+
deriveTokenHash: 'secrets',
|
|
57
|
+
createBetterAuthSession: 'auth:session',
|
|
58
|
+
deleteUser: 'auth:users',
|
|
59
|
+
exportUserData: 'auth:users',
|
|
60
|
+
revokeUserSessions: 'auth:users',
|
|
61
|
+
setUserActive: 'auth:users',
|
|
62
|
+
liftOwnBan: 'auth:users',
|
|
63
|
+
provisionUser: 'identity:provision',
|
|
64
|
+
listTenantUsers: 'identity:provision',
|
|
65
|
+
updateUserProfile: 'identity:provision',
|
|
66
|
+
addTenantMember: 'identity:provision',
|
|
67
|
+
removeTenantMember: 'identity:provision',
|
|
68
|
+
setTenantMembershipEnd: 'identity:provision',
|
|
69
|
+
readAuditActivity: 'audit:read',
|
|
70
|
+
createRecord: 'data:write',
|
|
71
|
+
updateRecord: 'data:write',
|
|
72
|
+
deleteRecord: 'data:write',
|
|
73
|
+
asSystem: 'data:system',
|
|
74
|
+
sendNotification: 'notifications',
|
|
75
|
+
moveToTrash: 'files',
|
|
76
|
+
generatePDFAsync: 'documents',
|
|
77
|
+
runEdgeFunction: 'edge-functions',
|
|
78
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Capability, MembersLockedFor } from './capabilities.js';
|
|
1
2
|
import type { Context, Hono } from 'hono';
|
|
2
3
|
import type { Kysely, RawBuilder } from 'kysely';
|
|
3
4
|
export { permissionGate } from './permission-gate.js';
|
|
@@ -164,7 +165,14 @@ export interface ExtensionConfig {
|
|
|
164
165
|
* Default `any` keeps legacy extensions compiling untouched. Migrated
|
|
165
166
|
* extensions get full Kysely autocomplete + typo detection.
|
|
166
167
|
*/
|
|
167
|
-
|
|
168
|
+
/**
|
|
169
|
+
* `C` — the capabilities this extension declared, when it says so through
|
|
170
|
+
* `defineExtension`. It narrows `internals` to the members those capabilities
|
|
171
|
+
* unlock and `adminDb` to `db:admin`, so an undeclared capability is a compile
|
|
172
|
+
* error rather than a `CapabilityDeniedError` on the first request that needs
|
|
173
|
+
* it. Defaults to every capability: code that does not opt in keeps today's type.
|
|
174
|
+
*/
|
|
175
|
+
export interface ExtensionContext<DB = any, C extends string = Capability> {
|
|
168
176
|
/**
|
|
169
177
|
* Kysely database instance (restricted — cannot query zv_* system tables).
|
|
170
178
|
* Typed via the `DB` generic; use `ZveltioExtension<MySchema>` to opt in.
|
|
@@ -190,7 +198,7 @@ export interface ExtensionContext<DB = any> {
|
|
|
190
198
|
* Use for legitimately global operations (platform-wide reporting, backup).
|
|
191
199
|
* Declaring `db:admin` surfaces the cross-tenant access at review + install.
|
|
192
200
|
*/
|
|
193
|
-
adminDb?: Kysely<DB
|
|
201
|
+
adminDb?: 'db:admin' extends C ? Kysely<DB> : never;
|
|
194
202
|
/**
|
|
195
203
|
* Per-request, tenant-scoped database. Returns the request's tenant
|
|
196
204
|
* transaction (so the `zveltio.current_tenant` GUC is set and FORCE-RLS rows
|
|
@@ -307,7 +315,7 @@ export interface ExtensionContext<DB = any> {
|
|
|
307
315
|
* Engine-internal helpers. Stable across patch versions but may break across
|
|
308
316
|
* minor versions. First-party extensions only.
|
|
309
317
|
*/
|
|
310
|
-
internals: ExtensionInternals<DB
|
|
318
|
+
internals: Omit<ExtensionInternals<DB>, MembersLockedFor<C>>;
|
|
311
319
|
}
|
|
312
320
|
/**
|
|
313
321
|
* Engine-internal helpers passed via `ctx.internals.*`.
|
|
@@ -383,6 +391,12 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
383
391
|
* like a request's: the tenant GUC is set and `SET LOCAL ROLE` applied, so
|
|
384
392
|
* the isolation policies bind.
|
|
385
393
|
*
|
|
394
|
+
* Only the tenant the work already runs as — the request's, or the job's —
|
|
395
|
+
* is entered without a capability. Another tenant, or any tenant where none
|
|
396
|
+
* runs (load time, a timer), needs `tenant:enter` (or `db:admin`, which
|
|
397
|
+
* implies it); without it the promise rejects.
|
|
398
|
+
* A fire-and-forget job started from a handler keeps the handler's tenant.
|
|
399
|
+
*
|
|
386
400
|
* await ctx.internals.withTenantIsolation(tenantId, async (db) => {
|
|
387
401
|
* await db.selectFrom('zvd_things').selectAll().execute();
|
|
388
402
|
* });
|
|
@@ -475,7 +489,12 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
475
489
|
hidden: Set<string>;
|
|
476
490
|
readOnly: Set<string>;
|
|
477
491
|
}>;
|
|
478
|
-
/**
|
|
492
|
+
/**
|
|
493
|
+
* The single role behind a user — what `getColumnAccess` keys on. Drawn from
|
|
494
|
+
* Casbin, the one source of roles: `god` (the instance attribute) > `member`
|
|
495
|
+
* (the `g <user> member *` row every account gets) > `public`. A user's full
|
|
496
|
+
* role set is `getUserRoles`.
|
|
497
|
+
*/
|
|
479
498
|
resolveUserRole: (user: {
|
|
480
499
|
id?: string;
|
|
481
500
|
role?: string;
|
|
@@ -542,6 +561,71 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
542
561
|
*/
|
|
543
562
|
getUserNames: (userIds: string[]) => Promise<Record<string, string>>;
|
|
544
563
|
isTenantAdmin: (userId: string) => Promise<boolean>;
|
|
564
|
+
/**
|
|
565
|
+
* Facts about the tenant this work runs as, scoped by the engine — the path
|
|
566
|
+
* in place of raw SQL on `"user"`, `zv_tenant_users`, `zv_tenants`,
|
|
567
|
+
* `pg_class`, `zvd_permissions` and `zv_settings`, which `ctx.db` refuses.
|
|
568
|
+
*/
|
|
569
|
+
/** People with a membership in force in the running tenant, and its owners/admins. */
|
|
570
|
+
countMembers: () => Promise<{
|
|
571
|
+
total: number;
|
|
572
|
+
admins: number;
|
|
573
|
+
}>;
|
|
574
|
+
/** Collection count, and a planner row estimate (`null` on a multi-tenant instance). */
|
|
575
|
+
getDataStats: () => Promise<{
|
|
576
|
+
collections: number;
|
|
577
|
+
records_estimate: number | null;
|
|
578
|
+
}>;
|
|
579
|
+
/** Role names granted in the running tenant or in every tenant. */
|
|
580
|
+
listRoles: () => Promise<string[]>;
|
|
581
|
+
/** A setting the instance marks public (`is_public`); `null` otherwise. */
|
|
582
|
+
getPublicSetting: (key: string) => Promise<unknown>;
|
|
583
|
+
/** Append to the audit log, as a row of the running tenant (instance-level where none runs). The engine stamps `metadata.extension` with the caller. */
|
|
584
|
+
audit: (event: {
|
|
585
|
+
type: string;
|
|
586
|
+
userId?: string;
|
|
587
|
+
resourceId?: string;
|
|
588
|
+
resourceType?: string;
|
|
589
|
+
metadata?: Record<string, unknown>;
|
|
590
|
+
ip?: string;
|
|
591
|
+
}) => Promise<void>;
|
|
592
|
+
/**
|
|
593
|
+
* Run `fn` acting as the system inside the running tenant, for the named
|
|
594
|
+
* collections (`['products']`, not `zvd_products`): collection permissions
|
|
595
|
+
* stand down for `ctx.db` during the call; tenant isolation and the table
|
|
596
|
+
* guard do not. Needs the `data:system` capability. Every call is audited
|
|
597
|
+
* with the extension, the user, the collections and `reason`.
|
|
598
|
+
*/
|
|
599
|
+
asSystem: <T>(collections: readonly string[], fn: () => Promise<T>, opts?: {
|
|
600
|
+
reason?: string;
|
|
601
|
+
}) => Promise<T>;
|
|
602
|
+
/**
|
|
603
|
+
* The running tenant's recent audit rows, newest first — never another
|
|
604
|
+
* tenant's, never instance-level events. `limit` defaults to 20, capped at
|
|
605
|
+
* 100. No `metadata` or `ip`. Needs the `audit:read` capability.
|
|
606
|
+
*/
|
|
607
|
+
readAuditActivity: (query?: {
|
|
608
|
+
limit?: number;
|
|
609
|
+
eventType?: string;
|
|
610
|
+
resourceType?: string;
|
|
611
|
+
since?: Date | string;
|
|
612
|
+
}) => Promise<{
|
|
613
|
+
id: string;
|
|
614
|
+
event_type: string;
|
|
615
|
+
user_id: string | null;
|
|
616
|
+
resource_type: string | null;
|
|
617
|
+
resource_id: string | null;
|
|
618
|
+
created_at: Date;
|
|
619
|
+
}[]>;
|
|
620
|
+
/**
|
|
621
|
+
* How many of the running tenant's audit rows match, from `since` on. Same
|
|
622
|
+
* scope as `readAuditActivity`; a number, so ungated.
|
|
623
|
+
*/
|
|
624
|
+
countAuditActivity: (query: {
|
|
625
|
+
since: Date | string;
|
|
626
|
+
eventType?: string;
|
|
627
|
+
resourceType?: string;
|
|
628
|
+
}) => Promise<number>;
|
|
545
629
|
/**
|
|
546
630
|
* Instance-level admin, as distinct from admin-within-a-tenant.
|
|
547
631
|
*
|
|
@@ -558,7 +642,10 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
558
642
|
extensionRegistry: any;
|
|
559
643
|
/** Queue an HTML→PDF render via the worker pool. */
|
|
560
644
|
generatePDFAsync: (html: string, options?: Record<string, unknown>) => Promise<unknown>;
|
|
561
|
-
/**
|
|
645
|
+
/**
|
|
646
|
+
* Move a file to the trash bucket (soft delete with TTL): `(db, fileId, deletedBy)`.
|
|
647
|
+
* Only a file of the tenant the request or job runs as; outside one it rejects.
|
|
648
|
+
*/
|
|
562
649
|
moveToTrash: (...args: any[]) => Promise<unknown>;
|
|
563
650
|
/** Enqueue an asynchronous DDL job (Ghost Tables, large alters). */
|
|
564
651
|
enqueueDDLJob: (...args: any[]) => Promise<unknown>;
|
|
@@ -681,6 +768,47 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
681
768
|
* cannot reach either. Needs the `auth:users` capability.
|
|
682
769
|
*/
|
|
683
770
|
revokeUserSessions: (userId: string) => Promise<void>;
|
|
771
|
+
/**
|
|
772
|
+
* What the engine's own tables hold about a user — profile, their audit
|
|
773
|
+
* rows in every tenant and at instance level, notifications, the API keys
|
|
774
|
+
* they created (names, never secrets) and their approval requests — for a
|
|
775
|
+
* data-subject access request. `null` when there is no such user. Needs the
|
|
776
|
+
* `auth:users` capability.
|
|
777
|
+
*/
|
|
778
|
+
exportUserData: (userId: string) => Promise<{
|
|
779
|
+
profile: {
|
|
780
|
+
id: string;
|
|
781
|
+
name: string | null;
|
|
782
|
+
email: string;
|
|
783
|
+
created_at: string;
|
|
784
|
+
} | null;
|
|
785
|
+
audit_log: Array<{
|
|
786
|
+
action: string;
|
|
787
|
+
collection: string | null;
|
|
788
|
+
record_id: string | null;
|
|
789
|
+
created_at: string;
|
|
790
|
+
}>;
|
|
791
|
+
notifications: Array<{
|
|
792
|
+
title: string;
|
|
793
|
+
message: string | null;
|
|
794
|
+
type: string | null;
|
|
795
|
+
is_read: boolean;
|
|
796
|
+
created_at: string;
|
|
797
|
+
}>;
|
|
798
|
+
api_keys: Array<{
|
|
799
|
+
name: string;
|
|
800
|
+
key_prefix: string | null;
|
|
801
|
+
scopes: unknown;
|
|
802
|
+
created_at: string;
|
|
803
|
+
}>;
|
|
804
|
+
approval_requests: Array<{
|
|
805
|
+
id: string;
|
|
806
|
+
collection: string;
|
|
807
|
+
record_id: string | null;
|
|
808
|
+
status: string;
|
|
809
|
+
requested_at: string;
|
|
810
|
+
}>;
|
|
811
|
+
} | null>;
|
|
684
812
|
/**
|
|
685
813
|
* `false` stops the user signing in by ANY method (password, magic link,
|
|
686
814
|
* passkey, OAuth, SSO) and revokes their sessions; `true` lets them back in
|
|
@@ -702,6 +830,100 @@ export interface ExtensionInternals<DB = unknown> {
|
|
|
702
830
|
* `auth:users` capability.
|
|
703
831
|
*/
|
|
704
832
|
liftOwnBan: (db: unknown, userId: string) => Promise<boolean>;
|
|
833
|
+
/** Whether at most one tenant exists — then every user belongs to it. Ungated. */
|
|
834
|
+
isSingleTenantInstance: () => Promise<boolean>;
|
|
835
|
+
/**
|
|
836
|
+
* The account for `email` (case-insensitive), created when there is none:
|
|
837
|
+
* verified, no password, never god, in no tenant. Works while
|
|
838
|
+
* self-registration is off. Inside `withTenantIsolation` it is created in
|
|
839
|
+
* that transaction, and a rollback takes it back. An account that already
|
|
840
|
+
* exists comes back only if the running tenant has a claim to it: a member
|
|
841
|
+
* here with no power elsewhere beyond plain membership, any account but god
|
|
842
|
+
* and the instance admins on a single-tenant instance, or a bare record (no
|
|
843
|
+
* credential, membership or grant). Anyone else is `account_exists`: they join
|
|
844
|
+
* through an invitation, which proves they control the address.
|
|
845
|
+
*/
|
|
846
|
+
provisionUser: (input: {
|
|
847
|
+
email: string;
|
|
848
|
+
name?: string;
|
|
849
|
+
}) => Promise<{
|
|
850
|
+
user: ProvisionedUser;
|
|
851
|
+
created: boolean;
|
|
852
|
+
}>;
|
|
853
|
+
/**
|
|
854
|
+
* Users of the running tenant — a membership there, lapsed included — or
|
|
855
|
+
* every user on a single-tenant instance (`membership: null`). By creation
|
|
856
|
+
* order; `limit` defaults to 100, at most 1000.
|
|
857
|
+
*/
|
|
858
|
+
listTenantUsers: (db: unknown, query?: {
|
|
859
|
+
email?: string;
|
|
860
|
+
userId?: string;
|
|
861
|
+
limit?: number;
|
|
862
|
+
offset?: number;
|
|
863
|
+
}) => Promise<Array<ProvisionedUser & {
|
|
864
|
+
membership: TenantMembershipInfo | null;
|
|
865
|
+
}>>;
|
|
866
|
+
/**
|
|
867
|
+
* Rename or re-address a user the running tenant ALONE holds: a membership
|
|
868
|
+
* here (any user on a single-tenant instance), not god, not an instance admin,
|
|
869
|
+
* no membership or grant in any other tenant (`user_not_owned` otherwise).
|
|
870
|
+
* `db` is your transaction.
|
|
871
|
+
*/
|
|
872
|
+
updateUserProfile: (db: unknown, userId: string, patch: {
|
|
873
|
+
name?: string;
|
|
874
|
+
email?: string;
|
|
875
|
+
}) => Promise<ProvisionedUser>;
|
|
876
|
+
/**
|
|
877
|
+
* Make the user a `member` (default) or `viewer` of the running tenant, or
|
|
878
|
+
* switch between the two, with the matching tenant role. Dates are kept: a
|
|
879
|
+
* membership that ended is not reopened. An `owner`/`admin` is refused.
|
|
880
|
+
*/
|
|
881
|
+
addTenantMember: (db: unknown, userId: string, role?: 'member' | 'viewer') => Promise<'added' | 'role_changed' | 'unchanged'>;
|
|
882
|
+
/**
|
|
883
|
+
* Remove the user from the running tenant with every role they hold in it.
|
|
884
|
+
* `orphaned`: no tenant and nothing else holds the account now — deleting it
|
|
885
|
+
* (`deleteUser`, `auth:users`) is your call. `inForceAnywhere`: a membership
|
|
886
|
+
* in force remains in some tenant. An owner or admin of the tenant (in the
|
|
887
|
+
* default tenant, an instance admin) is refused, `role_not_allowed`.
|
|
888
|
+
*/
|
|
889
|
+
removeTenantMember: (db: unknown, userId: string) => Promise<{
|
|
890
|
+
removed: boolean;
|
|
891
|
+
orphaned: boolean;
|
|
892
|
+
inForceAnywhere: boolean;
|
|
893
|
+
}>;
|
|
894
|
+
/**
|
|
895
|
+
* Set when the user's membership of the running tenant ends: `'now'`, an
|
|
896
|
+
* ISO-8601 instant, or `null` (open-ended). `ifInForce` changes only a
|
|
897
|
+
* membership in force; `ifValidTo` only one whose end is still exactly that
|
|
898
|
+
* value. `null` when the user is no member here. An owner or admin is
|
|
899
|
+
* refused, `role_not_allowed`.
|
|
900
|
+
*/
|
|
901
|
+
setTenantMembershipEnd: (db: unknown, userId: string, validTo: string | null, guard?: {
|
|
902
|
+
ifInForce?: boolean;
|
|
903
|
+
ifValidTo?: string | null;
|
|
904
|
+
}) => Promise<{
|
|
905
|
+
changed: boolean;
|
|
906
|
+
previousValidTo: string | null;
|
|
907
|
+
validTo: string | null;
|
|
908
|
+
inForceAnywhere: boolean;
|
|
909
|
+
} | null>;
|
|
910
|
+
}
|
|
911
|
+
/** A user as `ctx.internals` identity provisioning returns it. */
|
|
912
|
+
export interface ProvisionedUser {
|
|
913
|
+
id: string;
|
|
914
|
+
email: string;
|
|
915
|
+
name: string;
|
|
916
|
+
emailVerified: boolean;
|
|
917
|
+
createdAt: string;
|
|
918
|
+
updatedAt: string;
|
|
919
|
+
}
|
|
920
|
+
/** A user's membership of the running tenant. */
|
|
921
|
+
export interface TenantMembershipInfo {
|
|
922
|
+
role: 'owner' | 'admin' | 'member' | 'viewer';
|
|
923
|
+
validFrom: string;
|
|
924
|
+
/** `null`: open-ended. */
|
|
925
|
+
validTo: string | null;
|
|
926
|
+
inForce: boolean;
|
|
705
927
|
}
|
|
706
928
|
/**
|
|
707
929
|
* Entity-access scope handed to each extension via `ctx.entityAccess`.
|
|
@@ -776,18 +998,32 @@ export interface QueryAlterScope {
|
|
|
776
998
|
* `register()` is idempotent within a scope (replaces on duplicate), so an
|
|
777
999
|
* extension's `register()` can safely run multiple times during hot-reload.
|
|
778
1000
|
*/
|
|
1001
|
+
/**
|
|
1002
|
+
* A manifest `dependencies` / `optionalDependencies` entry. A required one must be
|
|
1003
|
+
* installed for this extension to load; an optional one need not be — its
|
|
1004
|
+
* services then read as `null`. Either way, the extensions named in these two
|
|
1005
|
+
* lists are the only ones whose services `ctx.services` hands this one.
|
|
1006
|
+
*/
|
|
1007
|
+
export interface ManifestDependency {
|
|
1008
|
+
name: string;
|
|
1009
|
+
minVersion?: string;
|
|
1010
|
+
}
|
|
779
1011
|
export interface ServiceRegistry {
|
|
780
1012
|
/**
|
|
781
|
-
* Publish a service under a name
|
|
1013
|
+
* Publish a service under a name, which must be `<this extension's name>.<x>`.
|
|
782
1014
|
* Idempotent: re-registering the same name from the same extension replaces.
|
|
783
1015
|
* Throws if a different extension already owns that name.
|
|
784
1016
|
*/
|
|
785
1017
|
register<T = unknown>(name: string, value: T): void;
|
|
786
1018
|
/** Remove a service this extension previously registered. No-op if not owned. */
|
|
787
1019
|
unregister(name: string): void;
|
|
788
|
-
/**
|
|
1020
|
+
/**
|
|
1021
|
+
* Get a service. Returns `null` if not registered (an optional dependency
|
|
1022
|
+
* that is not installed). Throws if its owner is not this extension or one in
|
|
1023
|
+
* its manifest `dependencies` / `optionalDependencies`.
|
|
1024
|
+
*/
|
|
789
1025
|
get<T = unknown>(name: string): T | null;
|
|
790
|
-
/** Check if a service is registered. */
|
|
1026
|
+
/** Check if a service is registered. Throws as `get` does for an undeclared owner. */
|
|
791
1027
|
has(name: string): boolean;
|
|
792
1028
|
/**
|
|
793
1029
|
* Wait for a service to be registered. Resolves immediately if already there.
|
|
@@ -912,7 +1148,7 @@ export type MountStrategy = 'global' | 'subapp';
|
|
|
912
1148
|
* }
|
|
913
1149
|
* };
|
|
914
1150
|
*/
|
|
915
|
-
export interface ZveltioExtension<DB = any> {
|
|
1151
|
+
export interface ZveltioExtension<DB = any, C extends string = Capability> {
|
|
916
1152
|
/** Unique name — must match manifest.json `name` exactly (e.g. `'hr/employees'`). */
|
|
917
1153
|
name: string;
|
|
918
1154
|
/** Category shown in the marketplace (e.g. `'hr'`, `'finance'`, `'content'`). */
|
|
@@ -927,7 +1163,7 @@ export interface ZveltioExtension<DB = any> {
|
|
|
927
1163
|
* Called once when the extension is activated.
|
|
928
1164
|
* Register Hono routes, subscribe to events, etc.
|
|
929
1165
|
*/
|
|
930
|
-
register: (app: Hono, ctx: ExtensionContext<DB>) => Promise<void>;
|
|
1166
|
+
register: (app: Hono, ctx: ExtensionContext<DB, C>) => Promise<void>;
|
|
931
1167
|
/** Register custom Studio field types contributed by this extension. */
|
|
932
1168
|
registerFieldTypes?: (registry: FieldTypeRegistryAPI) => void;
|
|
933
1169
|
/** Return absolute paths to SQL migration files, run in order on first activation. */
|
|
@@ -1057,5 +1293,21 @@ export interface AssetPreviewHandler {
|
|
|
1057
1293
|
}) => boolean;
|
|
1058
1294
|
component: any;
|
|
1059
1295
|
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Declare an extension together with the capabilities it uses, so `ctx` is typed
|
|
1298
|
+
* by them (roadmap R8). The list must match `permissions` in manifest.json —
|
|
1299
|
+
* the manifest is what the engine enforces and an administrator approves;
|
|
1300
|
+
* `zveltio extension validate` reports a difference.
|
|
1301
|
+
*
|
|
1302
|
+
* export default defineExtension(['secrets'], { name, category, register });
|
|
1303
|
+
*
|
|
1304
|
+
* Returns the extension unchanged, with the list attached as `capabilities`.
|
|
1305
|
+
*/
|
|
1306
|
+
/** `ZveltioExtension`'s own default schema type, without spelling it again. */
|
|
1307
|
+
type DefaultExtensionDB = ZveltioExtension extends ZveltioExtension<infer D> ? D : never;
|
|
1308
|
+
export declare function defineExtension<const C extends readonly Capability[], DB = DefaultExtensionDB>(capabilities: C, extension: ZveltioExtension<DB, C[number]>): ZveltioExtension<DB, C[number]> & {
|
|
1309
|
+
readonly capabilities: C;
|
|
1310
|
+
};
|
|
1060
1311
|
export type { Hono };
|
|
1312
|
+
export * from './capabilities.js';
|
|
1061
1313
|
//# sourceMappingURL=index.d.ts.map
|