@atscript/db-mysql 0.1.104 → 0.1.106

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/dist/index.cjs CHANGED
@@ -1,5 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugin = require("./plugin-AzwuXV59.cjs");
3
2
  let _atscript_db = require("@atscript/db");
4
3
  let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
5
4
  //#region src/sql-builder.ts
@@ -40,9 +39,53 @@ const mysqlDialect = {
40
39
  params: [pattern]
41
40
  };
42
41
  },
42
+ geoWithin(quotedCol, circle) {
43
+ const dist = mysqlGeoDistanceExpr(quotedCol, circle.center);
44
+ return {
45
+ sql: `${dist.sql} <= ?`,
46
+ params: [...dist.params, circle.radius]
47
+ };
48
+ },
43
49
  createViewPrefix: "CREATE OR REPLACE VIEW"
44
50
  };
45
51
  /**
52
+ * Spherical distance in meters from a POINT column to a query point.
53
+ * Used as the `distExpr` of the shared geo search builder.
54
+ */
55
+ function mysqlGeoDistanceExpr(quotedCol, point) {
56
+ return {
57
+ sql: `ST_Distance_Sphere(${quotedCol}, ST_SRID(POINT(?, ?), 4326))`,
58
+ params: [point[0], point[1]]
59
+ };
60
+ }
61
+ /**
62
+ * Encodes a `[lng, lat]` tuple in MySQL's internal geometry format
63
+ * (4-byte SRID LE + WKB point) — geometry columns accept it directly as a
64
+ * binary parameter, bypassing the WKT/WKB axis-order pitfalls entirely.
65
+ */
66
+ function geoPointToMysqlInternal(point) {
67
+ const buf = Buffer.alloc(25);
68
+ buf.writeUInt32LE(4326, 0);
69
+ buf.writeUInt8(1, 4);
70
+ buf.writeUInt32LE(1, 5);
71
+ buf.writeDoubleLE(point[0], 9);
72
+ buf.writeDoubleLE(point[1], 17);
73
+ return buf;
74
+ }
75
+ /**
76
+ * Decodes a geo read value back to `[lng, lat]`. The mysql2 driver parses
77
+ * POINT columns to `{x, y}` objects (internal order: x=lng, y=lat); raw
78
+ * internal-format buffers are handled for drivers that don't.
79
+ */
80
+ function mysqlGeoValueToPoint(value) {
81
+ if (typeof value === "object" && value !== null && typeof value.x === "number" && typeof value.y === "number") return [value.x, value.y];
82
+ if (value instanceof Uint8Array && value.length >= 25) {
83
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);
84
+ const littleEndian = buf.readUInt8(4) === 1;
85
+ if (((littleEndian ? buf.readUInt32LE(5) : buf.readUInt32BE(5)) & 255) === 1) return [littleEndian ? buf.readDoubleLE(9) : buf.readDoubleBE(9), littleEndian ? buf.readDoubleLE(17) : buf.readDoubleBE(17)];
86
+ }
87
+ }
88
+ /**
46
89
  * Builds an INSERT statement.
47
90
  */
48
91
  function buildInsert(table, data) {
@@ -122,6 +165,7 @@ function mysqlTypeFromField(field) {
122
165
  const metadata = field.type?.metadata;
123
166
  const mysqlTypeOverride = metadata?.get("db.mysql.type");
124
167
  if (mysqlTypeOverride) return mysqlTypeOverride;
168
+ if (field.isGeoPoint && !field.encrypted) return "POINT SRID 4326";
125
169
  const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
126
170
  const precision = metadata?.get("db.column.precision");
127
171
  switch (field.designType) {
@@ -410,6 +454,13 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
410
454
  toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
411
455
  fromStorage: utcDatetimeToEpochMs
412
456
  };
457
+ if (field.isGeoPoint && !field.encrypted) return {
458
+ toStorage: (value) => {
459
+ const point = (0, _atscript_db_sql_tools.normalizeGeoPointValue)(value);
460
+ return point ? geoPointToMysqlInternal(point) : value;
461
+ },
462
+ fromStorage: (value) => mysqlGeoValueToPoint(value) ?? value
463
+ };
413
464
  }
414
465
  /**
415
466
  * Wraps an async write operation to catch MySQL constraint errors
@@ -579,8 +630,11 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
579
630
  this._log(sql, params);
580
631
  return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
581
632
  }
582
- async ensureTable() {
633
+ async prepareTypeMapper() {
583
634
  if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
635
+ }
636
+ async ensureTable() {
637
+ await this.prepareTypeMapper();
584
638
  if (this._table instanceof _atscript_db.AtscriptDbView) return this._ensureView();
585
639
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
586
640
  engine: this._engine,
@@ -604,12 +658,12 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
604
658
  }
605
659
  async getExistingColumnsForTable(tableName) {
606
660
  const schema = this._schema;
607
- return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
661
+ return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, SRS_ID
608
662
  FROM INFORMATION_SCHEMA.COLUMNS
609
663
  WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
610
664
  ORDER BY ORDINAL_POSITION`, [tableName, schema])).map((r) => ({
611
665
  name: r.COLUMN_NAME,
612
- type: r.COLUMN_TYPE.toUpperCase(),
666
+ type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
613
667
  notnull: r.IS_NULLABLE === "NO",
614
668
  pk: r.COLUMN_KEY === "PRI",
615
669
  dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
@@ -630,7 +684,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
630
684
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
631
685
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
632
686
  if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
633
- else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
687
+ else if (!field.optional && !field.isPrimaryKey) ddl += field.isGeoPoint && !field.encrypted ? ` DEFAULT (ST_SRID(POINT(0, 0), 4326))` : ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
634
688
  if (field.collate) {
635
689
  const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
636
690
  ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
@@ -641,6 +695,10 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
641
695
  }
642
696
  for (const { field } of diff.typeChanged ?? []) {
643
697
  const sqlType = this.typeMapper(field);
698
+ if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
699
+ await this._migrateJsonColumnToPoint(tableName, field);
700
+ continue;
701
+ }
644
702
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
645
703
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
646
704
  this._log(ddl);
@@ -727,6 +785,21 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
727
785
  this._log(ddl);
728
786
  await this._exec().exec(ddl);
729
787
  }
788
+ async dropIndexesForColumns(columns) {
789
+ const placeholders = columns.map(() => "?").join(", ");
790
+ const rows = await this._exec().all(`SELECT DISTINCT INDEX_NAME AS name FROM INFORMATION_SCHEMA.STATISTICS
791
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
792
+ AND INDEX_NAME LIKE 'atscript\\_\\_%' AND COLUMN_NAME IN (${placeholders})`, [
793
+ this._table.tableName,
794
+ this._schema,
795
+ ...columns
796
+ ]);
797
+ for (const row of rows) {
798
+ const sql = `DROP INDEX ${qi(row.name)} ON ${quoteTableName(this.resolveTableName())}`;
799
+ this._log(sql);
800
+ await this._exec().exec(sql);
801
+ }
802
+ }
730
803
  async dropTableByName(tableName) {
731
804
  const ddl = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
732
805
  this._log(ddl);
@@ -763,9 +836,28 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
763
836
  const schema = this._schema;
764
837
  const stringFields = new Set(this._table.fieldDescriptors.filter((f) => f.designType === "string").map((f) => f.physicalName));
765
838
  await this.syncIndexesWithDiff({
766
- listExisting: async () => this._exec().all(`SELECT DISTINCT INDEX_NAME as name FROM INFORMATION_SCHEMA.STATISTICS
767
- WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [tableName, schema]),
839
+ listExisting: async () => {
840
+ return (await this._exec().all(`SELECT INDEX_NAME AS name,
841
+ GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') AS columns
842
+ FROM INFORMATION_SCHEMA.STATISTICS
843
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
844
+ GROUP BY INDEX_NAME`, [tableName, schema])).map((r) => ({
845
+ name: r.name,
846
+ columns: r.columns ? r.columns.split(",") : void 0
847
+ }));
848
+ },
768
849
  createIndex: async (index) => {
850
+ if (index.type === "geo") {
851
+ const fieldMeta = this._table.fieldDescriptors.find((f) => f.physicalName === index.fields[0]?.name);
852
+ if (fieldMeta?.optional) {
853
+ this.logger.warn(`[mysql] geo index "${index.name}" skipped — SPATIAL indexes require a NOT NULL column; make "${fieldMeta.path}" required to index it`);
854
+ return;
855
+ }
856
+ const sql = `CREATE SPATIAL INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${qi(index.fields[0].name)})`;
857
+ this._log(sql);
858
+ await this._exec().exec(sql);
859
+ return;
860
+ }
769
861
  const unique = index.type === "unique" ? "UNIQUE " : "";
770
862
  const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
771
863
  const isFulltext = index.type === "fulltext";
@@ -780,10 +872,6 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
780
872
  const sql = `DROP INDEX ${qi(name)} ON ${quoteTableName(this.resolveTableName())}`;
781
873
  this._log(sql);
782
874
  await this._exec().exec(sql);
783
- },
784
- warnUnsupportedTypes: {
785
- adapter: "mysql",
786
- types: ["geo"]
787
875
  }
788
876
  });
789
877
  }
@@ -1010,6 +1098,65 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
1010
1098
  if (queryThreshold !== void 0) return queryThreshold;
1011
1099
  return this._vectorThresholds.get(indexName);
1012
1100
  }
1101
+ /** Native POINT SRID 4326 + ST_Distance_Sphere — available on MySQL 8.0+. */
1102
+ isGeoSearchable() {
1103
+ return true;
1104
+ }
1105
+ async geoSearch(point, query, indexName) {
1106
+ const { sql, params } = this._buildGeoSearchSelect(this._prepareGeoSearch(point, query, indexName));
1107
+ this._log(sql, params);
1108
+ return (await this._exec().all(sql, params)).map((row) => (0, _atscript_db_sql_tools.renameGeoDistance)(row));
1109
+ }
1110
+ async geoSearchWithCount(point, query, indexName) {
1111
+ const ctx = this._prepareGeoSearch(point, query, indexName);
1112
+ const { sql, params } = this._buildGeoSearchSelect(ctx);
1113
+ const countFrag = (0, _atscript_db_sql_tools.buildGeoSearchCount)(mysqlDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window);
1114
+ this._log(sql, params);
1115
+ this._log(countFrag.sql, countFrag.params);
1116
+ const [rows, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countFrag.sql, countFrag.params)]);
1117
+ return {
1118
+ data: rows.map((row) => (0, _atscript_db_sql_tools.renameGeoDistance)(row)),
1119
+ count: Number(countRow?.cnt ?? 0)
1120
+ };
1121
+ }
1122
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
1123
+ _prepareGeoSearch(point, query, indexName) {
1124
+ const column = this._resolveGeoColumn(indexName);
1125
+ const controls = query.controls ?? {};
1126
+ return {
1127
+ tableName: this.resolveTableName(),
1128
+ where: buildWhere(query.filter),
1129
+ dist: mysqlGeoDistanceExpr(qi(column), point),
1130
+ window: (0, _atscript_db_sql_tools.geoWindowFromControls)(controls),
1131
+ controls
1132
+ };
1133
+ }
1134
+ _buildGeoSearchSelect(ctx) {
1135
+ return (0, _atscript_db_sql_tools.buildGeoSearchSelect)(mysqlDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1136
+ $limit: ctx.controls.$limit,
1137
+ $skip: ctx.controls.$skip
1138
+ });
1139
+ }
1140
+ /**
1141
+ * Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
1142
+ * temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
1143
+ */
1144
+ async _migrateJsonColumnToPoint(tableName, field) {
1145
+ const col = qi(field.physicalName);
1146
+ const tmp = qi(`${field.physicalName}__geo_mig`);
1147
+ const quotedTable = quoteTableName(tableName);
1148
+ const steps = [
1149
+ `ALTER TABLE ${quotedTable} ADD COLUMN ${tmp} POINT SRID 4326 NULL`,
1150
+ `UPDATE ${quotedTable} SET ${tmp} = ST_SRID(POINT(CAST(${col}->>'$[0]' AS DOUBLE), CAST(${col}->>'$[1]' AS DOUBLE)), 4326) WHERE ${col} IS NOT NULL`,
1151
+ `ALTER TABLE ${quotedTable} DROP COLUMN ${col}`,
1152
+ `ALTER TABLE ${quotedTable} RENAME COLUMN ${tmp} TO ${col}`,
1153
+ ...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${col} POINT SRID 4326 NOT NULL`]
1154
+ ];
1155
+ for (const ddl of steps) {
1156
+ this._log(ddl);
1157
+ await this._exec().exec(ddl);
1158
+ }
1159
+ }
1013
1160
  };
1014
1161
  /**
1015
1162
  * Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
@@ -1194,6 +1341,5 @@ function createAdapter(uri, options) {
1194
1341
  //#endregion
1195
1342
  exports.Mysql2Driver = Mysql2Driver;
1196
1343
  exports.MysqlAdapter = MysqlAdapter;
1197
- exports.MysqlPlugin = require_plugin.MysqlPlugin;
1198
1344
  exports.buildWhere = buildWhere;
1199
1345
  exports.createAdapter = createAdapter;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as MysqlPlugin } from "./index-BLQr6loH.cjs";
2
1
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TExistingTableOption, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
3
2
  import { TMetadataMap } from "@atscript/typescript/utils";
4
3
  import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
@@ -158,6 +157,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
158
157
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
159
158
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
160
159
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
160
+ prepareTypeMapper(): Promise<void>;
161
161
  ensureTable(): Promise<void>;
162
162
  private _ensureView;
163
163
  getExistingColumns(): Promise<TExistingColumn[]>;
@@ -166,6 +166,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
166
166
  recreateTable(): Promise<void>;
167
167
  dropTable(): Promise<void>;
168
168
  dropColumns(columns: string[]): Promise<void>;
169
+ dropIndexesForColumns(columns: string[]): Promise<void>;
169
170
  dropTableByName(tableName: string): Promise<void>;
170
171
  dropViewByName(viewName: string): Promise<void>;
171
172
  renameTable(oldName: string): Promise<void>;
@@ -201,6 +202,21 @@ declare class MysqlAdapter extends BaseDbAdapter {
201
202
  private _buildVectorSearchCountQuery;
202
203
  /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
203
204
  private _resolveVectorThreshold;
205
+ /** Native POINT SRID 4326 + ST_Distance_Sphere — available on MySQL 8.0+. */
206
+ isGeoSearchable(): boolean;
207
+ geoSearch(point: [number, number], query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
208
+ geoSearchWithCount(point: [number, number], query: DbQuery, indexName?: string): Promise<{
209
+ data: Array<Record<string, unknown>>;
210
+ count: number;
211
+ }>;
212
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
213
+ private _prepareGeoSearch;
214
+ private _buildGeoSearchSelect;
215
+ /**
216
+ * Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
217
+ * temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
218
+ */
219
+ private _migrateJsonColumnToPoint;
204
220
  }
205
221
  //#endregion
206
222
  //#region src/mysql2-driver.d.ts
@@ -268,4 +284,4 @@ declare function buildWhere(filter: FilterExpr$1): TSqlFragment$1;
268
284
  */
269
285
  declare function createAdapter(uri: string, options?: Record<string, unknown>): DbSpace;
270
286
  //#endregion
271
- export { Mysql2Driver, MysqlAdapter, MysqlPlugin, type TMysqlConnection, type TMysqlDriver, type TMysqlRunResult, type TSqlFragment, buildWhere, createAdapter };
287
+ export { Mysql2Driver, MysqlAdapter, type TMysqlConnection, type TMysqlDriver, type TMysqlRunResult, type TSqlFragment, buildWhere, createAdapter };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as MysqlPlugin } from "./index-BLQr6loH.mjs";
2
1
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TExistingTableOption, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
3
2
  import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
4
3
  import { TMetadataMap } from "@atscript/typescript/utils";
@@ -158,6 +157,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
158
157
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
159
158
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
160
159
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
160
+ prepareTypeMapper(): Promise<void>;
161
161
  ensureTable(): Promise<void>;
162
162
  private _ensureView;
163
163
  getExistingColumns(): Promise<TExistingColumn[]>;
@@ -166,6 +166,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
166
166
  recreateTable(): Promise<void>;
167
167
  dropTable(): Promise<void>;
168
168
  dropColumns(columns: string[]): Promise<void>;
169
+ dropIndexesForColumns(columns: string[]): Promise<void>;
169
170
  dropTableByName(tableName: string): Promise<void>;
170
171
  dropViewByName(viewName: string): Promise<void>;
171
172
  renameTable(oldName: string): Promise<void>;
@@ -201,6 +202,21 @@ declare class MysqlAdapter extends BaseDbAdapter {
201
202
  private _buildVectorSearchCountQuery;
202
203
  /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
203
204
  private _resolveVectorThreshold;
205
+ /** Native POINT SRID 4326 + ST_Distance_Sphere — available on MySQL 8.0+. */
206
+ isGeoSearchable(): boolean;
207
+ geoSearch(point: [number, number], query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
208
+ geoSearchWithCount(point: [number, number], query: DbQuery, indexName?: string): Promise<{
209
+ data: Array<Record<string, unknown>>;
210
+ count: number;
211
+ }>;
212
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
213
+ private _prepareGeoSearch;
214
+ private _buildGeoSearchSelect;
215
+ /**
216
+ * Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
217
+ * temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
218
+ */
219
+ private _migrateJsonColumnToPoint;
204
220
  }
205
221
  //#endregion
206
222
  //#region src/mysql2-driver.d.ts
@@ -268,4 +284,4 @@ declare function buildWhere(filter: FilterExpr$1): TSqlFragment$1;
268
284
  */
269
285
  declare function createAdapter(uri: string, options?: Record<string, unknown>): DbSpace;
270
286
  //#endregion
271
- export { Mysql2Driver, MysqlAdapter, MysqlPlugin, type TMysqlConnection, type TMysqlDriver, type TMysqlRunResult, type TSqlFragment, buildWhere, createAdapter };
287
+ export { Mysql2Driver, MysqlAdapter, type TMysqlConnection, type TMysqlDriver, type TMysqlRunResult, type TSqlFragment, buildWhere, createAdapter };
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { t as MysqlPlugin } from "./plugin-CPJY5K_I.mjs";
2
1
  import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
3
- import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, parseRegexString, refActionToSql, toSqlValue } from "@atscript/db-sql-tools";
2
+ import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, toSqlValue } from "@atscript/db-sql-tools";
4
3
  //#region src/sql-builder.ts
5
4
  /** Escapes a MySQL identifier by doubling backticks. */
6
5
  function esc(name) {
@@ -39,9 +38,53 @@ const mysqlDialect = {
39
38
  params: [pattern]
40
39
  };
41
40
  },
41
+ geoWithin(quotedCol, circle) {
42
+ const dist = mysqlGeoDistanceExpr(quotedCol, circle.center);
43
+ return {
44
+ sql: `${dist.sql} <= ?`,
45
+ params: [...dist.params, circle.radius]
46
+ };
47
+ },
42
48
  createViewPrefix: "CREATE OR REPLACE VIEW"
43
49
  };
44
50
  /**
51
+ * Spherical distance in meters from a POINT column to a query point.
52
+ * Used as the `distExpr` of the shared geo search builder.
53
+ */
54
+ function mysqlGeoDistanceExpr(quotedCol, point) {
55
+ return {
56
+ sql: `ST_Distance_Sphere(${quotedCol}, ST_SRID(POINT(?, ?), 4326))`,
57
+ params: [point[0], point[1]]
58
+ };
59
+ }
60
+ /**
61
+ * Encodes a `[lng, lat]` tuple in MySQL's internal geometry format
62
+ * (4-byte SRID LE + WKB point) — geometry columns accept it directly as a
63
+ * binary parameter, bypassing the WKT/WKB axis-order pitfalls entirely.
64
+ */
65
+ function geoPointToMysqlInternal(point) {
66
+ const buf = Buffer.alloc(25);
67
+ buf.writeUInt32LE(4326, 0);
68
+ buf.writeUInt8(1, 4);
69
+ buf.writeUInt32LE(1, 5);
70
+ buf.writeDoubleLE(point[0], 9);
71
+ buf.writeDoubleLE(point[1], 17);
72
+ return buf;
73
+ }
74
+ /**
75
+ * Decodes a geo read value back to `[lng, lat]`. The mysql2 driver parses
76
+ * POINT columns to `{x, y}` objects (internal order: x=lng, y=lat); raw
77
+ * internal-format buffers are handled for drivers that don't.
78
+ */
79
+ function mysqlGeoValueToPoint(value) {
80
+ if (typeof value === "object" && value !== null && typeof value.x === "number" && typeof value.y === "number") return [value.x, value.y];
81
+ if (value instanceof Uint8Array && value.length >= 25) {
82
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);
83
+ const littleEndian = buf.readUInt8(4) === 1;
84
+ if (((littleEndian ? buf.readUInt32LE(5) : buf.readUInt32BE(5)) & 255) === 1) return [littleEndian ? buf.readDoubleLE(9) : buf.readDoubleBE(9), littleEndian ? buf.readDoubleLE(17) : buf.readDoubleBE(17)];
85
+ }
86
+ }
87
+ /**
45
88
  * Builds an INSERT statement.
46
89
  */
47
90
  function buildInsert$1(table, data) {
@@ -121,6 +164,7 @@ function mysqlTypeFromField(field) {
121
164
  const metadata = field.type?.metadata;
122
165
  const mysqlTypeOverride = metadata?.get("db.mysql.type");
123
166
  if (mysqlTypeOverride) return mysqlTypeOverride;
167
+ if (field.isGeoPoint && !field.encrypted) return "POINT SRID 4326";
124
168
  const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
125
169
  const precision = metadata?.get("db.column.precision");
126
170
  switch (field.designType) {
@@ -409,6 +453,13 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
409
453
  toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
410
454
  fromStorage: utcDatetimeToEpochMs
411
455
  };
456
+ if (field.isGeoPoint && !field.encrypted) return {
457
+ toStorage: (value) => {
458
+ const point = normalizeGeoPointValue(value);
459
+ return point ? geoPointToMysqlInternal(point) : value;
460
+ },
461
+ fromStorage: (value) => mysqlGeoValueToPoint(value) ?? value
462
+ };
412
463
  }
413
464
  /**
414
465
  * Wraps an async write operation to catch MySQL constraint errors
@@ -578,8 +629,11 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
578
629
  this._log(sql, params);
579
630
  return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
580
631
  }
581
- async ensureTable() {
632
+ async prepareTypeMapper() {
582
633
  if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
634
+ }
635
+ async ensureTable() {
636
+ await this.prepareTypeMapper();
583
637
  if (this._table instanceof AtscriptDbView) return this._ensureView();
584
638
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
585
639
  engine: this._engine,
@@ -603,12 +657,12 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
603
657
  }
604
658
  async getExistingColumnsForTable(tableName) {
605
659
  const schema = this._schema;
606
- return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
660
+ return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, SRS_ID
607
661
  FROM INFORMATION_SCHEMA.COLUMNS
608
662
  WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
609
663
  ORDER BY ORDINAL_POSITION`, [tableName, schema])).map((r) => ({
610
664
  name: r.COLUMN_NAME,
611
- type: r.COLUMN_TYPE.toUpperCase(),
665
+ type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
612
666
  notnull: r.IS_NULLABLE === "NO",
613
667
  pk: r.COLUMN_KEY === "PRI",
614
668
  dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
@@ -629,7 +683,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
629
683
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
630
684
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
631
685
  if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
632
- else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
686
+ else if (!field.optional && !field.isPrimaryKey) ddl += field.isGeoPoint && !field.encrypted ? ` DEFAULT (ST_SRID(POINT(0, 0), 4326))` : ` DEFAULT ${defaultValueForType(field.designType)}`;
633
687
  if (field.collate) {
634
688
  const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
635
689
  ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
@@ -640,6 +694,10 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
640
694
  }
641
695
  for (const { field } of diff.typeChanged ?? []) {
642
696
  const sqlType = this.typeMapper(field);
697
+ if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
698
+ await this._migrateJsonColumnToPoint(tableName, field);
699
+ continue;
700
+ }
643
701
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
644
702
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
645
703
  this._log(ddl);
@@ -726,6 +784,21 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
726
784
  this._log(ddl);
727
785
  await this._exec().exec(ddl);
728
786
  }
787
+ async dropIndexesForColumns(columns) {
788
+ const placeholders = columns.map(() => "?").join(", ");
789
+ const rows = await this._exec().all(`SELECT DISTINCT INDEX_NAME AS name FROM INFORMATION_SCHEMA.STATISTICS
790
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
791
+ AND INDEX_NAME LIKE 'atscript\\_\\_%' AND COLUMN_NAME IN (${placeholders})`, [
792
+ this._table.tableName,
793
+ this._schema,
794
+ ...columns
795
+ ]);
796
+ for (const row of rows) {
797
+ const sql = `DROP INDEX ${qi(row.name)} ON ${quoteTableName(this.resolveTableName())}`;
798
+ this._log(sql);
799
+ await this._exec().exec(sql);
800
+ }
801
+ }
729
802
  async dropTableByName(tableName) {
730
803
  const ddl = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
731
804
  this._log(ddl);
@@ -762,9 +835,28 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
762
835
  const schema = this._schema;
763
836
  const stringFields = new Set(this._table.fieldDescriptors.filter((f) => f.designType === "string").map((f) => f.physicalName));
764
837
  await this.syncIndexesWithDiff({
765
- listExisting: async () => this._exec().all(`SELECT DISTINCT INDEX_NAME as name FROM INFORMATION_SCHEMA.STATISTICS
766
- WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [tableName, schema]),
838
+ listExisting: async () => {
839
+ return (await this._exec().all(`SELECT INDEX_NAME AS name,
840
+ GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') AS columns
841
+ FROM INFORMATION_SCHEMA.STATISTICS
842
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
843
+ GROUP BY INDEX_NAME`, [tableName, schema])).map((r) => ({
844
+ name: r.name,
845
+ columns: r.columns ? r.columns.split(",") : void 0
846
+ }));
847
+ },
767
848
  createIndex: async (index) => {
849
+ if (index.type === "geo") {
850
+ const fieldMeta = this._table.fieldDescriptors.find((f) => f.physicalName === index.fields[0]?.name);
851
+ if (fieldMeta?.optional) {
852
+ this.logger.warn(`[mysql] geo index "${index.name}" skipped — SPATIAL indexes require a NOT NULL column; make "${fieldMeta.path}" required to index it`);
853
+ return;
854
+ }
855
+ const sql = `CREATE SPATIAL INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${qi(index.fields[0].name)})`;
856
+ this._log(sql);
857
+ await this._exec().exec(sql);
858
+ return;
859
+ }
768
860
  const unique = index.type === "unique" ? "UNIQUE " : "";
769
861
  const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
770
862
  const isFulltext = index.type === "fulltext";
@@ -779,10 +871,6 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
779
871
  const sql = `DROP INDEX ${qi(name)} ON ${quoteTableName(this.resolveTableName())}`;
780
872
  this._log(sql);
781
873
  await this._exec().exec(sql);
782
- },
783
- warnUnsupportedTypes: {
784
- adapter: "mysql",
785
- types: ["geo"]
786
874
  }
787
875
  });
788
876
  }
@@ -1009,6 +1097,65 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
1009
1097
  if (queryThreshold !== void 0) return queryThreshold;
1010
1098
  return this._vectorThresholds.get(indexName);
1011
1099
  }
1100
+ /** Native POINT SRID 4326 + ST_Distance_Sphere — available on MySQL 8.0+. */
1101
+ isGeoSearchable() {
1102
+ return true;
1103
+ }
1104
+ async geoSearch(point, query, indexName) {
1105
+ const { sql, params } = this._buildGeoSearchSelect(this._prepareGeoSearch(point, query, indexName));
1106
+ this._log(sql, params);
1107
+ return (await this._exec().all(sql, params)).map((row) => renameGeoDistance(row));
1108
+ }
1109
+ async geoSearchWithCount(point, query, indexName) {
1110
+ const ctx = this._prepareGeoSearch(point, query, indexName);
1111
+ const { sql, params } = this._buildGeoSearchSelect(ctx);
1112
+ const countFrag = buildGeoSearchCount(mysqlDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window);
1113
+ this._log(sql, params);
1114
+ this._log(countFrag.sql, countFrag.params);
1115
+ const [rows, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countFrag.sql, countFrag.params)]);
1116
+ return {
1117
+ data: rows.map((row) => renameGeoDistance(row)),
1118
+ count: Number(countRow?.cnt ?? 0)
1119
+ };
1120
+ }
1121
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
1122
+ _prepareGeoSearch(point, query, indexName) {
1123
+ const column = this._resolveGeoColumn(indexName);
1124
+ const controls = query.controls ?? {};
1125
+ return {
1126
+ tableName: this.resolveTableName(),
1127
+ where: buildWhere(query.filter),
1128
+ dist: mysqlGeoDistanceExpr(qi(column), point),
1129
+ window: geoWindowFromControls(controls),
1130
+ controls
1131
+ };
1132
+ }
1133
+ _buildGeoSearchSelect(ctx) {
1134
+ return buildGeoSearchSelect(mysqlDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1135
+ $limit: ctx.controls.$limit,
1136
+ $skip: ctx.controls.$skip
1137
+ });
1138
+ }
1139
+ /**
1140
+ * Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
1141
+ * temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
1142
+ */
1143
+ async _migrateJsonColumnToPoint(tableName, field) {
1144
+ const col = qi(field.physicalName);
1145
+ const tmp = qi(`${field.physicalName}__geo_mig`);
1146
+ const quotedTable = quoteTableName(tableName);
1147
+ const steps = [
1148
+ `ALTER TABLE ${quotedTable} ADD COLUMN ${tmp} POINT SRID 4326 NULL`,
1149
+ `UPDATE ${quotedTable} SET ${tmp} = ST_SRID(POINT(CAST(${col}->>'$[0]' AS DOUBLE), CAST(${col}->>'$[1]' AS DOUBLE)), 4326) WHERE ${col} IS NOT NULL`,
1150
+ `ALTER TABLE ${quotedTable} DROP COLUMN ${col}`,
1151
+ `ALTER TABLE ${quotedTable} RENAME COLUMN ${tmp} TO ${col}`,
1152
+ ...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${col} POINT SRID 4326 NOT NULL`]
1153
+ ];
1154
+ for (const ddl of steps) {
1155
+ this._log(ddl);
1156
+ await this._exec().exec(ddl);
1157
+ }
1158
+ }
1012
1159
  };
1013
1160
  /**
1014
1161
  * Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
@@ -1191,4 +1338,4 @@ function createAdapter(uri, options) {
1191
1338
  return new DbSpace(() => new MysqlAdapter(driver));
1192
1339
  }
1193
1340
  //#endregion
1194
- export { Mysql2Driver, MysqlAdapter, MysqlPlugin, buildWhere, createAdapter };
1341
+ export { Mysql2Driver, MysqlAdapter, buildWhere, createAdapter };
package/dist/plugin.cjs CHANGED
@@ -1,3 +1,94 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugin = require("./plugin-AzwuXV59.cjs");
3
- exports.MysqlPlugin = require_plugin.MysqlPlugin;
2
+ let _atscript_core = require("@atscript/core");
3
+ //#region src/plugin/annotations.ts
4
+ /**
5
+ * MySQL-specific annotations.
6
+ *
7
+ * Merged into the global config under `{ db: { mysql: ... } }` so they
8
+ * live alongside core's `@db.table`, `@db.index.*`, etc.
9
+ *
10
+ * These annotations opt-in to MySQL-specific behavior. Files using only
11
+ * portable `@db.*` annotations remain adapter-agnostic.
12
+ */
13
+ const annotations = {
14
+ engine: new _atscript_core.AnnotationSpec({
15
+ description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
16
+ nodeType: ["interface"],
17
+ multiple: false,
18
+ argument: {
19
+ name: "engine",
20
+ type: "string",
21
+ values: [
22
+ "InnoDB",
23
+ "MyISAM",
24
+ "MEMORY",
25
+ "CSV",
26
+ "ARCHIVE"
27
+ ],
28
+ description: "MySQL storage engine name."
29
+ }
30
+ }),
31
+ charset: new _atscript_core.AnnotationSpec({
32
+ description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
33
+ nodeType: ["interface", "prop"],
34
+ multiple: false,
35
+ argument: {
36
+ name: "charset",
37
+ type: "string",
38
+ values: [
39
+ "utf8mb4",
40
+ "utf8",
41
+ "latin1",
42
+ "ascii",
43
+ "binary"
44
+ ],
45
+ description: "MySQL character set name."
46
+ }
47
+ }),
48
+ collate: new _atscript_core.AnnotationSpec({
49
+ description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
50
+ nodeType: ["interface", "prop"],
51
+ multiple: false,
52
+ argument: {
53
+ name: "collation",
54
+ type: "string",
55
+ description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
56
+ }
57
+ }),
58
+ unsigned: new _atscript_core.AnnotationSpec({
59
+ description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
60
+ nodeType: ["prop"],
61
+ multiple: false
62
+ }),
63
+ type: new _atscript_core.AnnotationSpec({
64
+ description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
65
+ nodeType: ["prop"],
66
+ multiple: false,
67
+ argument: {
68
+ name: "type",
69
+ type: "string",
70
+ description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
71
+ }
72
+ }),
73
+ onUpdate: new _atscript_core.AnnotationSpec({
74
+ description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
75
+ nodeType: ["prop"],
76
+ multiple: false,
77
+ argument: {
78
+ name: "expression",
79
+ type: "string",
80
+ values: ["CURRENT_TIMESTAMP"],
81
+ description: "Expression to evaluate on row update."
82
+ }
83
+ })
84
+ };
85
+ //#endregion
86
+ //#region src/plugin/index.ts
87
+ const MysqlPlugin = () => ({
88
+ name: "mysql",
89
+ config() {
90
+ return { annotations: { db: { mysql: annotations } } };
91
+ }
92
+ });
93
+ //#endregion
94
+ exports.MysqlPlugin = MysqlPlugin;
package/dist/plugin.d.cts CHANGED
@@ -1,2 +1,6 @@
1
- import { t as MysqlPlugin } from "./index-BLQr6loH.cjs";
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const MysqlPlugin: () => TAtscriptPlugin;
5
+ //#endregion
2
6
  export { MysqlPlugin };
package/dist/plugin.d.mts CHANGED
@@ -1,2 +1,6 @@
1
- import { t as MysqlPlugin } from "./index-BLQr6loH.mjs";
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const MysqlPlugin: () => TAtscriptPlugin;
5
+ //#endregion
2
6
  export { MysqlPlugin };
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,93 @@
1
- import { t as MysqlPlugin } from "./plugin-CPJY5K_I.mjs";
1
+ import { AnnotationSpec } from "@atscript/core";
2
+ //#region src/plugin/annotations.ts
3
+ /**
4
+ * MySQL-specific annotations.
5
+ *
6
+ * Merged into the global config under `{ db: { mysql: ... } }` so they
7
+ * live alongside core's `@db.table`, `@db.index.*`, etc.
8
+ *
9
+ * These annotations opt-in to MySQL-specific behavior. Files using only
10
+ * portable `@db.*` annotations remain adapter-agnostic.
11
+ */
12
+ const annotations = {
13
+ engine: new AnnotationSpec({
14
+ description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
15
+ nodeType: ["interface"],
16
+ multiple: false,
17
+ argument: {
18
+ name: "engine",
19
+ type: "string",
20
+ values: [
21
+ "InnoDB",
22
+ "MyISAM",
23
+ "MEMORY",
24
+ "CSV",
25
+ "ARCHIVE"
26
+ ],
27
+ description: "MySQL storage engine name."
28
+ }
29
+ }),
30
+ charset: new AnnotationSpec({
31
+ description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
32
+ nodeType: ["interface", "prop"],
33
+ multiple: false,
34
+ argument: {
35
+ name: "charset",
36
+ type: "string",
37
+ values: [
38
+ "utf8mb4",
39
+ "utf8",
40
+ "latin1",
41
+ "ascii",
42
+ "binary"
43
+ ],
44
+ description: "MySQL character set name."
45
+ }
46
+ }),
47
+ collate: new AnnotationSpec({
48
+ description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
49
+ nodeType: ["interface", "prop"],
50
+ multiple: false,
51
+ argument: {
52
+ name: "collation",
53
+ type: "string",
54
+ description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
55
+ }
56
+ }),
57
+ unsigned: new AnnotationSpec({
58
+ description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
59
+ nodeType: ["prop"],
60
+ multiple: false
61
+ }),
62
+ type: new AnnotationSpec({
63
+ description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
64
+ nodeType: ["prop"],
65
+ multiple: false,
66
+ argument: {
67
+ name: "type",
68
+ type: "string",
69
+ description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
70
+ }
71
+ }),
72
+ onUpdate: new AnnotationSpec({
73
+ description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
74
+ nodeType: ["prop"],
75
+ multiple: false,
76
+ argument: {
77
+ name: "expression",
78
+ type: "string",
79
+ values: ["CURRENT_TIMESTAMP"],
80
+ description: "Expression to evaluate on row update."
81
+ }
82
+ })
83
+ };
84
+ //#endregion
85
+ //#region src/plugin/index.ts
86
+ const MysqlPlugin = () => ({
87
+ name: "mysql",
88
+ config() {
89
+ return { annotations: { db: { mysql: annotations } } };
90
+ }
91
+ });
92
+ //#endregion
2
93
  export { MysqlPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-mysql",
3
- "version": "0.1.104",
3
+ "version": "0.1.106",
4
4
  "description": "MySQL adapter for @atscript/db with mysql2 driver support.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -41,19 +41,19 @@
41
41
  "access": "public"
42
42
  },
43
43
  "devDependencies": {
44
- "@atscript/core": "^0.1.75",
45
- "@atscript/typescript": "^0.1.75",
44
+ "@atscript/core": "^0.1.76",
45
+ "@atscript/typescript": "^0.1.76",
46
46
  "@uniqu/core": "^0.1.6",
47
47
  "mysql2": "^3.11.0",
48
- "unplugin-atscript": "^0.1.75"
48
+ "unplugin-atscript": "^0.1.76"
49
49
  },
50
50
  "peerDependencies": {
51
- "@atscript/core": "^0.1.75",
52
- "@atscript/typescript": "^0.1.75",
51
+ "@atscript/core": "^0.1.76",
52
+ "@atscript/typescript": "^0.1.76",
53
53
  "@uniqu/core": "^0.1.6",
54
54
  "mysql2": ">=3.0.0",
55
- "@atscript/db": "^0.1.104",
56
- "@atscript/db-sql-tools": "^0.1.104"
55
+ "@atscript/db": "^0.1.106",
56
+ "@atscript/db-sql-tools": "^0.1.106"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "mysql2": {
@@ -1,6 +0,0 @@
1
- import { TAtscriptPlugin } from "@atscript/core";
2
-
3
- //#region src/plugin/index.d.ts
4
- declare const MysqlPlugin: () => TAtscriptPlugin;
5
- //#endregion
6
- export { MysqlPlugin as t };
@@ -1,6 +0,0 @@
1
- import { TAtscriptPlugin } from "@atscript/core";
2
-
3
- //#region src/plugin/index.d.ts
4
- declare const MysqlPlugin: () => TAtscriptPlugin;
5
- //#endregion
6
- export { MysqlPlugin as t };
@@ -1,98 +0,0 @@
1
- let _atscript_core = require("@atscript/core");
2
- //#region src/plugin/annotations.ts
3
- /**
4
- * MySQL-specific annotations.
5
- *
6
- * Merged into the global config under `{ db: { mysql: ... } }` so they
7
- * live alongside core's `@db.table`, `@db.index.*`, etc.
8
- *
9
- * These annotations opt-in to MySQL-specific behavior. Files using only
10
- * portable `@db.*` annotations remain adapter-agnostic.
11
- */
12
- const annotations = {
13
- engine: new _atscript_core.AnnotationSpec({
14
- description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
15
- nodeType: ["interface"],
16
- multiple: false,
17
- argument: {
18
- name: "engine",
19
- type: "string",
20
- values: [
21
- "InnoDB",
22
- "MyISAM",
23
- "MEMORY",
24
- "CSV",
25
- "ARCHIVE"
26
- ],
27
- description: "MySQL storage engine name."
28
- }
29
- }),
30
- charset: new _atscript_core.AnnotationSpec({
31
- description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
32
- nodeType: ["interface", "prop"],
33
- multiple: false,
34
- argument: {
35
- name: "charset",
36
- type: "string",
37
- values: [
38
- "utf8mb4",
39
- "utf8",
40
- "latin1",
41
- "ascii",
42
- "binary"
43
- ],
44
- description: "MySQL character set name."
45
- }
46
- }),
47
- collate: new _atscript_core.AnnotationSpec({
48
- description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
49
- nodeType: ["interface", "prop"],
50
- multiple: false,
51
- argument: {
52
- name: "collation",
53
- type: "string",
54
- description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
55
- }
56
- }),
57
- unsigned: new _atscript_core.AnnotationSpec({
58
- description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
59
- nodeType: ["prop"],
60
- multiple: false
61
- }),
62
- type: new _atscript_core.AnnotationSpec({
63
- description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
64
- nodeType: ["prop"],
65
- multiple: false,
66
- argument: {
67
- name: "type",
68
- type: "string",
69
- description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
70
- }
71
- }),
72
- onUpdate: new _atscript_core.AnnotationSpec({
73
- description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
74
- nodeType: ["prop"],
75
- multiple: false,
76
- argument: {
77
- name: "expression",
78
- type: "string",
79
- values: ["CURRENT_TIMESTAMP"],
80
- description: "Expression to evaluate on row update."
81
- }
82
- })
83
- };
84
- //#endregion
85
- //#region src/plugin/index.ts
86
- const MysqlPlugin = () => ({
87
- name: "mysql",
88
- config() {
89
- return { annotations: { db: { mysql: annotations } } };
90
- }
91
- });
92
- //#endregion
93
- Object.defineProperty(exports, "MysqlPlugin", {
94
- enumerable: true,
95
- get: function() {
96
- return MysqlPlugin;
97
- }
98
- });
@@ -1,93 +0,0 @@
1
- import { AnnotationSpec } from "@atscript/core";
2
- //#region src/plugin/annotations.ts
3
- /**
4
- * MySQL-specific annotations.
5
- *
6
- * Merged into the global config under `{ db: { mysql: ... } }` so they
7
- * live alongside core's `@db.table`, `@db.index.*`, etc.
8
- *
9
- * These annotations opt-in to MySQL-specific behavior. Files using only
10
- * portable `@db.*` annotations remain adapter-agnostic.
11
- */
12
- const annotations = {
13
- engine: new AnnotationSpec({
14
- description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
15
- nodeType: ["interface"],
16
- multiple: false,
17
- argument: {
18
- name: "engine",
19
- type: "string",
20
- values: [
21
- "InnoDB",
22
- "MyISAM",
23
- "MEMORY",
24
- "CSV",
25
- "ARCHIVE"
26
- ],
27
- description: "MySQL storage engine name."
28
- }
29
- }),
30
- charset: new AnnotationSpec({
31
- description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
32
- nodeType: ["interface", "prop"],
33
- multiple: false,
34
- argument: {
35
- name: "charset",
36
- type: "string",
37
- values: [
38
- "utf8mb4",
39
- "utf8",
40
- "latin1",
41
- "ascii",
42
- "binary"
43
- ],
44
- description: "MySQL character set name."
45
- }
46
- }),
47
- collate: new AnnotationSpec({
48
- description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
49
- nodeType: ["interface", "prop"],
50
- multiple: false,
51
- argument: {
52
- name: "collation",
53
- type: "string",
54
- description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
55
- }
56
- }),
57
- unsigned: new AnnotationSpec({
58
- description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
59
- nodeType: ["prop"],
60
- multiple: false
61
- }),
62
- type: new AnnotationSpec({
63
- description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
64
- nodeType: ["prop"],
65
- multiple: false,
66
- argument: {
67
- name: "type",
68
- type: "string",
69
- description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
70
- }
71
- }),
72
- onUpdate: new AnnotationSpec({
73
- description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
74
- nodeType: ["prop"],
75
- multiple: false,
76
- argument: {
77
- name: "expression",
78
- type: "string",
79
- values: ["CURRENT_TIMESTAMP"],
80
- description: "Expression to evaluate on row update."
81
- }
82
- })
83
- };
84
- //#endregion
85
- //#region src/plugin/index.ts
86
- const MysqlPlugin = () => ({
87
- name: "mysql",
88
- config() {
89
- return { annotations: { db: { mysql: annotations } } };
90
- }
91
- });
92
- //#endregion
93
- export { MysqlPlugin as t };