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 +42 -1
- package/core/orm/model.js +6 -0
- package/core/orm/types.js +9 -0
- package/index.d.ts +482 -0
- package/index.js +2 -1
- package/package.json +7 -1
- package/plugins/admin-panel/components.js +120 -120
- package/plugins/admin-panel/field-renderers/array.js +2 -2
- package/plugins/admin-panel/field-renderers/basic.js +6 -6
- package/plugins/admin-panel/field-renderers/file-upload.js +2 -2
- package/plugins/admin-panel/field-renderers/json.js +1 -1
- package/plugins/admin-panel/field-renderers/relations.js +2 -2
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -3
- package/plugins/admin-panel/index.js +35 -4
- package/plugins/admin-panel/modules/bulk-actions.js +6 -6
- package/plugins/admin-panel/modules/custom-pages.js +7 -7
- package/plugins/admin-panel/modules/dashboard.js +14 -14
- package/plugins/admin-panel/modules/menu.js +71 -26
- package/plugins/index.js +2 -0
- package/plugins/rest-resources/index.js +350 -0
- package/src/server.js +128 -36
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.
|
|
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": {
|