metal-orm 1.0.41 → 1.0.42

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
@@ -171,6 +171,7 @@ MetalORM can be just a straightforward query builder.
171
171
  import mysql from 'mysql2/promise';
172
172
  import {
173
173
  defineTable,
174
+ tableRef,
174
175
  col,
175
176
  SelectQueryBuilder,
176
177
  eq,
@@ -187,15 +188,14 @@ const todos = defineTable('todos', {
187
188
  todos.columns.title.notNull = true;
188
189
  todos.columns.done.default = false;
189
190
 
191
+ // Optional: opt-in ergonomic column access
192
+ const t = tableRef(todos);
193
+
190
194
  // 2) Build a simple query
191
195
  const listOpenTodos = new SelectQueryBuilder(todos)
192
- .select({
193
- id: todos.columns.id,
194
- title: todos.columns.title,
195
- done: todos.columns.done,
196
- })
197
- .where(eq(todos.columns.done, false))
198
- .orderBy(todos.columns.id, 'ASC');
196
+ .selectColumns('id', 'title', 'done')
197
+ .where(eq(t.done, false))
198
+ .orderBy(t.id, 'ASC');
199
199
 
200
200
  // 3) Compile to SQL + params
201
201
  const dialect = new MySqlDialect();
@@ -214,6 +214,48 @@ console.log(rows);
214
214
 
215
215
  That’s it: schema, query, SQL, done.
216
216
 
217
+ #### Column pickers (preferred selection helpers)
218
+
219
+ `defineTable` still exposes the full `table.columns` map for schema metadata and constraint tweaks, but modern queries usually benefit from higher-level helpers instead of spelling `todo.columns.*` everywhere.
220
+
221
+ ```ts
222
+ const t = tableRef(todos);
223
+
224
+ const listOpenTodos = new SelectQueryBuilder(todos)
225
+ .selectColumns('id', 'title', 'done') // typed shorthand for the same fields
226
+ .where(eq(t.done, false))
227
+ .orderBy(t.id, 'ASC');
228
+ ```
229
+
230
+ `selectColumns`, `selectRelationColumns`, `includePick`, `selectColumnsDeep`, the `sel()` helpers for tables, and `esel()` for entities all build typed selection maps without repeating `table.columns.*`. Use those helpers when building query selections and reserve `table.columns.*` for schema definition, relations, or rare cases where you need a column reference outside of a picker. See the [Query Builder docs](./docs/query-builder.md#selection-helpers) for the reference, examples, and best practices for these helpers.
231
+
232
+ #### Ergonomic column access (opt-in) with `tableRef`
233
+
234
+ If you still want the convenience of accessing columns without spelling `.columns`, you can opt-in with `tableRef()`:
235
+
236
+ ```ts
237
+ import { tableRef, eq } from 'metal-orm';
238
+
239
+ // Existing style (always works)
240
+ const listOpenTodos = new SelectQueryBuilder(todos)
241
+ .selectColumns('id', 'title', 'done')
242
+ .where(eq(todos.columns.done, false))
243
+ .orderBy(todos.columns.id, 'ASC');
244
+
245
+ // Opt-in ergonomic style
246
+ const t = tableRef(todos);
247
+
248
+ const listOpenTodos2 = new SelectQueryBuilder(todos)
249
+ .selectColumns('id', 'title', 'done')
250
+ .where(eq(t.done, false))
251
+ .orderBy(t.id, 'ASC');
252
+ ```
253
+
254
+ Collision rule: real table fields win.
255
+
256
+ - `t.name` is the table name (string)
257
+ - `t.$.name` is the column definition for a colliding column name (escape hatch)
258
+
217
259
  #### 2. Relations & hydration (still no ORM)
218
260
 
219
261
  Now add relations and get nested objects, still without committing to a runtime.
@@ -227,6 +269,7 @@ import {
227
269
  eq,
228
270
  count,
229
271
  rowNumber,
272
+ sel,
230
273
  hydrateRows,
231
274
  } from 'metal-orm';
232
275
 
@@ -257,19 +300,15 @@ users.columns.email.unique = true;
257
300
  // Build a query with relation & window function
258
301
  const builder = new SelectQueryBuilder(users)
259
302
  .select({
260
- id: users.columns.id,
261
- name: users.columns.name,
262
- email: users.columns.email,
303
+ ...sel(users, 'id', 'name', 'email'),
263
304
  postCount: count(posts.columns.id),
264
- rank: rowNumber(), // window function helper
305
+ rank: rowNumber(), // window function helper
265
306
  })
266
307
  .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
267
308
  .groupBy(users.columns.id, users.columns.name, users.columns.email)
268
309
  .orderBy(count(posts.columns.id), 'DESC')
269
310
  .limit(10)
270
- .include('posts', {
271
- columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt],
272
- }); // eager relation for hydration
311
+ .includePick('posts', ['id', 'title', 'createdAt']); // eager relation for hydration
273
312
 
274
313
  const { sql, params } = builder.compile(dialect);
275
314
  const [rows] = await connection.execute(sql, params);
package/dist/index.cjs CHANGED
@@ -225,6 +225,7 @@ __export(index_exports, {
225
225
  substr: () => substr,
226
226
  sum: () => sum,
227
227
  synchronizeSchema: () => synchronizeSchema,
228
+ tableRef: () => tableRef,
228
229
  tan: () => tan,
229
230
  toColumnRef: () => toColumnRef,
230
231
  toTableRef: () => toTableRef,
@@ -264,6 +265,54 @@ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
264
265
  collation: options.collation
265
266
  };
266
267
  };
268
+ var TABLE_REF_CACHE = /* @__PURE__ */ new WeakMap();
269
+ var withColumnProps = (table) => {
270
+ const cached = TABLE_REF_CACHE.get(table);
271
+ if (cached) return cached;
272
+ const proxy = new Proxy(table, {
273
+ get(target, prop, receiver) {
274
+ if (prop === "$") return target.columns;
275
+ if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
276
+ if (typeof prop === "string" && prop in target.columns) return target.columns[prop];
277
+ return void 0;
278
+ },
279
+ has(target, prop) {
280
+ return prop === "$" || Reflect.has(target, prop) || typeof prop === "string" && prop in target.columns;
281
+ },
282
+ ownKeys(target) {
283
+ const base = Reflect.ownKeys(target);
284
+ const cols = Object.keys(target.columns);
285
+ for (const k of cols) {
286
+ if (!base.includes(k)) base.push(k);
287
+ }
288
+ if (!base.includes("$")) base.push("$");
289
+ return base;
290
+ },
291
+ getOwnPropertyDescriptor(target, prop) {
292
+ if (prop === "$") {
293
+ return {
294
+ configurable: true,
295
+ enumerable: false,
296
+ get() {
297
+ return target.columns;
298
+ }
299
+ };
300
+ }
301
+ if (typeof prop === "string" && prop in target.columns && !Reflect.has(target, prop)) {
302
+ return {
303
+ configurable: true,
304
+ enumerable: true,
305
+ value: target.columns[prop],
306
+ writable: false
307
+ };
308
+ }
309
+ return Reflect.getOwnPropertyDescriptor(target, prop);
310
+ }
311
+ });
312
+ TABLE_REF_CACHE.set(table, proxy);
313
+ return proxy;
314
+ };
315
+ var tableRef = (table) => withColumnProps(table);
267
316
 
268
317
  // src/schema/column.ts
269
318
  var col = {
@@ -3214,7 +3263,7 @@ var QueryAstService = class {
3214
3263
  }
3215
3264
  normalizeOrderingTerm(term) {
3216
3265
  const from = this.state.ast.from;
3217
- const tableRef = from.type === "Table" && from.alias ? { ...this.table, alias: from.alias } : this.table;
3266
+ const tableRef2 = from.type === "Table" && from.alias ? { ...this.table, alias: from.alias } : this.table;
3218
3267
  const termType = term?.type;
3219
3268
  if (termType === "Column") {
3220
3269
  return term;
@@ -3228,7 +3277,7 @@ var QueryAstService = class {
3228
3277
  if (termType === "BinaryExpression" || termType === "LogicalExpression" || termType === "NullExpression" || termType === "InExpression" || termType === "ExistsExpression" || termType === "BetweenExpression" || termType === "ArithmeticExpression") {
3229
3278
  return term;
3230
3279
  }
3231
- return buildColumnNode(tableRef, term);
3280
+ return buildColumnNode(tableRef2, term);
3232
3281
  }
3233
3282
  };
3234
3283
 
@@ -3634,8 +3683,8 @@ var ColumnSelector = class {
3634
3683
  */
3635
3684
  distinct(context, columns) {
3636
3685
  const from = context.state.ast.from;
3637
- const tableRef = from.type === "Table" && from.alias ? { ...this.env.table, alias: from.alias } : this.env.table;
3638
- const nodes = columns.map((col2) => buildColumnNode(tableRef, col2));
3686
+ const tableRef2 = from.type === "Table" && from.alias ? { ...this.env.table, alias: from.alias } : this.env.table;
3687
+ const nodes = columns.map((col2) => buildColumnNode(tableRef2, col2));
3639
3688
  const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
3640
3689
  const nextState = astService.withDistinct(nodes);
3641
3690
  return { state: nextState, hydration: context.hydration };
@@ -9595,6 +9644,7 @@ function createPooledExecutorFactory(opts) {
9595
9644
  substr,
9596
9645
  sum,
9597
9646
  synchronizeSchema,
9647
+ tableRef,
9598
9648
  tan,
9599
9649
  toColumnRef,
9600
9650
  toTableRef,