@objectstack/rest 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.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { IHttpServer, RouteHandler, Plugin } from '@objectstack/core';
2
- import { Shared, System } from '@objectstack/spec';
2
+ import * as System from '@objectstack/spec/system';
3
+ import * as Shared from '@objectstack/spec/shared';
3
4
  import { z } from 'zod';
4
5
  import { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';
5
6
 
@@ -154,6 +155,17 @@ declare class RouteGroupBuilder {
154
155
  private resolvePath;
155
156
  }
156
157
 
158
+ /**
159
+ * Structural subset of `KernelManager` that RestServer needs in order to
160
+ * resolve a per-project protocol at request time. Typed locally to avoid
161
+ * an @objectstack/runtime → @objectstack/rest → @objectstack/runtime
162
+ * package cycle.
163
+ */
164
+ interface RestKernelManager {
165
+ getOrCreate(projectId: string): Promise<{
166
+ getServiceAsync<T = unknown>(name: string): Promise<T>;
167
+ }>;
168
+ }
157
169
  /**
158
170
  * RestServer
159
171
  *
@@ -181,11 +193,128 @@ declare class RouteGroupBuilder {
181
193
  *
182
194
  * restServer.registerRoutes();
183
195
  */
196
+ /**
197
+ * Minimal env registry shape consumed by the REST server for hostname →
198
+ * projectId resolution and `X-Project-Id` header validation on unscoped
199
+ * routes. Mirrors the surface of `EnvironmentDriverRegistry` defined in
200
+ * `@objectstack/service-cloud`.
201
+ */
202
+ interface RestEnvRegistry {
203
+ resolveByHostname(hostname: string): Promise<{
204
+ projectId: string;
205
+ } | null | undefined>;
206
+ /**
207
+ * Look up a project by id. Returns a truthy value (typically an
208
+ * `IDataDriver`) when the project exists and is bound, `null` when
209
+ * unknown. The REST server only uses the truthiness; it does not
210
+ * touch the driver itself (the actual driver is loaded later via
211
+ * `KernelManager.getOrCreate(projectId)`).
212
+ */
213
+ resolveById?(projectId: string): Promise<unknown | null>;
214
+ }
184
215
  declare class RestServer {
185
216
  private protocol;
186
217
  private config;
187
218
  private routeManager;
188
- constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig);
219
+ private kernelManager?;
220
+ private envRegistry?;
221
+ private defaultProjectIdProvider?;
222
+ private authServiceProvider?;
223
+ private objectQLProvider?;
224
+ private emailServiceProvider?;
225
+ private sharingServiceProvider?;
226
+ private reportsServiceProvider?;
227
+ private approvalsServiceProvider?;
228
+ private sharingRulesServiceProvider?;
229
+ constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig, kernelManager?: RestKernelManager, envRegistry?: RestEnvRegistry, defaultProjectIdProvider?: () => string | undefined, authServiceProvider?: (projectId?: string) => Promise<any | undefined>, objectQLProvider?: (projectId?: string) => Promise<any | undefined>, emailServiceProvider?: (projectId?: string) => Promise<any | undefined>, sharingServiceProvider?: (projectId?: string) => Promise<any | undefined>, reportsServiceProvider?: (projectId?: string) => Promise<any | undefined>, approvalsServiceProvider?: (projectId?: string) => Promise<any | undefined>, sharingRulesServiceProvider?: (projectId?: string) => Promise<any | undefined>);
230
+ /**
231
+ * Resolve the protocol for a given request. When `projectId` is present
232
+ * and a KernelManager is wired, fetch the per-project kernel's
233
+ * `protocol` service so metadata / data / UI reads hit the project's
234
+ * own registry and datastore.
235
+ *
236
+ * When `projectId` is absent on an unscoped route and an `envRegistry`
237
+ * is wired (runtime mode), the resolution chain is:
238
+ * 1. Hostname → projectId (`envRegistry.resolveByHostname`)
239
+ * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)
240
+ * 3. Default-project fallback (`defaultProjectIdProvider`, set by
241
+ * `createSingleProjectPlugin`)
242
+ * 4. Control-plane protocol captured at boot.
243
+ *
244
+ * Special case: `projectId === 'platform'` is a reserved virtual id used
245
+ * by Studio to address the control plane through the regular project
246
+ * URL shape (`/projects/platform/...`). It is NOT a row in the projects
247
+ * table, so we must never call `KernelManager.getOrCreate('platform')`.
248
+ * Instead, return the control-plane protocol directly. This lets Studio
249
+ * (and any other client) speak a single, uniform URL family without
250
+ * duplicating route logic for the platform surface.
251
+ */
252
+ private resolveProtocol;
253
+ /**
254
+ * Resolve the i18n service for the request's project (or control plane
255
+ * when no project id is in scope). Returns `undefined` when no service is
256
+ * registered, so callers can short-circuit and skip translation rather
257
+ * than failing.
258
+ *
259
+ * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the
260
+ * route → kernel-managed `i18n` service. Control-plane / unscoped
261
+ * requests intentionally return `undefined` because the platform kernel
262
+ * does not own per-app translation bundles.
263
+ */
264
+ private resolveI18nService;
265
+ /**
266
+ * Reject anonymous requests with HTTP 401 when `api.requireAuth` is set.
267
+ * Returns `true` if the response was sent and the caller should stop
268
+ * processing. Returns `false` to continue.
269
+ *
270
+ * The check is intentionally narrow: only `context?.userId` counts as
271
+ * "authenticated". `isSystem` flags are never set on inbound HTTP
272
+ * requests (they're internal-only), so they cannot bypass this gate.
273
+ */
274
+ private enforceAuth;
275
+ /**
276
+ * Resolve the request's execution context (RBAC/RLS/FLS) by looking up
277
+ * the better-auth session via the project's `auth` service. Returns
278
+ * `undefined` for anonymous requests so callers can pass `context` as-is
279
+ * to the protocol layer (the SecurityPlugin treats undefined as anon).
280
+ */
281
+ private resolveExecCtx;
282
+ /**
283
+ * Build a `TranslationBundle` (`Record<locale, TranslationData>`) from an
284
+ * `II18nService` instance. Returns `undefined` when no locales are
285
+ * registered so callers can avoid translation work.
286
+ */
287
+ private buildTranslationBundle;
288
+ /**
289
+ * Parse the highest-priority locale from an `Accept-Language` header.
290
+ * Falls back to a `?locale=` query parameter, then to the i18n service's
291
+ * default locale. Returns `undefined` when no preference is expressed
292
+ * (callers will then return untranslated metadata).
293
+ */
294
+ private extractLocale;
295
+ /**
296
+ * Translate a single metadata document (view or action) when an i18n
297
+ * service is registered for the request's project and the requested
298
+ * locale yields a match. Falls through unchanged for unsupported types
299
+ * or missing translations.
300
+ */
301
+ private translateMetaItem;
302
+ /**
303
+ * Translate a list of metadata documents using `translateMetaItem`.
304
+ */
305
+ private translateMetaItems;
306
+ /**
307
+ * Pull the request hostname (without port) from a Node-style `req` or
308
+ * a Fetch-style request wrapper. Returns undefined when no Host header
309
+ * is available.
310
+ */
311
+ private extractHostname;
312
+ /**
313
+ * Pull the `X-Project-Id` header from a Node- or Fetch-style request.
314
+ * Header names are case-insensitive; we probe both casings to cover
315
+ * adapters that don't normalize headers (e.g. raw Node http).
316
+ */
317
+ private extractProjectIdHeader;
189
318
  /**
190
319
  * Normalize configuration with defaults
191
320
  */
@@ -194,8 +323,19 @@ declare class RestServer {
194
323
  * Get the full API base path
195
324
  */
196
325
  private getApiBasePath;
326
+ /**
327
+ * Get the project-scoped base path for a given unscoped base.
328
+ * Example: `/api/v1` → `/api/v1/projects/:projectId`.
329
+ */
330
+ private getScopedBasePath;
197
331
  /**
198
332
  * Register all REST API routes
333
+ *
334
+ * When `enableProjectScoping` is true, routes are registered under
335
+ * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy
336
+ * controls whether unscoped legacy routes remain available:
337
+ * - `required` → only scoped routes registered.
338
+ * - `optional` / `auto` → both scoped and unscoped routes registered.
199
339
  */
200
340
  registerRoutes(): void;
201
341
  /**
@@ -214,6 +354,112 @@ declare class RestServer {
214
354
  * Register CRUD endpoints for data operations
215
355
  */
216
356
  private registerCrudEndpoints;
357
+ /**
358
+ * Register object-specific action endpoints that don't fit the
359
+ * generic CRUD shape. These are domain operations (Salesforce
360
+ * convertLead, etc.) where the protocol implementation does its own
361
+ * multi-record orchestration and we just need a thin HTTP route.
362
+ *
363
+ * POST {basePath}/data/lead/:id/convert — M10.6 lead conversion.
364
+ */
365
+ private registerDataActionEndpoints;
366
+ /**
367
+ * Register global cross-object search endpoint (M10.5).
368
+ * GET {basePath}/search?q=acme&objects=lead,account&limit=20&perObject=5
369
+ */
370
+ private registerSearchEndpoints;
371
+ /**
372
+ * Register email endpoints (M11.B1 / M10.7).
373
+ *
374
+ * POST {basePath}/email/send — send a transactional email via the
375
+ * `IEmailService` provider registered by EmailServicePlugin. Returns
376
+ * 501 when no provider is wired so deployments without email
377
+ * configured fail cleanly.
378
+ *
379
+ * Request body:
380
+ * {
381
+ * to: "a@b.com" | ["a@b.com", { name, address }],
382
+ * from?: ..., cc?: ..., bcc?: ..., replyTo?: ...,
383
+ * subject: string,
384
+ * text?: string, html?: string, // at least one required
385
+ * attachments?: [{ filename, content, contentType?, cid? }],
386
+ * headers?: { [name]: value },
387
+ * relatedObject?: string, relatedId?: string,
388
+ * }
389
+ */
390
+ private registerEmailEndpoints;
391
+ /**
392
+ * Register record-level sharing endpoints (M11.C17).
393
+ *
394
+ * Surfaces `ISharingService` over HTTP so the UI can list, create
395
+ * and revoke per-record grants without going through ObjectQL. The
396
+ * three routes mirror the share-management drawer in Salesforce /
397
+ * ServiceNow:
398
+ *
399
+ * GET {basePath}/data/:object/:id/shares
400
+ * POST {basePath}/data/:object/:id/shares
401
+ * DELETE {basePath}/data/:object/:id/shares/:shareId
402
+ *
403
+ * All three resolve via `sharingServiceProvider`; routes return 501
404
+ * when no sharing service is configured so a deployment without the
405
+ * `@objectstack/plugin-sharing` plugin fails cleanly.
406
+ */
407
+ private registerSharingEndpoints;
408
+ /**
409
+ * Register sharing-rule endpoints (M10.17). Mirrors the existing
410
+ * sharing endpoints but operates on `sys_sharing_rule` rows.
411
+ *
412
+ * GET {basePath}/sharing/rules?object=&activeOnly=
413
+ * POST {basePath}/sharing/rules
414
+ * GET {basePath}/sharing/rules/:idOrName
415
+ * DELETE {basePath}/sharing/rules/:idOrName
416
+ * POST {basePath}/sharing/rules/:idOrName/evaluate
417
+ *
418
+ * Returns 501 when no sharing-rule service is configured.
419
+ */
420
+ private registerSharingRuleEndpoints;
421
+ /**
422
+ * Register saved-report + scheduled-digest endpoints (M11.C16).
423
+ *
424
+ * Surfaces `IReportService` over HTTP so the UI can build,
425
+ * run, and schedule reports without dropping to ObjectQL. Routes
426
+ * live at the top of the API surface (alongside `/approvals` and
427
+ * `/sharing`) — reports are a tenant-wide capability, not a record
428
+ * on a specific CRUD object:
429
+ *
430
+ * GET {basePath}/reports?object=&ownerId=
431
+ * POST {basePath}/reports
432
+ * GET {basePath}/reports/:id
433
+ * DELETE {basePath}/reports/:id
434
+ * POST {basePath}/reports/:id/run
435
+ * POST {basePath}/reports/:id/schedule
436
+ * GET {basePath}/reports/:id/schedules
437
+ * DELETE {basePath}/reports/schedules/:scheduleId
438
+ *
439
+ * All routes return 501 when `reportsServiceProvider` is unset so
440
+ * a deployment without `@objectstack/plugin-reports` fails cleanly.
441
+ */
442
+ private registerReportsEndpoints;
443
+ /**
444
+ * Register approval engine endpoints.
445
+ *
446
+ * Routes (all under {basePath}/approvals):
447
+ * GET /processes — list approval processes
448
+ * POST /processes — upsert (defineProcess)
449
+ * GET /processes/:id — get by id or name
450
+ * DELETE /processes/:id — delete process
451
+ * POST /requests — submit
452
+ * GET /requests — list (filters: status, object, recordId, approverId, submitterId)
453
+ * GET /requests/:id — get request
454
+ * POST /requests/:id/approve — approve current step
455
+ * POST /requests/:id/reject — reject current step
456
+ * POST /requests/:id/recall — recall (submitter only)
457
+ * GET /requests/:id/actions — audit trail
458
+ *
459
+ * Returns 501 when `approvalsServiceProvider` is unset so deployments
460
+ * without `@objectstack/plugin-approvals` fail cleanly.
461
+ */
462
+ private registerApprovalsEndpoints;
217
463
  /**
218
464
  * Register batch operation endpoints
219
465
  */
@@ -231,6 +477,12 @@ declare class RestServer {
231
477
  interface RestApiPluginConfig {
232
478
  serverServiceName?: string;
233
479
  protocolServiceName?: string;
480
+ /**
481
+ * Optional override for the kernel-manager service name. When the service
482
+ * is registered (by @objectstack/runtime's MultiProjectPlugin), scoped
483
+ * routes resolve per-project protocols at request time.
484
+ */
485
+ kernelManagerServiceName?: string;
234
486
  api?: RestServerConfig;
235
487
  }
236
488
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { IHttpServer, RouteHandler, Plugin } from '@objectstack/core';
2
- import { Shared, System } from '@objectstack/spec';
2
+ import * as System from '@objectstack/spec/system';
3
+ import * as Shared from '@objectstack/spec/shared';
3
4
  import { z } from 'zod';
4
5
  import { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';
5
6
 
@@ -154,6 +155,17 @@ declare class RouteGroupBuilder {
154
155
  private resolvePath;
155
156
  }
156
157
 
158
+ /**
159
+ * Structural subset of `KernelManager` that RestServer needs in order to
160
+ * resolve a per-project protocol at request time. Typed locally to avoid
161
+ * an @objectstack/runtime → @objectstack/rest → @objectstack/runtime
162
+ * package cycle.
163
+ */
164
+ interface RestKernelManager {
165
+ getOrCreate(projectId: string): Promise<{
166
+ getServiceAsync<T = unknown>(name: string): Promise<T>;
167
+ }>;
168
+ }
157
169
  /**
158
170
  * RestServer
159
171
  *
@@ -181,11 +193,128 @@ declare class RouteGroupBuilder {
181
193
  *
182
194
  * restServer.registerRoutes();
183
195
  */
196
+ /**
197
+ * Minimal env registry shape consumed by the REST server for hostname →
198
+ * projectId resolution and `X-Project-Id` header validation on unscoped
199
+ * routes. Mirrors the surface of `EnvironmentDriverRegistry` defined in
200
+ * `@objectstack/service-cloud`.
201
+ */
202
+ interface RestEnvRegistry {
203
+ resolveByHostname(hostname: string): Promise<{
204
+ projectId: string;
205
+ } | null | undefined>;
206
+ /**
207
+ * Look up a project by id. Returns a truthy value (typically an
208
+ * `IDataDriver`) when the project exists and is bound, `null` when
209
+ * unknown. The REST server only uses the truthiness; it does not
210
+ * touch the driver itself (the actual driver is loaded later via
211
+ * `KernelManager.getOrCreate(projectId)`).
212
+ */
213
+ resolveById?(projectId: string): Promise<unknown | null>;
214
+ }
184
215
  declare class RestServer {
185
216
  private protocol;
186
217
  private config;
187
218
  private routeManager;
188
- constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig);
219
+ private kernelManager?;
220
+ private envRegistry?;
221
+ private defaultProjectIdProvider?;
222
+ private authServiceProvider?;
223
+ private objectQLProvider?;
224
+ private emailServiceProvider?;
225
+ private sharingServiceProvider?;
226
+ private reportsServiceProvider?;
227
+ private approvalsServiceProvider?;
228
+ private sharingRulesServiceProvider?;
229
+ constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig, kernelManager?: RestKernelManager, envRegistry?: RestEnvRegistry, defaultProjectIdProvider?: () => string | undefined, authServiceProvider?: (projectId?: string) => Promise<any | undefined>, objectQLProvider?: (projectId?: string) => Promise<any | undefined>, emailServiceProvider?: (projectId?: string) => Promise<any | undefined>, sharingServiceProvider?: (projectId?: string) => Promise<any | undefined>, reportsServiceProvider?: (projectId?: string) => Promise<any | undefined>, approvalsServiceProvider?: (projectId?: string) => Promise<any | undefined>, sharingRulesServiceProvider?: (projectId?: string) => Promise<any | undefined>);
230
+ /**
231
+ * Resolve the protocol for a given request. When `projectId` is present
232
+ * and a KernelManager is wired, fetch the per-project kernel's
233
+ * `protocol` service so metadata / data / UI reads hit the project's
234
+ * own registry and datastore.
235
+ *
236
+ * When `projectId` is absent on an unscoped route and an `envRegistry`
237
+ * is wired (runtime mode), the resolution chain is:
238
+ * 1. Hostname → projectId (`envRegistry.resolveByHostname`)
239
+ * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)
240
+ * 3. Default-project fallback (`defaultProjectIdProvider`, set by
241
+ * `createSingleProjectPlugin`)
242
+ * 4. Control-plane protocol captured at boot.
243
+ *
244
+ * Special case: `projectId === 'platform'` is a reserved virtual id used
245
+ * by Studio to address the control plane through the regular project
246
+ * URL shape (`/projects/platform/...`). It is NOT a row in the projects
247
+ * table, so we must never call `KernelManager.getOrCreate('platform')`.
248
+ * Instead, return the control-plane protocol directly. This lets Studio
249
+ * (and any other client) speak a single, uniform URL family without
250
+ * duplicating route logic for the platform surface.
251
+ */
252
+ private resolveProtocol;
253
+ /**
254
+ * Resolve the i18n service for the request's project (or control plane
255
+ * when no project id is in scope). Returns `undefined` when no service is
256
+ * registered, so callers can short-circuit and skip translation rather
257
+ * than failing.
258
+ *
259
+ * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the
260
+ * route → kernel-managed `i18n` service. Control-plane / unscoped
261
+ * requests intentionally return `undefined` because the platform kernel
262
+ * does not own per-app translation bundles.
263
+ */
264
+ private resolveI18nService;
265
+ /**
266
+ * Reject anonymous requests with HTTP 401 when `api.requireAuth` is set.
267
+ * Returns `true` if the response was sent and the caller should stop
268
+ * processing. Returns `false` to continue.
269
+ *
270
+ * The check is intentionally narrow: only `context?.userId` counts as
271
+ * "authenticated". `isSystem` flags are never set on inbound HTTP
272
+ * requests (they're internal-only), so they cannot bypass this gate.
273
+ */
274
+ private enforceAuth;
275
+ /**
276
+ * Resolve the request's execution context (RBAC/RLS/FLS) by looking up
277
+ * the better-auth session via the project's `auth` service. Returns
278
+ * `undefined` for anonymous requests so callers can pass `context` as-is
279
+ * to the protocol layer (the SecurityPlugin treats undefined as anon).
280
+ */
281
+ private resolveExecCtx;
282
+ /**
283
+ * Build a `TranslationBundle` (`Record<locale, TranslationData>`) from an
284
+ * `II18nService` instance. Returns `undefined` when no locales are
285
+ * registered so callers can avoid translation work.
286
+ */
287
+ private buildTranslationBundle;
288
+ /**
289
+ * Parse the highest-priority locale from an `Accept-Language` header.
290
+ * Falls back to a `?locale=` query parameter, then to the i18n service's
291
+ * default locale. Returns `undefined` when no preference is expressed
292
+ * (callers will then return untranslated metadata).
293
+ */
294
+ private extractLocale;
295
+ /**
296
+ * Translate a single metadata document (view or action) when an i18n
297
+ * service is registered for the request's project and the requested
298
+ * locale yields a match. Falls through unchanged for unsupported types
299
+ * or missing translations.
300
+ */
301
+ private translateMetaItem;
302
+ /**
303
+ * Translate a list of metadata documents using `translateMetaItem`.
304
+ */
305
+ private translateMetaItems;
306
+ /**
307
+ * Pull the request hostname (without port) from a Node-style `req` or
308
+ * a Fetch-style request wrapper. Returns undefined when no Host header
309
+ * is available.
310
+ */
311
+ private extractHostname;
312
+ /**
313
+ * Pull the `X-Project-Id` header from a Node- or Fetch-style request.
314
+ * Header names are case-insensitive; we probe both casings to cover
315
+ * adapters that don't normalize headers (e.g. raw Node http).
316
+ */
317
+ private extractProjectIdHeader;
189
318
  /**
190
319
  * Normalize configuration with defaults
191
320
  */
@@ -194,8 +323,19 @@ declare class RestServer {
194
323
  * Get the full API base path
195
324
  */
196
325
  private getApiBasePath;
326
+ /**
327
+ * Get the project-scoped base path for a given unscoped base.
328
+ * Example: `/api/v1` → `/api/v1/projects/:projectId`.
329
+ */
330
+ private getScopedBasePath;
197
331
  /**
198
332
  * Register all REST API routes
333
+ *
334
+ * When `enableProjectScoping` is true, routes are registered under
335
+ * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy
336
+ * controls whether unscoped legacy routes remain available:
337
+ * - `required` → only scoped routes registered.
338
+ * - `optional` / `auto` → both scoped and unscoped routes registered.
199
339
  */
200
340
  registerRoutes(): void;
201
341
  /**
@@ -214,6 +354,112 @@ declare class RestServer {
214
354
  * Register CRUD endpoints for data operations
215
355
  */
216
356
  private registerCrudEndpoints;
357
+ /**
358
+ * Register object-specific action endpoints that don't fit the
359
+ * generic CRUD shape. These are domain operations (Salesforce
360
+ * convertLead, etc.) where the protocol implementation does its own
361
+ * multi-record orchestration and we just need a thin HTTP route.
362
+ *
363
+ * POST {basePath}/data/lead/:id/convert — M10.6 lead conversion.
364
+ */
365
+ private registerDataActionEndpoints;
366
+ /**
367
+ * Register global cross-object search endpoint (M10.5).
368
+ * GET {basePath}/search?q=acme&objects=lead,account&limit=20&perObject=5
369
+ */
370
+ private registerSearchEndpoints;
371
+ /**
372
+ * Register email endpoints (M11.B1 / M10.7).
373
+ *
374
+ * POST {basePath}/email/send — send a transactional email via the
375
+ * `IEmailService` provider registered by EmailServicePlugin. Returns
376
+ * 501 when no provider is wired so deployments without email
377
+ * configured fail cleanly.
378
+ *
379
+ * Request body:
380
+ * {
381
+ * to: "a@b.com" | ["a@b.com", { name, address }],
382
+ * from?: ..., cc?: ..., bcc?: ..., replyTo?: ...,
383
+ * subject: string,
384
+ * text?: string, html?: string, // at least one required
385
+ * attachments?: [{ filename, content, contentType?, cid? }],
386
+ * headers?: { [name]: value },
387
+ * relatedObject?: string, relatedId?: string,
388
+ * }
389
+ */
390
+ private registerEmailEndpoints;
391
+ /**
392
+ * Register record-level sharing endpoints (M11.C17).
393
+ *
394
+ * Surfaces `ISharingService` over HTTP so the UI can list, create
395
+ * and revoke per-record grants without going through ObjectQL. The
396
+ * three routes mirror the share-management drawer in Salesforce /
397
+ * ServiceNow:
398
+ *
399
+ * GET {basePath}/data/:object/:id/shares
400
+ * POST {basePath}/data/:object/:id/shares
401
+ * DELETE {basePath}/data/:object/:id/shares/:shareId
402
+ *
403
+ * All three resolve via `sharingServiceProvider`; routes return 501
404
+ * when no sharing service is configured so a deployment without the
405
+ * `@objectstack/plugin-sharing` plugin fails cleanly.
406
+ */
407
+ private registerSharingEndpoints;
408
+ /**
409
+ * Register sharing-rule endpoints (M10.17). Mirrors the existing
410
+ * sharing endpoints but operates on `sys_sharing_rule` rows.
411
+ *
412
+ * GET {basePath}/sharing/rules?object=&activeOnly=
413
+ * POST {basePath}/sharing/rules
414
+ * GET {basePath}/sharing/rules/:idOrName
415
+ * DELETE {basePath}/sharing/rules/:idOrName
416
+ * POST {basePath}/sharing/rules/:idOrName/evaluate
417
+ *
418
+ * Returns 501 when no sharing-rule service is configured.
419
+ */
420
+ private registerSharingRuleEndpoints;
421
+ /**
422
+ * Register saved-report + scheduled-digest endpoints (M11.C16).
423
+ *
424
+ * Surfaces `IReportService` over HTTP so the UI can build,
425
+ * run, and schedule reports without dropping to ObjectQL. Routes
426
+ * live at the top of the API surface (alongside `/approvals` and
427
+ * `/sharing`) — reports are a tenant-wide capability, not a record
428
+ * on a specific CRUD object:
429
+ *
430
+ * GET {basePath}/reports?object=&ownerId=
431
+ * POST {basePath}/reports
432
+ * GET {basePath}/reports/:id
433
+ * DELETE {basePath}/reports/:id
434
+ * POST {basePath}/reports/:id/run
435
+ * POST {basePath}/reports/:id/schedule
436
+ * GET {basePath}/reports/:id/schedules
437
+ * DELETE {basePath}/reports/schedules/:scheduleId
438
+ *
439
+ * All routes return 501 when `reportsServiceProvider` is unset so
440
+ * a deployment without `@objectstack/plugin-reports` fails cleanly.
441
+ */
442
+ private registerReportsEndpoints;
443
+ /**
444
+ * Register approval engine endpoints.
445
+ *
446
+ * Routes (all under {basePath}/approvals):
447
+ * GET /processes — list approval processes
448
+ * POST /processes — upsert (defineProcess)
449
+ * GET /processes/:id — get by id or name
450
+ * DELETE /processes/:id — delete process
451
+ * POST /requests — submit
452
+ * GET /requests — list (filters: status, object, recordId, approverId, submitterId)
453
+ * GET /requests/:id — get request
454
+ * POST /requests/:id/approve — approve current step
455
+ * POST /requests/:id/reject — reject current step
456
+ * POST /requests/:id/recall — recall (submitter only)
457
+ * GET /requests/:id/actions — audit trail
458
+ *
459
+ * Returns 501 when `approvalsServiceProvider` is unset so deployments
460
+ * without `@objectstack/plugin-approvals` fail cleanly.
461
+ */
462
+ private registerApprovalsEndpoints;
217
463
  /**
218
464
  * Register batch operation endpoints
219
465
  */
@@ -231,6 +477,12 @@ declare class RestServer {
231
477
  interface RestApiPluginConfig {
232
478
  serverServiceName?: string;
233
479
  protocolServiceName?: string;
480
+ /**
481
+ * Optional override for the kernel-manager service name. When the service
482
+ * is registered (by @objectstack/runtime's MultiProjectPlugin), scoped
483
+ * routes resolve per-project protocols at request time.
484
+ */
485
+ kernelManagerServiceName?: string;
234
486
  api?: RestServerConfig;
235
487
  }
236
488
  /**