forj 0.0.11 → 0.0.12

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "forj",
3
3
  "description": "SQLite ORM and Query Builder whitout dependencies",
4
- "version": "0.0.11",
4
+ "version": "0.0.12",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "files": ["src"],
@@ -1,5 +1,6 @@
1
1
  import Column from './column'
2
2
  import ForeignKey from './foreign-key'
3
+ import { tableName } from '../utils'
3
4
  import type {
4
5
  ColumnDefinition, IndexDefinition, ForeignKeyDefinition,
5
6
  } from './types'
@@ -13,7 +14,7 @@ export class Blueprint {
13
14
  #renameColumns: Map<string, string> = new Map()
14
15
 
15
16
  constructor(table: string) {
16
- this.#table = table
17
+ this.#table = tableName(table)
17
18
  }
18
19
 
19
20
  #column(definition: ColumnDefinition) {
@@ -22,96 +23,108 @@ export class Blueprint {
22
23
  return new Column(column)
23
24
  }
24
25
 
25
- id(name: string = 'id') { // Auto-increment ID (bigint unsigned)
26
- return this.#column({ name, type: 'INTEGER', autoIncrement: true, primary: true, nullable: false })
26
+ id(name: string = 'id') {
27
+ return this.#column({ name, type: 'INTEGER', primary: true, nullable: true })
27
28
  }
28
29
 
29
- string(name: string, length: number = 255) {
30
- return this.#column({ name, type: 'VARCHAR', length, nullable: false })
30
+ string(name: string, length: number = 0) {
31
+ return this.#column({ name, type: 'VARCHAR', length })
31
32
  }
32
33
 
33
34
  text(name: string) {
34
- return this.#column({ name, type: 'TEXT', nullable: false })
35
+ return this.#column({ name, type: 'TEXT' })
35
36
  }
36
37
 
37
38
  int(name: string) {
38
- return this.#column({ name, type: 'INTEGER', nullable: false })
39
+ return this.#column({ name, type: 'INTEGER' })
39
40
  }
40
41
  integer(name: string) {
41
42
  return this.int(name)
42
43
  }
43
44
  real(name: string) {
44
- return this.#column({ name, type: 'REAL', nullable: false })
45
+ return this.#column({ name, type: 'REAL' })
45
46
  }
46
47
  numeric(name: string) {
47
- return this.#column({ name, type: 'NUMERIC', nullable: false })
48
+ return this.#column({ name, type: 'NUMERIC' })
48
49
  }
49
50
 
50
51
  // bigInteger(name: string) {
51
- // return this.#column({ name, type: 'BIGINT', nullable: false })
52
+ // return this.#column({ name, type: 'BIGINT' })
52
53
  // }
53
54
 
54
55
  // tinyInteger(name: string) {
55
- // return this.#column({ name, type: 'TINYINT', nullable: false })
56
+ // return this.#column({ name, type: 'TINYINT' })
56
57
  // }
57
58
 
58
59
  boolean(name: string) {
59
- return this.#column({ name, type: 'INTEGER', nullable: false })
60
+ return this.#column({ name, type: 'INTEGER' })
60
61
  }
61
62
 
62
63
  // decimal(name: string, precision: number = 8, scale: number = 2) {
63
- // return this.#column({ name, type: `DECIMAL(${precision},${scale})`, nullable: false })
64
+ // return this.#column({ name, type: `DECIMAL(${precision},${scale})` })
64
65
  // }
65
66
 
66
67
  // float(name: string) {
67
- // return this.#column({ name, type: 'FLOAT', nullable: false })
68
+ // return this.#column({ name, type: 'FLOAT' })
68
69
  // }
69
70
 
70
71
  // double(name: string) {
71
- // return this.#column({ name, type: 'DOUBLE', nullable: false })
72
+ // return this.#column({ name, type: 'DOUBLE' })
72
73
  // }
73
74
 
74
75
  // date(name: string) {
75
- // return this.#column({ name, type: 'DATE', nullable: false })
76
+ // return this.#column({ name, type: 'DATE' })
76
77
  // }
77
78
 
78
79
  // dateTime(name: string) {
79
- // return this.#column({ name, type: 'DATETIME', nullable: false })
80
+ // return this.#column({ name, type: 'DATETIME' })
80
81
  // }
81
82
 
82
83
  // timestamp(name: string) {
83
- // return this.#column({ name, type: 'TIMESTAMP', nullable: false })
84
+ // return this.#column({ name, type: 'TIMESTAMP' })
84
85
  // }
85
86
 
86
87
  // time(name: string) {
87
- // return this.#column({ name, type: 'TIME', nullable: false })
88
+ // return this.#column({ name, type: 'TIME' })
88
89
  // }
89
90
 
90
- json(name: string) {
91
- return this.#column({ name, type: 'JSON', nullable: false })
92
- }
91
+ // json(name: string) {
92
+ // return this.#column({ name, type: 'JSON' })
93
+ // }
93
94
 
94
- enum(name: string, values: string[]) {
95
- return this.#column({ name, type: `ENUM(${values.map(v => `'${v}'`).join(', ')})`, nullable: false })
95
+ enum(name: string, values: string[] | number[]) {
96
+ return this.#column({
97
+ name,
98
+ type: values.every(v => typeof v == 'string') ? 'TEXT' : (values.every(v => Number.isInteger(v)) ? 'INTEGER' : 'REAL'),
99
+ // TODO:
100
+ // Checking floating-point numbers can be problematic due to the precision of REAL numbers
101
+ // SQLite might store 0.5 as 0.4999999 or 0.5000001, causing the check to fail
102
+ // Maybe works:
103
+ // rate NUMERIC(3,1) NOT NULL DEFAULT 0 CHECK(
104
+ // rate IN (0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5)
105
+ // )
106
+ raw: `CHECK(${name} IN (${values.map(v => typeof v == 'string' ? `'${v.replace(/'/g, "\\'")}'` : v).join(', ')}))`,
107
+ })
96
108
  }
97
109
 
98
110
  blob(name: string) {
99
- return this.#column({ name, type: 'BLOB', nullable: false })
111
+ return this.#column({ name, type: 'BLOB' })
100
112
  }
101
113
 
102
- timestamps() {
103
- this.#column({ name: 'created_at', type: 'TEXT', nullable: false })
104
- this.#column({ name: 'updated_at', type: 'TEXT', nullable: false })
114
+ timestamps(columnType: 'int' | 'date' = 'int') {
115
+ const isInt = columnType == 'int'
116
+ const type = isInt ? 'INTEGER' : 'DATETIME'
117
+ this.#column({ name: 'created_at', type, raw: 'DEFAULT '+ (isInt ? '(unixepoch())' : 'CURRENT_TIMESTAMP') })
118
+ this.#column({ name: 'updated_at', type, nullable: true })
105
119
  return this
106
120
  }
107
121
 
108
- softDelete(name: string = 'deleted_at') {
109
- this.#column({ name, type: 'TEXT', nullable: true })
122
+ softDelete(columnType: 'int' | 'date' = 'int', name: string = 'deleted_at') {
123
+ this.#column({ name, type: columnType == 'int' ? 'INTEGER' : 'DATETIME', nullable: true })
110
124
  return this
111
125
  }
112
-
113
- softDeletes(name: string = 'deleted_at') {
114
- return this.softDelete(name)
126
+ softDeletes(columnType: 'int' | 'date' = 'int', name: string = 'deleted_at') {
127
+ return this.softDelete(columnType, name)
115
128
  }
116
129
 
117
130
  foreign(column: string) {
@@ -1,15 +1,16 @@
1
1
  import { Blueprint } from './blueprint'
2
+ import { tableName, tableSlug } from '../utils'
2
3
  import type { ColumnDefinition, IndexDefinition, ForeignKeyDefinition } from './types'
3
4
 
4
5
  export default class SchemaBuilder {
5
- static create(blueprint: Blueprint): string {
6
- const tableName = blueprint.table
6
+ static create(blueprint: Blueprint, exist: boolean = false): string {
7
+ const table = blueprint.table
7
8
  const columns = blueprint.columns
8
9
  const indexes = blueprint.indexes
9
10
  const foreignKeys = blueprint.foreignKeys
10
11
 
11
12
  const columnDefinitions = columns.map(col => this.#column(col))
12
- const indexDefinitions = indexes.map(idx => this.#index(idx, tableName))
13
+ const indexDefinitions = indexes.map(idx => this.#index(idx, table))
13
14
  const foreignKeyDefinitions = foreignKeys.map(fk => this.#foreignKey(fk))
14
15
 
15
16
  const allDefinitions = [
@@ -18,31 +19,31 @@ export default class SchemaBuilder {
18
19
  ...foreignKeyDefinitions
19
20
  ].filter(Boolean)
20
21
 
21
- return `CREATE TABLE ${tableName} (\n ${allDefinitions.join(',\n ')}\n);`
22
+ return `CREATE TABLE ${exist ? 'IF NOT EXISTS ' : ''}${table} (\n ${allDefinitions.join(',\n ')}\n);`
22
23
  }
23
24
 
24
25
  static alter(blueprint: Blueprint): string[] {
25
- const tableName = blueprint.table
26
+ const table = blueprint.table
26
27
  const statements: string[] = []
27
28
 
28
29
  const columns = blueprint.columns
29
30
  if (columns.length > 0)
30
- columns.forEach(col => statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${this.#column(col)};`))
31
+ columns.forEach(col => statements.push(`ALTER TABLE ${table} ADD COLUMN ${this.#column(col)};`))
31
32
 
32
33
  const dropColumns = blueprint.dropColumns
33
- dropColumns.forEach(col => statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${col};`))
34
+ dropColumns.forEach(col => statements.push(`ALTER TABLE ${table} DROP COLUMN ${col};`))
34
35
 
35
36
  const renameColumns = blueprint.renameColumns
36
- renameColumns.forEach((newName, oldName) => statements.push(`ALTER TABLE ${tableName} RENAME COLUMN ${oldName} TO ${newName};`))
37
+ renameColumns.forEach((newName, oldName) => statements.push(`ALTER TABLE ${table} RENAME COLUMN ${oldName} TO ${newName};`))
37
38
 
38
39
  const indexes = blueprint.indexes
39
40
  indexes.forEach(idx => {
40
- const indexSql = this.#indexStatement(idx, tableName)
41
+ const indexSql = this.#indexStatement(idx, table)
41
42
  if (indexSql) statements.push(indexSql)
42
43
  })
43
44
 
44
45
  const foreignKeys = blueprint.foreignKeys
45
- foreignKeys.forEach(fk => statements.push(`ALTER TABLE ${tableName} ADD ${this.#foreignKey(fk)};`))
46
+ foreignKeys.forEach(fk => statements.push(`ALTER TABLE ${table} ADD ${this.#foreignKey(fk)};`))
46
47
 
47
48
  return statements
48
49
  }
@@ -62,36 +63,41 @@ export default class SchemaBuilder {
62
63
  // if (column.unsigned)
63
64
  // sql += ' UNSIGNED'
64
65
 
65
- if (column.nullable) {
66
- sql += ' NULL'
67
- } else {
66
+ if (!column.nullable)
68
67
  sql += ' NOT NULL'
69
- }
70
68
 
71
69
  if (column.default !== undefined) {
70
+ sql += ' DEFAULT '
72
71
  if (column.default === null) {
73
- sql += ' DEFAULT NULL'
74
- } else if (typeof column.default == 'string') {
75
- sql += ` DEFAULT '${column.default}'`
76
- } else if (typeof column.default == 'boolean') {
77
- sql += ` DEFAULT ${column.default ? 1 : 0}`
72
+ sql += 'NULL'
78
73
  } else {
79
- sql += ` DEFAULT ${column.default}`
74
+ const type = typeof column.default
75
+ if (type == 'string') {
76
+ sql += `'${column.default.replace(/'/g, "\\'")}'`
77
+ } else if (type == 'boolean') {
78
+ sql += Number(column.default)
79
+ } else {
80
+ sql += `${column.default}`.replace(/''/g, "'\\'")
81
+ }
80
82
  }
81
83
  }
82
84
 
83
85
  if (column.unique)
84
86
  sql += ' UNIQUE'
85
87
 
88
+ if (column.raw)
89
+ sql += ' '+ column.raw
90
+
86
91
  // if (column.comment)
87
92
  // sql += ` COMMENT '${column.comment.replace(/'/g, "''")}'`
88
93
 
89
94
  return sql
90
95
  }
91
96
 
92
- static #index(index: IndexDefinition, tableName: string): string {
93
- const indexName = index.name || `${tableName}_${index.columns.join('_')}_${index.type}`
97
+ static #index(index: IndexDefinition, table: string): string {
98
+ const indexName = index.name || tableSlug(table) +`_${index.columns.join('_')}_${index.type}`
94
99
  const columns = index.columns.join(', ')
100
+ table = tableName(table)
95
101
 
96
102
  switch (index.type) {
97
103
  case 'primary':
@@ -105,17 +111,18 @@ export default class SchemaBuilder {
105
111
  }
106
112
  }
107
113
 
108
- static #indexStatement(index: IndexDefinition, tableName: string): string {
109
- const indexName = index.name || `${tableName}_${index.columns.join('_')}_${index.type}`
114
+ static #indexStatement(index: IndexDefinition, table: string): string {
115
+ const indexName = index.name || tableSlug(table) +`_${index.columns.join('_')}_${index.type}`
110
116
  const columns = index.columns.join(', ')
117
+ table = tableName(table)
111
118
 
112
119
  switch (index.type) {
113
120
  case 'primary':
114
- return `ALTER TABLE ${tableName} ADD PRIMARY KEY (${columns});`
121
+ return `ALTER TABLE ${table} ADD PRIMARY KEY (${columns});`
115
122
  case 'unique':
116
- return `CREATE UNIQUE INDEX ${indexName} ON ${tableName} (${columns});`
123
+ return `CREATE UNIQUE INDEX ${indexName} ON ${table} (${columns});`
117
124
  case 'index':
118
- return `CREATE INDEX ${indexName} ON ${tableName} (${columns});`
125
+ return `CREATE INDEX ${indexName} ON ${table} (${columns});`
119
126
  default:
120
127
  return ''
121
128
  }
@@ -133,35 +140,43 @@ export default class SchemaBuilder {
133
140
  return sql
134
141
  }
135
142
 
136
- static drop(tableName: string) {
137
- return `DROP TABLE ${tableName};`
143
+ static drop(table: string, exist: boolean = false) {
144
+ return `DROP TABLE ${exist ? 'IF EXISTS ' : ''}${tableName(table)};`
145
+ }
146
+
147
+ static dropIfExists(table: string) {
148
+ return this.drop(table, true)
149
+ }
150
+
151
+ static dropView(view: string, exist: boolean = false) {
152
+ return `DROP VIEW ${exist ? 'IF EXISTS ' : ''}[${tableName(view)}];`
138
153
  }
139
154
 
140
- static dropIfExists(tableName: string) {
141
- return `DROP TABLE IF EXISTS ${tableName};`
155
+ static dropViewIfExists(view: string) {
156
+ return this.dropView(view, true)
142
157
  }
143
158
 
144
159
  static rename(from: string, to: string) {
145
- return `ALTER TABLE ${from} RENAME TO ${to};`
160
+ return `ALTER TABLE ${tableName(from)} RENAME TO ${tableName(to)};`
146
161
  }
147
162
 
148
- static hasTable(tableName: string) {
149
- return `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}';`
163
+ static hasTable(table: string) {
164
+ return `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName(table)}';`
150
165
  }
151
166
 
152
- static hasColumn(tableName: string, columnName: string) { // TODO refactor..
153
- return `PRAGMA table_info(${tableName});`
167
+ static hasColumn(table: string, columnName: string) { // TODO refactor..
168
+ return `PRAGMA table_info(${tableName(table)});`
154
169
  }
155
170
 
156
171
  static getAllTables() {
157
172
  return `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';`
158
173
  }
159
174
 
160
- static getColumns(tableName: string): string {
161
- return `PRAGMA table_info(${tableName});`
175
+ static getColumns(table: string): string {
176
+ return `PRAGMA table_info(${tableName(table)});`
162
177
  }
163
178
 
164
179
  static dropAllTables(tables: string[]) {
165
- return tables.map(table => `DROP TABLE IF EXISTS ${table};`)
180
+ return tables.map(table => `DROP TABLE IF EXISTS ${tableName(table)};`)
166
181
  }
167
182
  }
@@ -13,8 +13,18 @@ export default class Column {
13
13
  return this
14
14
  }
15
15
 
16
- default(value: any) {
17
- this.column.default = value
16
+ default(val: any) {
17
+ this.column.default = val
18
+ return this
19
+ }
20
+
21
+ defaultRaw(val: string) {
22
+ this.column.raw = 'DEFAULT '+ val
23
+ return this
24
+ }
25
+
26
+ raw(raw: string) {
27
+ this.column.raw = raw
18
28
  return this
19
29
  }
20
30
 
@@ -41,30 +41,42 @@ export class Schema {
41
41
  // this.#addStatement(...sql)
42
42
  // }
43
43
 
44
- static async create(tableName: string, fn: BlueprintFn): Promise<void> {
45
- const blueprint = new Blueprint(tableName)
44
+ static async create(table: string, fn: BlueprintFn, exist: boolean = false): Promise<void> {
45
+ const blueprint = new Blueprint(table)
46
46
  fn(blueprint)
47
- await this.#addStatement(Builder.create(blueprint))
47
+ await this.#addStatement(Builder.create(blueprint, exist))
48
48
  }
49
49
 
50
- static async table(tableName: string, fn: BlueprintFn): Promise<void> {
51
- const blueprint = new Blueprint(tableName)
50
+ static async createIfNotExists(table: string, fn: BlueprintFn): Promise<void> {
51
+ this.create(table, fn, true)
52
+ }
53
+
54
+ static async table(table: string, fn: BlueprintFn): Promise<void> {
55
+ const blueprint = new Blueprint(table)
52
56
  fn(blueprint)
53
57
  await this.#addStatement(Builder.alter(blueprint))
54
58
  }
55
59
 
56
- static async drop(tableName: string): Promise<void> {
57
- await this.#addStatement(Builder.drop(tableName))
60
+ static async drop(table: string): Promise<void> {
61
+ await this.#addStatement(Builder.drop(table))
58
62
  }
59
63
 
60
- static async dropIfExists(tableName: string): Promise<void> {
61
- await this.#addStatement(Builder.dropIfExists(tableName))
64
+ static async dropIfExists(table: string): Promise<void> {
65
+ await this.#addStatement(Builder.dropIfExists(table))
62
66
  }
63
67
 
64
68
  static async rename(from: string, to: string): Promise<void> {
65
69
  await this.#addStatement(Builder.rename(from, to))
66
70
  }
67
71
 
72
+ static async dropView(view: string): Promise<void> {
73
+ await this.#addStatement(Builder.dropView(view))
74
+ }
75
+
76
+ static async dropViewIfExists(view: string): Promise<void> {
77
+ await this.#addStatement(Builder.dropViewIfExists(view))
78
+ }
79
+
68
80
  static async disableForeignKeyConstraints(): Promise<void> {
69
81
  this.#foreignKeyConstraintsEnabled = false
70
82
  await this.#addStatement('PRAGMA foreign_keys = OFF;')
@@ -92,29 +104,29 @@ export class Schema {
92
104
  // await this.#addStatement(sql)
93
105
  // }
94
106
 
95
- // static async hasTable(tableName: string): Promise<boolean> {
107
+ // static async hasTable(table: string): Promise<boolean> {
96
108
  // if (!this.#c)
97
109
  // throw new Error('Database connection not set')
98
110
 
99
- // const sql = Builder.hasTable(tableName)
111
+ // const sql = Builder.hasTable(table)
100
112
  // const result = await this.#c.query(sql)
101
113
  // return result.length > 0
102
114
  // }
103
115
 
104
- // static async hasColumn(tableName: string, columnName: string): Promise<boolean> {
116
+ // static async hasColumn(table: string, columnName: string): Promise<boolean> {
105
117
  // if (!this.#c)
106
118
  // throw new Error('Database connection not set')
107
119
 
108
- // const sql = Builder.hasColumn(tableName, columnName)
120
+ // const sql = Builder.hasColumn(table, columnName)
109
121
  // const result = await this.#c.query(sql)
110
122
  // return result.some((row: any) => row.name === columnName)
111
123
  // }
112
124
 
113
- // static async hasColumns(tableName: string, ...columnNames: string[]): Promise<boolean> {
125
+ // static async hasColumns(table: string, ...columnNames: string[]): Promise<boolean> {
114
126
  // if (!this.#c)
115
127
  // throw new Error('Database connection not set')
116
128
 
117
- // const sql = Builder.hasColumn(tableName, '')
129
+ // const sql = Builder.hasColumn(table, '')
118
130
  // const result = await this.#c.query(sql)
119
131
  // const existingColumns = result.map((row: any) => row.name)
120
132
 
@@ -130,20 +142,20 @@ export class Schema {
130
142
  // return result.map((row: any) => row.name)
131
143
  // }
132
144
 
133
- // static async getColumns(tableName: string): Promise<any[]> {
145
+ // static async getColumns(table: string): Promise<any[]> {
134
146
  // if (!this.#c)
135
147
  // throw new Error('Database connection not set')
136
148
 
137
- // const sql = Builder.getColumns(tableName)
149
+ // const sql = Builder.getColumns(table)
138
150
  // return await this.#c.query(sql)
139
151
  // }
140
152
 
141
- // static async getColumnType(tableName: string, columnName: string): Promise<string | null> {
153
+ // static async getColumnType(table: string, columnName: string): Promise<string | null> {
142
154
  // if (!this.#c) {
143
155
  // throw new Error('Database connection not set')
144
156
  // }
145
157
 
146
- // const sql = Builder.getColumns(tableName)
158
+ // const sql = Builder.getColumns(table)
147
159
  // const result = await this.#c.query(sql)
148
160
  // const column = result.find((row: any) => row.name === columnName)
149
161
  // return column ? column.type : null
@@ -18,6 +18,7 @@ export interface ColumnDefinition {
18
18
  unsigned?: boolean,
19
19
  index?: boolean,
20
20
  comment?: string,
21
+ raw?: string,
21
22
  }
22
23
 
23
24
  export interface IndexDefinition {
package/src/utils.ts CHANGED
@@ -132,3 +132,48 @@ export function isJoinCompare(val: any, schema?: DBSchema) {
132
132
  const keys = zGet(val, schema)
133
133
  return keys && keys?.length
134
134
  }
135
+
136
+ const reservedWords = new Set([
137
+ 'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS',
138
+ 'ASC', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BY',
139
+ 'CASCADE', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT',
140
+ 'CONFLICT', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE',
141
+ 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATABASE', 'DEFAULT', 'DEFERRABLE',
142
+ 'DEFERRED', 'DELETE', 'DESC', 'DETACH', 'DISTINCT', 'DROP', 'EACH',
143
+ 'ELSE', 'END', 'ESCAPE', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXPLAIN',
144
+ 'FAIL', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'GLOB', 'GROUP', 'HAVING',
145
+ 'IF', 'IGNORE', 'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INITIALLY',
146
+ 'INNER', 'INSERT', 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'ISNULL',
147
+ 'JOIN', 'KEY', 'LEFT', 'LIKE', 'LIMIT', 'MATCH', 'NATURAL', 'NO',
148
+ 'NOT', 'NOTNULL', 'NULL', 'OF', 'OFFSET', 'ON', 'OR', 'ORDER',
149
+ 'OUTER', 'PLAN', 'PRAGMA', 'PRIMARY', 'QUERY', 'RAISE', 'RECURSIVE',
150
+ 'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPLACE',
151
+ 'RESTRICT', 'RIGHT', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SELECT',
152
+ 'SET', 'TABLE', 'TEMP', 'TEMPORARY', 'THEN', 'TO', 'TRANSACTION',
153
+ 'TRIGGER', 'UNION', 'UNIQUE', 'UPDATE', 'USING', 'VACUUM', 'VALUES',
154
+ 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE', 'WITH', 'WITHOUT',
155
+ ])
156
+ export function tableName(name: string) {
157
+ name = name.trim()
158
+ if (!name || name?.includes('.'))
159
+ throw new Error(`Invalid table name "${!name ? 'empty' : name}"`)
160
+
161
+ if (
162
+ /^[0-9]/.test(name)
163
+ || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
164
+ || reservedWords.has(name.toUpperCase())
165
+ ) {
166
+ return `"${name}"`
167
+ }
168
+
169
+ return name
170
+ }
171
+
172
+ export function tableSlug(name: string) {
173
+ return name.trim()
174
+ .replace(/([A-Z])/g, '_$1')
175
+ .replace(/[^a-zA-Z0-9_]/g, '_')
176
+ .toLowerCase()
177
+ .replace(/_+/g, '_')
178
+ .replace(/^_|_$/g, '')
179
+ }