@objectstack/objectql 4.0.4 → 4.1.0

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/index.d.mts CHANGED
@@ -1,61 +1,12 @@
1
- import { z } from 'zod';
2
- import * as _objectstack_spec_data from '@objectstack/spec/data';
3
- import { ObjectOwnership, ServiceObject, QueryAST, HookContext, EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions, EngineCountOptions, EngineAggregateOptions } from '@objectstack/spec/data';
1
+ import { ServiceObject, ObjectOwnership, HookContext, QueryAST, EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions, EngineCountOptions, EngineAggregateOptions, DateGranularityValue, Hook } from '@objectstack/spec/data';
4
2
  import { ObjectStackManifest, InstalledPackage, ExecutionContext } from '@objectstack/spec/kernel';
5
3
  import { ObjectStackProtocol, MetadataCacheRequest, MetadataCacheResponse, BatchUpdateRequest, BatchUpdateResponse, UpdateManyDataRequest, DeleteManyDataRequest } from '@objectstack/spec/api';
6
4
  import { IDataEngine, DriverInterface, Logger, Plugin, PluginContext, ObjectKernel } from '@objectstack/core';
7
5
  import { IFeedService, IRealtimeService } from '@objectstack/spec/contracts';
8
6
 
9
- /**
10
- * XState-inspired State Machine Protocol
11
- * Used to define strict business logic constraints and lifecycle management.
12
- * Prevent AI "hallucinations" by enforcing valid valid transitions.
13
- */
14
- /**
15
- * References a named action (side effect)
16
- * Can be a script, a webhook, or a field update.
17
- */
18
- declare const ActionRefSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
19
- type: z.ZodString;
20
- params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
21
- }, z.core.$strip>]>;
22
- /**
23
- * State Transition Definition
24
- * "When EVENT happens, if GUARD is true, go to TARGET and run ACTIONS"
25
- */
26
- declare const TransitionSchema: z.ZodObject<{
27
- target: z.ZodOptional<z.ZodString>;
28
- cond: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
29
- type: z.ZodString;
30
- params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
31
- }, z.core.$strip>]>>;
32
- actions: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
33
- type: z.ZodString;
34
- params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
35
- }, z.core.$strip>]>>>;
36
- description: z.ZodOptional<z.ZodString>;
37
- }, z.core.$strip>;
38
- type ActionRef = z.infer<typeof ActionRefSchema>;
39
- type Transition = z.infer<typeof TransitionSchema>;
40
- type StateNodeConfig = {
41
- type?: 'atomic' | 'compound' | 'parallel' | 'final' | 'history';
42
- entry?: ActionRef[];
43
- exit?: ActionRef[];
44
- on?: Record<string, string | Transition | Transition[]>;
45
- always?: Transition[];
46
- initial?: string;
47
- states?: Record<string, StateNodeConfig>;
48
- meta?: {
49
- label?: string;
50
- description?: string;
51
- color?: string;
52
- aiInstructions?: string;
53
- };
54
- };
55
-
56
7
  /**
57
8
  * Reserved namespaces that do not get FQN prefix applied.
58
- * Objects in these namespaces keep their short names (e.g., "user" not "base__user").
9
+ * Objects in these namespaces keep their short names (e.g., "user" short name IS the canonical key).
59
10
  */
60
11
  declare const RESERVED_NAMESPACES: Set<string>;
61
12
  /**
@@ -75,22 +26,25 @@ interface ObjectContributor {
75
26
  definition: ServiceObject;
76
27
  }
77
28
  /**
78
- * Compute Fully Qualified Name (FQN) for an object.
29
+ * Compute canonical registry key for an object.
30
+ *
31
+ * Under the current naming convention, object names are canonical identifiers
32
+ * and are used as-is (no namespace__ prefix). The namespace parameter is
33
+ * retained for backward compatibility but no longer affects the returned key.
79
34
  *
80
- * @param namespace - The package namespace (e.g., "crm", "todo")
81
- * @param shortName - The object's short name (e.g., "task", "account")
82
- * @returns FQN string (e.g., "crm__task") or just shortName for reserved namespaces
35
+ * @param namespace - The package namespace (unused, kept for API compatibility)
36
+ * @param shortName - The object's name (already the canonical identifier)
37
+ * @returns The object name unchanged
83
38
  *
84
39
  * @example
85
- * computeFQN('crm', 'account') // => 'crm__account'
86
- * computeFQN('base', 'user') // => 'user' (reserved, no prefix)
87
- * computeFQN(undefined, 'task') // => 'task' (legacy, no namespace)
40
+ * computeFQN('crm', 'account') // => 'account'
41
+ * computeFQN(undefined, 'task') // => 'task'
88
42
  */
89
- declare function computeFQN(namespace: string | undefined, shortName: string): string;
43
+ declare function computeFQN(_namespace: string | undefined, shortName: string): string;
90
44
  /**
91
45
  * Parse FQN back to namespace and short name.
92
46
  *
93
- * @param fqn - Fully qualified name (e.g., "crm__account" or "user")
47
+ * @param fqn - Object name (e.g., "account" or legacy "crm__account" for backward compat)
94
48
  * @returns { namespace, shortName } - namespace is undefined for unprefixed names
95
49
  */
96
50
  declare function parseFQN(fqn: string): {
@@ -105,7 +59,7 @@ declare function parseFQN(fqn: string): {
105
59
  *
106
60
  * Objects use a namespace-based FQN system:
107
61
  * - `namespace`: Short identifier from package manifest (e.g., "crm", "todo")
108
- * - `FQN`: `{namespace}__{short_name}` (e.g., "crm__account")
62
+ * - `name`: canonical object name (e.g., "account", "sys_user")
109
63
  * - Reserved namespaces (`base`, `system`) don't get prefixed
110
64
  *
111
65
  * Ownership modes:
@@ -120,37 +74,82 @@ declare function parseFQN(fqn: string): {
120
74
  * - A package may contain 0, 1, or many apps.
121
75
  */
122
76
  type RegistryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
77
+ /**
78
+ * Construction options for {@link SchemaRegistry}.
79
+ */
80
+ interface SchemaRegistryOptions {
81
+ /**
82
+ * Whether the host kernel runs in multi-tenant mode. When `true` (default),
83
+ * the registry auto-injects `organization_id` (lookup → sys_organization)
84
+ * into every registered user object that doesn't already declare it and
85
+ * isn't `managedBy` an external subsystem or explicitly opted-out via
86
+ * `systemFields: false`.
87
+ *
88
+ * Sourced from the `OS_MULTI_TENANT` env var when not explicitly set —
89
+ * matches how the SecurityPlugin and CLI startup banner pick the mode.
90
+ * Pass an explicit boolean to override (useful in tests).
91
+ */
92
+ multiTenant?: boolean;
93
+ }
94
+ /**
95
+ * Augment a registered object with implicit system fields.
96
+ *
97
+ * Returns a *new* schema object when fields are added; returns the input
98
+ * unchanged when nothing applies (the cheap path for system tables).
99
+ *
100
+ * Author-declared fields always win — we splice the system fields at the
101
+ * front of the field map, so any same-named author field overwrites them
102
+ * via the natural `{ ...sys, ...authored }` merge.
103
+ *
104
+ * Currently injects:
105
+ * - `organization_id` — multi-tenant deployments. Required-false (the
106
+ * SecurityPlugin populates it on insert; nullable rows are still
107
+ * filtered out by the `tenant_isolation` RLS USING clause).
108
+ * - `created_at` / `created_by` / `updated_at` / `updated_by` — audit
109
+ * fields. Marked `system: true, readonly: true` so detail views can
110
+ * surface them in a dedicated "System Information" section while
111
+ * edit forms / drawers filter them out. The driver populates the
112
+ * timestamps; the `*_by` lookups are filled by the runtime when an
113
+ * authenticated session is present (NULL otherwise — e.g. seeded
114
+ * rows).
115
+ */
116
+ declare function applySystemFields(schema: ServiceObject, opts: {
117
+ multiTenant: boolean;
118
+ }): ServiceObject;
123
119
  declare class SchemaRegistry {
124
120
  /** Controls verbosity of registry console messages. Default: 'info'. */
125
- private static _logLevel;
126
- static get logLevel(): RegistryLogLevel;
127
- static set logLevel(level: RegistryLogLevel);
128
- private static log;
121
+ private _logLevel;
122
+ /** Whether to auto-inject multi-tenant system fields. */
123
+ private readonly multiTenant;
124
+ constructor(options?: SchemaRegistryOptions);
125
+ get logLevel(): RegistryLogLevel;
126
+ set logLevel(level: RegistryLogLevel);
127
+ private log;
129
128
  /** FQN → Contributor[] (all packages that own/extend this object) */
130
- private static objectContributors;
129
+ private objectContributors;
131
130
  /** FQN → Merged ServiceObject (cached, invalidated on changes) */
132
- private static mergedObjectCache;
131
+ private mergedObjectCache;
133
132
  /** Namespace → Set<PackageId> (multiple packages can share a namespace) */
134
- private static namespaceRegistry;
133
+ private namespaceRegistry;
135
134
  /** Type → Name/ID → MetadataItem */
136
- private static metadata;
135
+ private metadata;
137
136
  /**
138
137
  * Register a namespace for a package.
139
138
  * Multiple packages can share the same namespace (e.g. 'sys').
140
139
  */
141
- static registerNamespace(namespace: string, packageId: string): void;
140
+ registerNamespace(namespace: string, packageId: string): void;
142
141
  /**
143
142
  * Unregister a namespace when a package is uninstalled.
144
143
  */
145
- static unregisterNamespace(namespace: string, packageId: string): void;
144
+ unregisterNamespace(namespace: string, packageId: string): void;
146
145
  /**
147
146
  * Get the packages that use a namespace.
148
147
  */
149
- static getNamespaceOwner(namespace: string): string | undefined;
148
+ getNamespaceOwner(namespace: string): string | undefined;
150
149
  /**
151
150
  * Get all packages that share a namespace.
152
151
  */
153
- static getNamespaceOwners(namespace: string): string[];
152
+ getNamespaceOwners(namespace: string): string[];
154
153
  /**
155
154
  * Register an object with ownership semantics.
156
155
  *
@@ -162,1086 +161,127 @@ declare class SchemaRegistry {
162
161
  *
163
162
  * @throws Error if trying to 'own' an object that already has an owner
164
163
  */
165
- static registerObject(schema: ServiceObject, packageId: string, namespace?: string, ownership?: ObjectOwnership, priority?: number): string;
164
+ registerObject(schema: ServiceObject, packageId: string, namespace?: string, ownership?: ObjectOwnership, priority?: number): string;
166
165
  /**
167
166
  * Resolve an object by FQN, merging all contributions.
168
167
  * Returns the merged object or undefined if not found.
169
168
  */
170
- static resolveObject(fqn: string): ServiceObject | undefined;
169
+ resolveObject(fqn: string): ServiceObject | undefined;
171
170
  /**
172
- * Get object by name (FQN, short name, or physical table name).
171
+ * Get object by name (short name canonical, FQN supported for disambiguation).
172
+ *
173
+ * Short names are canonical for user code, AI generation, and most lookups.
174
+ * FQN is accepted as an explicit fallback for cross-package disambiguation
175
+ * when two packages contribute objects with the same short name.
173
176
  *
174
177
  * Resolution order:
175
- * 1. Exact FQN match (e.g., 'crm__account')
176
- * 2. Short name fallback (e.g., 'account' → 'crm__account')
177
- * 3. Physical table name match (e.g., 'sys_user' 'sys__user')
178
- * ObjectSchema.create() auto-derives tableName as {namespace}_{name},
179
- * which uses a single underscore — different from the FQN double underscore.
178
+ * 1. Exact name match — the name IS the canonical key.
179
+ * If multiple packages contribute the same short name, a warning is logged
180
+ * and the first match wins disambiguate by passing the FQN explicitly.
181
+ * 2. Legacy FQN match (e.g., 'crm__account') backward compat.
180
182
  */
181
- static getObject(name: string): ServiceObject | undefined;
183
+ getObject(name: string): ServiceObject | undefined;
182
184
  /**
183
185
  * Get all registered objects (merged).
184
186
  *
185
187
  * @param packageId - Optional filter: only objects contributed by this package
186
188
  */
187
- static getAllObjects(packageId?: string): ServiceObject[];
189
+ getAllObjects(packageId?: string): ServiceObject[];
188
190
  /**
189
191
  * Get all contributors for an object.
190
192
  */
191
- static getObjectContributors(fqn: string): ObjectContributor[];
193
+ getObjectContributors(fqn: string): ObjectContributor[];
192
194
  /**
193
195
  * Get the owner contributor for an object.
194
196
  */
195
- static getObjectOwner(fqn: string): ObjectContributor | undefined;
197
+ getObjectOwner(fqn: string): ObjectContributor | undefined;
196
198
  /**
197
199
  * Unregister all objects contributed by a package.
198
200
  *
199
201
  * @throws Error if trying to uninstall an owner that has extenders
200
202
  */
201
- static unregisterObjectsByPackage(packageId: string, force?: boolean): void;
203
+ unregisterObjectsByPackage(packageId: string, force?: boolean): void;
202
204
  /**
203
205
  * Universal Register Method for non-object metadata.
204
206
  */
205
- static registerItem<T>(type: string, item: T, keyField?: keyof T, packageId?: string): void;
207
+ registerItem<T>(type: string, item: T, keyField?: keyof T, packageId?: string): void;
206
208
  /**
207
209
  * Validate Metadata against Spec Zod Schemas
208
210
  */
209
- static validate(type: string, item: any): true | {
210
- name: string;
211
- active: boolean;
212
- isSystem: boolean;
213
- abstract: boolean;
214
- datasource: string;
215
- fields: Record<string, {
216
- type: "number" | "boolean" | "tags" | "date" | "lookup" | "file" | "url" | "json" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "datetime" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "code" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector";
217
- required: boolean;
218
- searchable: boolean;
219
- multiple: boolean;
220
- unique: boolean;
221
- deleteBehavior: "set_null" | "cascade" | "restrict";
222
- auditTrail: boolean;
223
- hidden: boolean;
224
- readonly: boolean;
225
- sortable: boolean;
226
- index: boolean;
227
- externalId: boolean;
228
- name?: string | undefined;
229
- label?: string | undefined;
230
- description?: string | undefined;
231
- format?: string | undefined;
232
- columnName?: string | undefined;
233
- defaultValue?: unknown;
234
- maxLength?: number | undefined;
235
- minLength?: number | undefined;
236
- precision?: number | undefined;
237
- scale?: number | undefined;
238
- min?: number | undefined;
239
- max?: number | undefined;
240
- options?: {
241
- label: string;
242
- value: string;
243
- color?: string | undefined;
244
- default?: boolean | undefined;
245
- }[] | undefined;
246
- reference?: string | undefined;
247
- referenceFilters?: string[] | undefined;
248
- writeRequiresMasterRead?: boolean | undefined;
249
- expression?: string | undefined;
250
- summaryOperations?: {
251
- object: string;
252
- field: string;
253
- function: "min" | "max" | "count" | "sum" | "avg";
254
- } | undefined;
255
- language?: string | undefined;
256
- theme?: string | undefined;
257
- lineNumbers?: boolean | undefined;
258
- maxRating?: number | undefined;
259
- allowHalf?: boolean | undefined;
260
- displayMap?: boolean | undefined;
261
- allowGeocoding?: boolean | undefined;
262
- addressFormat?: "us" | "uk" | "international" | undefined;
263
- colorFormat?: "hex" | "rgb" | "rgba" | "hsl" | undefined;
264
- allowAlpha?: boolean | undefined;
265
- presetColors?: string[] | undefined;
266
- step?: number | undefined;
267
- showValue?: boolean | undefined;
268
- marks?: Record<string, string> | undefined;
269
- barcodeFormat?: "qr" | "ean13" | "ean8" | "code128" | "code39" | "upca" | "upce" | undefined;
270
- qrErrorCorrection?: "L" | "M" | "Q" | "H" | undefined;
271
- displayValue?: boolean | undefined;
272
- allowScanning?: boolean | undefined;
273
- currencyConfig?: {
274
- precision: number;
275
- currencyMode: "dynamic" | "fixed";
276
- defaultCurrency: string;
277
- } | undefined;
278
- vectorConfig?: {
279
- dimensions: number;
280
- distanceMetric: "cosine" | "euclidean" | "dotProduct" | "manhattan";
281
- normalized: boolean;
282
- indexed: boolean;
283
- indexType?: "hnsw" | "ivfflat" | "flat" | undefined;
284
- } | undefined;
285
- fileAttachmentConfig?: {
286
- virusScan: boolean;
287
- virusScanOnUpload: boolean;
288
- quarantineOnThreat: boolean;
289
- allowMultiple: boolean;
290
- allowReplace: boolean;
291
- allowDelete: boolean;
292
- requireUpload: boolean;
293
- extractMetadata: boolean;
294
- extractText: boolean;
295
- versioningEnabled: boolean;
296
- publicRead: boolean;
297
- presignedUrlExpiry: number;
298
- minSize?: number | undefined;
299
- maxSize?: number | undefined;
300
- allowedTypes?: string[] | undefined;
301
- blockedTypes?: string[] | undefined;
302
- allowedMimeTypes?: string[] | undefined;
303
- blockedMimeTypes?: string[] | undefined;
304
- virusScanProvider?: "custom" | "clamav" | "virustotal" | "metadefender" | undefined;
305
- storageProvider?: string | undefined;
306
- storageBucket?: string | undefined;
307
- storagePrefix?: string | undefined;
308
- imageValidation?: {
309
- generateThumbnails: boolean;
310
- preserveMetadata: boolean;
311
- autoRotate: boolean;
312
- minWidth?: number | undefined;
313
- maxWidth?: number | undefined;
314
- minHeight?: number | undefined;
315
- maxHeight?: number | undefined;
316
- aspectRatio?: string | undefined;
317
- thumbnailSizes?: {
318
- name: string;
319
- width: number;
320
- height: number;
321
- crop: boolean;
322
- }[] | undefined;
323
- } | undefined;
324
- maxVersions?: number | undefined;
325
- } | undefined;
326
- encryptionConfig?: {
327
- enabled: boolean;
328
- algorithm: "aes-256-gcm" | "aes-256-cbc" | "chacha20-poly1305";
329
- keyManagement: {
330
- provider: "local" | "aws-kms" | "azure-key-vault" | "gcp-kms" | "hashicorp-vault";
331
- keyId?: string | undefined;
332
- rotationPolicy?: {
333
- enabled: boolean;
334
- frequencyDays: number;
335
- retainOldVersions: number;
336
- autoRotate: boolean;
337
- } | undefined;
338
- };
339
- scope: "field" | "table" | "record" | "database";
340
- deterministicEncryption: boolean;
341
- searchableEncryption: boolean;
342
- } | undefined;
343
- maskingRule?: {
344
- field: string;
345
- strategy: "redact" | "partial" | "hash" | "tokenize" | "randomize" | "nullify" | "substitute";
346
- preserveFormat: boolean;
347
- preserveLength: boolean;
348
- pattern?: string | undefined;
349
- roles?: string[] | undefined;
350
- exemptRoles?: string[] | undefined;
351
- } | undefined;
352
- dependencies?: string[] | undefined;
353
- cached?: {
354
- enabled: boolean;
355
- ttl: number;
356
- invalidateOn: string[];
357
- } | undefined;
358
- dataQuality?: {
359
- uniqueness: boolean;
360
- completeness: number;
361
- accuracy?: {
362
- source: string;
363
- threshold: number;
364
- } | undefined;
365
- } | undefined;
366
- group?: string | undefined;
367
- conditionalRequired?: string | undefined;
368
- inlineHelpText?: string | undefined;
369
- trackFeedHistory?: boolean | undefined;
370
- caseSensitive?: boolean | undefined;
371
- autonumberFormat?: string | undefined;
372
- }>;
373
- label?: string | undefined;
374
- pluralLabel?: string | undefined;
375
- description?: string | undefined;
376
- icon?: string | undefined;
377
- namespace?: string | undefined;
378
- tags?: string[] | undefined;
379
- tableName?: string | undefined;
380
- indexes?: {
381
- fields: string[];
382
- type: "hash" | "btree" | "gin" | "gist" | "fulltext";
383
- unique: boolean;
384
- name?: string | undefined;
385
- partial?: string | undefined;
386
- }[] | undefined;
387
- tenancy?: {
388
- enabled: boolean;
389
- strategy: "shared" | "isolated" | "hybrid";
390
- tenantField: string;
391
- crossTenantAccess: boolean;
392
- } | undefined;
393
- softDelete?: {
394
- enabled: boolean;
395
- field: string;
396
- cascadeDelete: boolean;
397
- } | undefined;
398
- versioning?: {
399
- enabled: boolean;
400
- strategy: "snapshot" | "delta" | "event-sourcing";
401
- versionField: string;
402
- retentionDays?: number | undefined;
403
- } | undefined;
404
- partitioning?: {
405
- enabled: boolean;
406
- strategy: "hash" | "range" | "list";
407
- key: string;
408
- interval?: string | undefined;
409
- } | undefined;
410
- cdc?: {
411
- enabled: boolean;
412
- events: ("update" | "delete" | "insert")[];
413
- destination: string;
414
- } | undefined;
415
- validations?: _objectstack_spec_data.BaseValidationRuleShape[] | undefined;
416
- stateMachines?: Record<string, {
417
- id: string;
418
- initial: string;
419
- states: Record<string, StateNodeConfig>;
420
- description?: string | undefined;
421
- contextSchema?: Record<string, unknown> | undefined;
422
- on?: Record<string, string | {
423
- target?: string | undefined;
424
- cond?: string | {
425
- type: string;
426
- params?: Record<string, unknown> | undefined;
427
- } | undefined;
428
- actions?: (string | {
429
- type: string;
430
- params?: Record<string, unknown> | undefined;
431
- })[] | undefined;
432
- description?: string | undefined;
433
- } | {
434
- target?: string | undefined;
435
- cond?: string | {
436
- type: string;
437
- params?: Record<string, unknown> | undefined;
438
- } | undefined;
439
- actions?: (string | {
440
- type: string;
441
- params?: Record<string, unknown> | undefined;
442
- })[] | undefined;
443
- description?: string | undefined;
444
- }[]> | undefined;
445
- }> | undefined;
446
- displayNameField?: string | undefined;
447
- recordName?: {
448
- type: "text" | "autonumber";
449
- displayFormat?: string | undefined;
450
- startNumber?: number | undefined;
451
- } | undefined;
452
- titleFormat?: string | undefined;
453
- compactLayout?: string[] | undefined;
454
- search?: {
455
- fields: string[];
456
- displayFields?: string[] | undefined;
457
- filters?: string[] | undefined;
458
- } | undefined;
459
- enable?: {
460
- trackHistory: boolean;
461
- searchable: boolean;
462
- apiEnabled: boolean;
463
- files: boolean;
464
- feeds: boolean;
465
- activities: boolean;
466
- trash: boolean;
467
- mru: boolean;
468
- clone: boolean;
469
- apiMethods?: ("search" | "list" | "update" | "delete" | "upsert" | "history" | "get" | "create" | "bulk" | "aggregate" | "restore" | "purge" | "import" | "export")[] | undefined;
470
- } | undefined;
471
- recordTypes?: string[] | undefined;
472
- sharingModel?: "full" | "private" | "read" | "read_write" | undefined;
473
- keyPrefix?: string | undefined;
474
- actions?: {
475
- name: string;
476
- label: string;
477
- type: "url" | "flow" | "api" | "script" | "modal";
478
- refreshAfter: boolean;
479
- objectName?: string | undefined;
480
- icon?: string | undefined;
481
- locations?: ("list_toolbar" | "list_item" | "record_header" | "record_more" | "record_related" | "global_nav")[] | undefined;
482
- component?: "action:button" | "action:icon" | "action:menu" | "action:group" | undefined;
483
- target?: string | undefined;
484
- execute?: string | undefined;
485
- params?: {
486
- name: string;
487
- label: string;
488
- type: "number" | "boolean" | "date" | "lookup" | "file" | "url" | "json" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "datetime" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "code" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "tags" | "vector";
489
- required: boolean;
490
- options?: {
491
- label: string;
492
- value: string;
493
- }[] | undefined;
494
- }[] | undefined;
495
- variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
496
- confirmText?: string | undefined;
497
- successMessage?: string | undefined;
498
- visible?: string | undefined;
499
- disabled?: string | boolean | undefined;
500
- shortcut?: string | undefined;
501
- bulkEnabled?: boolean | undefined;
502
- timeout?: number | undefined;
503
- aria?: {
504
- ariaLabel?: string | undefined;
505
- ariaDescribedBy?: string | undefined;
506
- role?: string | undefined;
507
- } | undefined;
508
- }[] | undefined;
509
- } | {
510
- name: string;
511
- label: string;
512
- active: boolean;
513
- isDefault: boolean;
514
- version?: string | undefined;
515
- description?: string | undefined;
516
- icon?: string | undefined;
517
- branding?: {
518
- primaryColor?: string | undefined;
519
- logo?: string | undefined;
520
- favicon?: string | undefined;
521
- } | undefined;
522
- navigation?: any[] | undefined;
523
- areas?: {
524
- id: string;
525
- label: string;
526
- navigation: any[];
527
- icon?: string | undefined;
528
- order?: number | undefined;
529
- description?: string | undefined;
530
- visible?: string | undefined;
531
- requiredPermissions?: string[] | undefined;
532
- }[] | undefined;
533
- homePageId?: string | undefined;
534
- requiredPermissions?: string[] | undefined;
535
- objects?: unknown[] | undefined;
536
- apis?: unknown[] | undefined;
537
- sharing?: {
538
- enabled: boolean;
539
- allowAnonymous: boolean;
540
- publicLink?: string | undefined;
541
- password?: string | undefined;
542
- allowedDomains?: string[] | undefined;
543
- expiresAt?: string | undefined;
544
- } | undefined;
545
- embed?: {
546
- enabled: boolean;
547
- width: string;
548
- height: string;
549
- showHeader: boolean;
550
- showNavigation: boolean;
551
- responsive: boolean;
552
- allowedOrigins?: string[] | undefined;
553
- } | undefined;
554
- mobileNavigation?: {
555
- mode: "drawer" | "bottom_nav" | "hamburger";
556
- bottomNavItems?: string[] | undefined;
557
- } | undefined;
558
- aria?: {
559
- ariaLabel?: string | undefined;
560
- ariaDescribedBy?: string | undefined;
561
- role?: string | undefined;
562
- } | undefined;
563
- } | {
564
- manifest: {
565
- id: string;
566
- defaultDatasource: string;
567
- version: string;
568
- type: "theme" | "app" | "agent" | "driver" | "server" | "ui" | "module" | "objectql" | "plugin" | "gateway" | "adapter";
569
- name: string;
570
- namespace?: string | undefined;
571
- description?: string | undefined;
572
- permissions?: string[] | undefined;
573
- objects?: string[] | undefined;
574
- datasources?: string[] | undefined;
575
- dependencies?: Record<string, string> | undefined;
576
- configuration?: {
577
- properties: Record<string, {
578
- type: "string" | "number" | "boolean" | "object" | "array";
579
- default?: unknown;
580
- description?: string | undefined;
581
- required?: boolean | undefined;
582
- secret?: boolean | undefined;
583
- enum?: string[] | undefined;
584
- }>;
585
- title?: string | undefined;
586
- } | undefined;
587
- contributes?: {
588
- kinds?: {
589
- id: string;
590
- globs: string[];
591
- description?: string | undefined;
592
- }[] | undefined;
593
- events?: string[] | undefined;
594
- menus?: Record<string, {
595
- id: string;
596
- label: string;
597
- command?: string | undefined;
598
- }[]> | undefined;
599
- themes?: {
600
- id: string;
601
- label: string;
602
- path: string;
603
- }[] | undefined;
604
- translations?: {
605
- locale: string;
606
- path: string;
607
- }[] | undefined;
608
- actions?: {
609
- name: string;
610
- label?: string | undefined;
611
- description?: string | undefined;
612
- input?: unknown;
613
- output?: unknown;
614
- }[] | undefined;
615
- drivers?: {
616
- id: string;
617
- label: string;
618
- description?: string | undefined;
619
- }[] | undefined;
620
- fieldTypes?: {
621
- name: string;
622
- label: string;
623
- description?: string | undefined;
624
- }[] | undefined;
625
- functions?: {
626
- name: string;
627
- description?: string | undefined;
628
- args?: string[] | undefined;
629
- returnType?: string | undefined;
630
- }[] | undefined;
631
- routes?: {
632
- prefix: string;
633
- service: string;
634
- methods?: string[] | undefined;
635
- }[] | undefined;
636
- commands?: {
637
- name: string;
638
- description?: string | undefined;
639
- module?: string | undefined;
640
- }[] | undefined;
641
- } | undefined;
642
- data?: {
643
- object: string;
644
- externalId: string;
645
- mode: "update" | "upsert" | "insert" | "replace" | "ignore";
646
- env: ("prod" | "dev" | "test")[];
647
- records: Record<string, unknown>[];
648
- }[] | undefined;
649
- capabilities?: {
650
- implements?: {
651
- protocol: {
652
- id: string;
653
- label: string;
654
- version: {
655
- major: number;
656
- minor: number;
657
- patch: number;
658
- };
659
- specification?: string | undefined;
660
- description?: string | undefined;
661
- };
662
- conformance: "partial" | "full" | "deprecated" | "experimental";
663
- certified: boolean;
664
- implementedFeatures?: string[] | undefined;
665
- features?: {
666
- name: string;
667
- enabled: boolean;
668
- description?: string | undefined;
669
- sinceVersion?: string | undefined;
670
- deprecatedSince?: string | undefined;
671
- }[] | undefined;
672
- metadata?: Record<string, unknown> | undefined;
673
- certificationDate?: string | undefined;
674
- }[] | undefined;
675
- provides?: {
676
- id: string;
677
- name: string;
678
- version: {
679
- major: number;
680
- minor: number;
681
- patch: number;
682
- };
683
- methods: {
684
- name: string;
685
- async: boolean;
686
- description?: string | undefined;
687
- parameters?: {
688
- name: string;
689
- type: string;
690
- required: boolean;
691
- description?: string | undefined;
692
- }[] | undefined;
693
- returnType?: string | undefined;
694
- }[];
695
- stability: "experimental" | "alpha" | "stable" | "beta";
696
- description?: string | undefined;
697
- events?: {
698
- name: string;
699
- description?: string | undefined;
700
- payload?: string | undefined;
701
- }[] | undefined;
702
- }[] | undefined;
703
- requires?: {
704
- pluginId: string;
705
- version: string;
706
- optional: boolean;
707
- reason?: string | undefined;
708
- requiredCapabilities?: string[] | undefined;
709
- }[] | undefined;
710
- extensionPoints?: {
711
- id: string;
712
- name: string;
713
- type: "provider" | "action" | "hook" | "widget" | "transformer" | "validator" | "decorator";
714
- cardinality: "multiple" | "single";
715
- description?: string | undefined;
716
- contract?: {
717
- input?: string | undefined;
718
- output?: string | undefined;
719
- signature?: string | undefined;
720
- } | undefined;
721
- }[] | undefined;
722
- extensions?: {
723
- targetPluginId: string;
724
- extensionPointId: string;
725
- implementation: string;
726
- priority: number;
727
- }[] | undefined;
728
- } | undefined;
729
- extensions?: Record<string, unknown> | undefined;
730
- loading?: {
731
- strategy: "lazy" | "parallel" | "eager" | "deferred" | "on-demand";
732
- preload?: {
733
- enabled: boolean;
734
- priority: number;
735
- resources?: ("dependencies" | "code" | "metadata" | "assets" | "services")[] | undefined;
736
- conditions?: {
737
- routes?: string[] | undefined;
738
- roles?: string[] | undefined;
739
- deviceType?: ("desktop" | "mobile" | "tablet")[] | undefined;
740
- minNetworkSpeed?: "slow-2g" | "2g" | "3g" | "4g" | undefined;
741
- } | undefined;
742
- } | undefined;
743
- codeSplitting?: {
744
- enabled: boolean;
745
- strategy: "custom" | "size" | "route" | "feature";
746
- chunkNaming: "hashed" | "sequential" | "named";
747
- maxChunkSize?: number | undefined;
748
- sharedDependencies?: {
749
- enabled: boolean;
750
- minChunks: number;
751
- } | undefined;
752
- } | undefined;
753
- dynamicImport?: {
754
- enabled: boolean;
755
- mode: "lazy" | "eager" | "async" | "sync";
756
- prefetch: boolean;
757
- preload: boolean;
758
- timeout: number;
759
- webpackChunkName?: string | undefined;
760
- retry?: {
761
- enabled: boolean;
762
- maxAttempts: number;
763
- backoffMs: number;
764
- } | undefined;
765
- } | undefined;
766
- initialization?: {
767
- mode: "parallel" | "sequential" | "async" | "sync";
768
- timeout: number;
769
- priority: number;
770
- critical: boolean;
771
- retry?: {
772
- enabled: boolean;
773
- maxAttempts: number;
774
- backoffMs: number;
775
- } | undefined;
776
- healthCheckInterval?: number | undefined;
777
- } | undefined;
778
- dependencyResolution?: {
779
- strategy: "strict" | "pinned" | "latest" | "compatible";
780
- conflictResolution: "latest" | "manual" | "fail" | "oldest";
781
- circularDependencies: "warn" | "error" | "allow";
782
- peerDependencies?: {
783
- resolve: boolean;
784
- onMissing: "warn" | "error" | "ignore";
785
- onMismatch: "warn" | "error" | "ignore";
786
- } | undefined;
787
- optionalDependencies?: {
788
- load: boolean;
789
- onFailure: "warn" | "ignore";
790
- } | undefined;
791
- } | undefined;
792
- hotReload?: {
793
- enabled: boolean;
794
- environment: "development" | "production" | "staging";
795
- strategy: "partial" | "full" | "state-preserve";
796
- debounceMs: number;
797
- preserveState: boolean;
798
- watchPatterns?: string[] | undefined;
799
- ignorePatterns?: string[] | undefined;
800
- stateSerialization?: {
801
- enabled: boolean;
802
- handler?: string | undefined;
803
- } | undefined;
804
- hooks?: {
805
- beforeReload?: string | undefined;
806
- afterReload?: string | undefined;
807
- onError?: string | undefined;
808
- } | undefined;
809
- productionSafety?: {
810
- healthValidation: boolean;
811
- rollbackOnFailure: boolean;
812
- healthTimeout: number;
813
- drainConnections: boolean;
814
- drainTimeout: number;
815
- maxConcurrentReloads: number;
816
- minReloadInterval: number;
817
- } | undefined;
818
- } | undefined;
819
- caching?: {
820
- enabled: boolean;
821
- storage: "hybrid" | "indexeddb" | "memory" | "disk";
822
- keyStrategy: "hash" | "version" | "timestamp";
823
- ttl?: number | undefined;
824
- maxSize?: number | undefined;
825
- invalidateOn?: ("error" | "manual" | "version-change" | "dependency-change")[] | undefined;
826
- compression?: {
827
- enabled: boolean;
828
- algorithm: "gzip" | "brotli" | "deflate";
829
- } | undefined;
830
- } | undefined;
831
- sandboxing?: {
832
- enabled: boolean;
833
- scope: "automation-only" | "untrusted-only" | "all-plugins";
834
- isolationLevel: "none" | "process" | "vm" | "iframe" | "web-worker";
835
- allowedCapabilities?: string[] | undefined;
836
- resourceQuotas?: {
837
- maxMemoryMB?: number | undefined;
838
- maxCpuTimeMs?: number | undefined;
839
- maxFileDescriptors?: number | undefined;
840
- maxNetworkKBps?: number | undefined;
841
- } | undefined;
842
- permissions?: {
843
- allowedAPIs?: string[] | undefined;
844
- allowedPaths?: string[] | undefined;
845
- allowedEndpoints?: string[] | undefined;
846
- allowedEnvVars?: string[] | undefined;
847
- } | undefined;
848
- ipc?: {
849
- enabled: boolean;
850
- transport: "memory" | "message-port" | "unix-socket" | "tcp";
851
- maxMessageSize: number;
852
- timeout: number;
853
- allowedServices?: string[] | undefined;
854
- } | undefined;
855
- } | undefined;
856
- monitoring?: {
857
- enabled: boolean;
858
- samplingRate: number;
859
- reportingInterval: number;
860
- onBudgetViolation: "warn" | "error" | "ignore";
861
- metrics?: ("load-time" | "init-time" | "memory-usage" | "cpu-usage" | "api-calls" | "error-rate" | "cache-hit-rate")[] | undefined;
862
- budgets?: {
863
- maxLoadTimeMs?: number | undefined;
864
- maxInitTimeMs?: number | undefined;
865
- maxMemoryMB?: number | undefined;
866
- } | undefined;
867
- } | undefined;
868
- } | undefined;
869
- engine?: {
870
- objectstack: string;
871
- } | undefined;
872
- };
873
- status: "disabled" | "error" | "installed" | "installing" | "upgrading" | "uninstalling";
874
- enabled: boolean;
875
- installedAt?: string | undefined;
876
- updatedAt?: string | undefined;
877
- installedVersion?: string | undefined;
878
- previousVersion?: string | undefined;
879
- statusChangedAt?: string | undefined;
880
- errorMessage?: string | undefined;
881
- settings?: Record<string, unknown> | undefined;
882
- upgradeHistory?: {
883
- fromVersion: string;
884
- toVersion: string;
885
- upgradedAt: string;
886
- status: "success" | "failed" | "rolled_back";
887
- migrationLog?: string[] | undefined;
888
- }[] | undefined;
889
- registeredNamespaces?: string[] | undefined;
890
- } | {
891
- id: string;
892
- defaultDatasource: string;
893
- version: string;
894
- type: "theme" | "app" | "agent" | "driver" | "server" | "ui" | "module" | "objectql" | "plugin" | "gateway" | "adapter";
895
- name: string;
896
- namespace?: string | undefined;
897
- description?: string | undefined;
898
- permissions?: string[] | undefined;
899
- objects?: string[] | undefined;
900
- datasources?: string[] | undefined;
901
- dependencies?: Record<string, string> | undefined;
902
- configuration?: {
903
- properties: Record<string, {
904
- type: "string" | "number" | "boolean" | "object" | "array";
905
- default?: unknown;
906
- description?: string | undefined;
907
- required?: boolean | undefined;
908
- secret?: boolean | undefined;
909
- enum?: string[] | undefined;
910
- }>;
911
- title?: string | undefined;
912
- } | undefined;
913
- contributes?: {
914
- kinds?: {
915
- id: string;
916
- globs: string[];
917
- description?: string | undefined;
918
- }[] | undefined;
919
- events?: string[] | undefined;
920
- menus?: Record<string, {
921
- id: string;
922
- label: string;
923
- command?: string | undefined;
924
- }[]> | undefined;
925
- themes?: {
926
- id: string;
927
- label: string;
928
- path: string;
929
- }[] | undefined;
930
- translations?: {
931
- locale: string;
932
- path: string;
933
- }[] | undefined;
934
- actions?: {
935
- name: string;
936
- label?: string | undefined;
937
- description?: string | undefined;
938
- input?: unknown;
939
- output?: unknown;
940
- }[] | undefined;
941
- drivers?: {
942
- id: string;
943
- label: string;
944
- description?: string | undefined;
945
- }[] | undefined;
946
- fieldTypes?: {
947
- name: string;
948
- label: string;
949
- description?: string | undefined;
950
- }[] | undefined;
951
- functions?: {
952
- name: string;
953
- description?: string | undefined;
954
- args?: string[] | undefined;
955
- returnType?: string | undefined;
956
- }[] | undefined;
957
- routes?: {
958
- prefix: string;
959
- service: string;
960
- methods?: string[] | undefined;
961
- }[] | undefined;
962
- commands?: {
963
- name: string;
964
- description?: string | undefined;
965
- module?: string | undefined;
966
- }[] | undefined;
967
- } | undefined;
968
- data?: {
969
- object: string;
970
- externalId: string;
971
- mode: "update" | "upsert" | "insert" | "replace" | "ignore";
972
- env: ("prod" | "dev" | "test")[];
973
- records: Record<string, unknown>[];
974
- }[] | undefined;
975
- capabilities?: {
976
- implements?: {
977
- protocol: {
978
- id: string;
979
- label: string;
980
- version: {
981
- major: number;
982
- minor: number;
983
- patch: number;
984
- };
985
- specification?: string | undefined;
986
- description?: string | undefined;
987
- };
988
- conformance: "partial" | "full" | "deprecated" | "experimental";
989
- certified: boolean;
990
- implementedFeatures?: string[] | undefined;
991
- features?: {
992
- name: string;
993
- enabled: boolean;
994
- description?: string | undefined;
995
- sinceVersion?: string | undefined;
996
- deprecatedSince?: string | undefined;
997
- }[] | undefined;
998
- metadata?: Record<string, unknown> | undefined;
999
- certificationDate?: string | undefined;
1000
- }[] | undefined;
1001
- provides?: {
1002
- id: string;
1003
- name: string;
1004
- version: {
1005
- major: number;
1006
- minor: number;
1007
- patch: number;
1008
- };
1009
- methods: {
1010
- name: string;
1011
- async: boolean;
1012
- description?: string | undefined;
1013
- parameters?: {
1014
- name: string;
1015
- type: string;
1016
- required: boolean;
1017
- description?: string | undefined;
1018
- }[] | undefined;
1019
- returnType?: string | undefined;
1020
- }[];
1021
- stability: "experimental" | "alpha" | "stable" | "beta";
1022
- description?: string | undefined;
1023
- events?: {
1024
- name: string;
1025
- description?: string | undefined;
1026
- payload?: string | undefined;
1027
- }[] | undefined;
1028
- }[] | undefined;
1029
- requires?: {
1030
- pluginId: string;
1031
- version: string;
1032
- optional: boolean;
1033
- reason?: string | undefined;
1034
- requiredCapabilities?: string[] | undefined;
1035
- }[] | undefined;
1036
- extensionPoints?: {
1037
- id: string;
1038
- name: string;
1039
- type: "provider" | "action" | "hook" | "widget" | "transformer" | "validator" | "decorator";
1040
- cardinality: "multiple" | "single";
1041
- description?: string | undefined;
1042
- contract?: {
1043
- input?: string | undefined;
1044
- output?: string | undefined;
1045
- signature?: string | undefined;
1046
- } | undefined;
1047
- }[] | undefined;
1048
- extensions?: {
1049
- targetPluginId: string;
1050
- extensionPointId: string;
1051
- implementation: string;
1052
- priority: number;
1053
- }[] | undefined;
1054
- } | undefined;
1055
- extensions?: Record<string, unknown> | undefined;
1056
- loading?: {
1057
- strategy: "lazy" | "parallel" | "eager" | "deferred" | "on-demand";
1058
- preload?: {
1059
- enabled: boolean;
1060
- priority: number;
1061
- resources?: ("dependencies" | "code" | "metadata" | "assets" | "services")[] | undefined;
1062
- conditions?: {
1063
- routes?: string[] | undefined;
1064
- roles?: string[] | undefined;
1065
- deviceType?: ("desktop" | "mobile" | "tablet")[] | undefined;
1066
- minNetworkSpeed?: "slow-2g" | "2g" | "3g" | "4g" | undefined;
1067
- } | undefined;
1068
- } | undefined;
1069
- codeSplitting?: {
1070
- enabled: boolean;
1071
- strategy: "custom" | "size" | "route" | "feature";
1072
- chunkNaming: "hashed" | "sequential" | "named";
1073
- maxChunkSize?: number | undefined;
1074
- sharedDependencies?: {
1075
- enabled: boolean;
1076
- minChunks: number;
1077
- } | undefined;
1078
- } | undefined;
1079
- dynamicImport?: {
1080
- enabled: boolean;
1081
- mode: "lazy" | "eager" | "async" | "sync";
1082
- prefetch: boolean;
1083
- preload: boolean;
1084
- timeout: number;
1085
- webpackChunkName?: string | undefined;
1086
- retry?: {
1087
- enabled: boolean;
1088
- maxAttempts: number;
1089
- backoffMs: number;
1090
- } | undefined;
1091
- } | undefined;
1092
- initialization?: {
1093
- mode: "parallel" | "sequential" | "async" | "sync";
1094
- timeout: number;
1095
- priority: number;
1096
- critical: boolean;
1097
- retry?: {
1098
- enabled: boolean;
1099
- maxAttempts: number;
1100
- backoffMs: number;
1101
- } | undefined;
1102
- healthCheckInterval?: number | undefined;
1103
- } | undefined;
1104
- dependencyResolution?: {
1105
- strategy: "strict" | "pinned" | "latest" | "compatible";
1106
- conflictResolution: "latest" | "manual" | "fail" | "oldest";
1107
- circularDependencies: "warn" | "error" | "allow";
1108
- peerDependencies?: {
1109
- resolve: boolean;
1110
- onMissing: "warn" | "error" | "ignore";
1111
- onMismatch: "warn" | "error" | "ignore";
1112
- } | undefined;
1113
- optionalDependencies?: {
1114
- load: boolean;
1115
- onFailure: "warn" | "ignore";
1116
- } | undefined;
1117
- } | undefined;
1118
- hotReload?: {
1119
- enabled: boolean;
1120
- environment: "development" | "production" | "staging";
1121
- strategy: "partial" | "full" | "state-preserve";
1122
- debounceMs: number;
1123
- preserveState: boolean;
1124
- watchPatterns?: string[] | undefined;
1125
- ignorePatterns?: string[] | undefined;
1126
- stateSerialization?: {
1127
- enabled: boolean;
1128
- handler?: string | undefined;
1129
- } | undefined;
1130
- hooks?: {
1131
- beforeReload?: string | undefined;
1132
- afterReload?: string | undefined;
1133
- onError?: string | undefined;
1134
- } | undefined;
1135
- productionSafety?: {
1136
- healthValidation: boolean;
1137
- rollbackOnFailure: boolean;
1138
- healthTimeout: number;
1139
- drainConnections: boolean;
1140
- drainTimeout: number;
1141
- maxConcurrentReloads: number;
1142
- minReloadInterval: number;
1143
- } | undefined;
1144
- } | undefined;
1145
- caching?: {
1146
- enabled: boolean;
1147
- storage: "hybrid" | "indexeddb" | "memory" | "disk";
1148
- keyStrategy: "hash" | "version" | "timestamp";
1149
- ttl?: number | undefined;
1150
- maxSize?: number | undefined;
1151
- invalidateOn?: ("error" | "manual" | "version-change" | "dependency-change")[] | undefined;
1152
- compression?: {
1153
- enabled: boolean;
1154
- algorithm: "gzip" | "brotli" | "deflate";
1155
- } | undefined;
1156
- } | undefined;
1157
- sandboxing?: {
1158
- enabled: boolean;
1159
- scope: "automation-only" | "untrusted-only" | "all-plugins";
1160
- isolationLevel: "none" | "process" | "vm" | "iframe" | "web-worker";
1161
- allowedCapabilities?: string[] | undefined;
1162
- resourceQuotas?: {
1163
- maxMemoryMB?: number | undefined;
1164
- maxCpuTimeMs?: number | undefined;
1165
- maxFileDescriptors?: number | undefined;
1166
- maxNetworkKBps?: number | undefined;
1167
- } | undefined;
1168
- permissions?: {
1169
- allowedAPIs?: string[] | undefined;
1170
- allowedPaths?: string[] | undefined;
1171
- allowedEndpoints?: string[] | undefined;
1172
- allowedEnvVars?: string[] | undefined;
1173
- } | undefined;
1174
- ipc?: {
1175
- enabled: boolean;
1176
- transport: "memory" | "message-port" | "unix-socket" | "tcp";
1177
- maxMessageSize: number;
1178
- timeout: number;
1179
- allowedServices?: string[] | undefined;
1180
- } | undefined;
1181
- } | undefined;
1182
- monitoring?: {
1183
- enabled: boolean;
1184
- samplingRate: number;
1185
- reportingInterval: number;
1186
- onBudgetViolation: "warn" | "error" | "ignore";
1187
- metrics?: ("load-time" | "init-time" | "memory-usage" | "cpu-usage" | "api-calls" | "error-rate" | "cache-hit-rate")[] | undefined;
1188
- budgets?: {
1189
- maxLoadTimeMs?: number | undefined;
1190
- maxInitTimeMs?: number | undefined;
1191
- maxMemoryMB?: number | undefined;
1192
- } | undefined;
1193
- } | undefined;
1194
- } | undefined;
1195
- engine?: {
1196
- objectstack: string;
1197
- } | undefined;
1198
- };
211
+ validate(type: string, item: any): unknown;
1199
212
  /**
1200
213
  * Universal Unregister Method
1201
214
  */
1202
- static unregisterItem(type: string, name: string): void;
215
+ unregisterItem(type: string, name: string): void;
1203
216
  /**
1204
217
  * Universal Get Method
1205
218
  */
1206
- static getItem<T>(type: string, name: string): T | undefined;
219
+ getItem<T>(type: string, name: string): T | undefined;
1207
220
  /**
1208
221
  * Universal List Method
1209
222
  */
1210
- static listItems<T>(type: string, packageId?: string): T[];
223
+ listItems<T>(type: string, packageId?: string): T[];
1211
224
  /**
1212
225
  * Get all registered metadata types (Kinds)
1213
226
  */
1214
- static getRegisteredTypes(): string[];
1215
- static installPackage(manifest: ObjectStackManifest, settings?: Record<string, any>): InstalledPackage;
1216
- static uninstallPackage(id: string): boolean;
1217
- static getPackage(id: string): InstalledPackage | undefined;
1218
- static getAllPackages(): InstalledPackage[];
1219
- static enablePackage(id: string): InstalledPackage | undefined;
1220
- static disablePackage(id: string): InstalledPackage | undefined;
1221
- static registerApp(app: any, packageId?: string): void;
1222
- static getApp(name: string): any;
1223
- static getAllApps(): any[];
1224
- static registerPlugin(manifest: ObjectStackManifest): void;
1225
- static getAllPlugins(): ObjectStackManifest[];
1226
- static registerKind(kind: {
227
+ getRegisteredTypes(): string[];
228
+ installPackage(manifest: ObjectStackManifest, settings?: Record<string, any>): InstalledPackage;
229
+ uninstallPackage(id: string): boolean;
230
+ getPackage(id: string): InstalledPackage | undefined;
231
+ getAllPackages(): InstalledPackage[];
232
+ enablePackage(id: string): InstalledPackage | undefined;
233
+ disablePackage(id: string): InstalledPackage | undefined;
234
+ registerApp(app: any, packageId?: string): void;
235
+ getApp(name: string): any;
236
+ getAllApps(): any[];
237
+ registerPlugin(manifest: ObjectStackManifest): void;
238
+ getAllPlugins(): ObjectStackManifest[];
239
+ registerKind(kind: {
1227
240
  id: string;
1228
241
  globs: string[];
1229
242
  }): void;
1230
- static getAllKinds(): {
243
+ getAllKinds(): {
1231
244
  id: string;
1232
245
  globs: string[];
1233
246
  }[];
1234
247
  /**
1235
248
  * Clear all registry state. Use only for testing.
1236
249
  */
1237
- static reset(): void;
250
+ reset(): void;
1238
251
  }
1239
252
 
1240
253
  declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1241
254
  private engine;
1242
255
  private getServicesRegistry?;
1243
256
  private getFeedService?;
1244
- constructor(engine: IDataEngine, getServicesRegistry?: () => Map<string, any>, getFeedService?: () => IFeedService | undefined);
257
+ /**
258
+ * Project scope applied to sys_metadata reads/writes. When undefined
259
+ * (single-kernel deployments), rows land in / come from the
260
+ * platform-global bucket (`project_id IS NULL`). When set, every
261
+ * saveMetaItem insert/update and loadMetaFromDb query is filtered by
262
+ * `project_id = projectId`, so per-project kernels see only their own
263
+ * metadata even if several projects share the same physical database.
264
+ */
265
+ private projectId?;
266
+ constructor(engine: IDataEngine, getServicesRegistry?: () => Map<string, any>, getFeedService?: () => IFeedService | undefined, projectId?: string);
267
+ /**
268
+ * One-time guard for ensuring the overlay-uniqueness UNIQUE INDEX exists
269
+ * on `sys_metadata`. ADR-0005: scopes overlays by
270
+ * `(type, name, organization_id, project_id, scope)` for active rows only.
271
+ * Idempotent SQL — safe to attempt on every protocol instance.
272
+ *
273
+ * Inlined here (rather than importing from @objectstack/metadata/migrations)
274
+ * to avoid a circular dependency: metadata already depends on objectql.
275
+ */
276
+ private overlayIndexEnsured;
277
+ private ensureOverlayIndex;
278
+ /**
279
+ * Exposes the project scope the protocol is bound to. Consumers like
280
+ * the HTTP dispatcher use this to decide whether to trust the process-
281
+ * wide SchemaRegistry or whether they must route a read through the
282
+ * protocol's project_id-filtered lookup.
283
+ */
284
+ getProjectId(): string | undefined;
1245
285
  private requireFeedService;
1246
286
  getDiscovery(): Promise<{
1247
287
  version: string;
@@ -1266,7 +306,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1266
306
  };
1267
307
  services: Record<string, {
1268
308
  enabled: boolean;
1269
- status: "degraded" | "stub" | "available" | "registered" | "unavailable";
309
+ status: "available" | "registered" | "unavailable" | "degraded" | "stub";
1270
310
  handlerReady?: boolean | undefined;
1271
311
  route?: string | undefined;
1272
312
  provider?: string | undefined;
@@ -1290,6 +330,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1290
330
  getMetaItems(request: {
1291
331
  type: string;
1292
332
  packageId?: string;
333
+ organizationId?: string;
1293
334
  }): Promise<{
1294
335
  type: string;
1295
336
  items: unknown[];
@@ -1298,6 +339,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1298
339
  type: string;
1299
340
  name: string;
1300
341
  packageId?: string;
342
+ organizationId?: string;
1301
343
  }): Promise<{
1302
344
  type: string;
1303
345
  name: string;
@@ -1335,7 +377,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1335
377
  label: string | undefined;
1336
378
  required: boolean;
1337
379
  readonly: boolean;
1338
- type: "number" | "boolean" | "tags" | "date" | "lookup" | "file" | "url" | "json" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "datetime" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "code" | "color" | "rating" | "slider" | "signature" | "qrcode" | "progress" | "vector";
380
+ type: "number" | "boolean" | "tags" | "date" | "file" | "code" | "datetime" | "signature" | "progress" | "url" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "json" | "color" | "rating" | "slider" | "qrcode" | "vector";
1339
381
  colSpan: number;
1340
382
  }[];
1341
383
  }[];
@@ -1345,6 +387,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1345
387
  findData(request: {
1346
388
  object: string;
1347
389
  query?: any;
390
+ context?: any;
1348
391
  }): Promise<{
1349
392
  object: string;
1350
393
  records: any[];
@@ -1356,6 +399,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1356
399
  id: string;
1357
400
  expand?: string | string[];
1358
401
  select?: string | string[];
402
+ context?: any;
1359
403
  }): Promise<{
1360
404
  object: string;
1361
405
  id: string;
@@ -1364,6 +408,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1364
408
  createData(request: {
1365
409
  object: string;
1366
410
  data: any;
411
+ context?: any;
1367
412
  }): Promise<{
1368
413
  object: string;
1369
414
  id: any;
@@ -1373,6 +418,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1373
418
  object: string;
1374
419
  id: string;
1375
420
  data: any;
421
+ context?: any;
1376
422
  }): Promise<{
1377
423
  object: string;
1378
424
  id: string;
@@ -1381,11 +427,86 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1381
427
  deleteData(request: {
1382
428
  object: string;
1383
429
  id: string;
430
+ context?: any;
1384
431
  }): Promise<{
1385
432
  object: string;
1386
433
  id: string;
1387
434
  success: boolean;
1388
435
  }>;
436
+ /**
437
+ * Cross-object substring search across all registered objects that opt in
438
+ * via `enable.searchable !== false` and `enable.apiEnabled !== false`.
439
+ * Searches text-like fields (text/textarea/email/url/phone/markdown/html/string)
440
+ * whose `searchable: true` flag is set, falling back to the object's
441
+ * `displayNameField` (or `name`) when no fields are explicitly searchable.
442
+ *
443
+ * The query is split into whitespace-separated terms; each term must match
444
+ * (case-insensitive LIKE) at least one searchable field. RBAC/RLS is
445
+ * enforced by forwarding the caller's `context` to `engine.find` so users
446
+ * only see records they are entitled to read.
447
+ */
448
+ searchAll(request: {
449
+ q: string;
450
+ objects?: string[];
451
+ limit?: number;
452
+ perObject?: number;
453
+ context?: any;
454
+ }): Promise<{
455
+ query: string;
456
+ hits: Array<{
457
+ object: string;
458
+ id: string;
459
+ title: string;
460
+ snippet?: string;
461
+ record: any;
462
+ }>;
463
+ totalObjects: number;
464
+ totalHits: number;
465
+ truncated: boolean;
466
+ }>;
467
+ /**
468
+ * Convert a qualified Lead into an Account + Contact (+ optional
469
+ * Opportunity) and mark the Lead as converted. Mirrors the Salesforce
470
+ * lead-conversion model:
471
+ *
472
+ * - If `accountId` is provided, the lead's company info is NOT used
473
+ * to create a new account; the new contact and opportunity link to
474
+ * the existing account instead.
475
+ * - If `contactId` is provided, no new contact is created either —
476
+ * useful when the lead is a new contact at an existing account.
477
+ * - `createOpportunity` defaults to true; pass `false` to convert
478
+ * without producing an opportunity (some teams convert "logos
479
+ * only" first).
480
+ * - Lead is updated atomically: `is_converted=true`,
481
+ * `converted_account`/`converted_contact`/`converted_opportunity`
482
+ * pointers, `converted_date`, and `status='converted'`.
483
+ *
484
+ * Atomicity is enforced via the default driver's transaction support
485
+ * when available; otherwise a best-effort compensation (delete
486
+ * already-created child records on failure) is attempted. Permission
487
+ * checks on each child object are inherited from the caller's
488
+ * execution context so SecurityPlugin still gates account/contact/
489
+ * opportunity creates.
490
+ */
491
+ convertLead(request: {
492
+ leadId: string;
493
+ accountId?: string;
494
+ contactId?: string;
495
+ createOpportunity?: boolean;
496
+ opportunity?: {
497
+ name?: string;
498
+ amount?: number;
499
+ close_date?: string;
500
+ stage?: string;
501
+ };
502
+ convertedStatus?: string;
503
+ context?: any;
504
+ }): Promise<{
505
+ lead: any;
506
+ account: any;
507
+ contact: any;
508
+ opportunity: any | null;
509
+ }>;
1389
510
  getMetaItemCached(request: {
1390
511
  type: string;
1391
512
  name: string;
@@ -1405,23 +526,51 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1405
526
  private mapAnalyticsOperator;
1406
527
  triggerAutomation(_request: any): Promise<any>;
1407
528
  deleteManyData(request: DeleteManyDataRequest): Promise<any>;
529
+ /**
530
+ * Metadata types that are customer-overridable via {@link saveMetaItem}/
531
+ * {@link deleteMetaItem} in project-kernel mode. Derived from the canonical
532
+ * registry in {@link DEFAULT_METADATA_TYPE_REGISTRY}: a type opts in by
533
+ * setting `allowOrgOverride: true` on its registry entry. The set is
534
+ * augmented with the plural form of every singular so callers using REST
535
+ * conventions (`/api/v1/meta/views/...`) get the same gate. See ADR-0005
536
+ * §"Whitelist enforcement" for the rationale and the per-type rollout
537
+ * checklist.
538
+ */
539
+ private static readonly OVERLAY_ALLOWED_TYPES;
540
+ /** Normalize plural→singular before consulting the allow-list. */
541
+ private static isOverlayAllowed;
1408
542
  saveMetaItem(request: {
1409
543
  type: string;
1410
544
  name: string;
1411
545
  item?: any;
546
+ organizationId?: string;
1412
547
  }): Promise<{
1413
548
  success: boolean;
1414
549
  message: string;
1415
- warning?: undefined;
1416
- } | {
550
+ }>;
551
+ /**
552
+ * Remove a customization overlay row for the given metadata item, so the
553
+ * next read falls through to the artifact-loaded default. Implements the
554
+ * "Reset to factory default" semantic from ADR-0005. Whitelist is shared
555
+ * with {@link saveMetaItem}.
556
+ */
557
+ deleteMetaItem(request: {
558
+ type: string;
559
+ name: string;
560
+ organizationId?: string;
561
+ }): Promise<{
1417
562
  success: boolean;
1418
- message: string;
1419
- warning: any;
563
+ message?: string;
564
+ reset?: boolean;
1420
565
  }>;
1421
566
  /**
1422
567
  * Hydrate SchemaRegistry from the database on startup.
1423
568
  * Loads all active metadata records and registers them in the in-memory registry.
1424
569
  * Safe to call repeatedly — idempotent (latest DB record wins).
570
+ *
571
+ * Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
572
+ * customization overlay rows must survive restart. Scope filter
573
+ * (`project_id = this.projectId ?? null`) keeps tenants isolated.
1425
574
  */
1426
575
  loadMetaFromDb(): Promise<{
1427
576
  loaded: number;
@@ -1452,6 +601,14 @@ interface HookEntry {
1452
601
  object?: string | string[];
1453
602
  priority: number;
1454
603
  packageId?: string;
604
+ /**
605
+ * Original metadata-form `Hook` definition this entry was bound from
606
+ * (when registered via `bindHooksToEngine`). Pure code-paths that call
607
+ * `engine.registerHook` directly leave this undefined.
608
+ */
609
+ meta?: any;
610
+ /** Hook `name` from metadata; used for diagnostics & deduplication. */
611
+ hookName?: string;
1455
612
  }
1456
613
  /**
1457
614
  * Operation Context for Middleware Chain
@@ -1494,8 +651,10 @@ declare class ObjectQL implements IDataEngine {
1494
651
  private hooks;
1495
652
  private middlewares;
1496
653
  private actions;
654
+ private functions;
1497
655
  private hostContext;
1498
656
  private realtimeService?;
657
+ private _registry;
1499
658
  constructor(hostContext?: Record<string, any>);
1500
659
  /**
1501
660
  * Service Status Report
@@ -1508,9 +667,12 @@ declare class ObjectQL implements IDataEngine {
1508
667
  features: string[];
1509
668
  };
1510
669
  /**
1511
- * Expose the SchemaRegistry for plugins to register metadata
670
+ * Expose the SchemaRegistry for plugins to register metadata.
671
+ *
672
+ * Returns the per-engine instance, NOT the class. Each ObjectQL engine
673
+ * owns its registry so multi-project kernels remain isolated.
1512
674
  */
1513
- get registry(): typeof SchemaRegistry;
675
+ get registry(): SchemaRegistry;
1514
676
  /**
1515
677
  * Load and Register a Plugin
1516
678
  */
@@ -1525,7 +687,67 @@ declare class ObjectQL implements IDataEngine {
1525
687
  object?: string | string[];
1526
688
  priority?: number;
1527
689
  packageId?: string;
690
+ /** Original metadata Hook definition (set by `bindHooksToEngine`). */
691
+ meta?: any;
692
+ /** Stable name from metadata (set by `bindHooksToEngine`). */
693
+ hookName?: string;
694
+ }): void;
695
+ /**
696
+ * Remove all hooks registered under a given `packageId`. Used by
697
+ * `bindHooksToEngine` to make re-binding (hot reload, app reinstall)
698
+ * idempotent, and by app uninstall flows.
699
+ */
700
+ unregisterHooksByPackage(packageId: string): number;
701
+ /**
702
+ * Register a named function handler that can later be referenced by
703
+ * string from a `Hook.handler` field. This is the JSON-safe form of
704
+ * handler binding — declarative metadata persisted to disk or shipped
705
+ * over the wire only carries the name.
706
+ */
707
+ registerFunction(name: string, handler: HookHandler, packageId?: string): void;
708
+ /** Look up a registered function by name. */
709
+ resolveFunction(name: string): HookHandler | undefined;
710
+ /** Remove all functions registered under a given `packageId`. */
711
+ unregisterFunctionsByPackage(packageId: string): number;
712
+ /**
713
+ * Bind a list of declarative `Hook` metadata definitions to this engine.
714
+ *
715
+ * Convenience proxy to the canonical `bindHooksToEngine` so callers do
716
+ * not need a separate import. Use `import { bindHooksToEngine } from
717
+ * '@objectstack/objectql'` directly when you want the result object.
718
+ */
719
+ bindHooks(hooks: any[] | undefined, opts?: {
720
+ packageId?: string;
721
+ functions?: Record<string, HookHandler>;
722
+ bodyRunner?: any;
723
+ strict?: boolean;
724
+ warnLegacyHandler?: boolean;
725
+ metrics?: any;
1528
726
  }): void;
727
+ /**
728
+ * Install a default body-runner used when `bindHooks` is called without
729
+ * an explicit one. The runtime layer sets this once on each per-project
730
+ * engine so every binding path (template seed, metadata sync, AppPlugin)
731
+ * can execute hook `body.source` consistently.
732
+ */
733
+ setDefaultBodyRunner(runner: any): void;
734
+ /**
735
+ * Toggle strict hook-binding mode for this engine. When enabled, every
736
+ * subsequent `bindHooks` call rejects on the first unresolved hook
737
+ * instead of silently warning. Production runtimes should enable this.
738
+ */
739
+ setStrictHookBinding(strict: boolean): void;
740
+ /** Toggle deprecation warnings for hooks still using legacy `handler` ref. */
741
+ setWarnLegacyHandler(warn: boolean): void;
742
+ /**
743
+ * Install a metrics recorder used by every subsequent `bindHooks` call.
744
+ * The recorder's methods are invoked per-execution to count outcomes
745
+ * (success / error / timeout / capability_rejected), skips, and retries.
746
+ * Defaults to no-op so the engine pays zero cost when nobody is observing.
747
+ */
748
+ setHookMetricsRecorder(recorder: any): void;
749
+ /** Read the engine's installed metrics recorder, if any. */
750
+ getHookMetricsRecorder(): any;
1529
751
  triggerHooks(event: string, context: HookContext): Promise<void>;
1530
752
  /**
1531
753
  * Register a named action on an object.
@@ -1562,6 +784,40 @@ declare class ObjectQL implements IDataEngine {
1562
784
  * Build a HookContext.session from ExecutionContext
1563
785
  */
1564
786
  private buildSession;
787
+ /**
788
+ * Build the DriverOptions blob passed to every IDataDriver call.
789
+ *
790
+ * Always carries `tenantId` from the active ExecutionContext so the
791
+ * driver can enforce per-tenant isolation (SQL driver auto-scopes reads
792
+ * and auto-injects the tenant column on writes). Existing user-supplied
793
+ * shapes (transactions, AST extras) are preserved by spreading them
794
+ * first.
795
+ *
796
+ * System / isSystem callers may still cross tenants by clearing
797
+ * `tenantId` themselves on the resulting object; this helper does not
798
+ * mask the system path.
799
+ */
800
+ private buildDriverOptions;
801
+ /**
802
+ * Build a HookContext.api: a ScopedContext that hooks can use to
803
+ * read/write other objects within the same execution context.
804
+ * Falls back to a system-elevated empty context when no execCtx
805
+ * is supplied (e.g. system-triggered hooks).
806
+ */
807
+ private buildHookApi;
808
+ /**
809
+ * Apply field defaults to an incoming insert payload. Defaults that are
810
+ * Expression envelopes (e.g. `{ dialect: 'cel', source: 'today()' }`,
811
+ * `{ dialect: 'cel', source: 'os.user.id' }`) are evaluated via
812
+ * `ExpressionEngine` against the calling user/org/now snapshot. Static
813
+ * defaults are applied verbatim. Records that already supplied a value for a
814
+ * field are left untouched.
815
+ *
816
+ * Implements ROADMAP §M9.9b — `defaultValue` accepts Expression so authors
817
+ * can replace "write a hook to default to today/current-user" with a
818
+ * declarative `defaultValue: cel\`today()\``.
819
+ */
820
+ private applyFieldDefaults;
1565
821
  /**
1566
822
  * Register contribution (Manifest)
1567
823
  *
@@ -1573,6 +829,12 @@ declare class ObjectQL implements IDataEngine {
1573
829
  * the manifest contains UI navigation definitions (AppSchema).
1574
830
  */
1575
831
  registerApp(manifest: any): void;
832
+ /**
833
+ * Deep-clone an app definition, resolving objectName references in navigation
834
+ * items via the registry. Object names are canonical identifiers — no FQN
835
+ * expansion is applied.
836
+ */
837
+ private resolveNavObjectNames;
1576
838
  /**
1577
839
  * Register a nested plugin's metadata (objects, actions, views, etc.)
1578
840
  *
@@ -1601,14 +863,11 @@ declare class ObjectQL implements IDataEngine {
1601
863
  */
1602
864
  getSchema(objectName: string): ServiceObject | undefined;
1603
865
  /**
1604
- * Resolve an object name to its Fully Qualified Name (FQN).
866
+ * Resolve any object identifier to the physical storage name used by drivers.
1605
867
  *
1606
- * Short names like 'task' are resolved to FQN like 'todo__task'
1607
- * via SchemaRegistry lookup. If no match is found, the name is
1608
- * returned as-is (for ad-hoc / unregistered objects).
1609
- *
1610
- * This ensures that all driver operations use a consistent key
1611
- * regardless of whether the caller uses the short name or FQN.
868
+ * Accepts the canonical short name (e.g., 'account') or, for explicit
869
+ * cross-package disambiguation, the canonical object name (e.g., 'account'). The result is
870
+ * the physical table name derived via `StorageNameMapping.resolveTableName`.
1612
871
  */
1613
872
  private resolveObjectName;
1614
873
  /**
@@ -1672,7 +931,40 @@ declare class ObjectQL implements IDataEngine {
1672
931
  delete(object: string, options?: EngineDeleteOptions): Promise<any>;
1673
932
  count(object: string, query?: EngineCountOptions): Promise<number>;
1674
933
  aggregate(object: string, query: EngineAggregateOptions): Promise<any[]>;
934
+ /**
935
+ * Run raw driver-specific commands (SQL for SqlDriver, REST for RestDriver, …).
936
+ *
937
+ * ⚠️ **Tenant isolation bypass.** Raw `execute()` does NOT thread the
938
+ * caller's `ExecutionContext.tenantId` into a `WHERE organization_id`
939
+ * predicate — drivers see the command verbatim. Callers MUST inline the
940
+ * tenant filter themselves, or restrict raw execution to genuinely global
941
+ * statements (schema migrations, sys_* / control-plane tables).
942
+ *
943
+ * Prefer the typed entry points (`find`, `update`, `delete`, `count`, …)
944
+ * whenever feasible — they auto-apply tenancy + soft-delete + audit warnings.
945
+ */
1675
946
  execute(command: any, options?: Record<string, any>): Promise<any>;
947
+ /**
948
+ * Execute a callback inside a database transaction.
949
+ *
950
+ * The callback receives a context object that should be passed to all
951
+ * downstream `engine.insert/update/delete/find/findOne` calls (as
952
+ * `{ context: trxCtx }`). The transaction handle threads through
953
+ * `OperationContext.context.transaction` and the SQL driver's per-builder
954
+ * `.transacting(trx)` call.
955
+ *
956
+ * - If the default driver does not support `beginTransaction`, the callback
957
+ * runs directly with the supplied base context (no rollback). This keeps
958
+ * the API safe to call on drivers without ACID support (e.g. the
959
+ * in-memory driver in tests).
960
+ * - On callback success the transaction is committed; on any thrown error
961
+ * it is rolled back and the original error is re-thrown.
962
+ *
963
+ * Use case: multi-step operations that must be atomic (e.g. CRM
964
+ * `convertLead`, which creates an account + contact + opportunity + flips
965
+ * the lead in a single unit of work).
966
+ */
967
+ transaction<T>(callback: (trxCtx: any) => Promise<T>, baseContext?: any): Promise<T>;
1676
968
  /**
1677
969
  * Register a single object definition.
1678
970
  *
@@ -1712,6 +1004,13 @@ declare class ObjectQL implements IDataEngine {
1712
1004
  * @returns The resolved DriverInterface, or undefined if no driver is available.
1713
1005
  */
1714
1006
  getDriverForObject(objectName: string): DriverInterface | undefined;
1007
+ /**
1008
+ * Sync all registered object schemas to their respective drivers.
1009
+ * Call this after dynamically registering new objects at runtime
1010
+ * (e.g. after template seeding) to ensure tables/collections exist
1011
+ * before inserting seed data.
1012
+ */
1013
+ syncSchemas(): Promise<void>;
1715
1014
  /**
1716
1015
  * Get a registered driver by datasource name.
1717
1016
  * Alias matching @objectql/core datasource() API.
@@ -1834,6 +1133,249 @@ declare class ScopedContext {
1834
1133
  get transactionHandle(): unknown;
1835
1134
  }
1836
1135
 
1136
+ /**
1137
+ * Group + aggregate raw rows according to the AST's `groupBy` /
1138
+ * `aggregations`. When neither is present, returns the rows unchanged.
1139
+ */
1140
+ declare function applyInMemoryAggregation(rows: any[], ast: Pick<QueryAST, 'groupBy' | 'aggregations'>): any[];
1141
+ /**
1142
+ * Bucket a date-like value into an ISO-formatted period label. Weeks start
1143
+ * Monday and use ISO week numbering.
1144
+ */
1145
+ declare function bucketDateValue(value: unknown, granularity: DateGranularityValue): string;
1146
+
1147
+ /**
1148
+ * Hook Execution Metrics
1149
+ *
1150
+ * Lightweight, transport-agnostic recorder interface for per-hook execution
1151
+ * counters and latencies. The default implementation is a no-op so the
1152
+ * engine pays zero cost when nobody is observing.
1153
+ *
1154
+ * Wire a real recorder by calling `engine.setHookMetricsRecorder(recorder)`.
1155
+ * The runtime / kernel can adapt this to Otel, Prometheus, StatsD, or
1156
+ * whatever telemetry pipeline ships with the deployment.
1157
+ *
1158
+ * Recorded events:
1159
+ * - `recordExecution(label, outcome, durationMs)`
1160
+ * outcome ∈ 'success' | 'error' | 'timeout' | 'capability_rejected'
1161
+ * - `recordSkip(label, reason)`
1162
+ * reason ∈ 'condition' | 'fire_and_forget'
1163
+ * - `recordRetry(label, attempt)`
1164
+ */
1165
+ type HookMetricOutcome = 'success' | 'error' | 'timeout' | 'capability_rejected';
1166
+ type HookSkipReason = 'condition' | 'fire_and_forget';
1167
+ interface HookMetricLabel {
1168
+ /** Hook name (stable id from metadata). */
1169
+ hook: string;
1170
+ /** Object name the hook is bound to. May be undefined for global hooks. */
1171
+ object?: string;
1172
+ /** Lifecycle event (`beforeInsert`, `afterUpdate`, etc.). */
1173
+ event?: string;
1174
+ /** True when the handler comes from a metadata `body` (sandboxed JS). */
1175
+ body?: boolean;
1176
+ }
1177
+ interface HookMetricsRecorder {
1178
+ recordExecution(label: HookMetricLabel, outcome: HookMetricOutcome, durationMs: number): void;
1179
+ recordSkip(label: HookMetricLabel, reason: HookSkipReason): void;
1180
+ recordRetry(label: HookMetricLabel, attempt: number): void;
1181
+ }
1182
+ declare const noopHookMetricsRecorder: HookMetricsRecorder;
1183
+ /**
1184
+ * In-memory recorder useful for tests, dev-mode dashboards, and as a
1185
+ * starting point for adapter implementations. Aggregates counts + a
1186
+ * rolling sum of latency per (hook, outcome).
1187
+ */
1188
+ declare class InMemoryHookMetricsRecorder implements HookMetricsRecorder {
1189
+ private executions;
1190
+ private skips;
1191
+ private retries;
1192
+ recordExecution(label: HookMetricLabel, outcome: HookMetricOutcome, durationMs: number): void;
1193
+ recordSkip(label: HookMetricLabel, reason: HookSkipReason): void;
1194
+ recordRetry(label: HookMetricLabel, _attempt: number): void;
1195
+ snapshot(): {
1196
+ executions: Array<{
1197
+ hook: string;
1198
+ outcome: HookMetricOutcome;
1199
+ count: number;
1200
+ totalMs: number;
1201
+ }>;
1202
+ skips: Array<{
1203
+ hook: string;
1204
+ reason: HookSkipReason;
1205
+ count: number;
1206
+ }>;
1207
+ retries: Array<{
1208
+ hook: string;
1209
+ count: number;
1210
+ }>;
1211
+ };
1212
+ reset(): void;
1213
+ }
1214
+
1215
+ /**
1216
+ * Hook Binder
1217
+ *
1218
+ * Single, canonical entry point that turns declarative `Hook` metadata into
1219
+ * runtime registrations on the `ObjectQL` engine. Every metadata source —
1220
+ * `defineStack({ hooks })` (consumed by `AppPlugin`), the per-project
1221
+ * template seeder (`MultiProjectPlugin`), and the metadata service
1222
+ * (`ObjectQLPlugin.loadMetadataFromService`) — funnels through here so
1223
+ * that:
1224
+ *
1225
+ * - Inline function handlers and string-named handlers share one resolver.
1226
+ * - Declarative fields (`condition`, `async`, `retryPolicy`, `timeout`,
1227
+ * `onError`) are honoured uniformly via `wrapDeclarativeHook`.
1228
+ * - Hooks can be unregistered as a unit via `packageId`, enabling clean
1229
+ * hot-reload and app uninstall.
1230
+ *
1231
+ * The ObjectQL engine itself stays simple — it knows how to store and
1232
+ * trigger handlers, but knows nothing about declarative semantics. All
1233
+ * metadata-aware behaviour lives in this binder + the wrapper module.
1234
+ */
1235
+
1236
+ interface BindHooksOptions {
1237
+ /** Owning package / app id — used for `unregisterHooksByPackage`. */
1238
+ packageId?: string;
1239
+ /**
1240
+ * Optional name → function map for resolving string `handler` references.
1241
+ * Typically supplied by `defineStack({ functions })` and merged with any
1242
+ * functions previously registered on the engine.
1243
+ */
1244
+ functions?: Record<string, HookHandler>;
1245
+ /**
1246
+ * Optional factory that converts a metadata-only `Hook.body` (L1 expression
1247
+ * or L2 sandboxed JS source) into an executable `HookHandler`. The runtime
1248
+ * package wires this up using `QuickJSScriptRunner`; objectql itself stays
1249
+ * sandbox-free so it can run in lightweight environments.
1250
+ *
1251
+ * If `hook.body` is set and this factory is missing, the hook is skipped
1252
+ * with a clear error.
1253
+ */
1254
+ bodyRunner?: (hook: Hook) => HookHandler | undefined;
1255
+ /**
1256
+ * When true, treat unresolved hooks (body present but no runner, or handler
1257
+ * string with no implementation) as fatal errors instead of warnings. Used
1258
+ * by production runtimes to fail fast on misconfiguration. Defaults false.
1259
+ */
1260
+ strict?: boolean;
1261
+ /**
1262
+ * When true, emit a deprecation warning for every hook that still relies
1263
+ * on a `handler` ref string instead of the metadata-only `body`. Used by
1264
+ * the CLI (compile time) and runtime (boot time) to nudge users away from
1265
+ * the legacy `.mjs` runtime bundle path. Defaults false.
1266
+ */
1267
+ warnLegacyHandler?: boolean;
1268
+ /** Per-hook execution metrics sink. Defaults to no-op. */
1269
+ metrics?: HookMetricsRecorder;
1270
+ /** Logger; defaults to a silent no-op. */
1271
+ logger?: {
1272
+ debug: (msg: string, meta?: any) => void;
1273
+ info: (msg: string, meta?: any) => void;
1274
+ warn: (msg: string, meta?: any) => void;
1275
+ error: (msg: string, meta?: any) => void;
1276
+ };
1277
+ }
1278
+ /** Counter for stats. */
1279
+ interface BindHooksResult {
1280
+ registered: number;
1281
+ skipped: number;
1282
+ errors: Array<{
1283
+ hook: string;
1284
+ reason: string;
1285
+ }>;
1286
+ }
1287
+ /**
1288
+ * Bind a list of declarative `Hook` definitions to a running ObjectQL engine.
1289
+ *
1290
+ * Idempotent on `(packageId, hook.name, event, object)`: re-binding the
1291
+ * same set after a hot reload first calls `unregisterHooksByPackage`
1292
+ * (when `packageId` is provided).
1293
+ */
1294
+ declare function bindHooksToEngine(engine: ObjectQL, hooks: Hook[] | undefined, opts?: BindHooksOptions): BindHooksResult;
1295
+
1296
+ /**
1297
+ * Declarative Hook Wrappers
1298
+ *
1299
+ * Turns a raw `HookHandler` into one that honours the declarative metadata
1300
+ * fields defined on `HookSchema` (`condition`, `async`, `retryPolicy`,
1301
+ * `timeout`, `onError`). This lives outside the engine's `triggerHooks`
1302
+ * loop so the engine stays minimal and the semantics are unit-testable in
1303
+ * isolation.
1304
+ *
1305
+ * The resulting wrapped handler keeps the original `(ctx) => Promise<void>`
1306
+ * signature, so `engine.registerHook` does not need to know anything about
1307
+ * the metadata-driven behaviours.
1308
+ */
1309
+
1310
+ interface WrapDeclarativeOptions {
1311
+ /** Logger for declarative-layer diagnostics (timeouts, retries, swallowed errors). */
1312
+ logger?: {
1313
+ debug: (msg: string, meta?: any) => void;
1314
+ info: (msg: string, meta?: any) => void;
1315
+ warn: (msg: string, meta?: any) => void;
1316
+ error: (msg: string, meta?: any) => void;
1317
+ };
1318
+ /** Optional per-execution metrics sink. Defaults to no-op. */
1319
+ metrics?: HookMetricsRecorder;
1320
+ }
1321
+ /**
1322
+ * Wrap a hook handler so it honours the declarative fields defined on
1323
+ * `HookSchema`. The wrapping order, from outermost to innermost, is:
1324
+ *
1325
+ * 1. condition → skip when formula evaluates falsy
1326
+ * 2. async → fire-and-forget (after* events only)
1327
+ * 3. retry → repeat on throw with backoff
1328
+ * 4. timeout → abort if handler runs too long
1329
+ * 5. onError → swallow when set to 'log'
1330
+ *
1331
+ * The condition formula is evaluated against the most useful record-shaped
1332
+ * payload available on the context (write payloads first, then `previous`,
1333
+ * then a flat merge of input). Read events typically have no record yet,
1334
+ * so a condition on a `beforeFind` will simply skip when no data is
1335
+ * present.
1336
+ */
1337
+ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: WrapDeclarativeOptions): HookHandler;
1338
+
1339
+ interface FieldValidationError {
1340
+ field: string;
1341
+ code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option';
1342
+ message: string;
1343
+ /** Allowed values for select/multiselect, when applicable. */
1344
+ options?: string[];
1345
+ }
1346
+ declare class ValidationError extends Error {
1347
+ readonly code = "VALIDATION_FAILED";
1348
+ readonly fields: FieldValidationError[];
1349
+ constructor(fields: FieldValidationError[]);
1350
+ }
1351
+ type Mode = 'insert' | 'update';
1352
+ interface FieldDef {
1353
+ name?: string;
1354
+ type: string;
1355
+ required?: boolean;
1356
+ readonly?: boolean;
1357
+ system?: boolean;
1358
+ multiple?: boolean;
1359
+ maxLength?: number;
1360
+ minLength?: number;
1361
+ min?: number;
1362
+ max?: number;
1363
+ options?: Array<{
1364
+ value: string | number;
1365
+ label?: string;
1366
+ } | string | number>;
1367
+ }
1368
+ /**
1369
+ * Validate a payload against a list of declared fields. `objectSchema`
1370
+ * comes from `ObjectQL.getRegistry().getObject(name)` and exposes a
1371
+ * `fields` map of `{ [fieldName]: FieldDef }`.
1372
+ *
1373
+ * Returns void on success; throws `ValidationError` on failure.
1374
+ */
1375
+ declare function validateRecord(objectSchema: {
1376
+ fields?: Record<string, FieldDef>;
1377
+ } | undefined | null, data: Record<string, unknown> | undefined | null, mode: Mode): void;
1378
+
1837
1379
  /**
1838
1380
  * MetadataFacade
1839
1381
  *
@@ -1843,8 +1385,14 @@ declare class ScopedContext {
1843
1385
  *
1844
1386
  * Implements the async IMetadataService interface.
1845
1387
  * Internally delegates to SchemaRegistry (in-memory) with Promise wrappers.
1388
+ *
1389
+ * Each facade is bound to a specific SchemaRegistry instance — passed in the
1390
+ * constructor — so that multi-kernel servers can give every kernel its own
1391
+ * metadata surface without leaking state across tenants.
1846
1392
  */
1847
1393
  declare class MetadataFacade {
1394
+ private registry;
1395
+ constructor(registry: SchemaRegistry);
1848
1396
  /**
1849
1397
  * Register a metadata item
1850
1398
  */
@@ -1887,25 +1435,86 @@ declare class MetadataFacade {
1887
1435
  listObjects(): Promise<any[]>;
1888
1436
  }
1889
1437
 
1438
+ /**
1439
+ * Options for ObjectQLPlugin.
1440
+ *
1441
+ * `projectId` scopes all metadata writes + reads to a specific project.
1442
+ * When set, `protocol.saveMetaItem` stamps `project_id = <projectId>` on
1443
+ * new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
1444
+ * column. Leave undefined in single-kernel / self-hosted mode — rows land
1445
+ * in the platform-global scope (project_id IS NULL).
1446
+ */
1447
+ interface ObjectQLPluginOptions {
1448
+ /** Optional pre-built engine. When absent, one is lazily created in init. */
1449
+ ql?: ObjectQL;
1450
+ /** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
1451
+ hostContext?: Record<string, any>;
1452
+ /** Scope sys_metadata reads/writes to this project. */
1453
+ projectId?: string;
1454
+ /**
1455
+ * Override the kernel's default plugin-start timeout for this plugin.
1456
+ * Defaults to 120000 (120s). Schema sync to a remote SQL backend
1457
+ * (Neon/Postgres/Turso) is latency-bound — the SQL driver currently
1458
+ * does NOT support `batchSchemaSync`, so it issues one round-trip per
1459
+ * registered object × twice (Phase 1 + Phase 3 in `start()`). On a
1460
+ * cold remote DB with N tables this can blow past the kernel's
1461
+ * default 30s easily, even though everything is healthy.
1462
+ */
1463
+ startupTimeout?: number;
1464
+ /**
1465
+ * Skip both `syncRegisteredSchemas()` calls inside `start()` and
1466
+ * assume DDL is managed out-of-band (e.g. an `apps/cloud/scripts/migrate.ts`
1467
+ * run before deploy that connects directly to the database and creates
1468
+ * all `sys_*` + custom tables once).
1469
+ *
1470
+ * Use this on cold-start-sensitive runtimes (Cloudflare Containers,
1471
+ * Lambda) where the platform's inbound-request budget is shorter than
1472
+ * a fresh remote-DB schema sync. The plugin still hydrates the
1473
+ * SchemaRegistry from `sys_metadata` (Phase 2), so custom user
1474
+ * objects come up — they just aren't re-DDL'd on every cold boot.
1475
+ *
1476
+ * Falls back to `process.env.OS_SKIP_SCHEMA_SYNC === '1'` when the
1477
+ * option is unset, so containers can flip it via their env without a
1478
+ * code change.
1479
+ */
1480
+ skipSchemaSync?: boolean;
1481
+ }
1890
1482
  declare class ObjectQLPlugin implements Plugin {
1891
1483
  name: string;
1892
1484
  type: string;
1893
1485
  version: string;
1486
+ /**
1487
+ * Schema sync to remote SQL DBs is latency-bound (one round-trip per
1488
+ * table × 2 phases). Default to 120s instead of the kernel's 30s so
1489
+ * cold Neon/Turso starts don't get killed mid-sync.
1490
+ */
1491
+ startupTimeout: number;
1894
1492
  private ql;
1895
1493
  private hostContext?;
1896
- constructor(ql?: ObjectQL, hostContext?: Record<string, any>);
1494
+ private projectId?;
1495
+ private skipSchemaSync;
1496
+ constructor(qlOrOptions?: ObjectQL | ObjectQLPluginOptions, hostContext?: Record<string, any>);
1897
1497
  init: (ctx: PluginContext) => Promise<void>;
1898
1498
  start: (ctx: PluginContext) => Promise<void>;
1899
1499
  /**
1900
1500
  * Register built-in audit hooks for auto-stamping created_by/updated_by
1901
- * and fetching previousData for update/delete operations.
1501
+ * and fetching previousData for update/delete operations. These are
1502
+ * declared as canonical `Hook` metadata and bound through the same
1503
+ * `bindHooksToEngine` path used by `defineStack({ hooks })`, so the
1504
+ * engine's built-ins flow through the same rails as user code
1505
+ * (dogfooding the protocol).
1902
1506
  */
1903
1507
  private registerAuditHooks;
1904
1508
  /**
1905
- * Register tenant isolation middleware that auto-injects tenant_id filter
1906
- * for multi-tenant operations.
1509
+ * Tenant isolation moved to `@objectstack/plugin-security`'s
1510
+ * `member_default` permission set RLS
1511
+ * (`organization_id = current_user.organization_id`, with
1512
+ * field-existence guards). The legacy `registerTenantMiddleware`
1513
+ * method was removed because it (a) collided with SecurityPlugin's
1514
+ * RLS pipeline and (b) blindly filtered tables that don't have a
1515
+ * `tenant_id` column (e.g. `sys_organization`), returning 0 rows
1516
+ * instead of all rows.
1907
1517
  */
1908
- private registerTenantMiddleware;
1909
1518
  /**
1910
1519
  * Synchronize all registered object schemas to the database.
1911
1520
  *
@@ -2067,4 +1676,4 @@ declare function convertIntrospectedSchemaToObjects(introspectedSchema: Introspe
2067
1676
  skipSystemColumns?: boolean;
2068
1677
  }): ServiceObject[];
2069
1678
 
2070
- export { DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type HookEntry, type HookHandler, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, ScopedContext, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, parseFQN, toTitleCase };
1679
+ export { type BindHooksOptions, type BindHooksResult, DEFAULT_EXTENDER_PRIORITY, DEFAULT_OWNER_PRIORITY, type EngineMiddleware, type FieldValidationError, type HookEntry, type HookHandler, type HookMetricLabel, type HookMetricOutcome, type HookMetricsRecorder, type HookSkipReason, InMemoryHookMetricsRecorder, type IntrospectedColumn, type IntrospectedForeignKey, type IntrospectedSchema, type IntrospectedTable, MetadataFacade, type ObjectContributor, ObjectQL, type ObjectQLHostContext, type ObjectQLKernelOptions, ObjectQLPlugin, ObjectRepository, ObjectStackProtocolImplementation, type OperationContext, RESERVED_NAMESPACES, SchemaRegistry, type SchemaRegistryOptions, ScopedContext, ValidationError, type WrapDeclarativeOptions, applyInMemoryAggregation, applySystemFields, bindHooksToEngine, bucketDateValue, computeFQN, convertIntrospectedSchemaToObjects, createObjectQLKernel, noopHookMetricsRecorder, parseFQN, toTitleCase, validateRecord, wrapDeclarativeHook };