webspresso 0.0.16 → 0.0.18

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/core/orm/index.js CHANGED
@@ -7,24 +7,8 @@
7
7
  const path = require('path');
8
8
  const { createSchemaHelpers, extractColumnsFromSchema, getColumnMeta } = require('./schema-helpers');
9
9
  const { defineModel, getModel, getAllModels, hasModel, clearRegistry } = require('./model');
10
-
11
- // Create zdb instance with zod (zod is a dependency)
12
- let z;
13
- try {
14
- z = require('zod');
15
- } catch {
16
- // Zod not installed, zdb will be undefined
17
- z = null;
18
- }
19
-
20
- // Export zdb instance directly
21
- const zdb = z ? createSchemaHelpers(z) : null;
22
10
  const { createRepository } = require('./repository');
23
- const { createQueryBuilder, QueryBuilder } = require('./query-builder');
24
- const { runTransaction, createTransactionContext } = require('./transaction');
25
11
  const { createMigrationManager } = require('./migrations');
26
- const { scaffoldMigration, scaffoldAlterMigration, scaffoldDropMigration } = require('./migrations/scaffold');
27
- const { createScopeContext } = require('./scopes');
28
12
  const { createSeeder } = require('./seeder');
29
13
 
30
14
  /**
@@ -52,28 +36,14 @@ function createDatabase(config) {
52
36
  };
53
37
 
54
38
  const driverName = driverMap[client] || client;
39
+ const projectNodeModules = path.join(process.cwd(), 'node_modules');
55
40
 
56
- // Try to resolve and pre-load the driver from project's node_modules
57
- // This ensures Knex can find it when it tries to load it
58
- let driverPath = null;
59
- const resolvePaths = [
60
- path.join(process.cwd(), 'node_modules'),
61
- process.cwd(),
62
- ];
63
-
64
- for (const resolvePath of resolvePaths) {
65
- try {
66
- driverPath = require.resolve(driverName, { paths: [resolvePath] });
67
- // Pre-load the driver so Knex can find it in Module._cache
68
- require(driverPath);
69
- break;
70
- } catch (e) {
71
- // Continue to next path
72
- }
73
- }
74
-
75
- // If still not found, provide helpful error
76
- if (!driverPath) {
41
+ // Try to find and pre-load driver from project's node_modules
42
+ try {
43
+ const driverPath = require.resolve(driverName, { paths: [projectNodeModules] });
44
+ require(driverPath); // Pre-load into Module._cache
45
+ } catch (e) {
46
+ // Driver not found in project
77
47
  const installCmd = driverName === 'better-sqlite3'
78
48
  ? 'npm install better-sqlite3 --save'
79
49
  : driverName === 'pg'
@@ -83,24 +53,21 @@ function createDatabase(config) {
83
53
  : `npm install ${driverName} --save`;
84
54
 
85
55
  throw new Error(
86
- `Database driver "${driverName}" is not installed in your project. ` +
56
+ `Database driver "${driverName}" is not installed in your project.\n` +
87
57
  `Please install it with: ${installCmd}\n` +
88
- `Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
89
58
  `Current working directory: ${process.cwd()}`
90
59
  );
91
60
  }
92
61
  }
93
62
 
94
63
  // Create Knex instance
95
- // Knex will try to load the driver from its own node_modules
96
- // We need to ensure the driver is available in the project's node_modules
97
64
  let knexInstance;
98
65
  try {
99
66
  knexInstance = knex(config);
100
67
  } catch (e) {
101
- // If knex throws an error about missing driver, provide better message
102
- if (e.message && (e.message.includes('Cannot find module') || e.message.includes('run') || e.message.includes('npm install'))) {
103
- const driverName = config.client;
68
+ // Provide helpful error message
69
+ if (e.message && (e.message.includes('Cannot find module') || e.message.includes('npm install'))) {
70
+ const driverName = driverMap[config.client] || config.client;
104
71
  const installCmd = driverName === 'better-sqlite3'
105
72
  ? 'npm install better-sqlite3 --save'
106
73
  : driverName === 'pg'
@@ -109,34 +76,10 @@ function createDatabase(config) {
109
76
  ? 'npm install mysql2 --save'
110
77
  : `npm install ${driverName} --save`;
111
78
 
112
- // Check if driver exists in project's node_modules
113
- let driverExists = false;
114
- try {
115
- require.resolve(driverName, { paths: [path.join(process.cwd(), 'node_modules')] });
116
- driverExists = true;
117
- } catch (resolveError) {
118
- // Driver not found in project
119
- }
120
-
121
- if (!driverExists) {
122
- throw new Error(
123
- `Database driver "${driverName}" is not installed in your project. ` +
124
- `Please install it with: ${installCmd}\n` +
125
- `Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
126
- `Current working directory: ${process.cwd()}\n` +
127
- `Make sure you run "${installCmd}" in your project directory.`
128
- );
129
- } else {
130
- // Driver exists but Knex can't find it - this is a module resolution issue
131
- throw new Error(
132
- `Database driver "${driverName}" is installed but Knex cannot find it. ` +
133
- `This might be a module resolution issue. Try:\n` +
134
- `1. Delete node_modules and package-lock.json\n` +
135
- `2. Run "npm install" again\n` +
136
- `3. Make sure "${driverName}" is in your package.json dependencies\n` +
137
- `Original error: ${e.message}`
138
- );
139
- }
79
+ throw new Error(
80
+ `Failed to initialize database: ${e.message}\n` +
81
+ `Make sure "${driverName}" is installed: ${installCmd}`
82
+ );
140
83
  }
141
84
  throw e;
142
85
  }
@@ -146,114 +89,113 @@ function createDatabase(config) {
146
89
  const migrate = createMigrationManager(knexInstance, migrationConfig);
147
90
 
148
91
  // Default scope context
149
- let globalScopeContext = createScopeContext();
92
+ const defaultScopeContext = {
93
+ tenantId: null,
94
+ userId: null,
95
+ };
96
+
97
+ // Model registry
98
+ const models = new Map();
150
99
 
151
100
  /**
152
- * Set global tenant ID
153
- * @param {*} tenantId - Tenant ID
154
- * @returns {DatabaseInstance}
101
+ * Get a model by name
102
+ * @param {string} name - Model name
103
+ * @returns {import('./types').ModelDefinition}
155
104
  */
156
- function forTenant(tenantId) {
157
- globalScopeContext.tenantId = tenantId;
158
- return db;
105
+ function getModelInstance(name) {
106
+ const model = models.get(name);
107
+ if (!model) {
108
+ throw new Error(`Model "${name}" is not defined. Make sure you've called defineModel() first.`);
109
+ }
110
+ return model;
159
111
  }
160
112
 
161
113
  /**
162
- * Create a repository for a model
163
- * @param {import('./types').ModelDefinition} model - Model definition
164
- * @returns {import('./types').Repository}
114
+ * Check if a model exists
115
+ * @param {string} name - Model name
116
+ * @returns {boolean}
165
117
  */
166
- function createRepo(model) {
167
- return createRepository(model, knexInstance, { ...globalScopeContext });
118
+ function hasModelInstance(name) {
119
+ return models.has(name);
168
120
  }
169
121
 
170
122
  /**
171
- * Run a callback within a transaction
172
- * @param {function(import('./types').TransactionContext): Promise<*>} callback
173
- * @returns {Promise<*>}
123
+ * Get all registered models
124
+ * @returns {Array<import('./types').ModelDefinition>}
174
125
  */
175
- function transaction(callback) {
176
- return runTransaction(knexInstance, callback, { ...globalScopeContext });
126
+ function getAllModelInstances() {
127
+ return Array.from(models.values());
177
128
  }
178
129
 
179
130
  /**
180
- * Get raw Knex instance for advanced queries
181
- * @returns {import('knex').Knex}
131
+ * Register a model
132
+ * @param {import('./types').ModelDefinition} model - Model definition
182
133
  */
183
- function raw() {
184
- return knexInstance;
134
+ function registerModel(model) {
135
+ models.set(model.name, model);
185
136
  }
186
137
 
187
138
  /**
188
- * Close all database connections
189
- * @returns {Promise<void>}
139
+ * Get repository for a model
140
+ * @param {string} modelName - Model name
141
+ * @param {import('./types').ScopeContext} [scopeContext] - Scope context
142
+ * @returns {import('./types').Repository}
190
143
  */
191
- async function destroy() {
192
- await knexInstance.destroy();
144
+ function getRepository(modelName, scopeContext = defaultScopeContext) {
145
+ const model = getModelInstance(modelName);
146
+ return createRepository(model, knexInstance, scopeContext);
193
147
  }
194
148
 
195
149
  /**
196
- * Create a seeder instance
197
- * @param {Object} faker - Faker instance (@faker-js/faker)
198
- * @returns {Object} Seeder API
150
+ * Get query builder for a model
151
+ * @param {string} modelName - Model name
152
+ * @param {import('./types').ScopeContext} [scopeContext] - Scope context
153
+ * @returns {import('knex').Knex.QueryBuilder}
199
154
  */
200
- function seeder(faker) {
201
- return createSeeder(faker, knexInstance);
155
+ function query(modelName, scopeContext = defaultScopeContext) {
156
+ const model = getModelInstance(modelName);
157
+ const repo = createRepository(model, knexInstance, scopeContext);
158
+ return repo.query();
202
159
  }
203
160
 
204
- const db = {
161
+ /**
162
+ * Create seeder instance
163
+ * @returns {import('./types').Seeder}
164
+ */
165
+ function createSeederInstance() {
166
+ return createSeeder(knexInstance, models);
167
+ }
168
+
169
+ return {
205
170
  knex: knexInstance,
206
- createRepository: createRepo,
207
- transaction,
208
171
  migrate,
209
- seeder,
210
- forTenant,
211
- raw,
212
- destroy,
172
+ getModel: getModelInstance,
173
+ hasModel: hasModelInstance,
174
+ getAllModels: getAllModelInstances,
175
+ registerModel,
176
+ getRepository,
177
+ query,
178
+ createSeeder: createSeederInstance,
179
+ destroy: () => knexInstance.destroy(),
213
180
  };
214
-
215
- return db;
216
181
  }
217
182
 
218
- // Export everything
183
+ // Export zdb instance directly
184
+ const z = require('zod');
185
+ const zdb = z ? createSchemaHelpers(z) : null;
186
+
219
187
  module.exports = {
220
188
  // Main factory
221
189
  createDatabase,
222
-
223
- // Schema helpers - zdb instance (direct export)
190
+ // Schema helpers
224
191
  zdb,
225
- createSchemaHelpers,
226
- extractColumnsFromSchema,
227
- getColumnMeta,
228
-
229
- // Model
192
+ // Model utilities
230
193
  defineModel,
231
194
  getModel,
232
195
  getAllModels,
233
196
  hasModel,
234
197
  clearRegistry,
235
-
236
- // Repository (for direct use if needed)
237
- createRepository,
238
-
239
- // Query builder
240
- createQueryBuilder,
241
- QueryBuilder,
242
-
243
- // Transaction
244
- runTransaction,
245
- createTransactionContext,
246
-
247
- // Migrations
248
- createMigrationManager,
249
- scaffoldMigration,
250
- scaffoldAlterMigration,
251
- scaffoldDropMigration,
252
-
253
- // Scopes
254
- createScopeContext,
255
-
256
- // Seeder
257
- createSeeder,
198
+ // Column utilities
199
+ extractColumnsFromSchema,
200
+ getColumnMeta,
258
201
  };
259
-
package/core/orm/model.js CHANGED
@@ -80,7 +80,7 @@ function defineModel(options) {
80
80
  },
81
81
  columns,
82
82
  admin: {
83
- enabled: admin.enabled || false,
83
+ enabled: admin.enabled === true, // Explicit boolean check
84
84
  label: admin.label || name,
85
85
  icon: admin.icon || null,
86
86
  customFields: admin.customFields || {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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
6
  "bin": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@faker-js/faker": "^9.0.0",
53
- "better-sqlite3": "^9.0.0",
53
+ "better-sqlite3": "^11.10.0",
54
54
  "dotenv": "^16.0.0",
55
55
  "mysql2": "^3.0.0",
56
56
  "pg": "^8.0.0"
@@ -75,7 +75,7 @@
75
75
  "devDependencies": {
76
76
  "@faker-js/faker": "^9.3.0",
77
77
  "@vitest/coverage-v8": "^1.2.0",
78
- "better-sqlite3": "^11.0.0",
78
+ "better-sqlite3": "^11.10.0",
79
79
  "chokidar": "^3.5.3",
80
80
  "dotenv": "^16.3.1",
81
81
  "release-it": "^17.11.0",
@@ -131,7 +131,7 @@ function createApiHandlers(options) {
131
131
  const adminModels = [];
132
132
 
133
133
  for (const [name, model] of allModels) {
134
- if (model.admin && model.admin.enabled) {
134
+ if (model.admin && model.admin.enabled === true) {
135
135
  adminModels.push({
136
136
  name: model.name,
137
137
  table: model.table,
@@ -162,7 +162,7 @@ function createApiHandlers(options) {
162
162
  return res.status(404).json({ error: 'Model not found' });
163
163
  }
164
164
 
165
- if (!model.admin || !model.admin.enabled) {
165
+ if (!model.admin || model.admin.enabled !== true) {
166
166
  return res.status(403).json({ error: 'Model not enabled in admin panel' });
167
167
  }
168
168
 
@@ -204,7 +204,7 @@ function createApiHandlers(options) {
204
204
  const { model: modelName } = req.params;
205
205
  const model = getModel(modelName);
206
206
 
207
- if (!model || !model.admin || !model.admin.enabled) {
207
+ if (!model || !model.admin || model.admin.enabled !== true) {
208
208
  return res.status(404).json({ error: 'Model not found or not enabled' });
209
209
  }
210
210
 
@@ -270,7 +270,7 @@ function createApiHandlers(options) {
270
270
  const { model: modelName, id } = req.params;
271
271
  const model = getModel(modelName);
272
272
 
273
- if (!model || !model.admin || !model.admin.enabled) {
273
+ if (!model || !model.admin || model.admin.enabled !== true) {
274
274
  return res.status(404).json({ error: 'Model not found or not enabled' });
275
275
  }
276
276
 
@@ -295,7 +295,7 @@ function createApiHandlers(options) {
295
295
  const { model: modelName } = req.params;
296
296
  const model = getModel(modelName);
297
297
 
298
- if (!model || !model.admin || !model.admin.enabled) {
298
+ if (!model || !model.admin || model.admin.enabled !== true) {
299
299
  return res.status(404).json({ error: 'Model not found or not enabled' });
300
300
  }
301
301
 
@@ -316,7 +316,7 @@ function createApiHandlers(options) {
316
316
  const { model: modelName, id } = req.params;
317
317
  const model = getModel(modelName);
318
318
 
319
- if (!model || !model.admin || !model.admin.enabled) {
319
+ if (!model || !model.admin || model.admin.enabled !== true) {
320
320
  return res.status(404).json({ error: 'Model not found or not enabled' });
321
321
  }
322
322
 
@@ -341,7 +341,7 @@ function createApiHandlers(options) {
341
341
  const { model: modelName, id } = req.params;
342
342
  const model = getModel(modelName);
343
343
 
344
- if (!model || !model.admin || !model.admin.enabled) {
344
+ if (!model || !model.admin || model.admin.enabled !== true) {
345
345
  return res.status(404).json({ error: 'Model not found or not enabled' });
346
346
  }
347
347
 
@@ -366,7 +366,7 @@ function createApiHandlers(options) {
366
366
  const { model: modelName, relation: relationName } = req.params;
367
367
  const model = getModel(modelName);
368
368
 
369
- if (!model || !model.admin || !model.admin.enabled) {
369
+ if (!model || !model.admin || model.admin.enabled !== true) {
370
370
  return res.status(404).json({ error: 'Model not found or not enabled' });
371
371
  }
372
372
 
@@ -395,7 +395,7 @@ function createApiHandlers(options) {
395
395
  const { model: modelName, query: queryName } = req.params;
396
396
  const model = getModel(modelName);
397
397
 
398
- if (!model || !model.admin || !model.admin.enabled) {
398
+ if (!model || !model.admin || model.admin.enabled !== true) {
399
399
  return res.status(404).json({ error: 'Model not found or not enabled' });
400
400
  }
401
401