Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@gracile/engine

Package Overview
Dependencies
Maintainers
0
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gracile/engine - npm Package Compare versions

Comparing version 0.2.0-next.1 to 0.2.0-next.2

dist/server/node.d.ts

2

ambient.d.ts

@@ -10,4 +10,4 @@ /// <reference types="vite/client" />

declare namespace App {
declare namespace Gracile {
interface Locals {}
}

@@ -7,17 +7,24 @@ /* eslint-disable @typescript-eslint/no-floating-promises */

import { isLitNormalTemplate, isLitServerTemplate, isLitTemplate, isUnknownObject, } from './assertions.js';
describe('should assert lit templates', () => {
test('assert lit', () => {
const lit = html ` <div>Hello</div> `;
const litServer = serverHtml ` <div>Hello</div> `;
describe('should assert lit templates, unknown objects', () => {
const lit = html ` <div>Hello</div> `;
const litServer = serverHtml ` <div>Hello</div> `;
test('assert lit any', () => {
assert.equal(isLitTemplate(lit), true);
assert.equal(isLitTemplate(litServer), true);
});
test('assert lit normal', () => {
assert.equal(isLitNormalTemplate(lit), true);
});
test('assert lit server', () => {
assert.equal(isLitServerTemplate(litServer), true);
assert.equal(isUnknownObject({ something: 'something' }), true);
//
});
test('wrong lit templates', () => {
assert.equal(isLitTemplate([]), false);
assert.equal(isLitTemplate([]), false);
assert.equal(isLitNormalTemplate([]), false);
assert.equal(isLitServerTemplate([]), false);
});
test('unknown object', () => {
assert.equal(isUnknownObject({ something: 'something' }), true);
});
//
});
import { type ViteDevServer } from 'vite';
import { type ConnectLikeAsyncMiddleware } from '../server/request.js';
export declare function createHandlers({ vite, }: {
import { type GracileAsyncMiddleware } from '../server/request.js';
export declare function createDevHandler({ vite, }: {
vite: ViteDevServer;
}): Promise<{
handlers: ConnectLikeAsyncMiddleware;
handler: GracileAsyncMiddleware;
}>;
//# sourceMappingURL=dev.d.ts.map

@@ -6,5 +6,5 @@ import { logger } from '@gracile/internal-utils/logger';

import { createGracileMiddleware, } from '../server/request.js';
export async function createHandlers({ vite, }) {
export async function createDevHandler({ vite, }) {
const root = vite.config.root;
logger.info(c.green('creating handler…'), { timestamp: true });
logger.info(c.dim('\nCreating handler…'), { timestamp: true });
const routes = await collectRoutes(root /* vite */);

@@ -28,3 +28,3 @@ vite.watcher.on('all', (event, file) => {

const gracile = createGracileMiddleware({ vite, root, serverMode, routes });
return { handlers: gracile };
return { handler: gracile };
}

@@ -1,5 +0,3 @@

import { type PluginOption } from 'vite';
export declare const gracile: (config?: {
mode: "server" | "static";
}) => PluginOption;
import type { GracileConfig } from './user-config.js';
export declare const gracile: (config?: GracileConfig) => any[];
//# sourceMappingURL=plugin.d.ts.map

@@ -0,10 +1,17 @@

import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { logger } from '@gracile/internal-utils/logger';
import { rename, rm } from 'fs/promises';
import c from 'picocolors';
import { build, createServer } from 'vite';
import {} from './build/static.js';
import { createHandlers } from './dev/dev.js';
import { createDevHandler } from './dev/dev.js';
import { nodeAdapter } from './server/node.js';
import { buildRoutes } from './vite/plugins/build-routes.js';
import { virtualRoutes } from './vite/plugins/virtual-routes.js';
// Return as `any` to avoid Plugin type mismatches when there are multiple Vite versions installed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const gracile = (config) => {
const mode = config?.mode || 'static';
const outputMode = config?.output || 'static';
const clientAssets = {};

@@ -25,7 +32,13 @@ let routes = null;

async configureServer(server) {
// ({ plmugi: server.config.root });
const { handlers } = await createHandlers({ vite: server });
// Infos
const mainPjson = fileURLToPath(new URL('../../gracile/package.json', import.meta.url));
const { version } = JSON.parse(await readFile(mainPjson, 'utf-8'));
logger.info(`${c.cyan(c.italic(c.underline('🧚 Gracile ')))}` +
` ${c.green(` v${version}`)}`);
// ---
const { handler } = await createDevHandler({ vite: server });
return () => {
server.middlewares.use((req, res, next) => {
Promise.resolve(handlers(req, res, next)).catch((error) => next(error));
const locals = config?.dev?.locals?.({ nodeRequest: req });
Promise.resolve(nodeAdapter(handler)(req, res, locals)).catch((error) => next(error));
});

@@ -47,3 +60,3 @@ };

_config: {},
serverMode: mode === 'server',
serverMode: outputMode === 'server',
});

@@ -60,3 +73,3 @@ routes = htmlPages.routes;

},
outDir: mode === 'server' ? 'dist/client' : 'dist',
outDir: outputMode === 'server' ? 'dist/client' : 'dist',
},

@@ -69,3 +82,3 @@ };

writeBundle(_, bundle) {
if (mode === 'static')
if (outputMode === 'static')
return;

@@ -85,3 +98,3 @@ Object.entries(bundle).forEach(([, file]) => {

async closeBundle() {
if (mode === 'static' || !routes || !renderedRoutes)
if (outputMode === 'static' || !routes || !renderedRoutes)
return;

@@ -88,0 +101,0 @@ await build({

@@ -43,3 +43,3 @@ import type { ServerRenderedTemplate } from '@lit-labs/ssr';

request: Request;
locals: App.Locals;
locals: Gracile.Locals;
/**

@@ -46,0 +46,0 @@ * Let you mutate the downstream **page** response.

@@ -1,5 +0,4 @@

import type { IncomingMessage, ServerResponse } from 'node:http';
import { Readable } from 'node:stream';
import type { ViteDevServer } from 'vite';
import type * as R from '../routes/route.js';
type NextFunction = (error?: unknown) => void | Promise<void>;
/**

@@ -11,3 +10,3 @@ * This is fully compatible with Express or bare Node HTTP

*/
export type ConnectLikeAsyncMiddleware = (req: IncomingMessage, res: ServerResponse, next: NextFunction, locals?: unknown) => Promise<void | NextFunction | ServerResponse> | (void | NextFunction | ServerResponse);
export type GracileAsyncMiddleware = (request: Request, locals?: unknown) => Promise<Response | Readable | null>;
export declare function createGracileMiddleware({ vite, routes, routeImports, routeAssets, root, serverMode, }: {

@@ -20,4 +19,3 @@ vite?: ViteDevServer | undefined;

serverMode?: boolean | undefined;
}): ConnectLikeAsyncMiddleware;
export {};
}): GracileAsyncMiddleware;
//# sourceMappingURL=request.d.ts.map

@@ -1,4 +0,3 @@

import { Readable, Writable } from 'node:stream';
import { Readable } from 'node:stream';
import { logger } from '@gracile/internal-utils/logger';
import { createServerAdapter } from '@whatwg-node/server';
import c from 'picocolors';

@@ -10,27 +9,21 @@ import { isUnknownObject } from '../assertions.js';

import { getRoute } from '../routes/match.js';
// NOTE: Find a more canonical way to ponyfill the Node HTTP request to standard Request
// @ts-expect-error Abusing this feature!
const adapter = createServerAdapter((request) => request);
export function createGracileMiddleware({ vite, routes, routeImports, routeAssets, root, serverMode, }) {
const middleware = async (req, res, next, locals) => {
async function createErrorPage(urlPath, e) {
logger.error(e.message);
let errorPageHtml = await renderSsrTemplate(errorPage(e));
if (vite)
errorPageHtml = await vite.transformIndexHtml(urlPath, errorPageHtml);
return errorPageHtml;
}
const middleware = async (request, locals) => {
// HACK: Typing workaround
if (!req.url)
if (!request.url)
throw Error('Incorrect url');
if (!req.method)
if (!request.method)
throw Error('Incorrect method');
const { url: urlPath, method } = req;
const { url: urlPath, method } = request;
// if (urlPath === '/favicon.ico') return next();
async function createErrorPage(e) {
logger.error(e.message);
let errorPageHtml = await renderSsrTemplate(errorPage(e));
if (vite)
errorPageHtml = await vite.transformIndexHtml(urlPath, errorPageHtml);
return errorPageHtml;
}
try {
const requestPonyfilled = (await Promise.resolve(adapter.handleNodeRequest(
// HACK: Incorrect typings
req)));
// NOTE: Maybe it should be constructed from `req`
const fullUrl = requestPonyfilled.url;
const fullUrl = request.url;
// MARK: Get route infos

@@ -54,9 +47,10 @@ const routeOptions = {

const message = `404 not found!\n\n---\n\nCreate a /src/routes/404.{js,ts} to get a custom page.\n${method} - ${urlPath}`;
res.statusCode = 404;
res.statusMessage = '404 not found!';
const errorPage404 = await createErrorPage(new Error(message));
return res.end(errorPage404);
const errorPage404 = await createErrorPage(urlPath, new Error(message));
return new Response(errorPage404, {
status: 404,
statusText: '404 not found!',
});
}
const routeTemplateOptions = {
request: requestPonyfilled,
request,
vite,

@@ -73,9 +67,3 @@ mode: 'dev', // vite && vite.config.mode === 'dev' ? 'dev' : 'build',

let output;
// TODO: should move this to `special-file` so we don't recalculate on each request
// + we would be able to do some route codegen.
const response = {};
// NOTE: Only for Express for now.
// console.log({ locals });
let providedLocals = {};
// if ('locals' in res && isUnknownObject(res.locals)) locals = res.locals;
if (locals && isUnknownObject(locals))

@@ -85,8 +73,12 @@ providedLocals = locals;

const handler = routeInfos.routeModule.handler;
if ('handler' in routeInfos.routeModule &&
typeof handler !== 'undefined') {
const responseInit = {};
// TODO: should move this to `special-file` so we don't recalculate on each request
// + we would be able to do some route codegen.
if (('handler' in routeInfos.routeModule &&
typeof handler !== 'undefined') ||
(handler && 'GET' in handler === false && method !== 'GET')) {
const routeContext = Object.freeze({
request: requestPonyfilled,
request,
url: new URL(fullUrl),
response,
response: responseInit,
params: routeInfos.params,

@@ -125,4 +117,4 @@ locals: providedLocals,

}
else if (requestPonyfilled.method in handler) {
const handlerWithMethod = handler[requestPonyfilled.method];
else if (method in handler) {
const handlerWithMethod = handler[method];
if (typeof handlerWithMethod !== 'function')

@@ -137,2 +129,3 @@ throw TypeError('Handler must be a function.');

handlerInfos: { data: handlerOutput, method },
routeInfos,
}).then((r) => r.output);

@@ -142,16 +135,5 @@ }

}
else if (handler &&
'GET' in handler === false &&
requestPonyfilled.method === 'GET') {
output = await renderRouteTemplate({
...routeTemplateOptions,
handlerInfos: { data: null, method: 'GET' },
routeInfos,
}).then((r) => r.output);
}
else {
const message = `This route doesn't handle the \`${method}\` method!`;
res.statusCode = 404;
res.statusMessage = message;
return res.end(await createErrorPage(new Error(message)));
const statusText = `This route doesn't handle the \`${method}\` method!`;
return new Response(statusText, { status: 405, statusText });
}

@@ -163,2 +145,3 @@ }

handlerInfos: { data: null, method: 'GET' },
routeInfos,
}).then((r) => r.output);

@@ -171,56 +154,24 @@ }

const location = output.headers.get('location');
// if (location) return res.redirect(location);
if (location) {
res.statusCode = output.status;
res.setHeader('location', location);
return res.end(`Found. Redirecting to ${location}`);
return Response.redirect(location, output.status);
}
}
output.headers?.forEach((content, header) => res.setHeader(header, content));
if (output.status)
res.statusCode = output.status;
if (output.statusText)
res.statusMessage = output.statusText;
// TODO: use this with page only?
// if (output.bodyUsed === false)
// throw new Error('Missing body.');
if (output.body) {
const piped = await output.body
.pipeTo(Writable.toWeb(res))
.catch((e) => logger.error(String(e)));
return piped;
}
// else throw new Error('Missing body.');
// NOTE: Other shapes
return res.end(output);
return output;
// MARK: Stream page render
}
new Headers(response.headers)?.forEach((content, header) => res.setHeader(header, content));
if (response.status)
res.statusCode = response.status;
if (response.statusText)
res.statusMessage = response.statusText;
res.setHeader('Content-Type', 'text/html');
// MARK: Page stream error
return output
?.on('error', (error) => {
const errorMessage = `There was an error while rendering a template chunk on server-side.\n` +
`It was omitted from the resulting HTML.`;
logger.error(errorMessage);
logger.error(error.message);
res.statusCode = 500;
res.statusMessage = errorMessage;
/* NOTE: Safety closing tags, maybe add more */
// Maybe just returning nothing is better to not break the page?
// Should send a overlay message anyway via WebSocket
// vite.ws.send()
if (vite)
setTimeout(() => {
vite.hot.send('gracile:ssr-error', {
message: errorMessage,
});
}, 500);
return res.end('' /* errorInline(error) */);
})
.pipe(res);
return !output
? null
: output.on('error', (error) => {
const errorMessage = `There was an error while rendering a template chunk on server-side.\n` +
`It was omitted from the resulting HTML.`;
logger.error(errorMessage);
logger.error(error.message);
if (vite)
setTimeout(() => {
vite.hot.send('gracile:ssr-error', {
message: errorMessage,
});
}, 500);
});
// MARK: Errors

@@ -232,6 +183,7 @@ }

vite.ssrFixStacktrace(error);
const ultimateErrorPage = await createErrorPage(error);
res.statusCode = 500;
res.statusMessage = 'Gracile middleware error';
return res.end(ultimateErrorPage);
const ultimateErrorPage = await createErrorPage('__gracile_error', error);
return new Response(String(ultimateErrorPage), {
status: 500,
statusText: 'Gracile middleware error',
});
}

@@ -238,0 +190,0 @@ };

import type { IncomingMessage, Server, ServerResponse } from 'node:http';
export declare function printAddressInfos(options: {
instance?: Server;
address: string;
server?: Server;
address?: string;
}): void;

@@ -22,2 +22,3 @@ export declare function notFoundHandler(req: IncomingMessage, res: ServerResponse): void;

}) => boolean) => BasicAuthUser;
export declare function getClientDistPath(root: string): string;
//# sourceMappingURL=utils.d.ts.map
// NOTE: Util. to pretty print for user provided server.
import { fileURLToPath } from 'node:url';
// import type { AddressInfo } from 'node:net';

@@ -6,7 +7,7 @@ import { logger } from '@gracile/internal-utils/logger';

import c from 'picocolors';
import { IP_EXPOSED } from './env.js';
import { CLIENT_DIST_DIR, IP_EXPOSED } from './env.js';
export function printAddressInfos(options) {
let address = null;
if (options.instance) {
const infos = options.instance.address();
if (options.server) {
const infos = options.server.address();
if (typeof infos === 'object' && infos && infos.port && infos.address) {

@@ -19,3 +20,3 @@ address = `http://${infos.address}:${infos.port}/`;

}
else
if (!address)
throw new Error('Incorrect options');

@@ -26,5 +27,5 @@ logger.info(c.green(`${DEV ? 'development' : 'production'} server started`), {

logger.info(`
${c.dim('┃')} Local ${c.cyan(address)}` +
${c.dim('┃')} Local ${c.cyan(address.replace(/::1?/, 'localhost'))}` +
`${address?.includes(IP_EXPOSED)
? `${c.dim('┃')} Network ${c.cyan(address)}`
? `\n${c.dim('┃')} Network ${c.cyan(address)}`
: ''}

@@ -76,1 +77,4 @@ `);

};
export function getClientDistPath(root) {
return fileURLToPath(new URL(CLIENT_DIST_DIR, root));
}

@@ -1,19 +0,13 @@

import type { UserConfig } from 'vite';
export declare class GracileConfig {
port?: number;
import type { Connect } from 'vite';
export interface GracileConfig {
/**
* Root directory for the project
*/
root?: string;
/**
* @defaultValue 'static'
*/
output?: 'static' | 'server';
vite?: UserConfig;
server?: {
entrypoint?: string;
dev?: {
locals?: (context: {
nodeRequest: Connect.IncomingMessage;
}) => unknown;
};
constructor(options: GracileConfig);
}
export declare function defineConfig(options: GracileConfig): (ConfigClass: typeof GracileConfig) => GracileConfig;
//# sourceMappingURL=user-config.d.ts.map

@@ -1,19 +0,1 @@

export class GracileConfig {
port;
/**
* Root directory for the project
*/
root;
/**
* @defaultValue 'static'
*/
output;
vite;
server;
constructor(options) {
Object.assign(this, options);
}
}
export function defineConfig(options) {
return (ConfigClass) => new ConfigClass(options);
}
export {};
{
"name": "@gracile/engine",
"version": "0.2.0-next.1",
"version": "0.2.0-next.2",
"description": "A thin, full-stack, web framework",

@@ -78,3 +78,3 @@ "keywords": [

},
"gitHead": "a6f99f391dcaf12b0a64a2bd0736ed9d1fab8adc"
"gitHead": "6305693cb9f62e3ffef175e26660bf04b493bccc"
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc