@d-mok/quasar-app-extension-quasar-axe 2.2.3 → 2.2.4

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-mok/quasar-app-extension-quasar-axe",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "A Quasar App Extension",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -6,7 +6,8 @@ export * as qNotify from './notify'
6
6
 
7
7
  export { HANDLE_ERROR, supabase } from './supabase'
8
8
 
9
- export { TABLE, ENTITY, ORM } from './puppets'
9
+ export { TABLE, ENTITY } from './puppets'
10
+ export { ORM } from './puppets2'
10
11
 
11
12
  import { supabase } from './supabase'
12
13
 
@@ -1,3 +1,2 @@
1
1
  export { ENTITY } from './entity'
2
2
  export { TABLE } from './table'
3
- export { ORM } from './ORM'
@@ -18,7 +18,7 @@ export function ORM<
18
18
  type R = InstanceType<RBase>
19
19
 
20
20
  class E extends RClass {
21
- private _hostTable: any
21
+ private _hostTable: InstanceType<ReturnType<typeof getTable>>
22
22
 
23
23
  constructor(...args: any[]) {
24
24
  super()
@@ -69,8 +69,8 @@ export function ORM<
69
69
  return rec
70
70
  }
71
71
 
72
- protected onChange(): any {}
73
- protected onEdit(): any {}
72
+ protected onChange(): void {}
73
+ protected onEdit(): void {}
74
74
  protected onSanitize(row: Partial<R>): Partial<R> {
75
75
  return row
76
76
  }
@@ -0,0 +1,226 @@
1
+ import { Core } from '../core'
2
+ import { Filter, Key } from '../type'
3
+ import { ui } from './ui'
4
+
5
+ type Promisable<T> = T | PromiseLike<T>
6
+
7
+ type onfulfilled<T, U> = ((value: T) => Promisable<U>) | undefined | null
8
+
9
+ type onrejected<U> = ((reason: any) => Promisable<U>) | undefined | null
10
+
11
+ abstract class BuilderUI {
12
+ private isSlience: boolean = false
13
+ private isConfirm: boolean = false
14
+ private confirmContent: { title: string; msg: string } = {
15
+ title: '',
16
+ msg: '',
17
+ }
18
+ private isNotify: boolean = false
19
+ private notifyContent: string = ''
20
+
21
+ protected abstract dataLogic(): Promise<void>
22
+
23
+ protected async run() {
24
+ if (this.isSlience) ui.loadingOff()
25
+
26
+ if (this.isConfirm) {
27
+ let { title, msg } = this.confirmContent
28
+ await ui.onConfirm(title, msg)
29
+ }
30
+
31
+ await this.dataLogic()
32
+
33
+ if (this.isNotify) ui.onNotify(this.notifyContent)
34
+
35
+ if (this.isSlience) ui.loadingOn()
36
+ }
37
+
38
+ confirm(title: string, ...msgs: string[]): this {
39
+ this.isConfirm = true
40
+ this.confirmContent = { title, msg: msgs.join('<br/>') }
41
+ return this
42
+ }
43
+
44
+ notify(msg: string): this {
45
+ this.isNotify = true
46
+ this.notifyContent = msg
47
+ return this
48
+ }
49
+
50
+ mute(): this {
51
+ this.isNotify = false
52
+ return this
53
+ }
54
+
55
+ slience(): this {
56
+ this.isSlience = true
57
+ this.isNotify = false
58
+ return this
59
+ }
60
+ }
61
+
62
+ abstract class Builder<T, R> extends BuilderUI implements PromiseLike<void> {
63
+ constructor(
64
+ protected core: Core<T, R>,
65
+ private callback: (objs: T[]) => void
66
+ ) {
67
+ super()
68
+ }
69
+
70
+ protected abstract logType: string
71
+
72
+ protected abstract fetch(): Promise<R[]>
73
+
74
+ protected override async dataLogic(): Promise<void> {
75
+ let data = await this.fetch()
76
+ let entities = data.map($ => this.core.convertor($))
77
+ this.callback(entities)
78
+ ui.log(this.core.tableName, this.logType, entities)
79
+ }
80
+
81
+ async then<Y, N>(
82
+ onfulfilled?: onfulfilled<void, Y>,
83
+ onrejected?: onrejected<N>
84
+ ): Promise<Y | N> {
85
+ return this.run().then(onfulfilled, onrejected)
86
+ }
87
+ }
88
+
89
+ abstract class FilterBuilder<T, R> extends Builder<T, R> {
90
+ protected filters: Filter<R>[] = []
91
+
92
+ eq<K extends Key<R>>(key: K, val: R[K]): this {
93
+ this.filters.push({ type: 'eq', payload: { key, val } })
94
+ return this
95
+ }
96
+
97
+ in<K extends Key<R>>(key: K, vals: R[K][]): this {
98
+ this.filters.push({ type: 'in', payload: { key, vals } })
99
+ return this
100
+ }
101
+
102
+ like<K extends Key<R>>(key: K, pattern: string): this {
103
+ this.filters.push({ type: 'like', payload: { key, pattern } })
104
+ return this
105
+ }
106
+
107
+ limit(count: number): this {
108
+ this.filters.push({ type: 'limit', payload: count })
109
+ return this
110
+ }
111
+
112
+ order<K extends Key<R>>(key: K, ascending: boolean = true): this {
113
+ this.filters.push({ type: 'order', payload: { key, asc: ascending } })
114
+ return this
115
+ }
116
+ }
117
+
118
+ export class SelectBuilder<T, R> extends FilterBuilder<T, R> {
119
+ protected logType = 'select'
120
+
121
+ protected override async fetch() {
122
+ return this.core.fetchSelect(this.filters)
123
+ }
124
+ }
125
+
126
+ export class SelectInBuilder<T, R, K extends Key<R>> extends FilterBuilder<
127
+ T,
128
+ R
129
+ > {
130
+ protected logType = 'selectIn'
131
+
132
+ constructor(
133
+ private key: K,
134
+ private vals: R[K][],
135
+ core: Core<T, R>,
136
+ callback: (objs: T[]) => void
137
+ ) {
138
+ super(core, callback)
139
+ }
140
+
141
+ protected override async fetch() {
142
+ return this.core.fetchSelectIn(this.key, this.vals, this.filters)
143
+ }
144
+ }
145
+
146
+ export class SelectRPCBuilder<T, R> extends FilterBuilder<T, R> {
147
+ protected logType = 'rpc'
148
+
149
+ constructor(
150
+ private param: object,
151
+ core: Core<T, R>,
152
+ callback: (objs: T[]) => void
153
+ ) {
154
+ super(core, callback)
155
+ }
156
+
157
+ protected override async fetch() {
158
+ return this.core.fetchRPC(this.param, this.filters)
159
+ }
160
+ }
161
+
162
+ export class InsertBuilder<T, R> extends Builder<T, R> {
163
+ protected logType = 'insert'
164
+
165
+ constructor(
166
+ private rows: Partial<R>[],
167
+ core: Core<T, R>,
168
+ callback: (objs: T[]) => void
169
+ ) {
170
+ super(core, callback)
171
+ }
172
+
173
+ protected override async fetch() {
174
+ return this.core.fetchInsert(this.rows)
175
+ }
176
+ }
177
+
178
+ export class UpdateBuilder<T, R> extends Builder<T, R> {
179
+ protected logType = 'update'
180
+
181
+ constructor(
182
+ private idVal: R[keyof R],
183
+ private row: Partial<R>,
184
+ core: Core<T, R>,
185
+ callback: (objs: T[]) => void
186
+ ) {
187
+ super(core, callback)
188
+ }
189
+
190
+ protected override async fetch() {
191
+ return this.core.fetchUpdate(this.idVal, this.row)
192
+ }
193
+ }
194
+
195
+ export class DeleteBuilder<T, R> extends Builder<T, R> {
196
+ protected logType = 'delete'
197
+
198
+ constructor(
199
+ private idVal: R[keyof R],
200
+ core: Core<T, R>,
201
+ callback: (objs: T[]) => void
202
+ ) {
203
+ super(core, callback)
204
+ }
205
+
206
+ protected override async fetch() {
207
+ return this.core.fetchDelete(this.idVal)
208
+ }
209
+ }
210
+
211
+ export class UpsertBuilder<T, R> extends Builder<T, R> {
212
+ protected logType = 'upsert'
213
+
214
+ constructor(
215
+ private row: Partial<R>,
216
+ private conflictKeys: Key<R>[],
217
+ core: Core<T, R>,
218
+ callback: (objs: T[]) => void
219
+ ) {
220
+ super(core, callback)
221
+ }
222
+
223
+ protected override async fetch() {
224
+ return this.core.fetchUpsert(this.row, this.conflictKeys)
225
+ }
226
+ }
@@ -0,0 +1,31 @@
1
+ import { qDialog } from '../..'
2
+ import { qNotify } from '../..'
3
+ import { LoadingBar } from 'quasar'
4
+
5
+ /**
6
+ * I am responsible for providing UI interaction functions using Quasar.
7
+ * Purely functional.
8
+ */
9
+ class UI {
10
+ loadingOff(): void {
11
+ LoadingBar.setDefaults({ size: '0px' })
12
+ }
13
+
14
+ loadingOn(): void {
15
+ setTimeout(() => LoadingBar.setDefaults({ size: '5px' }), 3000)
16
+ }
17
+
18
+ onConfirm(title: string, msg: string): Promise<void> {
19
+ return qDialog.confirm(title, msg)
20
+ }
21
+
22
+ onNotify(msg: string): void {
23
+ qNotify.toast(msg)
24
+ }
25
+
26
+ log(table: string, type: string, data: any[]): void {
27
+ console.dev(`[${table}] ${type}`, data)
28
+ }
29
+ }
30
+
31
+ export const ui = new UI()
@@ -0,0 +1,127 @@
1
+ import {
2
+ supabase,
3
+ HANDLE_ERROR,
4
+ PostgrestFilterBuilder,
5
+ } from '../../supabase'
6
+ import { Filter } from '../type'
7
+
8
+ type FilterBuilder = PostgrestFilterBuilder<any, any, any, any>
9
+
10
+ async function send(q: any): Promise<any[]> {
11
+ let { data, error } = await q
12
+ HANDLE_ERROR(data, error)
13
+ return Array.isArray(data) ? data : [data]
14
+ }
15
+
16
+ function parseFilters(q: FilterBuilder, filters: Filter[]): FilterBuilder {
17
+ let sortedFilters = filters.sortedBy(f =>
18
+ ['eq', 'in', 'like', 'limit', 'order'].indexOf(f.type)
19
+ )
20
+ for (const { type: t, payload: p } of sortedFilters) {
21
+ if (t === 'eq') q = q.eq(p.key, p.val)
22
+ if (t === 'in') q = q.in(p.key, p.vals)
23
+ if (t === 'like') q = q.like(p.key, p.pattern)
24
+ if (t === 'limit') q = q.limit(p)
25
+ if (t === 'order') q = q.order(p.key, { ascending: p.asc })
26
+ }
27
+ return q
28
+ }
29
+
30
+ /**
31
+ * I am responsible for providing database operation.
32
+ * Purely functional.
33
+ */
34
+ class DB {
35
+ async select(
36
+ table: string,
37
+ fields: string[],
38
+ filters: Filter[]
39
+ ): Promise<any[]> {
40
+ let q = supabase.from(table).select(fields.join(','))
41
+ q = parseFilters(q, filters)
42
+ return send(q)
43
+ }
44
+
45
+ async selectIn(
46
+ table: string,
47
+ fields: string[],
48
+ key: string,
49
+ vals: any[],
50
+ filters: Filter[]
51
+ ): Promise<any[]> {
52
+ let getRows = (chunkPiece: any[]): Promise<any[]> => {
53
+ let q = supabase
54
+ .from(table)
55
+ .select(fields.join(','))
56
+ .in(key, chunkPiece)
57
+ q = parseFilters(q, filters)
58
+ return send(q)
59
+ }
60
+ let promises = vals.chunk(100).map(getRows)
61
+ let result = await Promise.all(promises)
62
+ return result.flat()
63
+ }
64
+
65
+ async rpc(fn: string, param: any, filters: Filter[]): Promise<any[]> {
66
+ let q = supabase.rpc(fn, param)
67
+ q = parseFilters(q, filters)
68
+ return send(q)
69
+ }
70
+
71
+ async insert(table: string, rows: any[]): Promise<any[]> {
72
+ let q = supabase.from(table).insert(rows).select('*')
73
+ return send(q)
74
+ }
75
+
76
+ async update(
77
+ table: string,
78
+ idKey: string,
79
+ idVal: any,
80
+ row: any
81
+ ): Promise<any[]> {
82
+ let q = supabase
83
+ .from(table)
84
+ .update(row)
85
+ .eq(idKey, idVal)
86
+ .select('*')
87
+ .single()
88
+ return send(q)
89
+ }
90
+
91
+ async delete(table: string, idKey: string, idVal: any): Promise<any[]> {
92
+ let q = supabase
93
+ .from(table)
94
+ .delete()
95
+ .eq(idKey, idVal)
96
+ .select('*')
97
+ .single()
98
+ return send(q)
99
+ }
100
+
101
+ async upsert(
102
+ table: string,
103
+ idKey: string,
104
+ row: any,
105
+ conflictKeys: (string & keyof typeof row)[]
106
+ ): Promise<any[]> {
107
+ let conflict: any = {}
108
+ for (let f of conflictKeys) {
109
+ conflict[f] = row[f]
110
+ }
111
+
112
+ let q = supabase.from(table).select('*').match(conflict)
113
+ let data = await send(q)
114
+
115
+ if (data.length > 1)
116
+ throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
117
+
118
+ if (data.length === 1) {
119
+ let id = data[0][idKey as keyof (typeof data)[0]]
120
+ return await this.update(table, idKey, id, row)
121
+ } else {
122
+ return await this.insert(table, [row])
123
+ }
124
+ }
125
+ }
126
+
127
+ export const db = new DB()
@@ -0,0 +1,44 @@
1
+ import { Filter, Key } from '../type'
2
+ import { db } from './db'
3
+
4
+ /** I am responsible for transfering the state from Table to Builders.*/
5
+ export class Core<T, R> {
6
+ constructor(
7
+ public tableName: string,
8
+ public idKey: Key<R>,
9
+ public fields: Key<R>[],
10
+ public convertor: ($: R) => T
11
+ ) {}
12
+
13
+ async fetchSelect(filters: Filter<R>[]): Promise<R[]> {
14
+ return db.select(this.tableName, this.fields, filters)
15
+ }
16
+
17
+ async fetchSelectIn<K extends Key<R>>(
18
+ key: K,
19
+ vals: R[K][],
20
+ filters: Filter<R>[]
21
+ ): Promise<R[]> {
22
+ return db.selectIn(this.tableName, this.fields, key, vals, filters)
23
+ }
24
+
25
+ async fetchRPC(param: object, filters: Filter<R>[]): Promise<R[]> {
26
+ return db.rpc(this.tableName, param, filters)
27
+ }
28
+
29
+ async fetchInsert(rows: Partial<R>[]): Promise<R[]> {
30
+ return db.insert(this.tableName, rows)
31
+ }
32
+
33
+ async fetchUpdate(idVal: R[keyof R], row: Partial<R>): Promise<R[]> {
34
+ return db.update(this.tableName, this.idKey, idVal, row)
35
+ }
36
+
37
+ async fetchDelete(idVal: R[keyof R]): Promise<R[]> {
38
+ return db.delete(this.tableName, this.idKey, idVal)
39
+ }
40
+
41
+ async fetchUpsert(row: Partial<R>, conflictKeys: Key<R>[]): Promise<R[]> {
42
+ return db.upsert(this.tableName, this.idKey, row, conflictKeys)
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ export { ORM } from './ORM'
@@ -0,0 +1,15 @@
1
+ export type Filter<R = any> =
2
+ | { type: 'eq'; payload: { key: Key<R>; val: any } }
3
+ | { type: 'in'; payload: { key: Key<R>; vals: any[] } }
4
+ | { type: 'like'; payload: { key: Key<R>; pattern: string } }
5
+ | { type: 'limit'; payload: number }
6
+ | { type: 'order'; payload: { key: Key<R>; asc: boolean } }
7
+
8
+ export type Constructor<T extends object = {}> = new (...args: any[]) => T
9
+
10
+ export type BooleanKeys<T> = {
11
+ [k in keyof T]: T[k] extends boolean ? k : never
12
+ }[keyof T]
13
+ // type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean }
14
+
15
+ export type Key<R> = string & keyof R