webspresso 0.0.94 → 0.0.96

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.
Files changed (83) hide show
  1. package/.agents/ADMIN.md +225 -0
  2. package/.agents/AGENTS.md +72 -0
  3. package/.agents/ARCHITECTURE.md +119 -0
  4. package/.agents/AUTH.md +103 -0
  5. package/.agents/CLI.md +115 -0
  6. package/.agents/COMPRESSION.md +59 -0
  7. package/.agents/CONVENTIONS.md +46 -0
  8. package/.agents/EMAIL.md +141 -0
  9. package/.agents/ERRORS.md +201 -0
  10. package/.agents/MCP.md +103 -0
  11. package/.agents/ORM.md +230 -0
  12. package/.agents/PLUGINS.md +419 -0
  13. package/.agents/QUEUE.md +113 -0
  14. package/.agents/REALTIME.md +213 -0
  15. package/.agents/ROUTING.md +292 -0
  16. package/.agents/SERVICES.md +341 -0
  17. package/.agents/SHUTDOWN.md +67 -0
  18. package/.agents/STREAMING.md +73 -0
  19. package/.agents/WORKFLOW.md +27 -0
  20. package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
  21. package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
  22. package/.cursorrules +14 -0
  23. package/AGENTS.md +175 -0
  24. package/CLAUDE.md +54 -0
  25. package/README.md +18 -14
  26. package/bin/commands/agents-init.js +169 -0
  27. package/bin/commands/api.js +114 -48
  28. package/bin/commands/doctor.js +35 -0
  29. package/bin/commands/new.js +13 -2
  30. package/bin/commands/page.js +75 -89
  31. package/bin/commands/service.js +77 -0
  32. package/bin/webspresso.js +2 -2
  33. package/core/compileSchema.js +1 -1
  34. package/core/errors/middleware.js +22 -2
  35. package/core/errors/normalize.js +29 -2
  36. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  37. package/index.d.ts +306 -0
  38. package/index.js +35 -1
  39. package/package.json +19 -6
  40. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  41. package/plugins/csrf/index.js +3 -2
  42. package/plugins/csv/engine.js +447 -0
  43. package/plugins/csv/index.js +94 -0
  44. package/plugins/csv/middleware.js +80 -0
  45. package/plugins/csv/services.js +138 -0
  46. package/plugins/file-manager/admin-component.js +613 -0
  47. package/plugins/file-manager/api-handlers.js +261 -0
  48. package/plugins/file-manager/core.js +601 -0
  49. package/plugins/file-manager/index.js +139 -0
  50. package/plugins/file-manager/security.js +207 -0
  51. package/plugins/index.js +8 -0
  52. package/plugins/polar/CHANGELOG.md +18 -0
  53. package/plugins/polar/README.md +30 -7
  54. package/plugins/polar/index.js +6 -1
  55. package/plugins/polar/src/billing-hooks.js +51 -0
  56. package/plugins/polar/src/config.js +2 -0
  57. package/plugins/polar/src/rate-limit.js +96 -28
  58. package/plugins/polar/src/routes.js +3 -2
  59. package/plugins/polar/src/urls.js +26 -2
  60. package/plugins/polar/src/user-resolver.js +5 -1
  61. package/plugins/rate-limit/index.js +5 -1
  62. package/plugins/xlsx/engine.js +397 -0
  63. package/plugins/xlsx/index.js +91 -0
  64. package/plugins/xlsx/middleware.js +87 -0
  65. package/plugins/xlsx/services.js +118 -0
  66. package/src/api/api-loader.js +233 -0
  67. package/src/api/define-api.js +35 -0
  68. package/src/discovery/file-route-parser.js +201 -0
  69. package/src/discovery/scan-routes.js +265 -0
  70. package/src/file-router.js +6 -0
  71. package/src/modules/define-module.js +99 -0
  72. package/src/modules/module-discovery.js +117 -0
  73. package/src/pages/define-page.js +36 -0
  74. package/src/pages/page-loader.js +214 -0
  75. package/src/routing/mount-discovered-routes.js +78 -0
  76. package/src/routing/route-table.js +191 -0
  77. package/src/server.js +112 -15
  78. package/src/services/builtins/file-manager.js +178 -0
  79. package/src/services/index.js +2 -0
  80. package/bin/commands/skill.js +0 -187
  81. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  82. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  83. package/templates/skills/webspresso-usage/SKILL.md +0 -48
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso XLSX Plugin
5
+ * Excel spreadsheet generation, parsing, ORM export, streaming, and res.xlsx() integration
6
+ * @module plugins/xlsx
7
+ */
8
+
9
+ const engine = require('./engine');
10
+ const { createXlsxMiddleware, XLSX_MIME_TYPE } = require('./middleware');
11
+ const { createXlsxServices } = require('./services');
12
+
13
+ /**
14
+ * Standalone XLSX Toolkit Singleton
15
+ */
16
+ const xlsx = {
17
+ ...engine,
18
+ MIME_TYPE: XLSX_MIME_TYPE,
19
+ createMiddleware: createXlsxMiddleware,
20
+ createServices: createXlsxServices,
21
+ };
22
+
23
+ /**
24
+ * XLSX Plugin for Webspresso
25
+ * @param {Object} [options]
26
+ * @param {boolean} [options.enabled=true]
27
+ * @param {boolean} [options.sanitizeFormulas=true] - Prevent Formula Injection attacks
28
+ * @param {Object} [options.headerStyle] - Default header styling (fill, font, border)
29
+ * @param {Object} [options.db] - Optional database instance for model export services
30
+ * @returns {import('../../index').Plugin}
31
+ */
32
+ function xlsxPlugin(options = {}) {
33
+ const {
34
+ enabled = true,
35
+ sanitizeFormulas = true,
36
+ headerStyle = { fill: '4F46E5', color: 'FFFFFF', bold: true },
37
+ db: dbOption,
38
+ } = options;
39
+
40
+ return {
41
+ name: 'xlsx',
42
+ version: '1.0.0',
43
+ description: 'Excel XLSX spreadsheet engine, res.xlsx() response decorator, and ORM export tools',
44
+ enabled,
45
+
46
+ register(ctx) {
47
+ if (!enabled) return;
48
+
49
+ // 1. Expose on PluginContext & global app
50
+ ctx.xlsx = xlsx;
51
+ if (ctx.app) {
52
+ ctx.app.xlsx = xlsx;
53
+ }
54
+
55
+ // 2. Register built-in services (xlsx.generate, xlsx.parse, xlsx.exportModel)
56
+ if (ctx.app && ctx.app.serviceRegistry) {
57
+ const db = dbOption ?? ctx.db ?? ctx.app?.get?.('db') ?? null;
58
+ const services = createXlsxServices({ db, ...options });
59
+ for (const [name, def] of Object.entries(services)) {
60
+ if (!ctx.app.serviceRegistry.has(name)) {
61
+ ctx.app.serviceRegistry.register(name, def);
62
+ }
63
+ }
64
+ }
65
+
66
+ // 3. Register named middleware & global decorator
67
+ const middleware = createXlsxMiddleware({
68
+ sanitizeFormulas,
69
+ headerStyle,
70
+ ...options,
71
+ });
72
+
73
+ if (ctx.middlewares) {
74
+ ctx.middlewares.xlsx = middleware;
75
+ }
76
+
77
+ if (ctx.app && typeof ctx.app.use === 'function') {
78
+ ctx.app.use(middleware);
79
+ }
80
+ },
81
+ };
82
+ }
83
+
84
+ module.exports = {
85
+ xlsxPlugin,
86
+ xlsx,
87
+ createXlsxMiddleware,
88
+ createXlsxServices,
89
+ XLSX_MIME_TYPE,
90
+ ...engine,
91
+ };
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * XLSX Express Middleware
5
+ * Decorates res.xlsx() and req.parseXlsx() helpers
6
+ * @module plugins/xlsx/middleware
7
+ */
8
+
9
+ const { generate, parse, fromModel } = require('./engine');
10
+
11
+ const XLSX_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
12
+
13
+ /**
14
+ * Creates the XLSX middleware
15
+ * @param {Object} [globalOptions]
16
+ * @returns {Function}
17
+ */
18
+ function createXlsxMiddleware(globalOptions = {}) {
19
+ return (req, res, next) => {
20
+ // 1. Response Helper: res.xlsx(data, filename, options)
21
+ res.xlsx = async function (data, filename = 'export.xlsx', options = {}) {
22
+ try {
23
+ let buffer;
24
+
25
+ if (Buffer.isBuffer(data)) {
26
+ buffer = data;
27
+ } else if (data && typeof data.find === 'function' && data.model) {
28
+ // Repository instance
29
+ buffer = await fromModel(data, options.filter || options.query || {}, {
30
+ ...globalOptions,
31
+ ...options,
32
+ });
33
+ } else if (Array.isArray(data)) {
34
+ buffer = await generate({
35
+ ...globalOptions,
36
+ ...options,
37
+ sheets: [
38
+ {
39
+ name: options.sheetName || 'Data',
40
+ columns: options.columns,
41
+ rows: data,
42
+ headerStyle: options.headerStyle,
43
+ },
44
+ ],
45
+ });
46
+ } else if (data && typeof data === 'object') {
47
+ buffer = await generate({
48
+ ...globalOptions,
49
+ ...options,
50
+ ...data,
51
+ });
52
+ } else {
53
+ throw new Error('res.xlsx() expects an Array of rows, a sheets object, a Model Repository, or a Buffer');
54
+ }
55
+
56
+ const safeFilename = filename.endsWith('.xlsx') ? filename : `${filename}.xlsx`;
57
+
58
+ res.setHeader('Content-Type', XLSX_MIME_TYPE);
59
+ res.setHeader(
60
+ 'Content-Disposition',
61
+ `attachment; filename="${encodeURIComponent(safeFilename)}"; filename*=UTF-8''${encodeURIComponent(safeFilename)}`
62
+ );
63
+ res.setHeader('Content-Length', buffer.length);
64
+
65
+ return res.end(buffer);
66
+ } catch (err) {
67
+ return next(err);
68
+ }
69
+ };
70
+
71
+ // 2. Request Helper: req.parseXlsx(fileOrBuffer, options)
72
+ req.parseXlsx = async function (fileOrBuffer, options = {}) {
73
+ const target = fileOrBuffer || req.file?.buffer || req.rawBody || req.body;
74
+ if (!target) {
75
+ throw new Error('req.parseXlsx requires a file Buffer or multipart req.file.buffer');
76
+ }
77
+ return parse(target, options);
78
+ };
79
+
80
+ next();
81
+ };
82
+ }
83
+
84
+ module.exports = {
85
+ createXlsxMiddleware,
86
+ XLSX_MIME_TYPE,
87
+ };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Built-in XLSX Services
5
+ * Exposed via ctx.service('xlsx.*') and req.service('xlsx.*')
6
+ * @module plugins/xlsx/services
7
+ */
8
+
9
+ const { z } = require('zod');
10
+ const { generate, parse, fromModel } = require('./engine');
11
+
12
+ /**
13
+ * Creates built-in services for the XLSX plugin
14
+ * @param {Object} [options]
15
+ * @param {Object} [options.db]
16
+ * @returns {Object.<string, Object>}
17
+ */
18
+ function createXlsxServices(options = {}) {
19
+ return {
20
+ 'xlsx.generate': {
21
+ schema: z.object({
22
+ sheets: z.array(
23
+ z.object({
24
+ name: z.string().optional(),
25
+ columns: z.array(z.object({ header: z.string(), key: z.string(), width: z.number().optional() })).optional(),
26
+ rows: z.array(z.record(z.any())),
27
+ })
28
+ ).or(z.object({
29
+ name: z.string().optional(),
30
+ columns: z.array(z.object({ header: z.string(), key: z.string(), width: z.number().optional() })).optional(),
31
+ rows: z.array(z.record(z.any())),
32
+ })),
33
+ creator: z.string().optional(),
34
+ title: z.string().optional(),
35
+ }),
36
+ handler: async (input) => {
37
+ const buffer = await generate(input);
38
+ return {
39
+ base64: buffer.toString('base64'),
40
+ size: buffer.length,
41
+ mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
42
+ };
43
+ },
44
+ },
45
+
46
+ 'xlsx.parse': {
47
+ schema: z.object({
48
+ base64: z.string().optional(),
49
+ sheet: z.union([z.string(), z.number()]).optional(),
50
+ headerRow: z.number().optional(),
51
+ }),
52
+ handler: async (input) => {
53
+ if (!input.base64 && !input.buffer) {
54
+ throw new Error('xlsx.parse requires base64 string or buffer');
55
+ }
56
+ const buffer = input.buffer || Buffer.from(input.base64, 'base64');
57
+ const data = await parse(buffer, {
58
+ sheet: input.sheet,
59
+ headerRow: input.headerRow,
60
+ });
61
+
62
+ const rowCount = Array.isArray(data)
63
+ ? data.length
64
+ : Object.values(data).reduce((acc, sheetRows) => acc + sheetRows.length, 0);
65
+
66
+ return {
67
+ data,
68
+ rowCount,
69
+ };
70
+ },
71
+ },
72
+
73
+ 'xlsx.exportModel': {
74
+ schema: z.object({
75
+ model: z.string().min(1),
76
+ filter: z.record(z.any()).optional().default({}),
77
+ columns: z.array(z.string()).optional(),
78
+ filename: z.string().optional(),
79
+ }),
80
+ handler: async (input, ctx) => {
81
+ const db = ctx.db || options.db;
82
+ if (!db || typeof db.getRepository !== 'function') {
83
+ throw new Error('xlsx.exportModel requires an active database connection');
84
+ }
85
+
86
+ const repo = db.getRepository(input.model);
87
+ if (!repo) {
88
+ throw new Error(`Model repository "${input.model}" not found`);
89
+ }
90
+
91
+ let customColumns;
92
+ if (input.columns) {
93
+ customColumns = input.columns.map((c) => ({
94
+ header: c.charAt(0).toUpperCase() + c.slice(1).replace(/_/g, ' '),
95
+ key: c,
96
+ }));
97
+ }
98
+
99
+ const buffer = await fromModel(repo, input.filter, {
100
+ columns: customColumns,
101
+ title: `${input.model} Export`,
102
+ });
103
+
104
+ const filename = input.filename || `${input.model.toLowerCase()}_export.xlsx`;
105
+
106
+ return {
107
+ filename: filename.endsWith('.xlsx') ? filename : `${filename}.xlsx`,
108
+ base64: buffer.toString('base64'),
109
+ size: buffer.length,
110
+ };
111
+ },
112
+ },
113
+ };
114
+ }
115
+
116
+ module.exports = {
117
+ createXlsxServices,
118
+ };
@@ -0,0 +1,233 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso API Loader
5
+ * Handles validation and execution of defineApi() endpoints
6
+ * @module src/api/api-loader
7
+ */
8
+
9
+ const { ZodError } = require('zod');
10
+ const { z: zForApi } = require('../../core/validation');
11
+ const { resolveMiddlewares } = require('../file-router');
12
+
13
+ /**
14
+ * Compiles a schema definition from an API module
15
+ * @param {Object|Function} schemaDef
16
+ * @returns {Object|null}
17
+ */
18
+ function compileApiSchema(schemaDef) {
19
+ if (!schemaDef) return null;
20
+ if (typeof schemaDef === 'function') {
21
+ try {
22
+ return schemaDef({ z: zForApi });
23
+ } catch (err) {
24
+ throw new Error(`Failed to compile API schema: ${err.message}`);
25
+ }
26
+ }
27
+ if (typeof schemaDef === 'object') {
28
+ return schemaDef;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Validates request input against compiled schema
35
+ * @param {import('express').Request} req
36
+ * @param {Object} compiledSchema
37
+ */
38
+ function validateRequestInput(req, compiledSchema) {
39
+ req.input = {
40
+ body: undefined,
41
+ params: undefined,
42
+ query: undefined,
43
+ headers: undefined,
44
+ };
45
+
46
+ if (!compiledSchema) return;
47
+
48
+ if (compiledSchema.body && typeof compiledSchema.body.parse === 'function') {
49
+ req.input.body = compiledSchema.body.parse(req.body);
50
+ } else {
51
+ req.input.body = req.body;
52
+ }
53
+
54
+ if (compiledSchema.params && typeof compiledSchema.params.parse === 'function') {
55
+ req.input.params = compiledSchema.params.parse(req.params);
56
+ } else {
57
+ req.input.params = req.params;
58
+ }
59
+
60
+ if (compiledSchema.query && typeof compiledSchema.query.parse === 'function') {
61
+ req.input.query = compiledSchema.query.parse(req.query);
62
+ } else {
63
+ req.input.query = req.query;
64
+ }
65
+
66
+ if (compiledSchema.headers && typeof compiledSchema.headers.parse === 'function') {
67
+ req.input.headers = compiledSchema.headers.parse(req.headers);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Creates an Express route handler for a discovered API descriptor
73
+ * @param {Object} descriptor - Route descriptor
74
+ * @param {Object} context - Server context
75
+ * @returns {Function}
76
+ */
77
+ function createApiHandler(descriptor, context) {
78
+ const {
79
+ middlewares = {},
80
+ serviceRegistry = null,
81
+ db = null,
82
+ options: appOptions = {},
83
+ } = context;
84
+
85
+ const isDev = process.env.NODE_ENV === 'development';
86
+ let cachedMiddlewares = null;
87
+ let cachedApiDef = null;
88
+ let cachedHandlerFn = null;
89
+ let cachedCompiledSchema = null;
90
+
91
+ return async (req, res, next) => {
92
+ try {
93
+ // 1. Reload module in development
94
+ if (isDev) {
95
+ try {
96
+ const resolvedPath = require.resolve(descriptor.file);
97
+ if (require.cache[resolvedPath]) {
98
+ delete require.cache[resolvedPath];
99
+ cachedMiddlewares = null;
100
+ cachedApiDef = null;
101
+ cachedHandlerFn = null;
102
+ cachedCompiledSchema = null;
103
+ }
104
+ } catch {}
105
+ }
106
+
107
+ let apiDef = cachedApiDef;
108
+ let handlerFn = cachedHandlerFn;
109
+ let compiledSchema = cachedCompiledSchema;
110
+
111
+ if (!apiDef) {
112
+ let apiModule = {};
113
+ try {
114
+ apiModule = require(descriptor.file);
115
+ } catch (loadErr) {
116
+ return next(loadErr);
117
+ }
118
+
119
+ apiDef = typeof apiModule === 'function' ? { handler: apiModule } : apiModule.default || apiModule;
120
+ handlerFn = apiDef.handler || (typeof apiModule === 'function' ? apiModule : null);
121
+
122
+ if (typeof handlerFn !== 'function') {
123
+ throw new Error(`API file "${descriptor.source}" does not export a valid handler function`);
124
+ }
125
+
126
+ compiledSchema = compileApiSchema(apiDef.schema);
127
+
128
+ if (!isDev) {
129
+ cachedApiDef = apiDef;
130
+ cachedHandlerFn = handlerFn;
131
+ cachedCompiledSchema = compiledSchema;
132
+ }
133
+ }
134
+
135
+ // 2. Injections
136
+ if (db) req.db = db;
137
+ if (serviceRegistry) {
138
+ req.service = (name, input, opts) =>
139
+ serviceRegistry.call(name, input, { req, res, db }, opts);
140
+ }
141
+
142
+ // 3. Schema validation
143
+ if (compiledSchema) {
144
+ try {
145
+ validateRequestInput(req, compiledSchema);
146
+ } catch (err) {
147
+ if (err instanceof ZodError || err.name === 'ZodError' || Array.isArray(err?.issues)) {
148
+ return res.status(400).json({
149
+ error: 'Validation Error',
150
+ issues: err.issues,
151
+ });
152
+ }
153
+ throw err;
154
+ }
155
+ } else {
156
+ req.input = {
157
+ body: req.body,
158
+ params: req.params,
159
+ query: req.query,
160
+ };
161
+ }
162
+
163
+ // 4. Resolve and execute middlewares (module-local first, cached across requests)
164
+ if (!cachedMiddlewares) {
165
+ const moduleMiddlewares = descriptor.moduleConfig?.middlewares || {};
166
+ const combinedRegistry = Object.assign({}, middlewares, moduleMiddlewares);
167
+ cachedMiddlewares = resolveMiddlewares(apiDef.middleware, combinedRegistry);
168
+ }
169
+
170
+ for (const mw of cachedMiddlewares) {
171
+ if (typeof mw !== 'function') continue;
172
+ await new Promise((resolve, reject) => {
173
+ try {
174
+ mw(req, res, (err) => {
175
+ if (err) reject(err);
176
+ else resolve();
177
+ });
178
+ } catch (syncErr) {
179
+ reject(syncErr);
180
+ }
181
+ });
182
+ if (res.headersSent) return;
183
+ }
184
+
185
+ // 5. Execute handler
186
+ const apiCtx = {
187
+ req,
188
+ res,
189
+ params: req.params || {},
190
+ query: req.query || {},
191
+ body: req.body || {},
192
+ input: req.input,
193
+ service: (name, input, opts) =>
194
+ serviceRegistry ? serviceRegistry.call(name, input, { req, res, db }, opts) : null,
195
+ module: descriptor.module || null,
196
+ config: appOptions,
197
+ logger: console,
198
+ db,
199
+ };
200
+
201
+ req.ctx = apiCtx;
202
+ res.service = apiCtx.service;
203
+ res.input = apiCtx.input;
204
+ res.module = apiCtx.module;
205
+
206
+ const result = await handlerFn(req, res, apiCtx);
207
+
208
+ // 6. Auto-respond if handler returned a value and response not yet sent
209
+ if (!res.headersSent) {
210
+ if (result !== undefined) {
211
+ return res.json(result);
212
+ }
213
+ }
214
+ } catch (err) {
215
+ if (err && typeof err === 'object') {
216
+ if (!err.route) err.route = descriptor.path;
217
+ if (!err.method) err.method = descriptor.method ? descriptor.method.toUpperCase() : (req.method || 'GET');
218
+ if (!err.source) err.source = descriptor.source || descriptor.file;
219
+ if (!err.file) err.file = descriptor.file;
220
+ if (!err.module) err.module = descriptor.module || null;
221
+ if (!err.phase) err.phase = 'handler';
222
+ if (!err.requestId) err.requestId = req.id || (req.headers && req.headers['x-request-id']) || null;
223
+ }
224
+ return next(err);
225
+ }
226
+ };
227
+ }
228
+
229
+ module.exports = {
230
+ createApiHandler,
231
+ compileApiSchema,
232
+ validateRequestInput,
233
+ };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Webspresso API Definition Helper
5
+ * @module src/api/define-api
6
+ */
7
+
8
+ /**
9
+ * Defines an API route with schema validation, response metadata, and handler
10
+ * @param {Object} config
11
+ * @param {string} [config.description] - Endpoint description for OpenAPI
12
+ * @param {Array<string>} [config.tags] - Endpoint tags for OpenAPI
13
+ * @param {Array<string|Function>} [config.middleware] - Named or functional middlewares
14
+ * @param {Object|Function} [config.schema] - Request schemas (params, query, body, headers)
15
+ * @param {Object} [config.response] - Expected response schemas by status code
16
+ * @param {Function} config.handler - Request handler: async (req, ctx) => data
17
+ * @returns {Object}
18
+ */
19
+ function defineApi(config = {}) {
20
+ if (typeof config === 'function') {
21
+ return {
22
+ handler: config,
23
+ __isWebspressoApi: true,
24
+ };
25
+ }
26
+
27
+ return {
28
+ ...config,
29
+ __isWebspressoApi: true,
30
+ };
31
+ }
32
+
33
+ module.exports = {
34
+ defineApi,
35
+ };