@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/README.md +94 -17
- package/dist/index.cjs +2288 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -2
- package/dist/index.d.ts +254 -2
- package/dist/index.js +2278 -96
- package/dist/index.js.map +1 -1
- package/package.json +32 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -481
- package/src/index.ts +0 -12
- package/src/rest-api-plugin.ts +0 -72
- package/src/rest-server.ts +0 -691
- package/src/rest.test.ts +0 -672
- package/src/route-manager.ts +0 -308
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -10
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IHttpServer, RouteHandler, Plugin } from '@objectstack/core';
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
/**
|