webspresso 0.0.63 → 0.0.64

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/README.md CHANGED
@@ -15,7 +15,8 @@ A minimal, file-based SSR framework for Node.js with Nunjucks templating.
15
15
  - **Lifecycle Hooks**: Global and route-level hooks for request processing
16
16
  - **Template Helpers**: Laravel-inspired helper functions available in templates
17
17
  - **Plugin System**: Extensible architecture with version control and inter-plugin communication
18
- - **Built-in Plugins**: Development dashboard, sitemap generator, SEO checker, analytics integration (Google, Yandex, Bing), self-hosted site analytics, optional Swagger UI for HTTP APIs, configurable HTTP health probe endpoint
18
+ - **Built-in Plugins**: Development dashboard, sitemap generator, SEO checker, analytics integration (Google, Yandex, Bing), self-hosted site analytics, optional Swagger UI for HTTP APIs, configurable HTTP health probe endpoint, optional REST CRUD routes from ORM models
19
+ - **TypeScript**: Published **`index.d.ts`** (via `package.json` `"types"`) for `createApp`, ORM, plugins, and router helpers — use from TS/JS with IDE autocomplete; runtime stays CommonJS
19
20
 
20
21
  ## Installation
21
22
 
@@ -25,6 +26,18 @@ npm install -g webspresso
25
26
  npm install webspresso
26
27
  ```
27
28
 
29
+ ## TypeScript
30
+
31
+ The npm package ships with **[`index.d.ts`](index.d.ts)** so consumers get typings for the public API (`createApp`, `defineModel`, `createDatabase`, `zdb`, plugins, etc.). No extra `@types/webspresso` package is required.
32
+
33
+ ```typescript
34
+ import { createApp, defineModel, zdb } from 'webspresso';
35
+ ```
36
+
37
+ Install **`@types/express`** in your app if you want full **`Express.Application`** / **`Request`** / **`Response`** inference when you touch `createApp().app` or write middleware. **`knex`** and **`zod`** bring their own types.
38
+
39
+ Framework development (this repo): run **`npm run check:types`** to typecheck the declarations against a small smoke file (`tests/ts-smoke/`).
40
+
28
41
  ## Quick Start
29
42
 
30
43
  ### Using CLI (Recommended)
@@ -1882,6 +1895,34 @@ const user = plugin.api.getModel('User'); // Single model
1882
1895
  const names = plugin.api.getModelNames(); // Model names
1883
1896
  ```
1884
1897
 
1898
+ ### REST resources plugin
1899
+
1900
+ Registers **REST-style CRUD** routes for ORM models (`GET` list, `GET /:id`, `POST`, `PATCH /:id`, `DELETE /:id`). Relations are eager-loaded only when the client passes **`?include=relation1,relation2`**; loading uses the ORM batch eager loader (no classic N+1 from relation queries). **Nested includes** (e.g. `posts.comments`) are **not** supported—only top-level relation names defined on the model.
1901
+
1902
+ **Model opt-in** via `defineModel({ ..., rest: { enabled: true, path: 'optional-segment', allowInclude: ['company'] } })`. If you pass **`models: ['User', 'Post']`** to the plugin, those names are exposed even when `rest.enabled` is false.
1903
+
1904
+ **Setup:**
1905
+
1906
+ ```javascript
1907
+ const { createApp, restResourcePlugin } = require('webspresso');
1908
+
1909
+ const { app } = createApp({
1910
+ pagesDir: './pages',
1911
+ db,
1912
+ plugins: [
1913
+ restResourcePlugin({
1914
+ path: '/api/rest',
1915
+ middleware: [], // optional Express handlers (e.g. auth) — `attachDbMiddleware` is applied after these
1916
+ models: null, // optional whitelist of model names; when omitted, only `rest.enabled` models are exposed
1917
+ excludeModels: [],
1918
+ filter: null, // optional (model) => boolean
1919
+ }),
1920
+ ],
1921
+ });
1922
+ ```
1923
+
1924
+ List query parameters: `page`, `perPage`, `sort`, `order`, `include`, `trashed` (`only` / `include` when the model uses soft delete), plus **equality filters** on real columns (unknown keys are ignored).
1925
+
1885
1926
  ### Health check plugin
1886
1927
 
1887
1928
  Exposes a lightweight **GET** endpoint for load balancers and orchestrators (Kubernetes, Docker healthcheck, etc.). **Enabled by default** in all environments; set `enabled: false` to turn it off.
package/core/orm/model.js CHANGED
@@ -27,6 +27,7 @@ function defineModel(options) {
27
27
  relations = {},
28
28
  scopes = {},
29
29
  admin = {},
30
+ rest = {},
30
31
  hooks = {},
31
32
  hidden = [],
32
33
  } = options;
@@ -89,6 +90,11 @@ function defineModel(options) {
89
90
  customFields: admin.customFields || {},
90
91
  queries: admin.queries || {},
91
92
  },
93
+ rest: {
94
+ enabled: rest.enabled === true,
95
+ path: typeof rest.path === 'string' && rest.path.length > 0 ? rest.path.replace(/^\/+|\/+$/g, '') : null,
96
+ allowInclude: Array.isArray(rest.allowInclude) ? rest.allowInclude.filter((x) => typeof x === 'string') : null,
97
+ },
92
98
  hidden: Array.isArray(hidden) ? hidden : [],
93
99
  hooks: {},
94
100
  };
package/core/orm/types.js CHANGED
@@ -134,6 +134,13 @@
134
134
  * @property {Object.<string, QueryConfig>} [queries={}] - Custom query functions
135
135
  */
136
136
 
137
+ /**
138
+ * @typedef {Object} RestMetadata
139
+ * @property {boolean} [enabled=false] - Expose this model via restResourcePlugin (when not using plugin `models` whitelist)
140
+ * @property {string} [path] - URL segment override (no slashes); default: pluralized model name
141
+ * @property {string[]} [allowInclude] - Allowed relation names for `?include=`; default: all `model.relations` keys
142
+ */
143
+
137
144
  // ============================================================================
138
145
  // Model Types
139
146
  // ============================================================================
@@ -147,6 +154,7 @@
147
154
  * @property {RelationsMap} [relations={}] - Relation definitions
148
155
  * @property {ScopeOptions} [scopes={}] - Scope options
149
156
  * @property {AdminMetadata} [admin] - Admin panel metadata
157
+ * @property {RestMetadata} [rest] - REST resource plugin metadata
150
158
  * @property {string[]} [hidden=[]] - Column names to never expose in API/templates (e.g. password_hash, api_token)
151
159
  */
152
160
 
@@ -160,6 +168,7 @@
160
168
  * @property {ScopeOptions} scopes - Scope options
161
169
  * @property {Map<string, ColumnMeta>} columns - Parsed column metadata
162
170
  * @property {AdminMetadata} [admin] - Admin panel metadata
171
+ * @property {RestMetadata} rest - REST resource plugin metadata
163
172
  * @property {string[]} hidden - Column names never exposed in API/templates
164
173
  */
165
174
 
package/index.d.ts ADDED
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Type definitions for webspresso (CommonJS package).
3
+ */
4
+
5
+ import type { Application, NextFunction, Request, RequestHandler, Response } from 'express';
6
+ import type { Knex } from 'knex';
7
+ import type { ZodObject, ZodTypeAny } from 'zod';
8
+
9
+ // --- Express / app ---
10
+
11
+ export interface ErrorPageContext {
12
+ fsy: Record<string, unknown>;
13
+ locale: string;
14
+ isDev: boolean;
15
+ url: string;
16
+ method: string;
17
+ [key: string]: unknown;
18
+ }
19
+
20
+ export interface CreateAppOptions {
21
+ pagesDir: string;
22
+ viewsDir?: string;
23
+ publicDir?: string;
24
+ logging?: boolean;
25
+ helmet?: boolean | Record<string, unknown>;
26
+ middlewares?: Record<string, RequestHandler>;
27
+ plugins?: WebspressoPlugin[];
28
+ assets?: {
29
+ version?: string;
30
+ manifestPath?: string;
31
+ prefix?: string;
32
+ };
33
+ errorPages?: {
34
+ notFound?: string | ((req: Request, res: Response, ctx: ErrorPageContext) => unknown);
35
+ serverError?:
36
+ | string
37
+ | ((err: unknown, req: Request, res: Response, ctx: ErrorPageContext) => unknown);
38
+ timeout?: string | ((req: Request, res: Response, ctx: ErrorPageContext) => unknown);
39
+ };
40
+ timeout?: string | false;
41
+ auth?: unknown;
42
+ db?: DatabaseInstance | null;
43
+ setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ export interface SetupRoutesContext {
48
+ nunjucksEnv: unknown;
49
+ authMiddleware?: RequestHandler;
50
+ pluginManager: PluginManager;
51
+ options: CreateAppOptions;
52
+ }
53
+
54
+ export interface CreateAppResult {
55
+ app: Application;
56
+ nunjucksEnv: unknown;
57
+ pluginManager: PluginManager;
58
+ authMiddleware?: RequestHandler;
59
+ }
60
+
61
+ export function createApp(options?: CreateAppOptions): CreateAppResult;
62
+
63
+ // --- App context ---
64
+
65
+ export function attachDbMiddleware(req: Request, res: Response, next: NextFunction): void;
66
+
67
+ export function getAppContext(): { db: DatabaseInstance | null };
68
+
69
+ export function getDb(): DatabaseInstance;
70
+
71
+ export function hasDb(): boolean;
72
+
73
+ export function resetAppContext(): void;
74
+
75
+ export function setAppContext(partial: { db?: DatabaseInstance | null }): void;
76
+
77
+ // --- File router ---
78
+
79
+ export function mountPages(
80
+ app: Application,
81
+ options: Record<string, unknown>
82
+ ): unknown;
83
+
84
+ export function filePathToRoute(filePath: string, pagesDir: string): string;
85
+
86
+ export function extractMethodFromFilename(filename: string): string | null;
87
+
88
+ export function scanDirectory(
89
+ dir: string,
90
+ options?: Record<string, unknown>
91
+ ): unknown[];
92
+
93
+ export function loadI18n(pagesDir: string, routePath?: string): Record<string, unknown>;
94
+
95
+ export function createTranslator(
96
+ dictionaries: Record<string, unknown>,
97
+ locale: string
98
+ ): (key: string, params?: Record<string, unknown>) => string;
99
+
100
+ export function detectLocale(
101
+ req: Request,
102
+ supportedLocales: string[],
103
+ defaultLocale: string
104
+ ): string;
105
+
106
+ // --- Helpers / assets ---
107
+
108
+ export function createHelpers(context: Record<string, unknown>): Record<string, unknown>;
109
+
110
+ export const utils: Record<string, unknown>;
111
+
112
+ export class AssetManager {
113
+ constructor(options?: Record<string, unknown>);
114
+ [key: string]: unknown;
115
+ }
116
+
117
+ export function configureAssets(
118
+ nunjucksEnv: unknown,
119
+ options?: Record<string, unknown>
120
+ ): void;
121
+
122
+ export function getAssetManager(): AssetManager | null;
123
+
124
+ // --- Plugins ---
125
+
126
+ export interface RoutesReadyContext {
127
+ app: Application;
128
+ nunjucksEnv: unknown;
129
+ options: CreateAppOptions;
130
+ db: DatabaseInstance | null;
131
+ routes: unknown;
132
+ usePlugin(name: string): unknown;
133
+ addHelper(name: string, fn: (...args: unknown[]) => unknown): void;
134
+ addFilter(name: string, fn: (...args: unknown[]) => unknown): void;
135
+ addRoute(method: string, path: string, ...handlers: RequestHandler[]): void;
136
+ [key: string]: unknown;
137
+ }
138
+
139
+ export interface PluginRegisterContext {
140
+ app: Application;
141
+ nunjucksEnv: unknown;
142
+ options: Record<string, unknown>;
143
+ db: DatabaseInstance | null;
144
+ usePlugin(name: string): unknown;
145
+ addHelper(name: string, fn: (...args: unknown[]) => unknown): void;
146
+ addFilter(name: string, fn: (...args: unknown[]) => unknown): void;
147
+ addRoute(method: string, path: string, ...handlers: RequestHandler[]): void;
148
+ routes: unknown;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ export interface WebspressoPlugin {
153
+ name: string;
154
+ version: string;
155
+ dependencies?: Record<string, string>;
156
+ register?(ctx: PluginRegisterContext): void | Promise<void>;
157
+ onRoutesReady?(ctx: RoutesReadyContext): void;
158
+ onReady?(): void | Promise<void>;
159
+ api?: Record<string, unknown>;
160
+ csp?: Record<string, unknown>;
161
+ [key: string]: unknown;
162
+ }
163
+
164
+ export class PluginManager {
165
+ [key: string]: unknown;
166
+ }
167
+
168
+ export function createPluginManager(): PluginManager;
169
+
170
+ export function getPluginManager(): PluginManager | null;
171
+
172
+ export function resetPluginManager(): void;
173
+
174
+ // --- ORM: scopes & model ---
175
+
176
+ export interface ScopeContext {
177
+ tenantId?: unknown;
178
+ withTrashed?: boolean;
179
+ onlyTrashed?: boolean;
180
+ [key: string]: unknown;
181
+ }
182
+
183
+ export type RelationType = 'belongsTo' | 'hasMany' | 'hasOne';
184
+
185
+ export interface RelationDefinition {
186
+ type: RelationType;
187
+ model: () => ModelDefinition;
188
+ foreignKey: string;
189
+ localKey?: string;
190
+ }
191
+
192
+ export interface AdminMetadata {
193
+ enabled?: boolean;
194
+ label?: string;
195
+ icon?: string | null;
196
+ customFields?: Record<string, unknown>;
197
+ queries?: Record<string, (repo: Repository) => Promise<unknown>>;
198
+ }
199
+
200
+ export interface RestMetadata {
201
+ enabled?: boolean;
202
+ path?: string;
203
+ allowInclude?: string[];
204
+ }
205
+
206
+ export interface ScopeOptions {
207
+ softDelete?: boolean;
208
+ timestamps?: boolean;
209
+ tenant?: string | null;
210
+ }
211
+
212
+ export interface ModelOptions {
213
+ name: string;
214
+ table: string;
215
+ schema: ZodObject<Record<string, ZodTypeAny>>;
216
+ primaryKey?: string;
217
+ relations?: Record<string, RelationDefinition>;
218
+ scopes?: ScopeOptions;
219
+ admin?: AdminMetadata;
220
+ rest?: RestMetadata;
221
+ hooks?: Record<string, (...args: unknown[]) => unknown>;
222
+ hidden?: string[];
223
+ }
224
+
225
+ export interface ModelDefinition {
226
+ name: string;
227
+ table: string;
228
+ schema: ZodObject<Record<string, ZodTypeAny>>;
229
+ primaryKey: string;
230
+ relations: Record<string, RelationDefinition>;
231
+ scopes: {
232
+ softDelete: boolean;
233
+ timestamps: boolean;
234
+ tenant: string | null;
235
+ };
236
+ columns: Map<string, unknown>;
237
+ admin: {
238
+ enabled: boolean;
239
+ label: string;
240
+ icon: string | null;
241
+ customFields: Record<string, unknown>;
242
+ queries: Record<string, (repo: Repository) => Promise<unknown>>;
243
+ };
244
+ rest: {
245
+ enabled: boolean;
246
+ path: string | null;
247
+ allowInclude: string[] | null;
248
+ };
249
+ hidden: string[];
250
+ hooks: Record<string, unknown>;
251
+ }
252
+
253
+ export function defineModel(options: ModelOptions): ModelDefinition;
254
+
255
+ export function getModel(name: string): ModelDefinition | undefined;
256
+
257
+ export function getAllModels(): Map<string, ModelDefinition>;
258
+
259
+ export function hasModel(name: string): boolean;
260
+
261
+ export function clearRegistry(): void;
262
+
263
+ // --- ORM: schema & columns ---
264
+
265
+ /** zdb column helpers (id, string, timestamp, …) + `schema()` */
266
+ export type Zdb = Record<string, any> & {
267
+ schema(shape: Record<string, ZodTypeAny>): ZodObject<Record<string, ZodTypeAny>>;
268
+ };
269
+
270
+ export const zdb: Zdb;
271
+
272
+ export function createSchemaHelpers(z: typeof import('zod').z): Zdb;
273
+
274
+ export function extractColumnsFromSchema(schema: ZodObject<Record<string, ZodTypeAny>>): Map<string, unknown>;
275
+
276
+ export function getColumnMeta(schema: ZodTypeAny): unknown;
277
+
278
+ // --- ORM: query / repository ---
279
+
280
+ export interface FindOptions {
281
+ with?: string[];
282
+ select?: string[];
283
+ }
284
+
285
+ export interface PaginatedResult {
286
+ data: Record<string, unknown>[];
287
+ total: number;
288
+ page: number;
289
+ perPage: number;
290
+ totalPages: number;
291
+ }
292
+
293
+ export interface QueryBuilder {
294
+ where(column: string, value: unknown): this;
295
+ where(column: string, operator: string, value: unknown): this;
296
+ where(conditions: Record<string, unknown>): this;
297
+ orWhere(...args: unknown[]): this;
298
+ whereIn(column: string, values: unknown[]): this;
299
+ whereBetween(column: string, range: [unknown, unknown]): this;
300
+ select(...columns: string[]): this;
301
+ orderBy(column: string, direction?: 'asc' | 'desc'): this;
302
+ limit(n: number): this;
303
+ offset(n: number): this;
304
+ with(...relations: string[]): this;
305
+ withTrashed(): this;
306
+ onlyTrashed(): this;
307
+ first(): Promise<Record<string, unknown> | null>;
308
+ list(): Promise<Record<string, unknown>[]>;
309
+ get(): Promise<Record<string, unknown>[]>;
310
+ count(): Promise<number>;
311
+ paginate(page?: number, perPage?: number): Promise<PaginatedResult>;
312
+ exists(): Promise<boolean>;
313
+ clone(): QueryBuilder;
314
+ getWiths(): string[];
315
+ delete(): Promise<number>;
316
+ update(data: Record<string, unknown>): Promise<number>;
317
+ [key: string]: unknown;
318
+ }
319
+
320
+ export interface Repository {
321
+ findById(id: string | number, options?: FindOptions): Promise<Record<string, unknown> | null>;
322
+ findOne(conditions: Record<string, unknown>, options?: FindOptions): Promise<Record<string, unknown> | null>;
323
+ findAll(options?: FindOptions): Promise<Record<string, unknown>[]>;
324
+ create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
325
+ createMany(data: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
326
+ update(id: string | number, data: Record<string, unknown>): Promise<Record<string, unknown> | null>;
327
+ updateWhere(conditions: Record<string, unknown>, data: Record<string, unknown>): Promise<number>;
328
+ delete(id: string | number): Promise<boolean>;
329
+ forceDelete(id: string | number): Promise<boolean>;
330
+ restore(id: string | number): Promise<Record<string, unknown> | null>;
331
+ query(): QueryBuilder;
332
+ raw(sql: string, bindings?: unknown[]): Promise<unknown>;
333
+ count(conditions?: Record<string, unknown>): Promise<number>;
334
+ exists(conditions: Record<string, unknown>): Promise<boolean>;
335
+ with(...relations: string[]): QueryBuilder;
336
+ model: ModelDefinition;
337
+ }
338
+
339
+ // --- ORM: database ---
340
+
341
+ export interface MigrationConfig {
342
+ directory?: string;
343
+ tableName?: string;
344
+ }
345
+
346
+ export interface DatabaseConfig {
347
+ client?: string;
348
+ connection?: string | Record<string, unknown>;
349
+ models?: string;
350
+ migrations?: MigrationConfig;
351
+ pool?: Record<string, unknown>;
352
+ useNullAsDefault?: boolean;
353
+ [key: string]: unknown;
354
+ }
355
+
356
+ export interface MigrationStatus {
357
+ name: string;
358
+ completed: boolean;
359
+ ran_at: Date | null;
360
+ batch: number | null;
361
+ }
362
+
363
+ export interface MigrationResult {
364
+ batch: number;
365
+ migrations: string[];
366
+ }
367
+
368
+ export interface MakeMigrationResult {
369
+ filename: string;
370
+ filepath: string;
371
+ content: string | null;
372
+ }
373
+
374
+ export interface MigrationManager {
375
+ latest(): Promise<MigrationResult>;
376
+ rollback(options?: { all?: boolean }): Promise<MigrationResult>;
377
+ currentVersion(): Promise<string>;
378
+ status(): Promise<MigrationStatus[]>;
379
+ make(name: string, options?: { content?: string }): Promise<string | MakeMigrationResult>;
380
+ up(name: string): Promise<void>;
381
+ down(name: string): Promise<void>;
382
+ getConfig(): MigrationConfig & { directory: string; tableName: string };
383
+ hasTable(): Promise<boolean>;
384
+ unlock(): Promise<void>;
385
+ }
386
+
387
+ export interface TransactionContext {
388
+ trx: Knex.Transaction;
389
+ getRepository(modelName: string, scopeContext?: ScopeContext): Repository;
390
+ createRepository(model: ModelDefinition, scopeContext?: ScopeContext): Repository;
391
+ }
392
+
393
+ export interface DatabaseInstance {
394
+ knex: Knex;
395
+ migrate: MigrationManager;
396
+ getModel(name: string): ModelDefinition;
397
+ hasModel(name: string): boolean;
398
+ getAllModels(): ModelDefinition[];
399
+ registerModel(model: ModelDefinition): void;
400
+ getRepository(modelName: string, scopeContext?: ScopeContext): Repository;
401
+ createRepository(model: ModelDefinition, scopeContext?: ScopeContext): Repository;
402
+ query(modelName: string, scopeContext?: ScopeContext): QueryBuilder;
403
+ transaction<T>(callback: (ctx: TransactionContext) => Promise<T>): Promise<T>;
404
+ createSeeder(): unknown;
405
+ destroy(): Promise<void>;
406
+ }
407
+
408
+ export function createDatabase(config: DatabaseConfig): DatabaseInstance;
409
+
410
+ // --- ORM: nanoid / zod ---
411
+
412
+ export function generateNanoid(options?: { maxLength?: number } | number): string;
413
+
414
+ export const zodNanoid: ZodTypeAny;
415
+
416
+ export function extendZ(z: typeof import('zod').z): typeof import('zod').z;
417
+
418
+ // --- ORM: sanitize ---
419
+
420
+ export function omitHiddenColumns(record: unknown, model: ModelDefinition): unknown;
421
+
422
+ export function sanitizeForOutput(
423
+ records: unknown,
424
+ model: ModelDefinition
425
+ ): unknown;
426
+
427
+ // --- ORM: events ---
428
+
429
+ export const ModelEvents: {
430
+ on(event: string, handler: (...args: unknown[]) => void): void;
431
+ emit(event: string, ...args: unknown[]): void;
432
+ emitAsync?(event: string, ...args: unknown[]): Promise<void>;
433
+ [key: string]: unknown;
434
+ };
435
+
436
+ export const Hooks: Record<string, string>;
437
+
438
+ export class HookCancellationError extends Error {
439
+ reason: string;
440
+ model: string;
441
+ hook: string;
442
+ constructor(reason: string, model: string, hook: string);
443
+ }
444
+
445
+ export function createEventContext(
446
+ model: string,
447
+ operation: string,
448
+ trx?: unknown | null
449
+ ): {
450
+ model: string;
451
+ operation: string;
452
+ trx: unknown | null;
453
+ isCancelled: boolean;
454
+ cancelReason: string | null;
455
+ cancel(reason?: string): void;
456
+ };
457
+
458
+ // --- Built-in plugins (subset re-exported from main index.js) ---
459
+
460
+ export function schemaExplorerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
461
+
462
+ export function adminPanelPlugin(options?: Record<string, unknown>): WebspressoPlugin;
463
+
464
+ export function siteAnalyticsPlugin(options?: Record<string, unknown>): WebspressoPlugin;
465
+
466
+ export function auditLogPlugin(options?: Record<string, unknown>): WebspressoPlugin;
467
+
468
+ export function recaptchaPlugin(options?: Record<string, unknown>): WebspressoPlugin;
469
+
470
+ export function swaggerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
471
+
472
+ export function healthCheckPlugin(options?: Record<string, unknown>): WebspressoPlugin;
473
+
474
+ export interface RestResourcePluginOptions {
475
+ path?: string;
476
+ middleware?: RequestHandler[];
477
+ models?: string[] | null;
478
+ excludeModels?: string[];
479
+ filter?: (model: ModelDefinition) => boolean;
480
+ }
481
+
482
+ export function restResourcePlugin(options?: RestResourcePluginOptions): WebspressoPlugin;
package/index.js CHANGED
@@ -38,7 +38,7 @@ const {
38
38
  const orm = require('./core/orm');
39
39
 
40
40
  // Built-in plugins
41
- const { schemaExplorerPlugin, adminPanelPlugin, siteAnalyticsPlugin, auditLogPlugin, recaptchaPlugin, swaggerPlugin, healthCheckPlugin } = require('./plugins');
41
+ const { schemaExplorerPlugin, adminPanelPlugin, siteAnalyticsPlugin, auditLogPlugin, recaptchaPlugin, swaggerPlugin, healthCheckPlugin, restResourcePlugin } = require('./plugins');
42
42
 
43
43
  module.exports = {
44
44
  // Main API
@@ -89,4 +89,5 @@ module.exports = {
89
89
  recaptchaPlugin,
90
90
  swaggerPlugin,
91
91
  healthCheckPlugin,
92
+ restResourcePlugin,
92
93
  };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
+ "types": "index.d.ts",
6
7
  "bin": {
7
8
  "webspresso": "./bin/webspresso.js"
8
9
  },
@@ -14,6 +15,7 @@
14
15
  "test:e2e:ui": "playwright test --ui",
15
16
  "test:e2e:debug": "playwright test --debug",
16
17
  "test:e2e:headed": "playwright test --headed",
18
+ "check:types": "tsc --project tests/ts-smoke/tsconfig.json",
17
19
  "release": "release-it"
18
20
  },
19
21
  "keywords": [
@@ -34,6 +36,7 @@
34
36
  },
35
37
  "files": [
36
38
  "index.js",
39
+ "index.d.ts",
37
40
  "bin/",
38
41
  "src/",
39
42
  "utils/",
@@ -82,6 +85,8 @@
82
85
  }
83
86
  },
84
87
  "devDependencies": {
88
+ "@types/express": "^4.17.21",
89
+ "@types/node": "^20.14.0",
85
90
  "@faker-js/faker": "^9.9.0",
86
91
  "@playwright/test": "^1.48.0",
87
92
  "@vitest/coverage-v8": "^3.0.0",
@@ -90,6 +95,7 @@
90
95
  "dotenv": "^16.3.1",
91
96
  "release-it": "^19.0.0",
92
97
  "supertest": "^6.3.4",
98
+ "typescript": "~5.6.3",
93
99
  "vitest": "^3.0.0"
94
100
  },
95
101
  "engines": {