@foul11/awesome-db 1.1.0

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/BitFields.d.ts +29 -0
  3. package/dist/BitFields.d.ts.map +1 -0
  4. package/dist/Error.d.ts +21 -0
  5. package/dist/Error.d.ts.map +1 -0
  6. package/dist/ORM.d.ts +14 -0
  7. package/dist/ORM.d.ts.map +1 -0
  8. package/dist/SQLParser.d.ts +1394 -0
  9. package/dist/SQLParser.d.ts.map +1 -0
  10. package/dist/WebpackFileProvider.d.ts +12 -0
  11. package/dist/WebpackFileProvider.d.ts.map +1 -0
  12. package/dist/alter/column_add.d.ts +7 -0
  13. package/dist/alter/column_add.d.ts.map +1 -0
  14. package/dist/alter/column_drop.d.ts +6 -0
  15. package/dist/alter/column_drop.d.ts.map +1 -0
  16. package/dist/alter/column_rename.d.ts +6 -0
  17. package/dist/alter/column_rename.d.ts.map +1 -0
  18. package/dist/alter/column_update.d.ts +7 -0
  19. package/dist/alter/column_update.d.ts.map +1 -0
  20. package/dist/alter/columns_order.d.ts +6 -0
  21. package/dist/alter/columns_order.d.ts.map +1 -0
  22. package/dist/alter/index.d.ts +7 -0
  23. package/dist/alter/index.d.ts.map +1 -0
  24. package/dist/alter/pragma.d.ts +4 -0
  25. package/dist/alter/pragma.d.ts.map +1 -0
  26. package/dist/alter/utils.d.ts +6 -0
  27. package/dist/alter/utils.d.ts.map +1 -0
  28. package/dist/defaults.d.ts +2 -0
  29. package/dist/defaults.d.ts.map +1 -0
  30. package/dist/index.d.ts +36 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.mjs +1540 -0
  33. package/dist/index.mjs.map +1 -0
  34. package/dist/indexer.d.ts +12 -0
  35. package/dist/indexer.d.ts.map +1 -0
  36. package/dist/log/access_log.d.ts +7 -0
  37. package/dist/log/access_log.d.ts.map +1 -0
  38. package/dist/log/db.d.ts +6 -0
  39. package/dist/log/db.d.ts.map +1 -0
  40. package/dist/log/index.d.ts +3 -0
  41. package/dist/log/index.d.ts.map +1 -0
  42. package/dist/tables/AccessLog/index.d.ts +79 -0
  43. package/dist/tables/AccessLog/index.d.ts.map +1 -0
  44. package/dist/tables/AccessLog/schema.d.ts +17 -0
  45. package/dist/tables/AccessLog/schema.d.ts.map +1 -0
  46. package/dist/tables/Permission/index.d.ts +43 -0
  47. package/dist/tables/Permission/index.d.ts.map +1 -0
  48. package/dist/tables/Permission/schema.d.ts +12 -0
  49. package/dist/tables/Permission/schema.d.ts.map +1 -0
  50. package/dist/tables/SetString/index.d.ts +10 -0
  51. package/dist/tables/SetString/index.d.ts.map +1 -0
  52. package/dist/tables/SetString/schema.d.ts +7 -0
  53. package/dist/tables/SetString/schema.d.ts.map +1 -0
  54. package/dist/tables/Settings/index.d.ts +42 -0
  55. package/dist/tables/Settings/index.d.ts.map +1 -0
  56. package/dist/tables/Settings/schema.d.ts +8 -0
  57. package/dist/tables/Settings/schema.d.ts.map +1 -0
  58. package/dist/tables/Transaction/index.d.ts +90 -0
  59. package/dist/tables/Transaction/index.d.ts.map +1 -0
  60. package/dist/tables/Transaction/schema.d.ts +16 -0
  61. package/dist/tables/Transaction/schema.d.ts.map +1 -0
  62. package/dist/types/index.d.ts +12 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/utils.d.ts +42 -0
  65. package/dist/utils.d.ts.map +1 -0
  66. package/eslint.config.js +7 -0
  67. package/package.json +54 -0
  68. package/src/BitFields.ts +160 -0
  69. package/src/Error.ts +13 -0
  70. package/src/ORM.ts +49 -0
  71. package/src/SQLParser.js +1204 -0
  72. package/src/WebpackFileProvider.ts +63 -0
  73. package/src/alter/column_add.ts +79 -0
  74. package/src/alter/column_drop.ts +54 -0
  75. package/src/alter/column_rename.ts +55 -0
  76. package/src/alter/column_update.ts +92 -0
  77. package/src/alter/columns_order.ts +60 -0
  78. package/src/alter/index.ts +6 -0
  79. package/src/alter/pragma.ts +10 -0
  80. package/src/alter/utils.ts +70 -0
  81. package/src/defaults.ts +3 -0
  82. package/src/index.ts +227 -0
  83. package/src/indexer.ts +75 -0
  84. package/src/log/access_log.ts +29 -0
  85. package/src/log/db.ts +28 -0
  86. package/src/log/index.ts +2 -0
  87. package/src/tables/AccessLog/index.ts +252 -0
  88. package/src/tables/AccessLog/schema.ts +20 -0
  89. package/src/tables/Permission/index.ts +220 -0
  90. package/src/tables/Permission/schema.ts +13 -0
  91. package/src/tables/SetString/index.ts +45 -0
  92. package/src/tables/SetString/schema.ts +7 -0
  93. package/src/tables/Settings/index.ts +135 -0
  94. package/src/tables/Settings/schema.ts +8 -0
  95. package/src/tables/Transaction/index.ts +343 -0
  96. package/src/tables/Transaction/schema.ts +20 -0
  97. package/src/types/index.ts +33 -0
  98. package/src/utils.ts +48 -0
  99. package/test/sqliteExtExpert.test.ts +39 -0
  100. package/tsconfig.build.json +17 -0
  101. package/tsconfig.json +16 -0
@@ -0,0 +1,343 @@
1
+ export * from './schema';
2
+
3
+ import { SetString } from '../SetString';
4
+ import { BitFields } from '../../BitFields';
5
+ import { master_table } from '../../utils';
6
+ import { ORM } from '../../ORM';
7
+
8
+ import type { Kysely } from 'kysely';
9
+ import type { DBTablesByType } from '../../types';
10
+ import type { TableSetString } from '../SetString';
11
+ import type { TableTransaction } from './schema';
12
+
13
+ type DefaultKeyTransaction = 'transaction';
14
+ interface DefaultKyselyTransaction {
15
+ transaction: TableTransaction
16
+ transaction_set_reason: TableSetString
17
+ }
18
+
19
+ interface TransactionData {
20
+ flag: number
21
+ reason: string
22
+ since_at?: Date
23
+ deadline_at?: Date
24
+ parent_id?: number
25
+ }
26
+
27
+ interface TransactionHistory {
28
+ amount: number
29
+ reason: string | null
30
+ date: string | null
31
+ }
32
+
33
+ export interface TransactionStore {
34
+ get(fk_id: number): Promise<number>
35
+ set(fk_id: number, value: number, options: TransactionData): Promise<number | undefined>
36
+ add(fk_id: number, amount: number, options: TransactionData): Promise<number | undefined>
37
+
38
+ accept(id: number): Promise<boolean>
39
+ reject(id: number): Promise<boolean>
40
+
41
+ history(fk_id: number): Promise<TransactionHistory[]>
42
+ }
43
+
44
+ export class TransactionStoreDB implements TransactionStore {
45
+ protected readonly db: Kysely<DefaultKyselyTransaction>;
46
+ protected readonly domain: DefaultKeyTransaction;
47
+
48
+ protected constructor(db: any, domain: any) {
49
+ this.db = db;
50
+ this.domain = domain;
51
+ }
52
+
53
+ static async create<
54
+ DB,
55
+ Domain extends DBTablesByType<DB, TableTransaction>,
56
+ >(
57
+ db: Kysely<DB>,
58
+ domain: Domain,
59
+ ) {
60
+ const master = await master_table(db);
61
+ const tables = [
62
+ `${domain}`,
63
+ `${domain}_set_reason`,
64
+ ];
65
+
66
+ for (const table of tables) {
67
+ const info = master.find(r => r.name == table && r.type == 'table');
68
+
69
+ if (!info)
70
+ throw new Error(`Table ${table} not found, can't create transaction store`);
71
+ }
72
+
73
+ return new TransactionStoreDB(db, domain);
74
+ }
75
+
76
+ protected async db_get(fk_id: number, curr_date: Date, db_builder = this.db): Promise<number> {
77
+ const curr_iso = curr_date.toISOString();
78
+
79
+ return Number((
80
+ await db_builder
81
+ .selectFrom(this.domain)
82
+ .select(sb => sb
83
+ .fn.sum(sb.ref('amount')).as('sum'))
84
+ .where('fk_id', '=', fk_id)
85
+ .where(eb => eb
86
+ .or([
87
+ eb('deadline_at', '>=', curr_iso),
88
+ eb('deadline_at', 'is', null),
89
+ ]))
90
+ .where(eb => eb
91
+ .or([
92
+ eb('since_at', '<=', curr_iso),
93
+ eb('since_at', 'is', null),
94
+ ]))
95
+ .where('created_at', '<=', curr_iso)
96
+ .where('accept_at', 'is not', null)
97
+ .where('reject_at', 'is', null)
98
+ .groupBy('fk_id')
99
+ .executeTakeFirst()
100
+ )?.sum ?? 0);
101
+ }
102
+
103
+ protected async db_set(
104
+ fk_id: number,
105
+ value: number,
106
+ options: TransactionData,
107
+ db_builder = this.db,
108
+ ): Promise<number | undefined> {
109
+ const curr_date = new Date();
110
+
111
+ return (
112
+ await db_builder
113
+ .transaction()
114
+ .execute(async (trx) => {
115
+ const curr_amount = await this.db_get(fk_id, curr_date, trx);
116
+ return this.db_add(fk_id, value - curr_amount, options, trx);
117
+ })
118
+ );
119
+ }
120
+
121
+ protected async db_add(
122
+ fk_id: number,
123
+ value: number,
124
+ options: TransactionData,
125
+ db_builder = this.db,
126
+ ): Promise<number | undefined> {
127
+ return (
128
+ await db_builder
129
+ .transaction()
130
+ .execute(async (trx) => {
131
+ const reason_id = await SetString.store_value_or_throw<DefaultKyselyTransaction>(options.reason, `${this.domain}_set_reason`, trx);
132
+
133
+ const trx_id = await trx
134
+ .insertInto(this.domain)
135
+ .returning('id')
136
+ .values({
137
+ fk_id,
138
+ flag: options.flag,
139
+ amount: value,
140
+ reason_id,
141
+ since_at: options?.since_at?.toISOString(),
142
+ deadline_at: options?.deadline_at?.toISOString(),
143
+ parent_id: options?.parent_id,
144
+ })
145
+ .executeTakeFirst();
146
+
147
+ return trx_id?.id;
148
+ })
149
+ );
150
+ }
151
+
152
+ protected async db_accept(id: number, db_builder = this.db): Promise<boolean> {
153
+ const curr_date = new Date();
154
+
155
+ return (
156
+ await db_builder
157
+ .updateTable(this.domain)
158
+ .where('id', '=', id)
159
+ .where('reject_at', 'is', null)
160
+ .where('accept_at', 'is', null)
161
+ .set({
162
+ accept_at: curr_date.toISOString(),
163
+ })
164
+ .executeTakeFirst()
165
+ ).numUpdatedRows != 0n;
166
+ }
167
+
168
+ protected async db_reject(id: number, db_builder = this.db): Promise<boolean> {
169
+ const curr_date = new Date();
170
+
171
+ return (
172
+ await db_builder
173
+ .updateTable(this.domain)
174
+ .where('id', '=', id)
175
+ .where('reject_at', 'is', null)
176
+ .where('accept_at', 'is', null)
177
+ .set({
178
+ reject_at: curr_date.toISOString(),
179
+ })
180
+ .executeTakeFirst()
181
+ ).numUpdatedRows != 0n;
182
+ }
183
+
184
+ async get(
185
+ fk_id: number,
186
+ ): Promise<number> {
187
+ const curr_date = new Date();
188
+ return this.db_get(fk_id, curr_date);
189
+ }
190
+
191
+ async set(
192
+ fk_id: number,
193
+ value: number,
194
+ options: TransactionData,
195
+ ): Promise<number | undefined> {
196
+ return this.db_set(fk_id, value, options);
197
+ }
198
+
199
+ async add(
200
+ fk_id: number,
201
+ value: number,
202
+ options: TransactionData,
203
+ ): Promise<number | undefined> {
204
+ return this.db_add(fk_id, value, options);
205
+ }
206
+
207
+ async accept(id: number): Promise<boolean> {
208
+ return this.db_accept(id);
209
+ }
210
+
211
+ async reject(id: number): Promise<boolean> {
212
+ return this.db_reject(id);
213
+ }
214
+
215
+ async history(fk_id: number, db_builder = this.db): Promise<TransactionHistory[]> { // FIXME: переписать, не достаточно точные данные, из-за того что добавились столбцы со сгоранием бонусов
216
+ const curr_iso = new Date().toISOString();
217
+
218
+ return (
219
+ await db_builder
220
+ .selectFrom(this.domain)
221
+ .leftJoin(`${this.domain}_set_reason`, `${this.domain}_set_reason.id`, `${this.domain}.reason_id`)
222
+ .select([
223
+ `${this.domain}.amount as amount`,
224
+ `${this.domain}.accept_at as date`,
225
+ `${this.domain}_set_reason.value as reason`,
226
+ ])
227
+ .where(`${this.domain}.fk_id`, '=', fk_id)
228
+ .where(`${this.domain}.created_at`, '<=', curr_iso)
229
+ .where(`${this.domain}.accept_at`, 'is not', null)
230
+ .where(`${this.domain}.reject_at`, 'is', null)
231
+ .execute()
232
+ );
233
+ }
234
+ }
235
+
236
+ const TransactionDefaultFlags = {
237
+ hidden: 0,
238
+ type: {
239
+ receive: 1,
240
+ spend: 2,
241
+ transfer: 3,
242
+ internal: 4,
243
+ other: 5,
244
+ },
245
+ } as const;
246
+
247
+ export class CurrentTransaction<TransactionItemId extends WithId> extends ORM {
248
+ protected readonly transaction: Transaction<TransactionItemId>;
249
+ protected readonly id: number | undefined;
250
+
251
+ constructor(transaction: Transaction<TransactionItemId>, id: number | undefined) {
252
+ super();
253
+
254
+ this.transaction = transaction;
255
+ this.id = id;
256
+ }
257
+
258
+ async accept(): Promise<boolean> {
259
+ if (!this.id)
260
+ return false;
261
+
262
+ return this.transaction.store.accept(this.id);
263
+ }
264
+
265
+ async reject(): Promise<boolean> {
266
+ if (!this.id)
267
+ return false;
268
+
269
+ return this.transaction.store.reject(this.id);
270
+ }
271
+
272
+ get_id(): number | undefined {
273
+ return this.id;
274
+ }
275
+ }
276
+
277
+ interface WithId {
278
+ id: number
279
+ }
280
+
281
+ export interface TransactionOptions {
282
+ type: keyof typeof TransactionDefaultFlags['type']
283
+ hidden?: boolean
284
+ reason: string
285
+ }
286
+
287
+ export class Transaction<TransactionItemId extends WithId> extends ORM {
288
+ public readonly flags: BitFields<typeof TransactionDefaultFlags>;
289
+ public readonly store: TransactionStore;
290
+
291
+ protected constructor(store: TransactionStore) {
292
+ super();
293
+
294
+ this.store = store;
295
+ this.flags = new BitFields(TransactionDefaultFlags);
296
+ }
297
+
298
+ static new<
299
+ TransactionItemId extends WithId,
300
+ >(options: {
301
+ store: TransactionStore
302
+ }) {
303
+ return new Transaction<TransactionItemId>(options.store);
304
+ }
305
+
306
+ transaction_from_id(id: number | undefined) {
307
+ return new CurrentTransaction<TransactionItemId>(this, id);
308
+ }
309
+
310
+ private transaction_parse_options(options: TransactionOptions): TransactionData {
311
+ return {
312
+ flag: this.flags.pack({
313
+ hidden: options.hidden,
314
+ type: { [options?.type] : true },
315
+ }),
316
+ reason: options?.reason,
317
+ };
318
+ }
319
+
320
+ async get_sum(fk_obj: TransactionItemId) {
321
+ return this.store.get(fk_obj.id);
322
+ }
323
+
324
+ async set_transaction(fk_obj: TransactionItemId, value: number, options: TransactionOptions) {
325
+ const trx_id = await this.store.set(fk_obj.id, value, (
326
+ this.transaction_parse_options(options)
327
+ ));
328
+
329
+ return this.transaction_from_id(trx_id);
330
+ }
331
+
332
+ async add_transaction(fk_obj: TransactionItemId, value: number, options: TransactionOptions) {
333
+ const trx_id = await this.store.add(fk_obj.id, value, (
334
+ this.transaction_parse_options(options)
335
+ ));
336
+
337
+ return this.transaction_from_id(trx_id);
338
+ }
339
+
340
+ async get_history(fk_obj: TransactionItemId) {
341
+ return this.store.history(fk_obj.id);
342
+ }
343
+ }
@@ -0,0 +1,20 @@
1
+ import type { Generated, GeneratedAlways } from 'kysely';
2
+
3
+ export interface TableTransaction {
4
+ id: Generated<number>
5
+ fk_id: number // fk, на строку-владельца "счёта"
6
+ flag: number // BitField
7
+ amount: number // Величина изменения
8
+ remain: number | null // Остаток, носит чисто информационный характер, в большинстве случаев дедлайны сломают это значение
9
+ reason_id: number // fk, TableSetString
10
+ accept_at: string | null // ISO 8601, не может быть заполнен вместе с denied_at
11
+ reject_at: string | null // ISO 8601, не может быть заполнен вместе с success_at
12
+ // category_id: number // fk, Говорит категорию на которую можно потратить баланс, ее нужно указывать для всех нужных положительных amount,
13
+ // // если после операции останется баланс 0, значит транзакцию не создаем, если мы можем только списать только часть,
14
+ // // то создаем несколько транзакций, у операций которые списывают баланс нужно указывать category_id родительского пополнения
15
+ // TODO: Доработать deadline
16
+ parent_id: number | null // fk, self, Родительская транзакция должна так же указывать сама на себя, используется вместе с deadline_at
17
+ deadline_at: string | null // ISO 8601, по истечению которого транзакция не будет учитывается в подсчёте баланса
18
+ since_at: string | null // ISO 8601, дата после наступления которой, транзакция будет начислена на счет
19
+ created_at: GeneratedAlways<string>
20
+ }
@@ -0,0 +1,33 @@
1
+ import type { SelectType } from 'kysely';
2
+ import type { AllUnionFields } from 'type-fest';
3
+
4
+ export type DBTablesByType<DB, TB_Type> = {
5
+ [K in keyof DB]:
6
+ DB[K] extends TB_Type
7
+ ? K extends string
8
+ ? K
9
+ : never
10
+ : never
11
+ }[keyof DB];
12
+
13
+ export type DBSelectTypeFromTable<
14
+ DB,
15
+ TB extends keyof DB,
16
+ Fields extends string,
17
+ Default = never,
18
+ > = {
19
+ [K in Fields]?:
20
+ K extends keyof AllUnionFields<DB[TB]>
21
+ ? SelectType<AllUnionFields<DB[TB]>[K]> | undefined
22
+ : Default
23
+ };
24
+
25
+ export type DBRequired<
26
+ ObjectFields,
27
+ Fields extends keyof ObjectFields = keyof ObjectFields,
28
+ > = {
29
+ [K in keyof ObjectFields]-?:
30
+ K extends Fields
31
+ ? Exclude<ObjectFields[K], undefined>
32
+ : ObjectFields[K]
33
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { sql } from 'kysely';
2
+ import { SQLParser } from './SQLParser';
3
+
4
+ import type { Kysely } from 'kysely';
5
+
6
+ export async function table_info<DB>(db: Kysely<DB>, table: Extract<keyof DB, string>) {
7
+ return (
8
+ (await sql`SELECT * FROM pragma_table_info('${sql.raw(table)}')`.execute(db))
9
+ .rows.map((r: any) => ({ ...r, notnull: !!r.notnull, pk: !!r.pk, type: r.type.toUpperCase() }))
10
+ ) as {
11
+ cid: number
12
+ name: string
13
+ type: 'INTEGER' | 'REAL' | 'TEXT' | 'BLOB' | 'NUMERIC'
14
+ notnull: boolean
15
+ dflt_value: string | null
16
+ pk: boolean
17
+ }[];
18
+ }
19
+
20
+ export async function master_table(db: Kysely<any>) {
21
+ return (
22
+ (await db
23
+ .selectFrom('sqlite_master')
24
+ .selectAll()
25
+ .execute()
26
+ ).filter((r: any) => r.name != 'sqlite_sequence')
27
+ ) as {
28
+ type: 'index' | 'table'
29
+ name: string
30
+ tbl_name: string
31
+ rootpage: number
32
+ sql: string
33
+ }[];
34
+ }
35
+
36
+ export async function get_table_schema<DB>(db: Kysely<DB>, table: Extract<keyof DB, string>) {
37
+ const master = (await master_table(db)).find(r => r.name == table);
38
+
39
+ if (!master)
40
+ return false;
41
+
42
+ const schema = SQLParser.parse(master.sql)[0];
43
+
44
+ if (schema?.type != 'CREATE_TABLE')
45
+ return false;
46
+
47
+ return schema;
48
+ }
@@ -0,0 +1,39 @@
1
+ import Database from 'better-sqlite3';
2
+ import sqliteExtExpert from 'better-sqlite3-expert';
3
+ import { randomBytes } from 'node:crypto';
4
+
5
+ const sql = String.raw;
6
+
7
+ it('Check extension work', () => {
8
+ const db = new Database(':memory:');
9
+
10
+ db.loadExtension(sqliteExtExpert());
11
+ db.prepare(sql`
12
+ CREATE TABLE test (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ name TEXT NOT NULL
15
+ );
16
+ `).run();
17
+
18
+ let hash;
19
+
20
+ for (let i = 0; i < 10000; i++) {
21
+ const name = randomBytes(16).toString('hex');
22
+
23
+ if (i === 8000) {
24
+ hash = name;
25
+ }
26
+
27
+ db.prepare(sql`
28
+ INSERT INTO test (name) VALUES ('${name}');
29
+ `).run();
30
+ }
31
+
32
+ const result_1 = db.prepare(sql`
33
+ SELECT expert_suggest(?) as suggestion;
34
+ `).pluck().get(
35
+ "SELECT * FROM test WHERE name = '${hash}'",
36
+ );
37
+
38
+ expect(result_1).toBe(`CREATE INDEX test_idx_00015c29 ON test(name);\n\n`);
39
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "noEmit": false
7
+ },
8
+ "include": [
9
+ "src/**/*"
10
+ ],
11
+ "exclude": [
12
+ "node_modules",
13
+ "dist",
14
+ "test/**/*",
15
+ "**/*.test.ts"
16
+ ]
17
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": ".",
6
+ "noEmit": true
7
+ },
8
+ "include": [
9
+ "src/**/*",
10
+ "test/**/*"
11
+ , "../core/src/requireContext.ts" ],
12
+ "exclude": [
13
+ "node_modules",
14
+ "dist"
15
+ ]
16
+ }