├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ ├── commitlint.yml
│ ├── main.yml
│ └── release.yml
├── .gitignore
├── .husky
├── .gitignore
├── commit-msg
└── pre-commit
├── .huskyrc
├── .lintstagedrc
├── .nvmrc
├── .nycrc.json
├── .prettierrc
├── README.md
├── adonis-typings
├── index.ts
└── stardust-middleware.ts
├── buildClient.js
├── commitlint.config.js
├── japaFile.js
├── middleware
└── Stardust.ts
├── package-lock.json
├── package.json
├── providers
└── StardustProvider.ts
├── release.config.js
├── src
├── .eslintrc.json
├── client
│ ├── Stardust.ts
│ ├── UrlBuilder.ts
│ └── index.ts
└── tsconfig.json
├── test
├── Stardust.spec.ts
├── StardustProvider.spec.ts
└── utils.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 |
2 | [*]
3 | indent_style = space
4 | indent_size = 2
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.json]
11 | insert_final_newline = ignore
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 | test
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:adonis/typescriptApp", "prettier"],
3 | "ignorePatterns": ["src/*", "client/*"],
4 | "rules": {
5 | "no-console": "error"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/workflows/commitlint.yml:
--------------------------------------------------------------------------------
1 | name: Lint Commit Messages
2 | on: [pull_request]
3 |
4 | jobs:
5 | commitlint:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 | with:
10 | fetch-depth: 0
11 | - uses: wagoid/commitlint-github-action@v2
12 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 |
7 | jobs:
8 | lint-and-test:
9 | name: Test
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v2
14 | - run: npm ci
15 | - run: npm run lint
16 | - run: npm test
17 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | branches: ['main']
6 | jobs:
7 | release:
8 | name: Release
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: actions/setup-node@v2
13 | - run: npm ci
14 | - run: npm run build
15 | - name: Semantic Release
16 | uses: cycjimmy/semantic-release-action@v2
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | client
4 | !src/client
5 | coverage
6 | .vscode
7 | .DS_STORE
8 | .env
9 | tmp
10 | .nyc_output
11 | yarn-error.log
12 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx commitlint --edit
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": ["lint-staged"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.{ts,d.ts}": ["eslint --fix"],
3 | "*.{json,md}": ["prettier --write"]
4 | }
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/*
2 |
--------------------------------------------------------------------------------
/.nycrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "check-coverage": true,
3 | "reporter": ["text", "lcov"],
4 | "exclude": ["build/**", "japaFile.js", "test/", "providers/InertiaProvider/index.ts"],
5 | "branches": 50,
6 | "lines": 70,
7 | "functions": 70,
8 | "statements": 70
9 | }
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | endOfLine: lf
2 | printWidth: 120
3 | singleQuote: true
4 | trailingComma: all
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Adonis Stardust
2 |
3 | 
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | # ⭐ Adonis Stardust ⭐
16 |
17 | Use your adonis named stardust in the client.
18 |
19 | ## Installation
20 |
21 | ```shell
22 | npm i @eidellev/adonis-stardust
23 |
24 | node ace configure @eidellev/adonis-stardust
25 | ```
26 |
27 | ## Setup
28 |
29 | ### Register Middleware
30 |
31 | Add the Stardust middleware to `start/kernel.ts`:
32 |
33 | ```typescript
34 | Server.middleware.register([
35 | () => import('@ioc:Adonis/Core/BodyParser'),
36 | () => import('@ioc:EidelLev/Stardust/Middleware'),
37 | ]);
38 | ```
39 |
40 | ### Register a Named Route
41 |
42 | Create a named route in your stardust file:
43 |
44 | ```typescript
45 | Route.get('users/:id', () => {
46 | ...
47 | }).as('users.show');
48 | ```
49 |
50 | ### In Your View
51 |
52 | Add the `@routes` Edge tag to your main layout (before your application's JavaScript).
53 |
54 | ```blade
55 | @routes
56 | @entryPointStyles('app')
57 | @entryPolintScripts('app')
58 | ```
59 |
60 | ## Client-Side Usage
61 |
62 | ### Client Setup
63 |
64 | Stardust should be initialized as early as possible, e.g. in your application's entrypoint
65 |
66 | ```typescript
67 | import { initRoutes } from '@eidellev/adonis-stardust/client';
68 |
69 | initRoutes();
70 | ```
71 |
72 | Now you can use the `stardust` helper to access your adonis routes:
73 |
74 | ```typescript
75 | import { stardust } from '@eidellev/adonis-stardust/client';
76 |
77 | stardust.route('users.show', { id: 1 }); // => `/users/1`
78 |
79 | /**
80 | * You can also pass path params as an array and they will populated
81 | * according to their order:
82 | */
83 | stardust.route('users.show', [1]); // => `/users/1`
84 | ```
85 |
86 | You can also pass query parameters like so:
87 |
88 | ```typescript
89 | stardust.route('tasks.index', undefined, { qs: { tags: ['work', 'personal'] } });
90 | // `/tasks?tags=work,personal
91 | ```
92 |
93 | ### Checking the Current Route
94 |
95 | ```typescript
96 | stardust.current; // => 'tasks.index'
97 | stardust.isCurrent('tasks.index'); // => true
98 | ```
99 |
--------------------------------------------------------------------------------
/adonis-typings/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/adonis-typings/stardust-middleware.ts:
--------------------------------------------------------------------------------
1 | declare module '@ioc:EidelLev/Stardust/Middleware' {
2 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
3 |
4 | export default class StardustMiddleware {
5 | public handle(ctx: HttpContextContract, next: () => Promise);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/buildClient.js:
--------------------------------------------------------------------------------
1 | require('esbuild').buildSync({
2 | entryPoints: ['src/client/index.ts'],
3 | outfile: 'client/index.js',
4 | allowOverwrite: true,
5 | bundle: true,
6 | platform: 'node',
7 | target: ['chrome91', 'edge90', 'firefox90', 'safari13'],
8 | });
9 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ['@commitlint/config-conventional']}
2 |
--------------------------------------------------------------------------------
/japaFile.js:
--------------------------------------------------------------------------------
1 | require('@adonisjs/require-ts/build/register');
2 |
3 | const { configure } = require('japa');
4 |
5 | configure({
6 | files: ['test/**/*.spec.ts'],
7 | });
8 |
--------------------------------------------------------------------------------
/middleware/Stardust.ts:
--------------------------------------------------------------------------------
1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
2 |
3 | export default class StardustMiddleware {
4 | public async handle({ request }: HttpContextContract, next: () => Promise) {
5 | const { pathname } = new URL(request.completeUrl());
6 |
7 | globalThis.stardust = {
8 | ...globalThis.stardust,
9 | pathname,
10 | };
11 |
12 | await next();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@eidellev/adonis-stardust",
3 | "version": "1.1.0",
4 | "private": false,
5 | "description": "Use adonis routes in the client",
6 | "repository": "https://github.com/eidellev/adonis-stardust",
7 | "bugs": "https://github.com/eidellev/adonis-stardust/issues",
8 | "main": "build/providers/StardustProvider.js",
9 | "types": "build/client/index.d.ts",
10 | "typings": "./build/adonis-typings/index.d.ts",
11 | "files": [
12 | "build/providers",
13 | "build/adonis-typings",
14 | "build/middleware",
15 | "client"
16 | ],
17 | "adonisjs": {
18 | "providers": [
19 | "@eidellev/adonis-stardust"
20 | ]
21 | },
22 | "license": "MIT",
23 | "scripts": {
24 | "lint": "tsc --noEmit && eslint . --ext=ts",
25 | "lint:fix": "eslint . --ext=ts --fix",
26 | "clean": "rimraf build",
27 | "build": "cross-env npm run clean && npm run build:node && npm run build:client",
28 | "build:node": "tsc",
29 | "build:client": "node buildClient && tsc -p ./src/tsconfig.json",
30 | "watch": "cross-env npm run clean && tsc -w",
31 | "test": "nyc node japaFile.js"
32 | },
33 | "peerDependencies": {
34 | "@adonisjs/core": ">=5"
35 | },
36 | "devDependencies": {
37 | "@adonisjs/core": "^5.3.4",
38 | "@adonisjs/mrm-preset": "^4.1.2",
39 | "@adonisjs/require-ts": "^2.0.8",
40 | "@adonisjs/view": "^6.1.1",
41 | "@commitlint/cli": "^13.1.0",
42 | "@commitlint/config-conventional": "^13.1.0",
43 | "@commitlint/prompt-cli": "^13.1.0",
44 | "@poppinss/dev-utils": "^1.1.5",
45 | "@typescript-eslint/parser": "4.33.0",
46 | "adonis-preset-ts": "^2.1.0",
47 | "copyfiles": "^2.4.1",
48 | "cross-env": "^7.0.3",
49 | "esbuild": "^0.12.28",
50 | "eslint": "^7.32.0",
51 | "eslint-config-prettier": "^8.3.0",
52 | "eslint-plugin-adonis": "^1.3.3",
53 | "eslint-plugin-prettier": "^4.0.0",
54 | "husky": "^7.0.2",
55 | "japa": "^3.1.1",
56 | "lint-staged": "^11.1.2",
57 | "nyc": "^15.1.0",
58 | "prettier": "^2.4.0",
59 | "rimraf": "^3.0.2",
60 | "semantic-release": "^17.4.7",
61 | "supertest": "^6.1.6",
62 | "typescript": "4.8.2"
63 | },
64 | "dependencies": {
65 | "@poppinss/matchit": "^3.1.2"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/providers/StardustProvider.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationContract } from '@ioc:Adonis/Core/Application';
2 | import { RouterContract } from '@ioc:Adonis/Core/Route';
3 | import { ViewContract } from '@ioc:Adonis/Core/View';
4 | import StardustMiddleware from '../middleware/Stardust';
5 |
6 | /*
7 | |--------------------------------------------------------------------------
8 | | Stardust Provider
9 | |--------------------------------------------------------------------------
10 | */
11 | export default class StardustProvider {
12 | constructor(protected app: ApplicationContract) {}
13 | public static needsApplication = true;
14 |
15 | /**
16 | * Returns list of named routes
17 | */
18 | private getNamedRoutes(Route: RouterContract) {
19 | /**
20 | * Only sharing the main domain routes. Subdomains are
21 | * ignored for now. Let's see if many people need it
22 | */
23 | const mainDomainRoutes = Route.toJSON()?.['root'] ?? [];
24 |
25 | return mainDomainRoutes.reduce>((routes, route) => {
26 | if (route.name) {
27 | routes[route.name] = route.pattern;
28 | } else if (typeof route.handler === 'string') {
29 | routes[route.handler] = route.pattern;
30 | }
31 |
32 | return routes;
33 | }, {});
34 | }
35 |
36 | /**
37 | * Register the `@routes()` tag
38 | */
39 | private registerStardustTag(View: ViewContract) {
40 | View.registerTag({
41 | block: false,
42 | tagName: 'routes',
43 | seekable: false,
44 | compile(_, buffer, token) {
45 | buffer.writeExpression(
46 | `\n
47 | out += template.sharedState.routes(template.sharedState.cspNonce)
48 | `,
49 | token.filename,
50 | token.loc.start.line,
51 | );
52 | },
53 | });
54 | }
55 |
56 | private registerRoutesGlobal(View: ViewContract, namedRoutes: Record) {
57 | View.global('routes', (cspNonce?: string) => {
58 | return `
59 |
62 | `;
63 | });
64 | }
65 |
66 | /**
67 | * Registers named routes on the global scope in order to seamlessly support
68 | * stardust's functionality on the server
69 | * @param namedRoutes
70 | */
71 | private registerSsrRoutes(namedRoutes: Record) {
72 | globalThis.stardust = { namedRoutes };
73 | }
74 |
75 | public ready() {
76 | this.app.container.bind('EidelLev/Stardust/Middleware', () => StardustMiddleware);
77 |
78 | this.app.container.withBindings(['Adonis/Core/View', 'Adonis/Core/Route'], (View, Route) => {
79 | const namedRoutes = this.getNamedRoutes(Route);
80 |
81 | this.registerRoutesGlobal(View, namedRoutes);
82 | this.registerStardustTag(View);
83 | this.registerSsrRoutes(namedRoutes);
84 | });
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branches: ['main'],
3 | };
4 |
--------------------------------------------------------------------------------
/src/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:@typescript-eslint/recommended"],
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint"],
5 | "parserOptions": {
6 | "project": "./tsconfig.json"
7 | },
8 | "rules": {
9 | "no-console": "off"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/client/Stardust.ts:
--------------------------------------------------------------------------------
1 | import { match, parse } from '@poppinss/matchit';
2 | import { UrlBuilder } from './UrlBuilder';
3 |
4 | /**
5 | * Options accepted by the route method
6 | */
7 | interface RouteOptions {
8 | qs?: Record;
9 | prefixUrl?: string;
10 | }
11 |
12 | export class Stardust {
13 | private routes: Record = {};
14 | private reverseRoutes: Record = {};
15 | private parsedRoutePatterns: any[];
16 |
17 | constructor(namedRoutes: Record) {
18 | if (!namedRoutes) {
19 | console.error('Routes could not be found. Please make sure you use the `@routes()` tag in your view!');
20 | return;
21 | }
22 |
23 | const parsedRoutePatterns = Object.entries(namedRoutes).map(([, pattern]) => parse(pattern));
24 |
25 | this.routes = namedRoutes;
26 | this.reverseRoutes = Object.fromEntries(Object.entries(namedRoutes).map(([key, value]) => [value, key]));
27 | this.parsedRoutePatterns = parsedRoutePatterns;
28 | }
29 |
30 | /**
31 | * Returns all AdonisJS named routes
32 | * @example
33 | * ```typescript
34 | * import { stardust } from '@eidellev/adonis-stardust';
35 | * ...
36 | * stardust.getRoutes();
37 | * ```
38 | */
39 | public getRoutes() {
40 | return this.routes;
41 | }
42 |
43 | /**
44 | * Get URL builder instance to make the URL
45 | * @returns Instance of URL builder
46 | */
47 | public builder() {
48 | return new UrlBuilder(this.routes);
49 | }
50 |
51 | /**
52 | * Resolve Adonis route
53 | * @param route Route name
54 | * @param params Route path params
55 | * @param options Make url options
56 | * @returns Full path with params
57 | */
58 | public route(route: string, params?: any[] | Record, options?: RouteOptions): string {
59 | return new UrlBuilder(this.routes).params(params).qs(options?.qs).prefixUrl(options?.prefixUrl).make(route);
60 | }
61 |
62 | /**
63 | * Current route.
64 | * If the current route doesn't match any named routes, the returned value will be `null`
65 | * @example
66 | * ```typescript
67 | * import { stardust } from '@eidellev/adonis-stardust';
68 | * ...
69 | * stardust.current; // => 'users.index'
70 | * ```
71 | */
72 | public get current(): string | null {
73 | const [matchedRoute] = match(this.pathname, this.parsedRoutePatterns);
74 |
75 | if (!matchedRoute) {
76 | return null;
77 | }
78 |
79 | const { old: pattern } = matchedRoute;
80 | return this.reverseRoutes[pattern];
81 | }
82 |
83 | private get pathname() {
84 | /**
85 | * When rendering on the server
86 | */
87 | if (globalThis.stardust.pathname) {
88 | return globalThis.stardust.pathname;
89 | }
90 |
91 | const { pathname } = new URL((window ?? globalThis).location.href);
92 | return pathname;
93 | }
94 |
95 | /**
96 | * Checks if a given route is the current route
97 | * @example
98 | * ```typescript
99 | * import { stardust } from '@eidellev/adonis-stardust';
100 | * ...
101 | * stardust.isCurrent('users.index'); // => true/false
102 | * ```
103 | */
104 | public isCurrent(route: string): boolean {
105 | return route === this.current;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/client/UrlBuilder.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copy of https://github.com/adonisjs/http-server/blob/develop/src/Router/LookupStore.ts#L26
3 | * with a few modifications like remove the `makeSigned` method.
4 | */
5 | export class UrlBuilder {
6 | /**
7 | * Params to be used for building the URL
8 | */
9 | private routeParams: any[] | Record;
10 |
11 | /**
12 | * A custom query string to append to the URL
13 | */
14 | private queryString: Record = {};
15 |
16 | /**
17 | * BaseURL to prefix to the endpoint
18 | */
19 | private baseUrl: string;
20 |
21 | constructor(private routes: Record) {}
22 |
23 | /**
24 | * Processes the pattern with the route params
25 | */
26 | private processPattern(pattern: string): string {
27 | let url: string[] = [];
28 | const isParamsAnArray = Array.isArray(this.routeParams);
29 |
30 | /*
31 | * Split pattern when route has dynamic segments
32 | */
33 | const tokens = pattern.split('/');
34 | let paramsIndex = 0;
35 |
36 | for (const token of tokens) {
37 | /**
38 | * Expected wildcard param to be at the end always and hence
39 | * we must break out from the loop
40 | */
41 | if (token === '*') {
42 | const wildcardParams = isParamsAnArray ? this.routeParams.slice(paramsIndex) : this.routeParams['*'];
43 | if (!Array.isArray(wildcardParams)) {
44 | throw new Error('Wildcard param must pass an array of values');
45 | }
46 |
47 | if (!wildcardParams.length) {
48 | throw new Error(`Wildcard param is required to make URL for "${pattern}" route`);
49 | }
50 |
51 | url = url.concat(wildcardParams);
52 | break;
53 | }
54 |
55 | /**
56 | * Token is a static value
57 | */
58 | if (!token.startsWith(':')) {
59 | url.push(token);
60 | } else {
61 | const isOptional = token.endsWith('?');
62 | const paramName = token.replace(/^:/, '').replace(/\?$/, '');
63 | const param = isParamsAnArray ? this.routeParams[paramsIndex] : this.routeParams[paramName];
64 |
65 | paramsIndex++;
66 |
67 | /*
68 | * A required param is always required to make the complete URL
69 | */
70 | if (!param && !isOptional) {
71 | throw new Error(`"${param}" param is required to make URL for "${pattern}" route`);
72 | }
73 |
74 | url.push(param);
75 | }
76 | }
77 |
78 | return url.join('/');
79 | }
80 |
81 | /**
82 | * Finds the route inside the list of registered routes and
83 | * raises exception when unable to
84 | */
85 | private findRouteOrFail(identifier: string) {
86 | const route = this.routes[identifier];
87 | if (!route) {
88 | throw new Error(`Cannot find route for "${identifier}"`);
89 | }
90 |
91 | return route;
92 | }
93 |
94 | /**
95 | * Suffix the query string to the URL
96 | */
97 | private suffixQueryString(url: string): string {
98 | if (this.queryString) {
99 | const params = new URLSearchParams();
100 |
101 | for (const [key, value] of Object.entries(this.queryString)) {
102 | if (Array.isArray(value)) {
103 | value.forEach((item) => params.append(key, item));
104 | } else {
105 | params.set(key, value);
106 | }
107 | }
108 |
109 | const encoded = params.toString();
110 | url = encoded ? `${url}?${encoded}` : url;
111 | }
112 |
113 | return url;
114 | }
115 |
116 | /**
117 | * Prefix a custom url to the final URI
118 | */
119 | public prefixUrl(url?: string): this {
120 | if (url) {
121 | this.baseUrl = url;
122 | }
123 | return this;
124 | }
125 |
126 | /**
127 | * Append query string to the final URI
128 | */
129 | public qs(queryString?: Record): this {
130 | if (queryString) {
131 | this.queryString = queryString;
132 | }
133 | return this;
134 | }
135 |
136 | /**
137 | * Define required params to resolve the route
138 | */
139 | public params(params?: any[] | Record): this {
140 | if (params) {
141 | this.routeParams = params;
142 | }
143 | return this;
144 | }
145 |
146 | /**
147 | * Generate url for the given route identifier
148 | */
149 | public make(identifier: string) {
150 | const route = this.findRouteOrFail(identifier);
151 | const url = this.processPattern(route);
152 | return this.suffixQueryString(this.baseUrl ? `${this.baseUrl}${url}` : url);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/client/index.ts:
--------------------------------------------------------------------------------
1 | import { Stardust } from './Stardust';
2 |
3 | declare global {
4 | interface Window {
5 | stardust: { namedRoutes: Record };
6 | }
7 | }
8 |
9 | export let stardust: Stardust;
10 |
11 | /**
12 | * Initialize stardust
13 | */
14 | export function initRoutes() {
15 | const { namedRoutes } = (globalThis ?? window).stardust;
16 | stardust = new Stardust(namedRoutes);
17 | }
18 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "moduleResolution": "node",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": false,
9 | "skipLibCheck": true,
10 | "emitDeclarationOnly": true,
11 | "declaration": true,
12 | "outDir": "../client"
13 | },
14 | "include": ["./client"]
15 | }
16 |
--------------------------------------------------------------------------------
/test/Stardust.spec.ts:
--------------------------------------------------------------------------------
1 | import test from 'japa';
2 | import { Stardust } from '../src/client/Stardust';
3 |
4 | test.group('Client', () => {
5 | test('should return all routes', (assert) => {
6 | const namedRoutes = { 'tasks.show': '/tasks/:id' };
7 | const stardust = new Stardust(namedRoutes);
8 |
9 | assert.equal(stardust.getRoutes(), namedRoutes);
10 | });
11 |
12 | test('should resolve route with object params', (assert) => {
13 | const stardust = new Stardust({ 'tasks.show': '/tasks/:id' });
14 |
15 | assert.equal(stardust.route('tasks.show', { id: 1 }), '/tasks/1');
16 | });
17 |
18 | test('should resolve route with array of params', (assert) => {
19 | const stardust = new Stardust({ 'tasks.show': '/tasks/:id' });
20 |
21 | assert.equal(stardust.route('tasks.show', [1]), '/tasks/1');
22 | });
23 |
24 | test('should resolve route with query string', (assert) => {
25 | const stardust = new Stardust({ 'tasks.show': '/tasks' });
26 |
27 | assert.equal(stardust.route('tasks.show', undefined, { qs: { status: 'done' } }), '/tasks?status=done');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/StardustProvider.spec.ts:
--------------------------------------------------------------------------------
1 | import test from 'japa';
2 | import { setup, teardown } from './utils';
3 |
4 | test.group('Server', (group) => {
5 | group.afterEach(async () => {
6 | await teardown();
7 | });
8 |
9 | test('Should handle empty router gracefully', async (assert) => {
10 | const app = await setup();
11 | await app.start();
12 |
13 | const view = app.container.use('Adonis/Core/View');
14 |
15 | view.registerTemplate('dummy', { template: '@routes()' });
16 |
17 | assert.equal(
18 | (await view.render('dummy')).trim(),
19 | ``,
22 | );
23 | });
24 |
25 | test('Should render nonce', async (assert) => {
26 | const app = await setup();
27 | await app.start();
28 |
29 | const view = app.container.use('Adonis/Core/View');
30 | const renderer = view.share({ cspNonce: 'test' });
31 |
32 | assert.equal(
33 | (await renderer.renderRaw('@routes')).trim(),
34 | ``,
37 | );
38 | });
39 |
40 | test('Should render named routes', async (assert) => {
41 | const app = await setup();
42 | const router = app.container.use('Adonis/Core/Route');
43 | const view = app.container.use('Adonis/Core/View');
44 |
45 | router.get('/', async () => {}).as('index');
46 | router.post('/users', async () => {}).as('users.store');
47 | router.commit();
48 |
49 | await app.start();
50 |
51 | view.registerTemplate('dummy', { template: '@routes()' });
52 | const dummy = await view.render('dummy');
53 |
54 | assert.equal(
55 | dummy.trim(),
56 | ``,
59 | );
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/utils.ts:
--------------------------------------------------------------------------------
1 | import { Filesystem } from '@poppinss/dev-utils';
2 | import { join } from 'path';
3 | import { Application } from '@adonisjs/core/build/standalone';
4 |
5 | export const fs = new Filesystem(join(__dirname, 'app'));
6 |
7 | export async function setup() {
8 | await fs.add(
9 | 'config/app.ts',
10 | `export const appKey = '${Math.random().toFixed(36).substring(2, 38)}',
11 | export const http = {
12 | cookie: {},
13 | trustProxy: () => true,
14 | }`,
15 | );
16 |
17 | const app = new Application(fs.basePath, 'web', {
18 | providers: ['@adonisjs/core', '@adonisjs/view', '../../providers/StardustProvider'],
19 | });
20 |
21 | await app.setup();
22 | await app.registerProviders();
23 | await app.bootProviders();
24 |
25 | return app;
26 | }
27 |
28 | export async function teardown() {
29 | await fs.cleanup();
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig",
3 | "compilerOptions": {
4 | "lib": ["dom", "es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"],
5 | "skipLibCheck": true
6 | },
7 | "files": [
8 | "./node_modules/@adonisjs/core/build/adonis-typings/index.d.ts",
9 | "./node_modules/@adonisjs/view/build/adonis-typings/index.d.ts"
10 | ],
11 | "exclude": ["build/*", "node_modules/*", "src/*", "test/*"]
12 | }
13 |
--------------------------------------------------------------------------------