@d-mok/quasar-app-extension-quasar-axe 3.1.68 → 3.1.71

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": "3.1.68",
3
+ "version": "3.1.71",
4
4
  "description": "A Quasar App Extension",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -8,25 +8,25 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@handsontable/vue3": "^15.2.0",
11
- "@supabase/supabase-js": "^2.74.0",
11
+ "@supabase/supabase-js": "^2.89.0",
12
12
  "@types/lodash": "^4.17.20",
13
13
  "@types/webpack-env": "^1.18.8",
14
- "@vueuse/core": "^13.7.0",
15
- "@vueuse/integrations": "^13.7.0",
16
- "@vueuse/router": "^13.7.0",
14
+ "@vueuse/core": "^14.1.0",
15
+ "@vueuse/integrations": "^14.1.0",
16
+ "@vueuse/router": "^14.1.0",
17
17
  "handsontable": "^15.2.0",
18
18
  "jszip": "^3.10.1",
19
19
  "lodash": "^4.17.21",
20
- "sapphire-js": "^2.1.49",
20
+ "sapphire-js": "^2.1.50",
21
21
  "sortablejs": "^1.15.6",
22
- "valibot": "^1.1.0"
22
+ "valibot": "^1.2.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@quasar/app-vite": "^2.3.0",
25
+ "@quasar/app-vite": "^2.4.0",
26
26
  "@quasar/extras": "^1.17.0 ",
27
- "@types/node": "^24.3.0",
28
- "core-js": "^3.45.0",
29
- "quasar": "^2.18.2",
27
+ "@types/node": "^25.0.3",
28
+ "core-js": "^3.47.0",
29
+ "quasar": "^2.18.6",
30
30
  "typescript": "^5.9.2",
31
31
  "vue": "^3.5.18"
32
32
  },
@@ -4,7 +4,7 @@ import './extension'
4
4
  export { qDialog } from './dialog'
5
5
  export * as qNotify from './notify'
6
6
 
7
- export { HANDLE_ERROR, handleThrow, supabase } from './supabase'
7
+ export { handleError, supabase } from './supabase'
8
8
 
9
9
  export { ORM } from './puppets'
10
10
 
@@ -1,20 +1,23 @@
1
- import {
2
- DeleteBuilder,
3
- InsertBuilder,
4
- SelectBuilder,
5
- SelectInBuilder,
6
- SelectRPCBuilder,
7
- UpdateBuilder,
8
- UpsertBuilder,
9
- } from './builder'
10
- import { Constructor, Key, BooleanKeys } from './type'
11
- import { Core } from './core'
12
1
  import { reactive } from 'vue'
2
+ import { supabase, handleError } from '../supabase'
3
+ import { qDialog, qNotify } from '..'
4
+ import { LoadingBar } from 'quasar'
5
+
6
+ import type { PostgrestFilterBuilder } from '@supabase/postgrest-js'
7
+
8
+ type Constructor<T extends object = {}> = new (...args: any[]) => T
9
+
10
+ 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
+ type Key<R> = string & keyof R
13
16
 
14
17
  export function ORM<
15
18
  RBase extends Constructor,
16
- K extends Key<InstanceType<RBase>>
17
- >(RClass: RBase, tableName: string, idKey: K) {
19
+ IdK extends Key<InstanceType<RBase>>
20
+ >(RClass: RBase, tableName: string, idKey: IdK) {
18
21
  type R = InstanceType<RBase>
19
22
 
20
23
  class E extends RClass {
@@ -27,18 +30,26 @@ export function ORM<
27
30
  this._hostTable = hostTable
28
31
  }
29
32
 
30
- update(this: E & R, row: Partial<R>) {
31
- return this._hostTable!.update(this[idKey], row)
33
+ async update(
34
+ this: E & R,
35
+ row: Partial<R>,
36
+ options?: uiOptions
37
+ ): Promise<void> {
38
+ this._hostTable!.update(this[idKey], row, options)
32
39
  }
33
40
 
34
- delete(this: E & R) {
35
- return this._hostTable!.delete(this[idKey])
41
+ async delete(this: E & R, options?: uiOptions): Promise<void> {
42
+ this._hostTable!.delete(this[idKey], options)
36
43
  }
37
44
 
38
- toggle(this: E & R, key: BooleanKeys<R>) {
39
- let val = !this[key]
40
- let obj = { [key]: val } as Partial<R>
41
- return this._hostTable!.update(this[idKey], obj)
45
+ async toggle(
46
+ this: E & R,
47
+ key: BooleanKeys<R>,
48
+ options?: uiOptions
49
+ ): Promise<void> {
50
+ const val = !this[key]
51
+ const obj = { [key]: val } as Partial<R>
52
+ this._hostTable!.update(this[idKey], obj, options)
42
53
  }
43
54
 
44
55
  toPlain(): R {
@@ -51,21 +62,11 @@ export function ORM<
51
62
  function getTable<EBase extends RBase>(EClass: EBase) {
52
63
  type T = InstanceType<EBase>
53
64
 
54
- function convertor(host: TableClass) {
55
- return ($: R) => new EClass($, host) as T
56
- }
65
+ const fields = Object.keys(new RClass()).join(',') as '*'
57
66
 
58
- function core(host: TableClass) {
59
- return new Core<T, R>(
60
- tableName,
61
- idKey,
62
- Object.keys(new RClass()) as Key<R>[],
63
- convertor(host)
64
- )
65
- }
67
+ type Filter = PostgrestFilterBuilder<any, any, R, any>
66
68
 
67
69
  class TableClass extends Array<InstanceType<EBase>> {
68
- /** make reactive */
69
70
  reactive(): this {
70
71
  const rec = reactive(this) as this
71
72
  if (process.env.DEBUGGING) {
@@ -77,130 +78,205 @@ export function ORM<
77
78
 
78
79
  protected onChange(): void {}
79
80
  protected onEdit(): void {}
80
- protected onSanitize(row: Partial<R>): Partial<R> {
81
+ protected onSanitize<P extends Partial<R>>(row: P): P {
81
82
  return row
82
83
  }
83
84
 
84
- select() {
85
- let callback = ($: T[]) => {
86
- this.set($)
87
- this.onChange()
88
- }
89
- return new SelectBuilder(core(this), callback)
85
+ private convert(R: R): T {
86
+ return new EClass(R, this) as T
90
87
  }
91
88
 
92
- patch() {
93
- let callback = ($: T[]) => {
94
- this.absorb($, idKey)
95
- this.onChange()
96
- }
97
- return new SelectBuilder(core(this), callback)
89
+ private convertAll(Rs: R[]): T[] {
90
+ return Rs.map(r => this.convert(r))
98
91
  }
99
92
 
100
- selectIn<K extends Key<R>>(key: K, vals: R[K][]) {
101
- let callback = ($: T[]) => {
102
- this.set($)
103
- this.sortBy($ => vals.indexOf($[key]))
104
- this.onChange()
93
+ private async wrapper<X>(
94
+ logType: 'select' | 'insert' | 'update' | 'delete',
95
+ options: uiOptions | undefined,
96
+ fn: () => Promise<X>
97
+ ): Promise<X> {
98
+ const { confirm, notify, loading } = options ?? {}
99
+
100
+ if (confirm) {
101
+ await qDialog.confirm(...confirm)
105
102
  }
106
- return new SelectInBuilder(key, vals, core(this), callback)
107
- }
108
103
 
109
- patchIn<K extends Key<R>>(key: K, vals: R[K][]) {
110
- let callback = ($: T[]) => {
111
- this.absorb($, idKey)
112
- this.sortBy($ => vals.indexOf($[key]))
113
- this.onChange()
104
+ if (loading === false) {
105
+ LoadingBar.setDefaults({ size: '0px' })
114
106
  }
115
- return new SelectInBuilder(key, vals, core(this), callback)
107
+
108
+ const entities = await fn()
109
+
110
+ if (loading === false) {
111
+ setTimeout(
112
+ () => LoadingBar.setDefaults({ size: '5px' }),
113
+ 3000
114
+ )
115
+ }
116
+
117
+ const n =
118
+ notify ??
119
+ {
120
+ select: false as const,
121
+ insert: 'Created!',
122
+ update: 'Updated!',
123
+ delete: 'Deleted!',
124
+ }[logType]
125
+
126
+ if (n !== false) {
127
+ qNotify.toast(n)
128
+ }
129
+
130
+ console.dev(`[${tableName}] ${logType}`, entities)
131
+
132
+ return entities
116
133
  }
117
134
 
118
- selectRPC(param: object = {}) {
119
- let callback = ($: T[]) => {
120
- this.set($)
135
+ async read(query: (q: Filter) => Filter = q => q): Promise<R[]> {
136
+ const q = supabase.from(tableName).select(fields)
137
+ return await query(q).then(handleError)
138
+ }
139
+
140
+ async select(
141
+ query: (q: Filter) => Filter = q => q,
142
+ options?: selectOptions
143
+ ): Promise<void> {
144
+ await this.wrapper('select', options, async () => {
145
+ const data = await this.read(query)
146
+ const entities = this.convertAll(data)
147
+
148
+ options?.patch === true
149
+ ? this.absorb(entities, idKey)
150
+ : this.set(entities)
151
+
121
152
  this.onChange()
122
- }
123
- return new SelectRPCBuilder(param, core(this), callback)
153
+ })
124
154
  }
125
155
 
126
- patchRPC(param: object = {}) {
127
- let callback = ($: T[]) => {
128
- this.absorb($, idKey)
156
+ async selectIn<K extends Key<R>>(
157
+ key: K,
158
+ vals: R[K][],
159
+ options?: uiOptions & { patch?: true }
160
+ ): Promise<void> {
161
+ await this.wrapper('select', options, async () => {
162
+ const promises = vals.chunk(100).map(async chunkPiece => {
163
+ return await supabase
164
+ .from(tableName)
165
+ .select(fields)
166
+ .in(key, chunkPiece as any)
167
+ .then(handleError)
168
+ })
169
+ const data: R[] = (await Promise.all(promises)).flat()
170
+
171
+ const entities = this.convertAll(data)
172
+
173
+ options?.patch === true
174
+ ? this.absorb(entities, idKey)
175
+ : this.set(entities)
176
+
177
+ this.sortBy($ => vals.indexOf($[key]))
129
178
  this.onChange()
130
- }
131
- return new SelectRPCBuilder(param, core(this), callback)
179
+ })
132
180
  }
133
181
 
134
- selectCustom(rows: R[]) {
135
- let convert = core(this).convertor
136
- let entities = rows.map(convert)
137
- this.set(entities)
182
+ selectCustom(rows: R[], options?: { patch?: true }): void {
183
+ const entities = this.convertAll(rows)
184
+ options?.patch === true
185
+ ? this.absorb(entities, idKey)
186
+ : this.set(entities)
138
187
  this.onChange()
139
188
  }
140
189
 
141
- insert(row: Partial<R>) {
142
- row = this.onSanitize(row)
143
- let callback = ($: T[]) => {
144
- this.push(...$)
190
+ async insertMany(
191
+ rows: Partial<R>[],
192
+ options?: uiOptions
193
+ ): Promise<T[]> {
194
+ return await this.wrapper('insert', options, async () => {
195
+ const data: R[] = await supabase
196
+ .from(tableName)
197
+ .insert(rows.map($ => this.onSanitize($)))
198
+ .select(fields)
199
+ .then(handleError)
200
+
201
+ const entities = this.convertAll(data)
202
+
203
+ this.push(...entities)
145
204
  this.onChange()
146
205
  this.onEdit()
147
- }
148
- return new InsertBuilder([row], core(this), callback).notify(
149
- 'Created!'
150
- )
206
+
207
+ return entities
208
+ })
151
209
  }
152
210
 
153
- insertMany(rows: Partial<R>[]) {
154
- rows = rows.map(this.onSanitize)
155
- let callback = ($: T[]) => {
156
- this.push(...$)
157
- this.onChange()
158
- this.onEdit()
159
- }
160
- return new InsertBuilder(rows, core(this), callback).notify(
161
- 'Created!'
162
- )
211
+ async insert(row: Partial<R>, options?: uiOptions): Promise<T> {
212
+ let entities = await this.insertMany([row], options)
213
+ return entities[0]!
163
214
  }
164
215
 
165
- update(idVal: R[K], row: Partial<R>) {
166
- row = this.onSanitize(row)
167
- let callback = ($: T[]) => {
168
- this.merge($, idKey)
216
+ async update(
217
+ idVal: R[IdK],
218
+ row: Partial<R>,
219
+ options?: uiOptions
220
+ ): Promise<T> {
221
+ return await this.wrapper('update', options, async () => {
222
+ const data: R = await supabase
223
+ .from(tableName)
224
+ .update(this.onSanitize(row))
225
+ .eq(idKey, idVal as any)
226
+ .select(fields)
227
+ .single()
228
+ .then(handleError)
229
+
230
+ const entity = this.convert(data)
231
+
232
+ this.merge([entity], idKey)
169
233
  this.onChange()
170
234
  this.onEdit()
171
- }
172
- return new UpdateBuilder(
173
- idVal,
174
- row,
175
- core(this),
176
- callback
177
- ).notify('Updated!')
235
+
236
+ return entity
237
+ })
178
238
  }
179
239
 
180
- delete(idVal: R[K]) {
181
- let callback = ($: T[]) => {
240
+ async delete(idVal: R[IdK], options?: uiOptions): Promise<void> {
241
+ await this.wrapper('delete', options, async () => {
242
+ await supabase
243
+ .from(tableName)
244
+ .delete()
245
+ .eq(idKey, idVal as any)
246
+ .single()
247
+ .then(handleError)
248
+
182
249
  this.remove($ => $[idKey] === idVal)
183
250
  this.onChange()
184
251
  this.onEdit()
185
- }
186
- return new DeleteBuilder(idVal, core(this), callback).notify(
187
- 'Deleted!'
188
- )
252
+ })
189
253
  }
190
254
 
191
- upsert(row: Partial<R>, conflictKeys: Key<R>[]) {
255
+ async upsert<P extends Partial<R>>(
256
+ row: P,
257
+ conflictKeys: Key<P>[],
258
+ options?: uiOptions
259
+ ): Promise<T> {
192
260
  row = this.onSanitize(row)
193
- let callback = ($: T[]) => {
194
- this.absorb($, idKey)
195
- this.onChange()
196
- this.onEdit()
261
+
262
+ let found: R[] = await this.read(q =>
263
+ q.match(Object.pick(row, conflictKeys))
264
+ )
265
+
266
+ if (found.length > 1) {
267
+ await qDialog.error(
268
+ 'Fail to upsert',
269
+ 'More than 1 rows matching the conflictKeys are found!'
270
+ )
271
+ throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
272
+ }
273
+
274
+ if (found[0]) {
275
+ let id = found[0][idKey]
276
+ return await this.update(id, row, options)
277
+ } else {
278
+ return await this.insert(row, options)
197
279
  }
198
- return new UpsertBuilder(
199
- row,
200
- conflictKeys,
201
- core(this),
202
- callback
203
- ).notify('Updated!')
204
280
  }
205
281
  }
206
282
 
@@ -218,3 +294,14 @@ if (process.env.DEBUGGING) {
218
294
  // @ts-ignore
219
295
  globalThis.TABLES = []
220
296
  }
297
+
298
+ type uiOptions = {
299
+ confirm?: [string, string]
300
+ notify?: string | false
301
+ loading?: boolean
302
+ }
303
+
304
+ type selectOptions = {
305
+ loading?: boolean
306
+ patch?: true
307
+ }
@@ -55,21 +55,23 @@ class StudentTable extends Table(Student) {
55
55
 
56
56
  let ss = new StudentTable()
57
57
 
58
- ss.select().eq('name', 'a')
58
+ let x1 = await ss.read()
59
+
60
+ ss.select(q => q.eq('name', 'a').eq('num', 3).limit(100).order('class'))
59
61
  // @ts-expect-error
60
- ss.select().eq('full', 'a')
62
+ ss.select(q => q.eq('isPrefect', 'a'))
61
63
  ss.selectIn('num', [1, 2, 3])
62
64
  // @ts-expect-error
63
65
  ss.selectIn('name', [1, 2, 3])
64
66
  // @ts-expect-error
65
67
  ss.selectIn('full', [1, 2, 3])
66
- ss.insert({ name: 'a' })
68
+ let x2 = await ss.insert({ name: 'a' })
67
69
  // @ts-expect-error
68
70
  ss.insert({ name: 0 })
69
71
  ss.delete('')
70
72
  // @ts-expect-error
71
73
  ss.delete(1)
72
- ss.update('', { name: 'a' })
74
+ let x3 = await ss.update('', { name: 'a' })
73
75
  // @ts-expect-error
74
76
  ss.update('', { name: 0 })
75
77
  // @ts-expect-error
@@ -77,6 +79,6 @@ ss.update('', { full: 'a' })
77
79
  // @ts-expect-error
78
80
  ss.update(0, { name: 'a' })
79
81
 
80
- let st = ss[0]
82
+ let st = ss[0]!
81
83
  st.name
82
84
  st.full
@@ -1,4 +1,4 @@
1
- import { supabase, handleThrow } from '.'
1
+ import { supabase, handleError } from '.'
2
2
  import { reactive } from 'vue'
3
3
  import { FileOptions } from '@supabase/storage-js'
4
4
  import { mimeTypesToExt } from './mimeTypesToExt'
@@ -13,7 +13,7 @@ export class SupabaseBucket extends Map<string, string> {
13
13
  const data = await supabase.storage
14
14
  .from(this.bucketName)
15
15
  .list(undefined, { limit: 10000 })
16
- .then(handleThrow)
16
+ .then(handleError)
17
17
  return data.pluck('name')
18
18
  }
19
19
 
@@ -21,7 +21,7 @@ export class SupabaseBucket extends Map<string, string> {
21
21
  const data = await supabase.storage
22
22
  .from(this.bucketName)
23
23
  .createSignedUrl(path, 60 * 60 * 24)
24
- .then(handleThrow)
24
+ .then(handleError)
25
25
  return data.signedUrl
26
26
  }
27
27
 
@@ -31,7 +31,7 @@ export class SupabaseBucket extends Map<string, string> {
31
31
  const data = await supabase.storage
32
32
  .from(this.bucketName)
33
33
  .createSignedUrls(paths, 60 * 60 * 24)
34
- .then(handleThrow)
34
+ .then(handleError)
35
35
  return data
36
36
  }
37
37
 
@@ -58,7 +58,7 @@ export class SupabaseBucket extends Map<string, string> {
58
58
  const data = await supabase.storage
59
59
  .from(this.bucketName)
60
60
  .upload(path, file, fileOptions)
61
- .then(handleThrow)
61
+ .then(handleError)
62
62
  await this.load(path)
63
63
  }
64
64
 
@@ -70,7 +70,7 @@ export class SupabaseBucket extends Map<string, string> {
70
70
  const data = await supabase.storage
71
71
  .from(this.bucketName)
72
72
  .update(path, file, fileOptions)
73
- .then(handleThrow)
73
+ .then(handleError)
74
74
  await this.load(path)
75
75
  }
76
76
 
@@ -82,7 +82,7 @@ export class SupabaseBucket extends Map<string, string> {
82
82
  const data = await supabase.storage
83
83
  .from(this.bucketName)
84
84
  .upload(path, file, { ...fileOptions, upsert: true })
85
- .then(handleThrow)
85
+ .then(handleError)
86
86
  await this.load(path)
87
87
  }
88
88
 
@@ -90,7 +90,7 @@ export class SupabaseBucket extends Map<string, string> {
90
90
  const data = await supabase.storage
91
91
  .from(this.bucketName)
92
92
  .remove([path])
93
- .then(handleThrow)
93
+ .then(handleError)
94
94
  this.delete(path)
95
95
  }
96
96
 
@@ -98,7 +98,7 @@ export class SupabaseBucket extends Map<string, string> {
98
98
  const data = await supabase.storage
99
99
  .from(this.bucketName)
100
100
  .copy(fromPath, toPath)
101
- .then(handleThrow)
101
+ .then(handleError)
102
102
  await this.load(toPath)
103
103
  }
104
104
 
@@ -106,7 +106,7 @@ export class SupabaseBucket extends Map<string, string> {
106
106
  const data = await supabase.storage
107
107
  .from(this.bucketName)
108
108
  .move(fromPath, toPath)
109
- .then(handleThrow)
109
+ .then(handleError)
110
110
  await this.load(toPath)
111
111
  this.delete(fromPath)
112
112
  }
@@ -1,22 +1,11 @@
1
- import {
2
- createClient,
3
- PostgrestError,
4
- SupabaseClient,
5
- Session,
6
- } from '@supabase/supabase-js'
1
+ import { PostgrestError, SupabaseClient } from '@supabase/supabase-js'
7
2
  import { qDialog } from './dialog'
8
3
 
9
- export type {
10
- PostgrestFilterBuilder,
11
- PostgrestSingleResponse,
12
- } from '@supabase/postgrest-js'
13
-
14
4
  let SUPABASE_URL = process.env.SUPABASE_URL ?? ''
15
5
  let SUPABASE_KEY = process.env.SUPABASE_KEY ?? ''
16
6
 
17
7
  export class MySupabaseClient extends SupabaseClient {
18
8
  email: string = 'unauthenticated'
19
- // session: Session | null = null
20
9
 
21
10
  constructor() {
22
11
  super(SUPABASE_URL, SUPABASE_KEY, {
@@ -24,15 +13,9 @@ export class MySupabaseClient extends SupabaseClient {
24
13
  // @ts-ignore
25
14
  schema: process.env.SUPABASE_SCHEMA ?? 'public',
26
15
  },
27
- // auth: {
28
- // autoRefreshToken: true,
29
- // persistSession: true,
30
- // detectSessionInUrl: true,
31
- // },
32
16
  })
33
17
  this.auth.onAuthStateChange((event, session) => {
34
18
  console.log('[SUPABASE AUTH]', event, session?.user?.email)
35
- // this.session = session
36
19
  this.email = session?.user?.email ?? 'unauthenticated'
37
20
  })
38
21
  }
@@ -51,41 +34,10 @@ export class MySupabaseClient extends SupabaseClient {
51
34
  })
52
35
  }
53
36
 
54
- async call<T>(fn: string, params?: object | undefined): Promise<T[]> {
55
- return await this.rpc<string, any>(fn, params).then(handleThrow)
56
- }
57
-
58
- async callSingle<T>(fn: string, params?: object | undefined): Promise<T> {
59
- return (await this.rpc<string, any>(fn, params)
60
- .single()
61
- .then(handleThrow)) as T
62
- }
63
-
64
- // private async tryRefreshSession() {
65
- // try {
66
- // await this.auth.refreshSession()
67
- // } catch (e) {
68
- // console.error('[Supabase] Refresh session failed', e)
69
- // }
70
- // }
71
-
72
37
  async waitForSignin(): Promise<void> {
73
- // await this.tryRefreshSession()
74
38
  await this.auth.initialize()
75
39
  const { data } = await this.auth.getClaims()
76
40
  if (data === null) this.signIn()
77
- // setInterval(async () => {
78
- // if (this.session === null) await this.tryRefreshSession()
79
- // if (this.session === null) this.signIn()
80
- // }, 1000)
81
- // setInterval(async () => {
82
- // await this.auth.refreshSession()
83
- // }, 1000 * 60 * 30 * 0.99)
84
- // await waitFor(
85
- // () => this.session !== null,
86
- // '[Wait for SignIn] waiting...',
87
- // '[Wait for SignIn] DONE!'
88
- // )
89
41
  }
90
42
  }
91
43
 
@@ -93,7 +45,7 @@ export let supabase: MySupabaseClient
93
45
  if (SUPABASE_URL !== '' && SUPABASE_KEY !== '')
94
46
  supabase = new MySupabaseClient()
95
47
 
96
- export function HANDLE_ERROR<T>(
48
+ function HANDLE_ERROR<T>(
97
49
  data: T[] | T | null,
98
50
  error: PostgrestError | Error | null
99
51
  ): asserts data {
@@ -119,7 +71,7 @@ export function HANDLE_ERROR<T>(
119
71
  }
120
72
  }
121
73
 
122
- export function handleThrow<D>({
74
+ export function handleError<D>({
123
75
  data,
124
76
  error,
125
77
  }: {
@@ -129,22 +81,3 @@ export function handleThrow<D>({
129
81
  HANDLE_ERROR(data, error)
130
82
  return data
131
83
  }
132
-
133
- // async function waitFor(
134
- // predicate: () => boolean,
135
- // waitingMsg: string,
136
- // doneMsg: string
137
- // ) {
138
- // return new Promise(resolve => {
139
- // async function checker() {
140
- // if (predicate()) {
141
- // console.log(doneMsg)
142
- // resolve(true)
143
- // } else {
144
- // console.log(waitingMsg)
145
- // setTimeout(checker, 50)
146
- // }
147
- // }
148
- // checker()
149
- // })
150
- // }
@@ -1,34 +0,0 @@
1
- <template>
2
- <div
3
- v-bind="$attrs"
4
- :class="{
5
- 'text-caption': caption,
6
- 'text-subtitle1': subtitle1,
7
- 'text-subtitle2': subtitle2,
8
- 'text-body1': body1,
9
- 'text-body2': body2,
10
- 'text-h6': h6,
11
- 'text-h5': h5,
12
- 'text-h4': h4,
13
- 'text-weight-bold': bold,
14
- 'text-weight-bolder': bolder,
15
- }"
16
- >
17
- <slot></slot>
18
- </div>
19
- </template>
20
-
21
- <script lang="ts" setup>
22
- const props = defineProps<{
23
- bold?: boolean
24
- bolder?: boolean
25
- caption?: boolean
26
- subtitle1?: boolean
27
- subtitle2?: boolean
28
- body1?: boolean
29
- body2?: boolean
30
- h6?: boolean
31
- h5?: boolean
32
- h4?: boolean
33
- }>()
34
- </script>
@@ -1,235 +0,0 @@
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 isSilence: 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.isSilence) 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.isSilence) 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
- /**
56
- * @deprecated
57
- */
58
- slience(): this {
59
- this.isSilence = true
60
- this.isNotify = false
61
- return this
62
- }
63
-
64
- silence(): this {
65
- this.isSilence = true
66
- this.isNotify = false
67
- return this
68
- }
69
- }
70
-
71
- abstract class Builder<T, R> extends BuilderUI implements PromiseLike<void> {
72
- constructor(
73
- protected core: Core<T, R>,
74
- private callback: (objs: T[]) => void
75
- ) {
76
- super()
77
- }
78
-
79
- protected abstract logType: string
80
-
81
- protected abstract fetch(): Promise<R[]>
82
-
83
- protected override async dataLogic(): Promise<void> {
84
- let data = await this.fetch()
85
- let entities = data.map($ => this.core.convertor($))
86
- this.callback(entities)
87
- ui.log(this.core.tableName, this.logType, entities)
88
- }
89
-
90
- async then<Y, N>(
91
- onfulfilled?: onfulfilled<void, Y>,
92
- onrejected?: onrejected<N>
93
- ): Promise<Y | N> {
94
- return this.run().then(onfulfilled, onrejected)
95
- }
96
- }
97
-
98
- abstract class FilterBuilder<T, R> extends Builder<T, R> {
99
- protected filters: Filter<R>[] = []
100
-
101
- eq<K extends Key<R>>(key: K, val: R[K]): this {
102
- this.filters.push({ type: 'eq', payload: { key, val } })
103
- return this
104
- }
105
-
106
- in<K extends Key<R>>(key: K, vals: R[K][]): this {
107
- this.filters.push({ type: 'in', payload: { key, vals } })
108
- return this
109
- }
110
-
111
- like<K extends Key<R>>(key: K, pattern: string): this {
112
- this.filters.push({ type: 'like', payload: { key, pattern } })
113
- return this
114
- }
115
-
116
- limit(count: number): this {
117
- this.filters.push({ type: 'limit', payload: count })
118
- return this
119
- }
120
-
121
- order<K extends Key<R>>(key: K, ascending: boolean = true): this {
122
- this.filters.push({ type: 'order', payload: { key, asc: ascending } })
123
- return this
124
- }
125
- }
126
-
127
- export class SelectBuilder<T, R> extends FilterBuilder<T, R> {
128
- protected logType = 'select'
129
-
130
- protected override async fetch() {
131
- return this.core.fetchSelect(this.filters)
132
- }
133
- }
134
-
135
- export class SelectInBuilder<T, R, K extends Key<R>> extends FilterBuilder<
136
- T,
137
- R
138
- > {
139
- protected logType = 'selectIn'
140
-
141
- constructor(
142
- private key: K,
143
- private vals: R[K][],
144
- core: Core<T, R>,
145
- callback: (objs: T[]) => void
146
- ) {
147
- super(core, callback)
148
- }
149
-
150
- protected override async fetch() {
151
- return this.core.fetchSelectIn(this.key, this.vals, this.filters)
152
- }
153
- }
154
-
155
- export class SelectRPCBuilder<T, R> extends FilterBuilder<T, R> {
156
- protected logType = 'rpc'
157
-
158
- constructor(
159
- private param: object,
160
- core: Core<T, R>,
161
- callback: (objs: T[]) => void
162
- ) {
163
- super(core, callback)
164
- }
165
-
166
- protected override async fetch() {
167
- return this.core.fetchRPC(this.param, this.filters)
168
- }
169
- }
170
-
171
- export class InsertBuilder<T, R> extends Builder<T, R> {
172
- protected logType = 'insert'
173
-
174
- constructor(
175
- private rows: Partial<R>[],
176
- core: Core<T, R>,
177
- callback: (objs: T[]) => void
178
- ) {
179
- super(core, callback)
180
- }
181
-
182
- protected override async fetch() {
183
- return this.core.fetchInsert(this.rows)
184
- }
185
- }
186
-
187
- export class UpdateBuilder<T, R> extends Builder<T, R> {
188
- protected logType = 'update'
189
-
190
- constructor(
191
- private idVal: R[keyof R],
192
- private row: Partial<R>,
193
- core: Core<T, R>,
194
- callback: (objs: T[]) => void
195
- ) {
196
- super(core, callback)
197
- }
198
-
199
- protected override async fetch() {
200
- return this.core.fetchUpdate(this.idVal, this.row)
201
- }
202
- }
203
-
204
- export class DeleteBuilder<T, R> extends Builder<T, R> {
205
- protected logType = 'delete'
206
-
207
- constructor(
208
- private idVal: R[keyof R],
209
- core: Core<T, R>,
210
- callback: (objs: T[]) => void
211
- ) {
212
- super(core, callback)
213
- }
214
-
215
- protected override async fetch() {
216
- return this.core.fetchDelete(this.idVal)
217
- }
218
- }
219
-
220
- export class UpsertBuilder<T, R> extends Builder<T, R> {
221
- protected logType = 'upsert'
222
-
223
- constructor(
224
- private row: Partial<R>,
225
- private conflictKeys: Key<R>[],
226
- core: Core<T, R>,
227
- callback: (objs: T[]) => void
228
- ) {
229
- super(core, callback)
230
- }
231
-
232
- protected override async fetch() {
233
- return this.core.fetchUpsert(this.row, this.conflictKeys)
234
- }
235
- }
@@ -1,31 +0,0 @@
1
- import { qDialog } from '../../dialog'
2
- import * as qNotify from '../../notify'
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()
@@ -1,122 +0,0 @@
1
- import { supabase, handleThrow, PostgrestFilterBuilder } from '../../supabase'
2
- import { Filter } from '../type'
3
-
4
- type FilterBuilder = PostgrestFilterBuilder<any, any, any, any>
5
-
6
- async function send(q: any): Promise<any[]> {
7
- let data = await q.then(handleThrow)
8
- return Array.isArray(data) ? data : [data]
9
- }
10
-
11
- function parseFilters(q: FilterBuilder, filters: Filter[]): FilterBuilder {
12
- let sortedFilters = filters.sortedBy(f =>
13
- ['eq', 'in', 'like', 'limit', 'order'].indexOf(f.type)
14
- )
15
- for (const { type: t, payload: p } of sortedFilters) {
16
- if (t === 'eq') q = q.eq(p.key, p.val)
17
- if (t === 'in') q = q.in(p.key, p.vals)
18
- if (t === 'like') q = q.like(p.key, p.pattern)
19
- if (t === 'limit') q = q.limit(p)
20
- if (t === 'order') q = q.order(p.key, { ascending: p.asc })
21
- }
22
- return q
23
- }
24
-
25
- /**
26
- * I am responsible for providing database operation.
27
- * Purely functional.
28
- */
29
- class DB {
30
- async select(
31
- table: string,
32
- fields: string[],
33
- filters: Filter[]
34
- ): Promise<any[]> {
35
- let q = supabase.from(table).select(fields.join(','))
36
- q = parseFilters(q, filters)
37
- return send(q)
38
- }
39
-
40
- async selectIn(
41
- table: string,
42
- fields: string[],
43
- key: string,
44
- vals: any[],
45
- filters: Filter[]
46
- ): Promise<any[]> {
47
- let getRows = (chunkPiece: any[]): Promise<any[]> => {
48
- let q = supabase
49
- .from(table)
50
- .select(fields.join(','))
51
- .in(key, chunkPiece)
52
- q = parseFilters(q, filters)
53
- return send(q)
54
- }
55
- let promises = vals.chunk(100).map(getRows)
56
- let result = await Promise.all(promises)
57
- return result.flat()
58
- }
59
-
60
- async rpc(fn: string, param: any, filters: Filter[]): Promise<any[]> {
61
- let q = supabase.rpc(fn, param)
62
- q = parseFilters(q, filters)
63
- return send(q)
64
- }
65
-
66
- async insert(table: string, rows: any[]): Promise<any[]> {
67
- let q = supabase.from(table).insert(rows).select('*')
68
- return send(q)
69
- }
70
-
71
- async update(
72
- table: string,
73
- idKey: string,
74
- idVal: any,
75
- row: any
76
- ): Promise<any[]> {
77
- let q = supabase
78
- .from(table)
79
- .update(row)
80
- .eq(idKey, idVal)
81
- .select('*')
82
- .single()
83
- return send(q)
84
- }
85
-
86
- async delete(table: string, idKey: string, idVal: any): Promise<any[]> {
87
- let q = supabase
88
- .from(table)
89
- .delete()
90
- .eq(idKey, idVal)
91
- .select('*')
92
- .single()
93
- return send(q)
94
- }
95
-
96
- async upsert(
97
- table: string,
98
- idKey: string,
99
- row: any,
100
- conflictKeys: (string & keyof typeof row)[]
101
- ): Promise<any[]> {
102
- let conflict: any = {}
103
- for (let f of conflictKeys) {
104
- conflict[f] = row[f]
105
- }
106
-
107
- let q = supabase.from(table).select('*').match(conflict)
108
- let data = await send(q)
109
-
110
- if (data.length > 1)
111
- throw 'Fail to upsert! More than 1 rows matching the conflictKeys are found!'
112
-
113
- if (data.length === 1) {
114
- let id = data[0][idKey as keyof (typeof data)[0]]
115
- return await this.update(table, idKey, id, row)
116
- } else {
117
- return await this.insert(table, [row])
118
- }
119
- }
120
- }
121
-
122
- export const db = new DB()
@@ -1,44 +0,0 @@
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
- }
@@ -1,15 +0,0 @@
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