metal-orm 1.0.50 → 1.0.52

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
@@ -74,20 +74,21 @@ MetalORM is a TypeScript-first, AST-driven SQL toolkit you can dial up or down d
74
74
  <a id="documentation"></a>
75
75
  ## Documentation 📚
76
76
 
77
- Full docs live in the `docs/` folder:
78
-
79
- - [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
80
- - [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
81
- - [Level 3 Backend Tutorial](https://github.com/celsowm/metal-orm/blob/main/docs/level-3-backend-tutorial.md)
82
- - [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
83
- - [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
84
- - [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
85
- - [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
86
- - [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
87
- - [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
88
- - [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
89
- - [Schema Generation (DDL)](https://github.com/celsowm/metal-orm/blob/main/docs/schema-generation.md)
90
- - [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
77
+ Full docs live in the `docs/` folder:
78
+
79
+ - [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
80
+ - [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
81
+ - [Level 3 Backend Tutorial](https://github.com/celsowm/metal-orm/blob/main/docs/level-3-backend-tutorial.md)
82
+ - [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
83
+ - [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
84
+ - [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
85
+ - [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
86
+ - [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
87
+ - [Save Graph](https://github.com/celsowm/metal-orm/blob/main/docs/save-graph.md)
88
+ - [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
89
+ - [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
90
+ - [Schema Generation (DDL)](https://github.com/celsowm/metal-orm/blob/main/docs/schema-generation.md)
91
+ - [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
91
92
  - [DB ➜ TS Type Mapping](https://github.com/celsowm/metal-orm/blob/main/docs/db-to-ts-types.md)
92
93
 
93
94
  ---
package/dist/index.cjs CHANGED
@@ -163,6 +163,7 @@ __export(index_exports, {
163
163
  isValueOperandInput: () => isValueOperandInput,
164
164
  isWindowFunctionNode: () => isWindowFunctionNode,
165
165
  jsonPath: () => jsonPath,
166
+ jsonify: () => jsonify,
166
167
  lag: () => lag,
167
168
  lastValue: () => lastValue,
168
169
  lead: () => lead,
@@ -379,6 +380,10 @@ var col = {
379
380
  * @returns ColumnDef with VARCHAR type
380
381
  */
381
382
  varchar: (length2) => ({ name: "", type: "VARCHAR", args: [length2] }),
383
+ /**
384
+ * Creates a text column definition
385
+ */
386
+ text: () => ({ name: "", type: "TEXT" }),
382
387
  /**
383
388
  * Creates a fixed precision decimal column definition
384
389
  */
@@ -6887,6 +6892,29 @@ var postgresIntrospector = {
6887
6892
  if (!trimmed) return;
6888
6893
  columnComments.set(key, trimmed);
6889
6894
  });
6895
+ const tableCommentRows = await queryRows(
6896
+ ctx.executor,
6897
+ `
6898
+ SELECT
6899
+ ns.nspname AS table_schema,
6900
+ cls.relname AS table_name,
6901
+ pg_catalog.obj_description(cls.oid) AS description
6902
+ FROM pg_catalog.pg_class cls
6903
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cls.relnamespace
6904
+ WHERE ns.nspname = $1
6905
+ AND cls.relkind IN ('r', 'p')
6906
+ `,
6907
+ [schema]
6908
+ );
6909
+ const tableComments = /* @__PURE__ */ new Map();
6910
+ tableCommentRows.forEach((r) => {
6911
+ if (!shouldIncludeTable(r.table_name, options)) return;
6912
+ if (!r.description) return;
6913
+ const key = `${r.table_schema}.${r.table_name}`;
6914
+ const trimmed = r.description.trim();
6915
+ if (!trimmed) return;
6916
+ tableComments.set(key, trimmed);
6917
+ });
6890
6918
  const qbPk = new SelectQueryBuilder(PgKeyColumnUsage).select({
6891
6919
  table_schema: PgKeyColumnUsage.columns.table_schema,
6892
6920
  table_name: PgKeyColumnUsage.columns.table_name,
@@ -7027,7 +7055,8 @@ var postgresIntrospector = {
7027
7055
  schema: r.table_schema,
7028
7056
  columns: [],
7029
7057
  primaryKey: pkMap.get(key) || [],
7030
- indexes: []
7058
+ indexes: [],
7059
+ comment: tableComments.get(key)
7031
7060
  });
7032
7061
  }
7033
7062
  const cols = tablesByKey.get(key);
@@ -7412,6 +7441,53 @@ var runPragma = async (name, table, alias, columnAliases, ctx) => {
7412
7441
  const query = buildPragmaQuery(name, table, alias, columnAliases);
7413
7442
  return await runSelectNode(query, ctx);
7414
7443
  };
7444
+ var loadSqliteSchemaComments = async (ctx) => {
7445
+ const tableComments = /* @__PURE__ */ new Map();
7446
+ const columnComments = /* @__PURE__ */ new Map();
7447
+ const tableExists = await queryRows(
7448
+ ctx.executor,
7449
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='schema_comments' LIMIT 1`
7450
+ );
7451
+ if (!tableExists.length) {
7452
+ return { tableComments, columnComments };
7453
+ }
7454
+ const commentRows = await queryRows(
7455
+ ctx.executor,
7456
+ `SELECT object_type, schema_name, table_name, column_name, comment FROM schema_comments`
7457
+ );
7458
+ for (const row of commentRows) {
7459
+ const objectType = typeof row.object_type === "string" ? row.object_type.toLowerCase() : "";
7460
+ const tableName = typeof row.table_name === "string" ? row.table_name : "";
7461
+ if (!tableName) continue;
7462
+ const columnName = typeof row.column_name === "string" ? row.column_name : "";
7463
+ const schemaName = typeof row.schema_name === "string" ? row.schema_name : "";
7464
+ const rawComment = row.comment;
7465
+ if (rawComment == null) continue;
7466
+ const commentText = String(rawComment).trim();
7467
+ if (!commentText) continue;
7468
+ const addTableComment = () => {
7469
+ tableComments.set(tableName, commentText);
7470
+ if (schemaName) {
7471
+ tableComments.set(`${schemaName}.${tableName}`, commentText);
7472
+ }
7473
+ };
7474
+ const addColumnComment = () => {
7475
+ columnComments.set(`${tableName}.${columnName}`, commentText);
7476
+ if (schemaName) {
7477
+ columnComments.set(`${schemaName}.${tableName}.${columnName}`, commentText);
7478
+ }
7479
+ };
7480
+ if (objectType === "table") {
7481
+ addTableComment();
7482
+ } else if (objectType === "column" && columnName) {
7483
+ addColumnComment();
7484
+ }
7485
+ }
7486
+ return {
7487
+ tableComments,
7488
+ columnComments
7489
+ };
7490
+ };
7415
7491
  var sqliteIntrospector = {
7416
7492
  async introspect(ctx, options) {
7417
7493
  const alias = "sqlite_master";
@@ -7425,6 +7501,7 @@ var sqliteIntrospector = {
7425
7501
  notLike(columnNode2(alias, "name"), "sqlite_%")
7426
7502
  )
7427
7503
  };
7504
+ const { tableComments, columnComments } = await loadSqliteSchemaComments(ctx);
7428
7505
  const tableRows = await runSelectNode(tablesQuery, ctx);
7429
7506
  const tables = [];
7430
7507
  for (const row of tableRows) {
@@ -7451,15 +7528,26 @@ var sqliteIntrospector = {
7451
7528
  ["seq", "name", "unique"],
7452
7529
  ctx
7453
7530
  );
7454
- const tableEntry = { name: tableName, columns: [], primaryKey: [], indexes: [] };
7531
+ const tableEntry = {
7532
+ name: tableName,
7533
+ columns: [],
7534
+ primaryKey: [],
7535
+ indexes: [],
7536
+ comment: tableComments.get(tableName)
7537
+ };
7455
7538
  tableInfo.forEach((info) => {
7456
- tableEntry.columns.push({
7539
+ const column = {
7457
7540
  name: info.name,
7458
7541
  type: info.type,
7459
7542
  notNull: info.notnull === 1,
7460
7543
  default: info.dflt_value ?? void 0,
7461
7544
  autoIncrement: false
7462
- });
7545
+ };
7546
+ const columnComment = columnComments.get(`${tableName}.${info.name}`);
7547
+ if (columnComment) {
7548
+ column.comment = columnComment;
7549
+ }
7550
+ tableEntry.columns.push(column);
7463
7551
  if (info.pk && info.pk > 0) {
7464
7552
  tableEntry.primaryKey = tableEntry.primaryKey || [];
7465
7553
  tableEntry.primaryKey.push(info.name);
@@ -7731,6 +7819,60 @@ var mssqlIntrospector = {
7731
7819
  async introspect(ctx, options) {
7732
7820
  const schema = options.schema;
7733
7821
  const schemaCondition = schema ? eq(columnNode3("sch", "name"), schema) : void 0;
7822
+ const schemaFilter = schema ? "AND sch.name = @p1" : "";
7823
+ const schemaParams = schema ? [schema] : [];
7824
+ const tableCommentRows = await queryRows(
7825
+ ctx.executor,
7826
+ `
7827
+ SELECT
7828
+ sch.name AS table_schema,
7829
+ t.name AS table_name,
7830
+ CONVERT(nvarchar(4000), ep.value) AS comment
7831
+ FROM sys.extended_properties ep
7832
+ JOIN sys.tables t ON t.object_id = ep.major_id
7833
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
7834
+ WHERE ep.class = 1
7835
+ AND ep.minor_id = 0
7836
+ AND ep.name = 'MS_Description'
7837
+ ${schemaFilter}
7838
+ `,
7839
+ schemaParams
7840
+ );
7841
+ const columnCommentRows = await queryRows(
7842
+ ctx.executor,
7843
+ `
7844
+ SELECT
7845
+ sch.name AS table_schema,
7846
+ t.name AS table_name,
7847
+ col.name AS column_name,
7848
+ CONVERT(nvarchar(4000), ep.value) AS comment
7849
+ FROM sys.extended_properties ep
7850
+ JOIN sys.columns col ON col.object_id = ep.major_id AND col.column_id = ep.minor_id
7851
+ JOIN sys.tables t ON t.object_id = col.object_id
7852
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
7853
+ WHERE ep.class = 1
7854
+ AND ep.minor_id > 0
7855
+ AND ep.name = 'MS_Description'
7856
+ ${schemaFilter}
7857
+ `,
7858
+ schemaParams
7859
+ );
7860
+ const tableComments = /* @__PURE__ */ new Map();
7861
+ tableCommentRows.forEach((r) => {
7862
+ if (!shouldIncludeTable(r.table_name, options)) return;
7863
+ if (!r.comment) return;
7864
+ const trimmed = r.comment.trim();
7865
+ if (!trimmed) return;
7866
+ tableComments.set(`${r.table_schema}.${r.table_name}`, trimmed);
7867
+ });
7868
+ const columnComments = /* @__PURE__ */ new Map();
7869
+ columnCommentRows.forEach((r) => {
7870
+ if (!shouldIncludeTable(r.table_name, options)) return;
7871
+ if (!r.comment) return;
7872
+ const trimmed = r.comment.trim();
7873
+ if (!trimmed) return;
7874
+ columnComments.set(`${r.table_schema}.${r.table_name}.${r.column_name}`, trimmed);
7875
+ });
7734
7876
  const dataTypeExpression = buildMssqlDataType(
7735
7877
  { table: "ty", name: "name" },
7736
7878
  { table: "c", name: "max_length" },
@@ -8036,7 +8178,8 @@ var mssqlIntrospector = {
8036
8178
  schema: r.table_schema,
8037
8179
  columns: [],
8038
8180
  primaryKey: pkMap.get(key) || [],
8039
- indexes: []
8181
+ indexes: [],
8182
+ comment: tableComments.get(key)
8040
8183
  });
8041
8184
  }
8042
8185
  const table = tablesByKey.get(key);
@@ -8047,6 +8190,10 @@ var mssqlIntrospector = {
8047
8190
  default: r.column_default ?? void 0,
8048
8191
  autoIncrement: !!r.is_identity
8049
8192
  };
8193
+ const columnComment = columnComments.get(`${key}.${r.column_name}`);
8194
+ if (columnComment) {
8195
+ column.comment = columnComment;
8196
+ }
8050
8197
  const fk = fkMap.get(`${key}.${r.column_name}`)?.[0];
8051
8198
  if (fk) {
8052
8199
  column.references = {
@@ -9375,18 +9522,30 @@ var runInTransaction = async (executor, action) => {
9375
9522
 
9376
9523
  // src/orm/save-graph.ts
9377
9524
  var toKey8 = (value) => value === null || value === void 0 ? "" : String(value);
9378
- var pickColumns = (table, payload) => {
9525
+ var coerceColumnValue = (table, columnName, value, options) => {
9526
+ if (options.coerce !== "json") return value;
9527
+ if (value === null || value === void 0) return value;
9528
+ const column = table.columns[columnName];
9529
+ if (!column) return value;
9530
+ const normalized = normalizeColumnType(column.type);
9531
+ const isDateLikeColumn = normalized === "date" || normalized === "datetime" || normalized === "timestamp" || normalized === "timestamptz";
9532
+ if (isDateLikeColumn && value instanceof Date) {
9533
+ return value.toISOString();
9534
+ }
9535
+ return value;
9536
+ };
9537
+ var pickColumns = (table, payload, options) => {
9379
9538
  const columns = {};
9380
9539
  for (const key of Object.keys(table.columns)) {
9381
9540
  if (payload[key] !== void 0) {
9382
- columns[key] = payload[key];
9541
+ columns[key] = coerceColumnValue(table, key, payload[key], options);
9383
9542
  }
9384
9543
  }
9385
9544
  return columns;
9386
9545
  };
9387
- var ensureEntity = (session, table, payload) => {
9546
+ var ensureEntity = (session, table, payload, options) => {
9388
9547
  const pk = findPrimaryKey(table);
9389
- const row = pickColumns(table, payload);
9548
+ const row = pickColumns(table, payload, options);
9390
9549
  const pkValue = payload[pk];
9391
9550
  if (pkValue !== void 0 && pkValue !== null) {
9392
9551
  const tracked = session.getEntity(table, pkValue);
@@ -9399,10 +9558,10 @@ var ensureEntity = (session, table, payload) => {
9399
9558
  }
9400
9559
  return createEntityFromRow(session, table, row);
9401
9560
  };
9402
- var assignColumns = (table, entity, payload) => {
9561
+ var assignColumns = (table, entity, payload, options) => {
9403
9562
  for (const key of Object.keys(table.columns)) {
9404
9563
  if (payload[key] !== void 0) {
9405
- entity[key] = payload[key];
9564
+ entity[key] = coerceColumnValue(table, key, payload[key], options);
9406
9565
  }
9407
9566
  }
9408
9567
  };
@@ -9429,8 +9588,8 @@ var handleHasMany = async (session, root, relationName, relation, payload, optio
9429
9588
  const asObj = typeof item === "object" ? item : { [targetPk]: item };
9430
9589
  const pkValue = asObj[targetPk];
9431
9590
  const current = findInCollectionByPk(existing, targetPk, pkValue) ?? (pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) : void 0);
9432
- const entity = current ?? ensureEntity(session, targetTable, asObj);
9433
- assignColumns(targetTable, entity, asObj);
9591
+ const entity = current ?? ensureEntity(session, targetTable, asObj, options);
9592
+ assignColumns(targetTable, entity, asObj, options);
9434
9593
  await applyGraphToEntity(session, targetTable, entity, asObj, options);
9435
9594
  if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
9436
9595
  collection.attach(entity);
@@ -9505,8 +9664,8 @@ var handleBelongsToMany = async (session, root, relationName, relation, payload,
9505
9664
  }
9506
9665
  const asObj = item;
9507
9666
  const pkValue = asObj[targetPk];
9508
- const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj) : ensureEntity(session, targetTable, asObj);
9509
- assignColumns(targetTable, entity, asObj);
9667
+ const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj, options) : ensureEntity(session, targetTable, asObj, options);
9668
+ assignColumns(targetTable, entity, asObj, options);
9510
9669
  await applyGraphToEntity(session, targetTable, entity, asObj, options);
9511
9670
  if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
9512
9671
  collection.attach(entity);
@@ -9537,7 +9696,7 @@ var applyRelation = async (session, table, entity, relationName, relation, paylo
9537
9696
  }
9538
9697
  };
9539
9698
  var applyGraphToEntity = async (session, table, entity, payload, options) => {
9540
- assignColumns(table, entity, payload);
9699
+ assignColumns(table, entity, payload, options);
9541
9700
  for (const [relationName, relation] of Object.entries(table.relations)) {
9542
9701
  if (!(relationName in payload)) continue;
9543
9702
  await applyRelation(session, table, entity, relationName, relation, payload[relationName], options);
@@ -9548,7 +9707,7 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
9548
9707
  if (!table) {
9549
9708
  throw new Error("Entity metadata has not been bootstrapped");
9550
9709
  }
9551
- const root = ensureEntity(session, table, payload);
9710
+ const root = ensureEntity(session, table, payload, options);
9552
9711
  await applyGraphToEntity(session, table, root, payload, options);
9553
9712
  return root;
9554
9713
  };
@@ -9731,13 +9890,6 @@ var OrmSession = class {
9731
9890
  async findMany(qb) {
9732
9891
  return executeHydrated(this, qb);
9733
9892
  }
9734
- /**
9735
- * Saves an entity graph (root + nested relations) based on a DTO-like payload.
9736
- * @param entityClass - Root entity constructor
9737
- * @param payload - DTO payload containing column values and nested relations
9738
- * @param options - Graph save options
9739
- * @returns The root entity instance
9740
- */
9741
9893
  async saveGraph(entityClass, payload, options) {
9742
9894
  const { transactional = true, ...graphOptions } = options ?? {};
9743
9895
  const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
@@ -9938,6 +10090,17 @@ var Orm = class {
9938
10090
  }
9939
10091
  };
9940
10092
 
10093
+ // src/orm/jsonify.ts
10094
+ var jsonify = (value) => {
10095
+ const record = value;
10096
+ const result = {};
10097
+ for (const key of Object.keys(record)) {
10098
+ const entry = record[key];
10099
+ result[key] = entry instanceof Date ? entry.toISOString() : entry;
10100
+ }
10101
+ return result;
10102
+ };
10103
+
9941
10104
  // src/decorators/decorator-metadata.ts
9942
10105
  var METADATA_KEY = "metal-orm:decorators";
9943
10106
  var isStandardDecoratorContext = (value) => {
@@ -10822,6 +10985,7 @@ function createPooledExecutorFactory(opts) {
10822
10985
  isValueOperandInput,
10823
10986
  isWindowFunctionNode,
10824
10987
  jsonPath,
10988
+ jsonify,
10825
10989
  lag,
10826
10990
  lastValue,
10827
10991
  lead,