@objectstack/rest 4.0.3 → 4.0.5

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.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,113 @@ 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
+ 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>);
225
+ /**
226
+ * Resolve the protocol for a given request. When `projectId` is present
227
+ * and a KernelManager is wired, fetch the per-project kernel's
228
+ * `protocol` service so metadata / data / UI reads hit the project's
229
+ * own registry and datastore.
230
+ *
231
+ * When `projectId` is absent on an unscoped route and an `envRegistry`
232
+ * is wired (runtime mode), the resolution chain is:
233
+ * 1. Hostname → projectId (`envRegistry.resolveByHostname`)
234
+ * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)
235
+ * 3. Default-project fallback (`defaultProjectIdProvider`, set by
236
+ * `createSingleProjectPlugin`)
237
+ * 4. Control-plane protocol captured at boot.
238
+ *
239
+ * Special case: `projectId === 'platform'` is a reserved virtual id used
240
+ * by Studio to address the control plane through the regular project
241
+ * URL shape (`/projects/platform/...`). It is NOT a row in the projects
242
+ * table, so we must never call `KernelManager.getOrCreate('platform')`.
243
+ * Instead, return the control-plane protocol directly. This lets Studio
244
+ * (and any other client) speak a single, uniform URL family without
245
+ * duplicating route logic for the platform surface.
246
+ */
247
+ private resolveProtocol;
248
+ /**
249
+ * Resolve the i18n service for the request's project (or control plane
250
+ * when no project id is in scope). Returns `undefined` when no service is
251
+ * registered, so callers can short-circuit and skip translation rather
252
+ * than failing.
253
+ *
254
+ * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the
255
+ * route → kernel-managed `i18n` service. Control-plane / unscoped
256
+ * requests intentionally return `undefined` because the platform kernel
257
+ * does not own per-app translation bundles.
258
+ */
259
+ private resolveI18nService;
260
+ /**
261
+ * Resolve the request's execution context (RBAC/RLS/FLS) by looking up
262
+ * the better-auth session via the project's `auth` service. Returns
263
+ * `undefined` for anonymous requests so callers can pass `context` as-is
264
+ * to the protocol layer (the SecurityPlugin treats undefined as anon).
265
+ */
266
+ private resolveExecCtx;
267
+ /**
268
+ * Build a `TranslationBundle` (`Record<locale, TranslationData>`) from an
269
+ * `II18nService` instance. Returns `undefined` when no locales are
270
+ * registered so callers can avoid translation work.
271
+ */
272
+ private buildTranslationBundle;
273
+ /**
274
+ * Parse the highest-priority locale from an `Accept-Language` header.
275
+ * Falls back to a `?locale=` query parameter, then to the i18n service's
276
+ * default locale. Returns `undefined` when no preference is expressed
277
+ * (callers will then return untranslated metadata).
278
+ */
279
+ private extractLocale;
280
+ /**
281
+ * Translate a single metadata document (view or action) when an i18n
282
+ * service is registered for the request's project and the requested
283
+ * locale yields a match. Falls through unchanged for unsupported types
284
+ * or missing translations.
285
+ */
286
+ private translateMetaItem;
287
+ /**
288
+ * Translate a list of metadata documents using `translateMetaItem`.
289
+ */
290
+ private translateMetaItems;
291
+ /**
292
+ * Pull the request hostname (without port) from a Node-style `req` or
293
+ * a Fetch-style request wrapper. Returns undefined when no Host header
294
+ * is available.
295
+ */
296
+ private extractHostname;
297
+ /**
298
+ * Pull the `X-Project-Id` header from a Node- or Fetch-style request.
299
+ * Header names are case-insensitive; we probe both casings to cover
300
+ * adapters that don't normalize headers (e.g. raw Node http).
301
+ */
302
+ private extractProjectIdHeader;
189
303
  /**
190
304
  * Normalize configuration with defaults
191
305
  */
@@ -194,8 +308,19 @@ declare class RestServer {
194
308
  * Get the full API base path
195
309
  */
196
310
  private getApiBasePath;
311
+ /**
312
+ * Get the project-scoped base path for a given unscoped base.
313
+ * Example: `/api/v1` → `/api/v1/projects/:projectId`.
314
+ */
315
+ private getScopedBasePath;
197
316
  /**
198
317
  * Register all REST API routes
318
+ *
319
+ * When `enableProjectScoping` is true, routes are registered under
320
+ * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy
321
+ * controls whether unscoped legacy routes remain available:
322
+ * - `required` → only scoped routes registered.
323
+ * - `optional` / `auto` → both scoped and unscoped routes registered.
199
324
  */
200
325
  registerRoutes(): void;
201
326
  /**
@@ -231,6 +356,12 @@ declare class RestServer {
231
356
  interface RestApiPluginConfig {
232
357
  serverServiceName?: string;
233
358
  protocolServiceName?: string;
359
+ /**
360
+ * Optional override for the kernel-manager service name. When the service
361
+ * is registered (by @objectstack/runtime's MultiProjectPlugin), scoped
362
+ * routes resolve per-project protocols at request time.
363
+ */
364
+ kernelManagerServiceName?: string;
234
365
  api?: RestServerConfig;
235
366
  }
236
367
  /**