@gravito/ion 3.0.1 → 3.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,201 +1,448 @@
1
1
  import { GravitoContext, GravitoVariables, GravitoOrbit, PlanetCore } from '@gravito/core';
2
2
 
3
3
  /**
4
- * @fileoverview Inertia.js Service for Gravito
4
+ * @fileoverview Inertia.js Service for Gravito.
5
5
  *
6
- * Provides server-side Inertia.js integration for building modern
7
- * single-page applications with server-side routing.
6
+ * This service implements the Inertia.js server-side protocol, enabling
7
+ * seamless page transitions and data synchronization for monolith SPAs.
8
8
  *
9
9
  * @module @gravito/ion
10
- * @since 1.0.0
11
10
  */
12
11
 
13
12
  /**
14
- * Configuration options for InertiaService
13
+ * Configuration options for the InertiaService instance.
14
+ *
15
+ * Defines how the service interacts with the root template, manages asset versioning,
16
+ * and handles Server-Side Rendering (SSR).
15
17
  */
16
18
  interface InertiaConfig {
17
19
  /**
18
- * The root view template name
19
- * @default 'app'
20
+ * The name of the root view template used for the initial page load.
21
+ *
22
+ * This template must contain the `{{{ page }}}` placeholder to inject the
23
+ * Inertia page object.
24
+ *
25
+ * @defaultValue 'app'
20
26
  */
21
27
  rootView?: string;
22
28
  /**
23
- * Asset version for cache busting
29
+ * Asset version string or resolver.
30
+ *
31
+ * Used by Inertia to detect if the client-side assets are out of sync with the server.
32
+ * If the version sent by the client (X-Inertia-Version) doesn't match, the server
33
+ * triggers a full page reload via a 409 Conflict response.
34
+ */
35
+ version?: string | (() => string | Promise<string>);
36
+ /**
37
+ * Minimum logging level for internal operations.
38
+ *
39
+ * @defaultValue 'info'
40
+ */
41
+ logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent';
42
+ /**
43
+ * Performance monitoring callback triggered after each render.
44
+ *
45
+ * Useful for tracking rendering latency and prop payload sizes in production.
24
46
  */
25
- version?: string;
47
+ onRender?: (metrics: RenderMetrics) => void;
48
+ /**
49
+ * SSR configuration options.
50
+ *
51
+ * Enables pre-rendering of components on the server to improve SEO and
52
+ * initial load performance.
53
+ */
54
+ ssr?: {
55
+ /** Whether SSR is enabled for the current environment. */
56
+ enabled: boolean;
57
+ /**
58
+ * Function to handle SSR rendering.
59
+ *
60
+ * Should interface with your frontend framework's SSR renderer (e.g., React's `renderToString`).
61
+ */
62
+ render?: (page: any) => Promise<{
63
+ head: string[];
64
+ body: string;
65
+ }>;
66
+ };
67
+ }
68
+ /**
69
+ * Encapsulates performance and status metrics for a single render operation.
70
+ *
71
+ * Provides insights into the rendering lifecycle, including component name,
72
+ * execution time, and payload complexity.
73
+ */
74
+ interface RenderMetrics {
75
+ /** Name of the frontend component rendered. */
76
+ component: string;
77
+ /** Time taken in milliseconds for the entire render lifecycle. */
78
+ duration: number;
79
+ /** Indicates if the request was an Inertia AJAX request (true) or a full page load (false). */
80
+ isInertiaRequest: boolean;
81
+ /** Number of top-level props passed to the component after resolution. */
82
+ propsCount: number;
83
+ /** Epoch timestamp of when the operation completed. */
84
+ timestamp: number;
85
+ /** Resulting HTTP status code of the response. */
86
+ status?: number;
26
87
  }
27
88
  /**
28
- * InertiaService - Server-side Inertia.js adapter
89
+ * Server-side adapter for the Inertia.js protocol.
29
90
  *
30
- * This service handles the Inertia.js protocol for seamless
31
- * SPA-like navigation with server-side routing.
91
+ * The `InertiaService` is responsible for:
92
+ * 1. Resolving component props (including lazy/async props).
93
+ * 2. Handling partial reloads (filtering props based on `X-Inertia-Partial-Data`).
94
+ * 3. Managing asset versioning to ensure client-server synchronization.
95
+ * 4. Orchestrating SSR pre-rendering when enabled.
96
+ * 5. Generating the final HTTP response (JSON for AJAX, HTML for initial load).
32
97
  *
33
98
  * @example
34
99
  * ```typescript
35
- * // In a controller
36
- * async index(ctx: GravitoContext) {
37
- * const inertia = ctx.get('inertia') as InertiaService
38
- * return inertia.render('Home', { users: await User.all() })
39
- * }
100
+ * const inertia = new InertiaService(ctx, {
101
+ * version: () => getGitHash(),
102
+ * rootView: 'main'
103
+ * });
104
+ *
105
+ * // Share global data
106
+ * inertia.share('user', ctx.get('user'));
107
+ *
108
+ * // Render a component
109
+ * return await inertia.render('Dashboard/Index', {
110
+ * stats: async () => await fetchStats() // Lazy prop
111
+ * });
40
112
  * ```
41
113
  */
42
114
  declare class InertiaService {
43
115
  private context;
44
116
  private config;
45
117
  private sharedProps;
118
+ private readonly logLevel;
119
+ private readonly onRenderCallback?;
46
120
  /**
47
- * Create a new InertiaService instance
121
+ * Initializes a new instance of the Inertia service.
48
122
  *
49
- * @param context - The Gravito request context
50
- * @param config - Optional configuration
123
+ * @param context - The current Gravito request context.
124
+ * @param config - Instance configuration options.
51
125
  */
52
126
  constructor(context: GravitoContext<GravitoVariables>, config?: InertiaConfig);
53
127
  /**
54
- * Escape a string for safe use in HTML attributes
128
+ * Internal logging helper that respects the configured log level.
55
129
  *
56
- * Strategy: JSON.stringify already escapes special characters including
57
- * quotes as \". We need to escape these for HTML attributes, but we must
58
- * be careful not to break JSON escape sequences.
130
+ * Routes logs to the Gravito context logger if available.
59
131
  *
60
- * The solution: Escape backslash-quote sequences (\" from JSON.stringify)
61
- * as \\&quot; so they become \\&quot; in HTML, which the browser decodes
62
- * to \\" (valid JSON), not \&quot; (invalid JSON).
132
+ * @param level - Log severity level.
133
+ * @param message - Descriptive message.
134
+ * @param data - Optional metadata for the log.
135
+ */
136
+ private log;
137
+ /**
138
+ * Escapes a string for safe embedding into a single-quoted HTML attribute.
63
139
  *
64
- * @param value - The string to escape.
65
- * @returns The escaped string.
140
+ * This ensures that JSON strings can be safely passed to the frontend
141
+ * via the `data-page` attribute without breaking the HTML structure or
142
+ * introducing XSS vulnerabilities.
143
+ *
144
+ * @param value - Raw JSON or text string.
145
+ * @returns Safely escaped HTML attribute value.
66
146
  */
67
147
  private escapeForSingleQuotedHtmlAttribute;
68
148
  /**
69
- * Render an Inertia component
149
+ * Renders an Inertia component and returns the appropriate HTTP response.
150
+ *
151
+ * This method handles the entire Inertia lifecycle:
152
+ * - Detects if the request is an Inertia AJAX request.
153
+ * - Validates asset versions.
154
+ * - Resolves props (executes functions, handles partial reloads).
155
+ * - Performs SSR if configured.
156
+ * - Wraps the result in a JSON response or the root HTML template.
70
157
  *
71
- * @param component - The component name to render
72
- * @param props - Props to pass to the component
73
- * @param rootVars - Additional variables for the root template
74
- * @returns HTTP Response
158
+ * @param component - Frontend component name (e.g., 'Users/Profile').
159
+ * @param props - Data passed to the component. Can include functions for lazy evaluation.
160
+ * @param rootVars - Variables passed to the root HTML template (only used on initial load).
161
+ * @param status - Optional HTTP status code.
162
+ * @returns A promise that resolves to a Gravito HTTP Response.
163
+ *
164
+ * @throws {@link InertiaError}
165
+ * Thrown if:
166
+ * - The `ViewService` is missing from the context (required for initial load).
167
+ * - Prop serialization fails due to circular references or non-serializable data.
75
168
  *
76
169
  * @example
77
170
  * ```typescript
78
- * return inertia.render('Users/Index', {
79
- * users: await User.all(),
80
- * filters: { search: ctx.req.query('search') }
81
- * })
171
+ * // Standard render
172
+ * return await inertia.render('Welcome', { name: 'Guest' });
173
+ *
174
+ * // Partial reload support (only 'stats' will be resolved if requested)
175
+ * return await inertia.render('Dashboard', {
176
+ * user: auth.user,
177
+ * stats: () => db.getStats()
178
+ * });
82
179
  * ```
83
180
  */
84
- render(component: string, props?: Record<string, unknown>, rootVars?: Record<string, unknown>): Response;
181
+ render<T extends Record<string, unknown> = Record<string, unknown>>(component: string, props?: T, rootVars?: Record<string, unknown>, status?: number): Promise<Response>;
85
182
  /**
86
- * Share data with all Inertia responses
183
+ * Registers a piece of data to be shared with all Inertia responses for the current request.
87
184
  *
88
- * Shared props are merged with component-specific props on every render.
185
+ * Shared props are automatically merged with component-specific props during the render phase.
186
+ * This is the primary mechanism for providing global state like authentication details,
187
+ * flash messages, or configuration to the frontend.
89
188
  *
90
- * @param key - The prop key
91
- * @param value - The prop value
189
+ * @param key - Identifier for the shared prop.
190
+ * @param value - Value to share. Must be JSON serializable.
92
191
  *
93
192
  * @example
94
193
  * ```typescript
95
- * // In middleware
96
- * inertia.share('auth', { user: ctx.get('auth')?.user() })
97
- * inertia.share('flash', ctx.get('session')?.getFlash('message'))
194
+ * inertia.share('appName', 'Gravito Store');
195
+ * inertia.share('auth', { user: ctx.get('user') });
98
196
  * ```
99
197
  */
100
198
  share(key: string, value: unknown): void;
101
199
  /**
102
- * Share multiple props at once
200
+ * Shares multiple props in a single operation.
201
+ *
202
+ * Merges the provided object into the existing shared props state. Existing keys
203
+ * with the same name will be overwritten.
103
204
  *
104
- * @param props - Object of props to share
205
+ * @param props - Object containing key-value pairs to merge into the shared state.
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * inertia.shareAll({
210
+ * version: '1.2.0',
211
+ * environment: 'production',
212
+ * features: ['chat', 'search']
213
+ * });
214
+ * ```
105
215
  */
106
216
  shareAll(props: Record<string, unknown>): void;
107
217
  /**
108
- * Get all shared props
218
+ * Returns a shallow copy of the current shared props.
219
+ *
220
+ * Useful for inspecting the shared state or for manual prop merging in advanced scenarios.
221
+ *
222
+ * @returns A dictionary containing all currently registered shared props.
109
223
  *
110
- * @returns A shallow copy of the shared props object.
224
+ * @example
225
+ * ```typescript
226
+ * const shared = inertia.getSharedProps();
227
+ * if (!shared.auth) {
228
+ * console.warn('Authentication data is missing from shared props');
229
+ * }
230
+ * ```
111
231
  */
112
232
  getSharedProps(): Record<string, unknown>;
113
233
  }
114
234
 
115
235
  /**
116
- * @fileoverview Orbit Inertia - Inertia.js integration for Gravito
236
+ * Base error class for all Inertia-related operations in Gravito.
237
+ */
238
+ declare class InertiaError extends Error {
239
+ readonly code: string;
240
+ readonly httpStatus: number;
241
+ readonly details?: Record<string, any> | undefined;
242
+ constructor(code: string, httpStatus?: number, details?: Record<string, any> | undefined);
243
+ toJSON(): {
244
+ name: string;
245
+ code: string;
246
+ httpStatus: number;
247
+ message: string;
248
+ details: Record<string, any> | undefined;
249
+ };
250
+ }
251
+ /**
252
+ * Configuration errors - e.g., missing services.
253
+ */
254
+ declare class InertiaConfigError extends InertiaError {
255
+ constructor(message: string, details?: Record<string, any>);
256
+ }
257
+ /**
258
+ * Data/Serialization errors - e.g., circular references or resolution failure.
259
+ */
260
+ declare class InertiaDataError extends InertiaError {
261
+ constructor(message: string, details?: Record<string, any>);
262
+ }
263
+ /**
264
+ * Template/Rendering errors.
265
+ */
266
+ declare class InertiaTemplateError extends InertiaError {
267
+ constructor(message: string, details?: Record<string, any>);
268
+ }
269
+
270
+ /**
271
+ * @fileoverview Orbit Inertia - Inertia.js integration for Gravito.
117
272
  *
118
- * Provides server-side Inertia.js integration for building modern
119
- * single-page applications with server-side routing.
273
+ * This module provides the core Orbit definition and helper interfaces for
274
+ * building modern single-page applications using server-side routing.
120
275
  *
121
276
  * @module @gravito/ion
122
- * @since 1.0.0
123
277
  */
124
278
 
125
279
  /**
126
- * InertiaHelper is a callable function and service suite injected into the Gravito context.
127
- * It allows you to render Inertia components directly or manage shared data.
280
+ * Enhanced helper interface for Inertia operations within the Gravito context.
281
+ *
282
+ * This interface is both a callable function for quick rendering and a
283
+ * service container for managing shared data and accessing the underlying service.
284
+ * It is typically accessed via `ctx.get('inertia')`.
128
285
  *
129
286
  * @example
130
287
  * ```typescript
131
- * // Direct call rendering
132
- * return ctx.get('inertia')('Welcome', { user });
288
+ * // 1. Direct rendering (Callable)
289
+ * export const index = async (ctx: Context) => {
290
+ * return await ctx.get('inertia')('Home', { title: 'Welcome' });
291
+ * };
133
292
  *
134
- * // Using shared data
135
- * ctx.get('inertia').share('appName', 'Gravito App');
293
+ * // 2. Sharing global data
294
+ * ctx.get('inertia').share('app_name', 'My Project');
295
+ *
296
+ * // 3. Accessing the underlying service
297
+ * const service = ctx.get('inertia').service;
136
298
  * ```
137
- * @public
138
299
  */
139
300
  interface InertiaHelper {
140
301
  /**
141
- * Render an Inertia component.
142
- * Shortcut for context.get('inertia').render()
302
+ * Renders an Inertia component.
303
+ *
304
+ * This is a shortcut for calling `render()` on the underlying service.
305
+ *
306
+ * @param component - Frontend component name (e.g., 'Pages/Dashboard').
307
+ * @param props - Data object passed to the component. Supports lazy/async props.
308
+ * @param rootVars - Variables passed to the root HTML template (initial load only).
309
+ * @param status - Optional HTTP status code (defaults to 200).
310
+ * @returns A promise resolving to a Gravito-compatible HTTP Response.
311
+ *
312
+ * @throws {@link InertiaError}
313
+ * Thrown if serialization fails or the ViewService is missing during initial load.
314
+ */
315
+ <T extends Record<string, unknown> = Record<string, unknown>>(component: string, props?: T, rootVars?: Record<string, unknown>, status?: number): Promise<Response>;
316
+ /**
317
+ * Shares a single piece of data with all subsequent Inertia responses for this request.
143
318
  *
144
- * @param component - The name of the frontend component (e.g., 'Pages/Home')
145
- * @param props - Data to pass to the component
146
- * @param rootVars - Variables for the root HTML template (e.g., meta tags)
319
+ * @param key - Unique identifier for the shared prop.
320
+ * @param value - Data value. Must be JSON serializable.
147
321
  */
148
- (component: string, props?: Record<string, unknown>, rootVars?: Record<string, unknown>): Response;
149
- /** Share data with all Inertia responses for the remainder of the request */
150
322
  share(key: string, value: unknown): void;
151
- /** Share multiple props at once */
323
+ /**
324
+ * Shares multiple props simultaneously by merging them into the shared state.
325
+ *
326
+ * @param props - Key-value pairs to merge into shared props.
327
+ */
152
328
  shareAll(props: Record<string, unknown>): void;
153
- /** Get all currently shared props */
329
+ /**
330
+ * Retrieves a copy of all currently shared props for the current request.
331
+ *
332
+ * @returns Current shared props dictionary.
333
+ */
154
334
  getSharedProps(): Record<string, unknown>;
155
- /** Explicitly render an Inertia component */
156
- render(component: string, props?: Record<string, unknown>, rootVars?: Record<string, unknown>): Response;
157
- /** Direct access to the low-level Inertia service instance */
335
+ /**
336
+ * Explicitly renders an Inertia component.
337
+ *
338
+ * Identical to the callable interface but provided for semantic clarity.
339
+ *
340
+ * @param component - Frontend component name.
341
+ * @param props - Component data.
342
+ * @param rootVars - Template variables.
343
+ * @param status - HTTP status code.
344
+ * @returns A promise resolving to an HTTP Response.
345
+ *
346
+ * @throws {@link InertiaError}
347
+ * Thrown if the rendering lifecycle encounters a fatal error.
348
+ */
349
+ render<T extends Record<string, unknown> = Record<string, unknown>>(component: string, props?: T, rootVars?: Record<string, unknown>, status?: number): Promise<Response>;
350
+ /**
351
+ * Direct access to the low-level `InertiaService` instance.
352
+ *
353
+ * Use this for advanced operations not exposed by the helper interface.
354
+ */
158
355
  service: InertiaService;
159
356
  }
160
357
  /**
161
- * Options for configuring OrbitIon.
162
- * @public
358
+ * Configuration options for the OrbitIon extension.
359
+ *
360
+ * Controls how the Inertia protocol is integrated into the Gravito ecosystem.
163
361
  */
164
362
  interface OrbitIonOptions {
165
- /** Current asset version to detect staleness (X-Inertia-Version) */
166
- version?: string;
167
- /** The root HTML template view (default: 'app') */
363
+ /**
364
+ * Asset version string or resolver used for cache busting.
365
+ *
366
+ * If not provided, it defaults to the `APP_VERSION` defined in the core configuration.
367
+ *
368
+ * @defaultValue '1.0.0'
369
+ */
370
+ version?: string | (() => string | Promise<string>);
371
+ /**
372
+ * The name of the root HTML template file (without extension).
373
+ *
374
+ * This template is used for the initial page load.
375
+ *
376
+ * @defaultValue 'app'
377
+ */
168
378
  rootView?: string;
379
+ /**
380
+ * SSR configuration options.
381
+ *
382
+ * Enables server-side pre-rendering of components.
383
+ */
384
+ ssr?: {
385
+ /** Whether SSR is enabled for this application. */
386
+ enabled: boolean;
387
+ /**
388
+ * Function to handle SSR rendering.
389
+ *
390
+ * Receives the Inertia page object and should return the rendered HTML.
391
+ */
392
+ render?: (page: any) => Promise<{
393
+ head: string[];
394
+ body: string;
395
+ }>;
396
+ };
169
397
  }
170
398
  /**
171
399
  * OrbitIon provides official Inertia.js integration for Gravito.
172
400
  *
173
- * It handles Inertia requests, partial reloads, asset versioning, and seamless
174
- * data sharing between the server and your frontend application (React, Vue, etc.).
175
- *
176
- * It injects an `InertiaHelper` into the context, allowing you to render
177
- * components with a simple function call: `c.get('inertia')('Page', { props })`.
401
+ * As an infrastructure extension (Orbit), it:
402
+ * 1. Registers a global middleware to handle the Inertia protocol.
403
+ * 2. Manages asset version synchronization (X-Inertia-Version).
404
+ * 3. Injects the `InertiaHelper` into the request context for easy access in controllers.
405
+ * 4. Supports partial reloads and SSR out of the box.
178
406
  *
179
407
  * @example
180
408
  * ```typescript
181
409
  * import { OrbitIon } from '@gravito/ion';
182
410
  *
411
+ * // In your application bootstrap
183
412
  * core.addOrbit(new OrbitIon({
184
- * version: '1.0.0',
185
- * rootView: 'app'
413
+ * version: 'v2.1.0',
414
+ * rootView: 'layouts/app',
415
+ * ssr: {
416
+ * enabled: process.env.SSR === 'true',
417
+ * render: async (page) => await mySsrRenderer(page)
418
+ * }
186
419
  * }));
187
420
  * ```
188
- *
189
- * @public
190
- * @since 3.0.0
191
421
  */
192
422
  declare class OrbitIon implements GravitoOrbit {
193
423
  private options;
424
+ /**
425
+ * Initializes the Orbit with custom configuration.
426
+ *
427
+ * @param options - Configuration overrides for the Inertia service.
428
+ */
194
429
  constructor(options?: OrbitIonOptions);
195
430
  /**
196
- * Install the inertia orbit into PlanetCore
431
+ * Registers the Inertia middleware and service factory into PlanetCore.
432
+ *
433
+ * This method is called automatically by Gravito during the boot process.
434
+ * It sets up the `InertiaService` for each request and attaches
435
+ * the `InertiaHelper` proxy to the context.
436
+ *
437
+ * @param core - The Gravito micro-kernel instance.
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * const ion = new OrbitIon({ version: '1.0' });
442
+ * ion.install(core);
443
+ * ```
197
444
  */
198
445
  install(core: PlanetCore): void;
199
446
  }
200
447
 
201
- export { type InertiaConfig, type InertiaHelper, InertiaService, OrbitIon, type OrbitIonOptions, OrbitIon as default };
448
+ export { type InertiaConfig, InertiaConfigError, InertiaDataError, InertiaError, type InertiaHelper, InertiaService, InertiaTemplateError, OrbitIon, type OrbitIonOptions, type RenderMetrics, OrbitIon as default };