@adonisjs/inertia 1.0.0-0 → 1.0.0-2

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.
@@ -1,27 +0,0 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import type { AssetsVersion } from './types.js';
3
- /**
4
- * VersionCache is used to cache the version of the assets.
5
- *
6
- * If the user has provided a version, it will be used.
7
- * Otherwise, we will compute a hash from the manifest file
8
- * and cache it.
9
- */
10
- export declare class VersionCache {
11
- #private;
12
- protected appRoot: URL;
13
- protected assetsVersion?: AssetsVersion;
14
- constructor(appRoot: URL, assetsVersion?: AssetsVersion);
15
- /**
16
- * Pre-compute the version
17
- */
18
- computeVersion(): Promise<this>;
19
- /**
20
- * Returns the current assets version
21
- */
22
- getVersion(): string | number;
23
- /**
24
- * Set the assets version
25
- */
26
- setVersion(version: AssetsVersion): Promise<void>;
27
- }
@@ -1,68 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
- import crc32 from 'crc-32';
10
- import { readFile } from 'node:fs/promises';
11
- /**
12
- * VersionCache is used to cache the version of the assets.
13
- *
14
- * If the user has provided a version, it will be used.
15
- * Otherwise, we will compute a hash from the manifest file
16
- * and cache it.
17
- */
18
- export class VersionCache {
19
- appRoot;
20
- assetsVersion;
21
- #cachedVersion;
22
- constructor(appRoot, assetsVersion) {
23
- this.appRoot = appRoot;
24
- this.assetsVersion = assetsVersion;
25
- this.#cachedVersion = assetsVersion;
26
- }
27
- /**
28
- * Compute the hash of the manifest file and cache it
29
- */
30
- async #getManifestHash() {
31
- try {
32
- const manifestPath = new URL('public/assets/manifest.json', this.appRoot);
33
- const manifestFile = await readFile(manifestPath, 'utf-8');
34
- this.#cachedVersion = crc32.str(manifestFile);
35
- return this.#cachedVersion;
36
- }
37
- catch {
38
- /**
39
- * If the manifest file does not exist, it probably means that we are in
40
- * development mode
41
- */
42
- this.#cachedVersion = '1';
43
- return this.#cachedVersion;
44
- }
45
- }
46
- /**
47
- * Pre-compute the version
48
- */
49
- async computeVersion() {
50
- if (!this.assetsVersion)
51
- await this.#getManifestHash();
52
- return this;
53
- }
54
- /**
55
- * Returns the current assets version
56
- */
57
- getVersion() {
58
- if (!this.#cachedVersion)
59
- throw new Error('Version has not been computed yet');
60
- return this.#cachedVersion;
61
- }
62
- /**
63
- * Set the assets version
64
- */
65
- async setVersion(version) {
66
- this.#cachedVersion = version;
67
- }
68
- }
@@ -1 +0,0 @@
1
- export declare const stubsRoot: string;
@@ -1,10 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
- import { getDirname } from '@poppinss/utils';
10
- export const stubsRoot = getDirname(import.meta.url);
@@ -1,66 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import { HttpContext } from '@adonisjs/core/http'
11
- import type { ApplicationService } from '@adonisjs/core/types'
12
-
13
- import { Inertia } from '../src/inertia.js'
14
- import type { InertiaConfig } from '../src/types.js'
15
- import { VersionCache } from '../src/version_cache.js'
16
- import InertiaMiddleware from '../src/inertia_middleware.js'
17
-
18
- /**
19
- * HttpContext augmentations
20
- */
21
- declare module '@adonisjs/core/http' {
22
- export interface HttpContext {
23
- inertia: Inertia
24
- }
25
- }
26
-
27
- /**
28
- * Inertia provider
29
- */
30
- export default class InertiaProvider {
31
- constructor(protected app: ApplicationService) {}
32
-
33
- /**
34
- * Registers edge plugin when edge is installed
35
- */
36
- protected async registerEdgePlugin() {
37
- try {
38
- const edgeExports = await import('edge.js')
39
- const { edgePluginInertia } = await import('../src/plugins/edge.js')
40
-
41
- edgeExports.default.use(edgePluginInertia())
42
- } catch {}
43
- }
44
-
45
- /**
46
- * Register Inertia middleware, edge plugin, and add
47
- * `inertia` property to the HttpContext
48
- */
49
- async boot() {
50
- const appRoot = this.app.appRoot
51
- const config = this.app.config.get<InertiaConfig>('inertia', { view: 'app' })
52
-
53
- const versionCache = await new VersionCache(appRoot, config.assetsVersion).computeVersion()
54
- this.app.container.singleton(InertiaMiddleware, () => new InertiaMiddleware(versionCache))
55
-
56
- HttpContext.getter(
57
- 'inertia',
58
- function inertia(this: HttpContext) {
59
- return new Inertia(this, config, versionCache.getVersion())
60
- },
61
- false
62
- )
63
-
64
- await this.registerEdgePlugin()
65
- }
66
- }
package/src/debug.ts DELETED
@@ -1,12 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import { debuglog } from 'node:util'
11
-
12
- export default debuglog('adonisjs:inertia')
@@ -1,17 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type { InertiaConfig } from './types.js'
11
-
12
- /**
13
- * Define the Inertia configuration
14
- */
15
- export function defineConfig(config: InertiaConfig) {
16
- return config
17
- }
package/src/inertia.ts DELETED
@@ -1,140 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- /// <reference types="@adonisjs/core/providers/edge_provider" />
11
-
12
- import type { HttpContext } from '@adonisjs/core/http'
13
- import type { InertiaConfig, MaybePromise, PageProps, AssetsVersion } from './types.js'
14
-
15
- /**
16
- * Main class used to interact with Inertia
17
- */
18
- export class Inertia {
19
- /**
20
- * The name of the Edge view that will be used to render the page.
21
- */
22
- #edgeRootView: string
23
-
24
- /**
25
- * Symbol used to identify lazy props
26
- */
27
- #kLazySymbol = Symbol('lazy')
28
-
29
- constructor(
30
- protected ctx: HttpContext,
31
- protected config: InertiaConfig = {},
32
- protected version: AssetsVersion
33
- ) {
34
- this.#edgeRootView = config.rootView || 'root'
35
- }
36
-
37
- /**
38
- * Check if a value is a lazy prop
39
- */
40
- #isLazyProps(value: any) {
41
- return typeof value === 'object' && value && this.#kLazySymbol in value
42
- }
43
-
44
- /**
45
- * Pick props to resolve based on x-inertia-partial-data header
46
- *
47
- * If header is not present, resolve all props except lazy props
48
- * If header is present, resolve only the props that are listed in the header
49
- */
50
- #pickPropsToResolve(component: string, props: PageProps) {
51
- const partialData = this.ctx.request
52
- .header('x-inertia-partial-data')
53
- ?.split(',')
54
- .filter(Boolean)
55
-
56
- const partialComponent = this.ctx.request.header('x-inertia-partial-component')
57
-
58
- let entriesToResolve = Object.entries(props)
59
- if (partialData && partialComponent === component) {
60
- entriesToResolve = entriesToResolve.filter(([key]) => partialData.includes(key))
61
- } else {
62
- entriesToResolve = entriesToResolve.filter(([key]) => !this.#isLazyProps(props[key]))
63
- }
64
-
65
- return entriesToResolve
66
- }
67
-
68
- /**
69
- * Resolve the props that will be sent to the client
70
- */
71
- async #resolvePageProps(component: string, props: PageProps) {
72
- const entriesToResolve = this.#pickPropsToResolve(component, props)
73
-
74
- const entries = entriesToResolve.map(async ([key, value]) => {
75
- if (typeof value === 'function') {
76
- return [key, await value(this.ctx)]
77
- }
78
-
79
- if (this.#isLazyProps(value)) {
80
- const lazyValue = (value as any)[this.#kLazySymbol]
81
- return [key, await lazyValue()]
82
- }
83
-
84
- return [key, value]
85
- })
86
-
87
- return Object.fromEntries(await Promise.all(entries))
88
- }
89
-
90
- /**
91
- * Build the page object that will be returned to the client
92
- *
93
- * See https://inertiajs.com/the-protocol#the-page-object
94
- */
95
- async #buildPageObject(component: string, pageProps?: PageProps) {
96
- return {
97
- component,
98
- version: this.version,
99
- props: await this.#resolvePageProps(component, { ...this.config.sharedData, ...pageProps }),
100
- url: this.ctx.request.url(true),
101
- }
102
- }
103
-
104
- /**
105
- * Render a page using Inertia
106
- */
107
- async render(component: string, pageProps?: PageProps) {
108
- const pageObject = await this.#buildPageObject(component, pageProps)
109
- const isInertiaRequest = !!this.ctx.request.header('x-inertia')
110
-
111
- if (!isInertiaRequest) {
112
- return this.ctx.view.render(this.#edgeRootView, { page: pageObject })
113
- }
114
-
115
- return pageObject
116
- }
117
-
118
- /**
119
- * Create a lazy prop
120
- *
121
- * Lazy props are never resolved on first visit, but only when the client
122
- * request a partial reload explicitely with this value.
123
- *
124
- * See https://inertiajs.com/partial-reloads#lazy-data-evaluation
125
- */
126
- lazy(callback: () => MaybePromise<any>) {
127
- return { [this.#kLazySymbol]: callback }
128
- }
129
-
130
- /**
131
- * This method can be used to redirect the user to an external website
132
- * or even a non-inertia route of your application.
133
- *
134
- * See https://inertiajs.com/redirects#external-redirects
135
- */
136
- async location(url: string) {
137
- this.ctx.response.header('X-Inertia-Location', url)
138
- this.ctx.response.status(409)
139
- }
140
- }
@@ -1,54 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type { HttpContext } from '@adonisjs/core/http'
11
- import type { NextFn } from '@adonisjs/core/types/http'
12
-
13
- import type { VersionCache } from './version_cache.js'
14
-
15
- /**
16
- * Inertia middleware to handle the Inertia requests and
17
- * set appropriate headers/status
18
- */
19
- export default class InertiaMiddleware {
20
- constructor(protected version: VersionCache) {}
21
-
22
- async handle({ request, response }: HttpContext, next: NextFn) {
23
- await next()
24
-
25
- const isInertiaRequest = !!request.header('x-inertia')
26
- if (!isInertiaRequest) return
27
-
28
- response.header('Vary', 'Accept')
29
- response.header('X-Inertia', 'true')
30
-
31
- /**
32
- * When redirecting a PUT/PATCH/DELETE request, we need to change the
33
- * we must use a 303 status code instead of a 302 to force
34
- * the browser to use a GET request after redirecting.
35
- *
36
- * See https://inertiajs.com/redirects
37
- */
38
- const method = request.method()
39
- if (response.getStatus() === 302 && ['PUT', 'PATCH', 'DELETE'].includes(method)) {
40
- response.status(303)
41
- }
42
-
43
- /**
44
- * Handle version change
45
- *
46
- * See https://inertiajs.com/the-protocol#asset-versioning
47
- */
48
- const version = this.version.getVersion()
49
- if (method === 'GET' && request.header('x-inertia-version', '') !== version) {
50
- response.header('x-inertia-location', request.url())
51
- response.status(409)
52
- }
53
- }
54
- }
@@ -1,127 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type { PluginFn } from '@japa/runner/types'
11
- import { ApiRequest, ApiResponse } from '@japa/api-client'
12
-
13
- import type { PageProps } from '../types.js'
14
-
15
- declare module '@japa/api-client' {
16
- export interface ApiRequest {
17
- /**
18
- * Set `X-Inertia` header on the request
19
- */
20
- withInertia(): this
21
-
22
- /**
23
- * Set `X-Inertia-Partial-Data` and `X-Inertia-Partial-Component` headers on the request
24
- */
25
- withInertiaPartialReload(component: string, data: string[]): this
26
- }
27
-
28
- export interface ApiResponse {
29
- /**
30
- * The inertia component
31
- */
32
- inertiaComponent?: string
33
-
34
- /**
35
- * The inertia response props
36
- */
37
- inertiaProps: PageProps
38
-
39
- /**
40
- * Assert component name of inertia response
41
- */
42
- assertInertiaComponent(component: string): this
43
-
44
- /**
45
- * Assert props to be exactly the same as the given props
46
- */
47
- assertInertiaProps(props: PageProps): this
48
-
49
- /**
50
- * Assert inertia props contains a subset of the given props
51
- */
52
- assertInertiaPropsContains(props: PageProps): this
53
- }
54
- }
55
-
56
- /**
57
- * Ensure the response is an inertia response, otherwise throw an error
58
- */
59
- function ensureIsInertiaResponse(this: ApiResponse) {
60
- if (!this.header('x-inertia')) {
61
- throw new Error(
62
- 'Response is not an Inertia response. Make sure to call `withInertia()` on the request'
63
- )
64
- }
65
- }
66
-
67
- export function inertiaApiClient(): PluginFn {
68
- return () => {
69
- ApiRequest.macro('withInertia', function (this: ApiRequest) {
70
- this.header('x-inertia', 'true')
71
- return this
72
- })
73
-
74
- ApiRequest.macro(
75
- 'withInertiaPartialReload',
76
- function (this: ApiRequest, component: string, data: string[]) {
77
- this.withInertia()
78
- this.header('X-Inertia-Partial-Data', data.join(','))
79
- this.header('X-Inertia-Partial-Component', component)
80
- return this
81
- }
82
- )
83
-
84
- /**
85
- * Response getters
86
- */
87
- ApiResponse.getter('inertiaComponent', function (this: ApiResponse) {
88
- ensureIsInertiaResponse.call(this)
89
- return this.body().component
90
- })
91
-
92
- ApiResponse.getter('inertiaProps', function (this: ApiResponse) {
93
- ensureIsInertiaResponse.call(this)
94
- return this.body().props
95
- })
96
-
97
- /**
98
- * Response assertions
99
- */
100
- ApiResponse.macro('assertInertiaComponent', function (this: ApiResponse, component: string) {
101
- ensureIsInertiaResponse.call(this)
102
-
103
- this.assert!.deepEqual(this.body().component, component)
104
- return this
105
- })
106
-
107
- ApiResponse.macro(
108
- 'assertInertiaProps',
109
- function (this: ApiResponse, props: Record<string, unknown>) {
110
- this.ensureHasAssert()
111
- ensureIsInertiaResponse.call(this)
112
- this.assert!.deepEqual(this.body().props, props)
113
- return this
114
- }
115
- )
116
-
117
- ApiResponse.macro(
118
- 'assertInertiaPropsContains',
119
- function (this: ApiResponse, props: Record<string, unknown>) {
120
- this.ensureHasAssert()
121
- ensureIsInertiaResponse.call(this)
122
- this.assert!.containsSubset(this.body().props, props)
123
- return this
124
- }
125
- )
126
- }
127
- }
@@ -1,50 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import { encode } from 'html-entities'
11
- import type { PluginFn } from 'edge.js/types'
12
-
13
- import debug from '../debug.js'
14
-
15
- /**
16
- * Register the Inertia tags and globals within Edge
17
- */
18
- export const edgePluginInertia: () => PluginFn<undefined> = () => {
19
- return (edge) => {
20
- debug('sharing globals and inertia tags with edge')
21
-
22
- edge.global('inertia', (page: Record<string, unknown> = {}) => {
23
- if (page.ssrBody) return page.ssrBody
24
- return `<div id="app" data-page="${encode(JSON.stringify(page))}"></div>`
25
- })
26
-
27
- edge.global('inertiaHead', (page: Record<string, unknown>) => {
28
- const { ssrHead = [] }: { ssrHead?: string[] } = page || {}
29
- return ssrHead.join('\n')
30
- })
31
-
32
- edge.registerTag({
33
- block: false,
34
- tagName: 'inertia',
35
- seekable: false,
36
- compile(_, buffer, { filename, loc }) {
37
- buffer.writeExpression(`out += state.inertia(state.page)`, filename, loc.start.line)
38
- },
39
- })
40
-
41
- edge.registerTag({
42
- block: false,
43
- tagName: 'inertiaHead',
44
- seekable: false,
45
- compile(_, buffer, { filename, loc }) {
46
- buffer.writeExpression(`out += state.inertiaHead(state.page)`, filename, loc.start.line)
47
- },
48
- })
49
- }
50
- }
package/src/types.ts DELETED
@@ -1,48 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type { HttpContext } from '@adonisjs/core/http'
11
-
12
- export type MaybePromise<T> = T | Promise<T>
13
-
14
- /**
15
- * Props that will be passed to inertia render method
16
- */
17
- export type PageProps = Record<string, unknown>
18
-
19
- /**
20
- * Shared data types
21
- */
22
- export type Data = string | number | object | boolean
23
- export type SharedDatumFactory = (ctx: HttpContext) => MaybePromise<Data>
24
- export type SharedData = Record<string, Data | SharedDatumFactory>
25
-
26
- /**
27
- * Allowed values for the assets version
28
- */
29
- export type AssetsVersion = string | number | undefined
30
-
31
- export interface InertiaConfig {
32
- /**
33
- * Path to the Edge view that will be used as the root view for Inertia responses.
34
- * @default root (resources/views/root.edge)
35
- */
36
- rootView?: string
37
-
38
- /**
39
- * The version of your assets. Every client request will be checked against this version.
40
- * If the version is not the same, the client will do a full reload.
41
- */
42
- assetsVersion?: AssetsVersion
43
-
44
- /**
45
- * Data that should be shared with all rendered pages
46
- */
47
- sharedData?: SharedData
48
- }
@@ -1,74 +0,0 @@
1
- /*
2
- * @adonisjs/inertia
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import crc32 from 'crc-32'
11
- import { readFile } from 'node:fs/promises'
12
-
13
- import type { AssetsVersion } from './types.js'
14
-
15
- /**
16
- * VersionCache is used to cache the version of the assets.
17
- *
18
- * If the user has provided a version, it will be used.
19
- * Otherwise, we will compute a hash from the manifest file
20
- * and cache it.
21
- */
22
- export class VersionCache {
23
- #cachedVersion?: AssetsVersion
24
-
25
- constructor(
26
- protected appRoot: URL,
27
- protected assetsVersion?: AssetsVersion
28
- ) {
29
- this.#cachedVersion = assetsVersion
30
- }
31
-
32
- /**
33
- * Compute the hash of the manifest file and cache it
34
- */
35
- async #getManifestHash(): Promise<AssetsVersion> {
36
- try {
37
- const manifestPath = new URL('public/assets/manifest.json', this.appRoot)
38
- const manifestFile = await readFile(manifestPath, 'utf-8')
39
- this.#cachedVersion = crc32.str(manifestFile)
40
-
41
- return this.#cachedVersion
42
- } catch {
43
- /**
44
- * If the manifest file does not exist, it probably means that we are in
45
- * development mode
46
- */
47
- this.#cachedVersion = '1'
48
- return this.#cachedVersion
49
- }
50
- }
51
-
52
- /**
53
- * Pre-compute the version
54
- */
55
- async computeVersion() {
56
- if (!this.assetsVersion) await this.#getManifestHash()
57
- return this
58
- }
59
-
60
- /**
61
- * Returns the current assets version
62
- */
63
- getVersion() {
64
- if (!this.#cachedVersion) throw new Error('Version has not been computed yet')
65
- return this.#cachedVersion
66
- }
67
-
68
- /**
69
- * Set the assets version
70
- */
71
- async setVersion(version: AssetsVersion) {
72
- this.#cachedVersion = version
73
- }
74
- }