@3lineas/d1-orm 1.0.3 → 1.0.5

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
@@ -1,52 +1,52 @@
1
1
  # D1 ORM
2
2
 
3
- Un ORM ligero y potente diseñado específicamente para Cloudflare D1, inspirado en Eloquent de Laravel.
3
+ A lightweight and powerful ORM designed specifically for Cloudflare D1, inspired by Laravel's Eloquent.
4
4
 
5
- ## Características
5
+ ## Features
6
6
 
7
- - 🚀 **Ligero y Rápido**: Optimizado para Cloudflare Workers.
8
- - 🛠 **Basado en Eloquent**: Sintaxis familiar para desarrolladores de Laravel.
9
- - 📦 **TypeScript**: Tipado estático completo para mayor seguridad.
10
- - relations **Relaciones**: Soporte para `hasOne`, `hasMany` y `belongsTo`.
11
- - ⌨️ **CLI Integrado**: Herramientas para migraciones y generación de modelos.
7
+ - 🚀 **Lightweight & Fast**: Optimized for Cloudflare Workers environment.
8
+ - 🛠 **Eloquent-based**: Familiar syntax for Laravel developers.
9
+ - 📦 **TypeScript**: Full static typing for enhanced security.
10
+ - 🔗 **Relationships**: Built-in support for `hasOne`, `hasMany`, and `belongsTo`.
11
+ - ⌨️ **Integrated CLI**: Tools for migrations, model generation, and seeding.
12
12
 
13
- ## Instalación
13
+ ## Installation
14
14
 
15
15
  ```bash
16
16
  npm install @3lineas/d1-orm
17
- # O usando pnpm
17
+ # Or using pnpm
18
18
  pnpm add @3lineas/d1-orm
19
19
  ```
20
20
 
21
- ## Configuración Inicial
21
+ ## Initial Setup
22
22
 
23
- Para comenzar, debes configurar la conexión a tu base de datos D1 en tu Worker. Normalmente esto se hace en el punto de entrada de tu aplicación (por ejemplo, `index.ts` o `server.ts`).
23
+ To get started, configure the database connection in your Worker. This is typically done in your application's entry point (e.g., `index.ts` or `server.ts`).
24
24
 
25
25
  ```typescript
26
26
  import { Database } from "@3lineas/d1-orm";
27
27
 
28
28
  export default {
29
29
  async fetch(request, env, ctx) {
30
- // Inicializa la conexión con tu base de datos D1 (asumiendo que se llama DB en wrangler.toml)
30
+ // Initialize the D1 connection (assuming it's named DB in wrangler.toml)
31
31
  Database.setup(env.DB);
32
32
 
33
- // ... tu código de manejo de rutas
33
+ // ... your routing logic
34
34
  },
35
35
  };
36
36
  ```
37
37
 
38
- ## Definición de Modelos
38
+ ## Defining Models
39
39
 
40
- Los modelos se definen extendiendo la clase `Model`. Por defecto, el ORM asumirá que la tabla es el nombre de la clase en plural y minúsculas (ej: `User` -> `users`).
40
+ Define your models by extending the `Model` class. By default, the ORM assumes the table name is the lowercase plural form of the class name (e.g., `User` -> `users`).
41
41
 
42
42
  ```typescript
43
43
  import { Model } from "@3lineas/d1-orm";
44
44
 
45
45
  export class User extends Model {
46
- // Opcional: Definir tabla personalizada
46
+ // Optional: Custom table name
47
47
  // protected static table = 'my_users';
48
48
 
49
- // Opcional: Definir atributos para tipado (recomendado)
49
+ // Optional: Define attributes for typing (recommended)
50
50
  declare id: number;
51
51
  declare name: string;
52
52
  declare email: string;
@@ -54,49 +54,49 @@ export class User extends Model {
54
54
  }
55
55
  ```
56
56
 
57
- ## Operaciones CRUD
57
+ ## CRUD Operations
58
58
 
59
- ### Crear
59
+ ### Create
60
60
 
61
61
  ```typescript
62
62
  const user = await User.create({
63
- name: "Juan Perez",
64
- email: "juan@example.com",
63
+ name: "John Doe",
64
+ email: "john@example.com",
65
65
  });
66
66
  ```
67
67
 
68
- ### Leer
68
+ ### Read
69
69
 
70
70
  ```typescript
71
- // Obtener todos los registros
71
+ // Get all records
72
72
  const users = await User.all();
73
73
 
74
- // Encontrar por ID
74
+ // Find by ID
75
75
  const user = await User.find(1);
76
76
 
77
- // Consultas personalizadas
77
+ // Custom queries
78
78
  const activeUsers = await User.where("status", "=", "active")
79
79
  .orderBy("created_at", "desc")
80
80
  .get();
81
81
 
82
- // Obtener el primer resultado
83
- const firstUser = await User.where("email", "juan@example.com").first();
82
+ // Get the first result
83
+ const firstUser = await User.where("email", "john@example.com").first();
84
84
  ```
85
85
 
86
- ### Actualizar
86
+ ### Update
87
87
 
88
88
  ```typescript
89
89
  const user = await User.find(1);
90
90
  if (user) {
91
- user.fill({ name: "Juan Actualizado" });
91
+ user.fill({ name: "Updated Name" });
92
92
  await user.save();
93
93
  }
94
94
 
95
- // O actualizar directamente desde una consulta
95
+ // Or update directly via query
96
96
  await User.where("status", "inactive").update({ status: "active" });
97
97
  ```
98
98
 
99
- ### Eliminar
99
+ ### Delete
100
100
 
101
101
  ```typescript
102
102
  const user = await User.find(1);
@@ -104,104 +104,114 @@ if (user) {
104
104
  await user.delete();
105
105
  }
106
106
 
107
- // O eliminar directamente desde una consulta
107
+ // Or delete directly via query
108
108
  await User.where("status", "banned").delete();
109
109
  ```
110
110
 
111
- ## Relaciones
111
+ ## Relationships
112
112
 
113
- El ORM soporta relaciones básicas para estructurar tus datos.
113
+ Support for basic relationships to structure your data.
114
114
 
115
- ### Uno a Uno (HasOne)
115
+ ### One to One (HasOne)
116
116
 
117
117
  ```typescript
118
- // Modelo User
119
- hasOne(Profile) {
118
+ // In User Model
119
+ hasOneProfile() {
120
120
  return this.hasOne(Profile);
121
121
  }
122
122
 
123
- // Uso
124
- const profile = await user.hasOne(Profile).get();
123
+ // Usage
124
+ const profile = await user.hasOneProfile().get();
125
125
  ```
126
126
 
127
- ### Uno a Muchos (HasMany)
127
+ ### One to Many (HasMany)
128
128
 
129
129
  ```typescript
130
- // Modelo User
130
+ // In User Model
131
131
  posts() {
132
132
  return this.hasMany(Post);
133
133
  }
134
134
 
135
- // Uso
135
+ // Usage
136
136
  const posts = await user.posts().get();
137
137
  ```
138
138
 
139
- ### Pertenece A (BelongsTo)
139
+ ### Belongs To (BelongsTo)
140
140
 
141
141
  ```typescript
142
- // Modelo Post
142
+ // In Post Model
143
143
  user() {
144
144
  return this.belongsTo(User);
145
145
  }
146
146
 
147
- // Uso
147
+ // Usage
148
148
  const author = await post.user().get();
149
149
  ```
150
150
 
151
- ## CLI y Migraciones
151
+ ## CLI & Migrations
152
152
 
153
- El ORM incluye una CLI para facilitar la gestión de la base de datos.
153
+ The ORM includes a CLI to simplify database management.
154
154
 
155
- ### Scripts Recomendados
155
+ ### Automatic Configuration
156
156
 
157
- Agrega este script a tu `package.json` para facilitar el uso:
157
+ The `init` command will automatically configure the `orm` script in your `package.json`.
158
+
159
+ To run the initial command (before the script is added):
160
+
161
+ ```bash
162
+ npx d1-orm init
163
+ ```
164
+
165
+ Once executed, the `orm` script will be added to your `package.json`:
158
166
 
159
167
  ```json
160
168
  "scripts": {
161
- "orm": "npx tsx src/cli/index.ts"
169
+ "orm": "d1-orm"
162
170
  }
163
171
  ```
164
172
 
165
- ### Comandos Disponibles
166
-
167
- #### Inicializar Proyecto
168
-
169
- Crea las carpetas necesarias (`models`, `database/migrations`).
173
+ From then on, you can use:
170
174
 
171
175
  ```bash
172
- pnpm orm init
176
+ pnpm orm ...
173
177
  ```
174
178
 
175
- #### Crear un Modelo
179
+ ### Available Commands
180
+
181
+ #### Initialize Project
182
+
183
+ An interactive command that sets up your project. It will ask where you want to keep your models and will automatically generate the directory structure, an example `User` model, its migration, and a seeder.
184
+
185
+ #### Create a Model
176
186
 
177
- Genera un archivo de modelo en `src/models`.
187
+ Generate a model file interactively. It allows you to optionally create a linked migration and seeder.
178
188
 
179
189
  ```bash
180
- pnpm orm make:model NombreModelo
190
+ pnpm orm make:model
181
191
  ```
182
192
 
183
- #### Crear una Migración
193
+ #### Create a Migration
184
194
 
185
- Genera un archivo de migración en `database/migrations`.
195
+ Generate a migration file interactively in `database/migrations`.
186
196
 
187
197
  ```bash
188
- pnpm orm make:migration create_users_table
198
+ pnpm orm make:migration
189
199
  ```
190
200
 
191
- #### Ejecutar Migraciones
201
+ #### Run Migrations
192
202
 
193
- Ejecuta las migraciones pendientes.
203
+ Run pending migrations.
194
204
 
195
205
  ```bash
196
- # Local (por defecto)
206
+ # Local (default)
197
207
  pnpm orm migrate
198
208
 
199
- # Remoto (Producción)
200
- pnpm orm migrate -- --remote
209
+ # Remote (Production)
210
+ pnpm orm migrate --remote
201
211
  ```
202
212
 
203
- > **Nota:** Las migraciones locales requieren que `wrangler` esté configurado y funcionando correctamente en tu entorno.
213
+ > **Note:** Local migrations require `wrangler` to be configured and running in your environment.
204
214
 
205
215
  ---
206
216
 
207
- Desarrollado con ❤️ por **3Lineas**.
217
+ Crafted with ❤️ by **3Lineas**.
package/dist/cli/index.js CHANGED
@@ -53,7 +53,7 @@ async function init() {
53
53
  console.log(`Directory already exists: ${folder}`);
54
54
  }
55
55
  });
56
- const userModelContent = `import { Model } from 'd1-orm';
56
+ const userModelContent = `import { Model } from '@3lineas/d1-orm';
57
57
 
58
58
  export class User extends Model {
59
59
  // protected static table = 'users';
@@ -73,7 +73,7 @@ export class User extends Model {
73
73
  }
74
74
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0].replace("T", "_");
75
75
  const migrationName = `${timestamp}_create_users_table.ts`;
76
- const migrationContent = `import { Blueprint, Schema } from 'd1-orm';
76
+ const migrationContent = `import { Blueprint, Schema } from '@3lineas/d1-orm';
77
77
 
78
78
  export const up = async () => {
79
79
  return Schema.create('users', (table: Blueprint) => {
@@ -105,8 +105,8 @@ export const down = async () => {
105
105
 
106
106
  export const seed = async () => {
107
107
  await User.create({
108
- name: 'Juan Perez',
109
- email: 'juan@perez.com',
108
+ name: 'John Doe',
109
+ email: 'john@example.com',
110
110
  password: 'password'
111
111
  });
112
112
  };
@@ -120,59 +120,166 @@ export const seed = async () => {
120
120
  fs.writeFileSync(seederPath, seederContent);
121
121
  console.log(`Created seeder: ${seederPath}`);
122
122
  }
123
+ const packageJsonPath = path.join(process.cwd(), "package.json");
124
+ if (fs.existsSync(packageJsonPath)) {
125
+ try {
126
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
127
+ if (!packageJson.scripts) {
128
+ packageJson.scripts = {};
129
+ }
130
+ if (!packageJson.scripts.orm) {
131
+ packageJson.scripts.orm = "d1-orm";
132
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
133
+ console.log('Added "orm" script to package.json');
134
+ } else {
135
+ console.log('"orm" script already exists in package.json');
136
+ }
137
+ } catch (e) {
138
+ console.error("Failed to update package.json:", e);
139
+ }
140
+ }
123
141
  }
124
142
 
125
143
  // src/cli/commands/make-model.ts
144
+ var fs3 = __toESM(require("fs"));
145
+ var path3 = __toESM(require("path"));
146
+ var p2 = __toESM(require("@clack/prompts"));
147
+
148
+ // src/cli/commands/make-migration.ts
126
149
  var fs2 = __toESM(require("fs"));
127
150
  var path2 = __toESM(require("path"));
128
- function makeModel(name) {
129
- const filename = `${name}.ts`;
130
- const targetPath = path2.join(process.cwd(), "models", filename);
131
- const template = `import { Model } from 'd1-orm';
132
-
133
- export class ${name} extends Model {
134
- // protected static table = '${name.toLowerCase()}s';
135
-
136
- // Define attributes explicitly for type safety if desired
137
- // declare id: number;
138
- // declare created_at: string;
139
- // declare updated_at: string;
140
- }
141
- `;
142
- if (fs2.existsSync(targetPath)) {
143
- console.error(`Model ${filename} already exists.`);
144
- process.exit(1);
151
+ var p = __toESM(require("@clack/prompts"));
152
+ async function makeMigration(name) {
153
+ let migrationName = name;
154
+ if (!migrationName) {
155
+ migrationName = await p.text({
156
+ message: "What should the migration be named?",
157
+ placeholder: "e.g. create_users_table",
158
+ validate: (value) => {
159
+ if (!value) return "Please enter a name.";
160
+ }
161
+ });
162
+ if (p.isCancel(migrationName)) {
163
+ p.cancel("Operation cancelled.");
164
+ return;
165
+ }
145
166
  }
146
- fs2.writeFileSync(targetPath, template);
147
- console.log(`Created model: models/${filename}`);
148
- }
149
-
150
- // src/cli/commands/make-migration.ts
151
- var fs3 = __toESM(require("fs"));
152
- var path3 = __toESM(require("path"));
153
- function makeMigration(name) {
154
167
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0].replace("T", "_");
155
- const filename = `${timestamp}_${name}.ts`;
156
- const targetPath = path3.join(process.cwd(), "database/migrations", filename);
157
- const template = `import { Blueprint, Schema } from 'd1-orm';
168
+ const filename = `${timestamp}_${migrationName}.ts`;
169
+ const targetPath = path2.join(process.cwd(), "database/migrations", filename);
170
+ const template = `import { Blueprint, Schema } from '@3lineas/d1-orm';
158
171
 
159
172
  export const up = async () => {
160
- return Schema.create('${name}', (table: Blueprint) => {
173
+ return Schema.create('${migrationName.replace("create_", "").replace("_table", "")}', (table: Blueprint) => {
161
174
  table.id();
162
175
  table.timestamps();
163
176
  });
164
177
  };
165
178
 
166
179
  export const down = async () => {
167
- return Schema.dropIfExists('${name}');
180
+ return Schema.dropIfExists('${migrationName.replace("create_", "").replace("_table", "")}');
168
181
  };
182
+ `;
183
+ if (!fs2.existsSync(path2.join(process.cwd(), "database/migrations"))) {
184
+ fs2.mkdirSync(path2.join(process.cwd(), "database/migrations"), {
185
+ recursive: true
186
+ });
187
+ }
188
+ if (fs2.existsSync(targetPath)) {
189
+ p.log.error(`Migration ${filename} already exists.`);
190
+ return;
191
+ }
192
+ fs2.writeFileSync(targetPath, template);
193
+ p.log.success(`Created migration: database/migrations/${filename}`);
194
+ }
195
+
196
+ // src/cli/commands/make-model.ts
197
+ async function makeModel(name) {
198
+ let modelName = name;
199
+ if (!modelName) {
200
+ modelName = await p2.text({
201
+ message: "What should the model be named?",
202
+ placeholder: "e.g. Flight",
203
+ validate: (value) => {
204
+ if (!value) return "Please enter a name.";
205
+ }
206
+ });
207
+ if (p2.isCancel(modelName)) {
208
+ p2.cancel("Operation cancelled.");
209
+ return;
210
+ }
211
+ }
212
+ const modelPath = await findModelsPath() || "src/models";
213
+ const filename = `${modelName}.ts`;
214
+ const targetPath = path3.join(process.cwd(), modelPath, filename);
215
+ const template = `import { Model } from '@3lineas/d1-orm';
216
+
217
+ export class ${modelName} extends Model {
218
+ // protected static table = '${modelName.toLowerCase()}s';
219
+
220
+ // declare id: number;
221
+ // declare created_at: string;
222
+ // declare updated_at: string;
223
+ }
169
224
  `;
170
225
  if (fs3.existsSync(targetPath)) {
171
- console.error(`Migration ${filename} already exists.`);
172
- process.exit(1);
226
+ p2.log.error(`Model ${filename} already exists at ${modelPath}.`);
227
+ return;
228
+ }
229
+ if (!fs3.existsSync(path3.join(process.cwd(), modelPath))) {
230
+ fs3.mkdirSync(path3.join(process.cwd(), modelPath), { recursive: true });
173
231
  }
174
232
  fs3.writeFileSync(targetPath, template);
175
- console.log(`Created migration: database/migrations/${filename}`);
233
+ p2.log.success(`Created model: ${modelPath}/${filename}`);
234
+ const options = await p2.multiselect({
235
+ message: "Would you like to create any of the following?",
236
+ options: [
237
+ { value: "migration", label: "Migration" },
238
+ { value: "seeder", label: "Database Seeder" }
239
+ ],
240
+ required: false
241
+ });
242
+ if (p2.isCancel(options)) {
243
+ p2.cancel("Operation cancelled.");
244
+ return;
245
+ }
246
+ if (options.includes("migration")) {
247
+ const migrationName = `create_${modelName.toLowerCase()}s_table`;
248
+ await makeMigration(migrationName);
249
+ }
250
+ if (options.includes("seeder")) {
251
+ await makeSeeder(modelName, modelPath);
252
+ }
253
+ }
254
+ async function findModelsPath() {
255
+ const packageJsonPath = path3.join(process.cwd(), "package.json");
256
+ if (!fs3.existsSync(packageJsonPath)) return null;
257
+ const commonPaths = ["src/models", "models", "app/Models"];
258
+ for (const p3 of commonPaths) {
259
+ if (fs3.existsSync(path3.join(process.cwd(), p3))) return p3;
260
+ }
261
+ return null;
262
+ }
263
+ async function makeSeeder(modelName, modelPath) {
264
+ const seederDir = path3.join(process.cwd(), "database/seeders");
265
+ const seederName = `${modelName}Seeder.ts`;
266
+ const targetPath = path3.join(seederDir, seederName);
267
+ if (!fs3.existsSync(seederDir)) {
268
+ fs3.mkdirSync(seederDir, { recursive: true });
269
+ }
270
+ const relativeModelPath = path3.relative(seederDir, path3.join(process.cwd(), modelPath, modelName)).replace(/\\/g, "/");
271
+ const template = `import { ${modelName} } from '${relativeModelPath}';
272
+
273
+ export const seed = async () => {
274
+ // await ${modelName}.create({ ... });
275
+ };
276
+ `;
277
+ if (fs3.existsSync(targetPath)) {
278
+ p2.log.warn(`Seeder ${seederName} already exists.`);
279
+ return;
280
+ }
281
+ fs3.writeFileSync(targetPath, template);
282
+ p2.log.success(`Created seeder: database/seeders/${seederName}`);
176
283
  }
177
284
 
178
285
  // src/cli/commands/migrate.ts
@@ -188,23 +295,22 @@ async function migrate(args2) {
188
295
  }
189
296
  const files = fs4.readdirSync(migrationsDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
190
297
  for (const file of files) {
191
- console.log(`Processing ${file}...`);
298
+ console.log(`Processing migration: ${file}`);
192
299
  const filePath = path4.join(migrationsDir, file);
193
300
  try {
194
301
  const migration = await import(filePath);
195
302
  if (migration.up) {
196
303
  const sql = await migration.up();
197
304
  if (sql) {
198
- console.log(`Executing SQL: ${sql}`);
199
305
  const isRemote = args2.includes("--remote");
200
306
  const dbName = "DB";
201
307
  const command2 = isRemote ? "--remote" : "--local";
202
308
  try {
203
309
  const execCmd = `npx wrangler d1 execute ${dbName} --command "${sql.replace(/"/g, '\\"')}" ${command2}`;
204
- console.log(`Running: ${execCmd}`);
310
+ console.log(`Executing: ${execCmd}`);
205
311
  (0, import_child_process.execSync)(execCmd, { stdio: "inherit" });
206
312
  } catch (e) {
207
- console.error(`Failed to execute migration ${file}.`);
313
+ console.error(`Failed to execute migration: ${file}`);
208
314
  throw e;
209
315
  }
210
316
  }
@@ -224,17 +330,9 @@ switch (command) {
224
330
  init();
225
331
  break;
226
332
  case "make:model":
227
- if (!param) {
228
- console.error("Usage: d1-orm make:model <Name>");
229
- process.exit(1);
230
- }
231
333
  makeModel(param);
232
334
  break;
233
335
  case "make:migration":
234
- if (!param) {
235
- console.error("Usage: d1-orm make:migration <Name>");
236
- process.exit(1);
237
- }
238
336
  makeMigration(param);
239
337
  break;
240
338
  case "migrate":