@symbo.ls/fetch 3.6.4 → 3.6.7

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.
@@ -0,0 +1,148 @@
1
+ 'use strict'
2
+
3
+ const matchesParams = (item, params) => {
4
+ if (!params) return true
5
+ for (const key in params) {
6
+ const val = params[key]
7
+ if (val === null) {
8
+ if (item[key] !== null && item[key] !== undefined) return false
9
+ } else if (Array.isArray(val)) {
10
+ if (!val.includes(item[key])) return false
11
+ } else if (typeof val === 'object') {
12
+ for (const op in val) {
13
+ if (op === 'gt' && !(item[key] > val[op])) return false
14
+ if (op === 'gte' && !(item[key] >= val[op])) return false
15
+ if (op === 'lt' && !(item[key] < val[op])) return false
16
+ if (op === 'lte' && !(item[key] <= val[op])) return false
17
+ if (op === 'neq' && item[key] === val[op]) return false
18
+ if (op === 'like' && !String(item[key]).includes(val[op])) return false
19
+ }
20
+ } else if (item[key] !== val) {
21
+ return false
22
+ }
23
+ }
24
+ return true
25
+ }
26
+
27
+ const applyModifiers = (items, { order, limit, offset, single }) => {
28
+ let result = [...items]
29
+
30
+ if (order) {
31
+ const by = typeof order === 'string' ? order : order.by
32
+ const asc = order.asc !== false
33
+ result.sort((a, b) => {
34
+ if (a[by] < b[by]) return asc ? -1 : 1
35
+ if (a[by] > b[by]) return asc ? 1 : -1
36
+ return 0
37
+ })
38
+ }
39
+
40
+ if (offset) result = result.slice(offset)
41
+ if (limit) result = result.slice(0, limit)
42
+ if (single) return { data: result[0] || null, error: null }
43
+
44
+ return { data: result, error: null }
45
+ }
46
+
47
+ export const setup = async ({ data, ...options }) => localAdapter(data, options)
48
+
49
+ export const localAdapter = (initialData = {}, options = {}) => {
50
+ const store = {}
51
+ const listeners = {}
52
+ const persist = options.persist !== false && typeof localStorage !== 'undefined'
53
+ const prefix = options.prefix || 'smbls_db_'
54
+
55
+ const getTable = (name) => {
56
+ if (!store[name]) {
57
+ if (persist) {
58
+ try {
59
+ const saved = localStorage.getItem(prefix + name)
60
+ store[name] = saved ? JSON.parse(saved) : (initialData[name] || [])
61
+ } catch { store[name] = initialData[name] || [] }
62
+ } else {
63
+ store[name] = initialData[name] || []
64
+ }
65
+ }
66
+ return store[name]
67
+ }
68
+
69
+ const save = (name) => {
70
+ if (persist) {
71
+ try { localStorage.setItem(prefix + name, JSON.stringify(store[name])) } catch {}
72
+ }
73
+ }
74
+
75
+ const notify = (name, event, newItem, oldItem) => {
76
+ const fns = listeners[name]
77
+ if (!fns) return
78
+ for (const fn of fns) fn(newItem, oldItem, { event, table: name })
79
+ }
80
+
81
+ let idCounter = Date.now()
82
+
83
+ return {
84
+ name: 'local',
85
+ store,
86
+
87
+ select: async ({ from, params, ...modifiers }) => {
88
+ const table = getTable(from)
89
+ const filtered = table.filter(item => matchesParams(item, params))
90
+ return applyModifiers(filtered, modifiers)
91
+ },
92
+
93
+ insert: async ({ from, data }) => {
94
+ const table = getTable(from)
95
+ const items = Array.isArray(data) ? data : [data]
96
+ const inserted = items.map(item => {
97
+ const row = { id: item.id || ++idCounter, ...item }
98
+ table.push(row)
99
+ return row
100
+ })
101
+ save(from)
102
+ for (const row of inserted) notify(from, 'INSERT', row, null)
103
+ return { data: Array.isArray(data) ? inserted : inserted[0], error: null }
104
+ },
105
+
106
+ update: async ({ from, data, params }) => {
107
+ const table = getTable(from)
108
+ const updated = []
109
+ for (let i = 0; i < table.length; i++) {
110
+ if (matchesParams(table[i], params)) {
111
+ const old = { ...table[i] }
112
+ Object.assign(table[i], data)
113
+ updated.push(table[i])
114
+ notify(from, 'UPDATE', table[i], old)
115
+ }
116
+ }
117
+ save(from)
118
+ return { data: updated, error: null }
119
+ },
120
+
121
+ delete: async ({ from, params }) => {
122
+ const table = getTable(from)
123
+ const removed = []
124
+ for (let i = table.length - 1; i >= 0; i--) {
125
+ if (matchesParams(table[i], params)) {
126
+ removed.push(table[i])
127
+ table.splice(i, 1)
128
+ notify(from, 'DELETE', null, removed[removed.length - 1])
129
+ }
130
+ }
131
+ save(from)
132
+ return { data: removed, error: null }
133
+ },
134
+
135
+ subscribe: ({ from, on }, callback) => {
136
+ if (!listeners[from]) listeners[from] = []
137
+ const fn = (newItem, oldItem, payload) => {
138
+ if (on && on !== '*' && on !== payload.event) return
139
+ callback(newItem, oldItem, payload)
140
+ }
141
+ listeners[from].push(fn)
142
+ return () => {
143
+ const idx = listeners[from].indexOf(fn)
144
+ if (idx > -1) listeners[from].splice(idx, 1)
145
+ }
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,147 @@
1
+ 'use strict'
2
+
3
+ const buildUrl = (base, from, params) => {
4
+ const slash = from && !from.startsWith('/') ? '/' : ''
5
+ const url = new URL(`${base}${slash}${from}`)
6
+ if (params) {
7
+ for (const key in params) {
8
+ const val = params[key]
9
+ if (val !== undefined && val !== null) {
10
+ url.searchParams.set(key, typeof val === 'object' ? JSON.stringify(val) : val)
11
+ }
12
+ }
13
+ }
14
+ return url.toString()
15
+ }
16
+
17
+ const request = async (url, options) => {
18
+ const res = await globalThis.fetch(url, options)
19
+ const contentType = res.headers.get('content-type')
20
+ const data = contentType && contentType.includes('json')
21
+ ? await res.json()
22
+ : await res.text()
23
+ return { data, error: res.ok ? null : (data?.message || data?.error || res.statusText), status: res.status }
24
+ }
25
+
26
+ export const setup = async ({ url, headers, auth, fetchOptions }) => {
27
+ if (!url) throw new Error('@symbo.ls/fetch rest: url is required')
28
+ return restAdapter(url, headers, auth, fetchOptions)
29
+ }
30
+
31
+ export const restAdapter = (baseUrl, defaultHeaders = {}, authConfig, defaultFetchOptions = {}) => {
32
+ let token = authConfig?.token || null
33
+
34
+ const getHeaders = (extra) => {
35
+ const h = { ...defaultHeaders, ...extra }
36
+ if (token) h.Authorization = `Bearer ${token}`
37
+ return h
38
+ }
39
+
40
+ const getJsonHeaders = (extra) => ({ ...getHeaders(extra), 'Content-Type': 'application/json' })
41
+
42
+ const getFetchOptions = (opts) => ({ ...defaultFetchOptions, ...opts })
43
+
44
+ const resolveUrl = (from, base) => {
45
+ const b = base || baseUrl
46
+ if (from && (from.startsWith('http://') || from.startsWith('https://'))) return from
47
+ const slash = from && !from.startsWith('/') ? '/' : ''
48
+ return `${b}${slash}${from || ''}`
49
+ }
50
+
51
+ const adapter = {
52
+ name: 'rest',
53
+
54
+ // Auth
55
+ setToken: (t) => { token = t },
56
+
57
+ getSession: async () => {
58
+ if (!authConfig?.sessionUrl) {
59
+ return token ? { token } : null
60
+ }
61
+ const result = await request(
62
+ resolveUrl(authConfig.sessionUrl, authConfig.baseUrl),
63
+ getFetchOptions({ headers: getHeaders() })
64
+ )
65
+ return result.error ? null : result.data
66
+ },
67
+
68
+ signIn: async (credentials) => {
69
+ if (!authConfig?.signInUrl) throw new Error('rest: auth.signInUrl not configured')
70
+ const result = await request(resolveUrl(authConfig.signInUrl, authConfig.baseUrl), getFetchOptions({
71
+ method: 'POST',
72
+ headers: getJsonHeaders(),
73
+ body: JSON.stringify(credentials)
74
+ }))
75
+ if (result.data?.token) token = result.data.token
76
+ return result
77
+ },
78
+
79
+ signOut: async () => {
80
+ if (authConfig?.signOutUrl) {
81
+ await request(resolveUrl(authConfig.signOutUrl, authConfig.baseUrl), getFetchOptions({
82
+ method: 'POST',
83
+ headers: getHeaders()
84
+ }))
85
+ }
86
+ token = null
87
+ return { error: null }
88
+ },
89
+
90
+ // CRUD
91
+ select: ({ from, params, select, limit, offset, order, single, headers, baseUrl: fromBase }) => {
92
+ const allParams = { ...params }
93
+ if (select) allParams.select = select
94
+ if (limit) allParams.limit = limit
95
+ if (offset) allParams.offset = offset
96
+ if (single) allParams.single = true
97
+ if (order) {
98
+ if (typeof order === 'string') {
99
+ allParams.order = order
100
+ } else if (Array.isArray(order)) {
101
+ allParams.order = order.map(o => `${o.by}:${o.asc === false ? 'desc' : 'asc'}`).join(',')
102
+ } else if (order.by) {
103
+ allParams.order = `${order.by}:${order.asc === false ? 'desc' : 'asc'}`
104
+ }
105
+ }
106
+ return request(
107
+ buildUrl(fromBase || baseUrl, from || '', allParams),
108
+ getFetchOptions({ headers: getHeaders(headers) })
109
+ )
110
+ },
111
+
112
+ rpc: ({ from, params, headers, baseUrl: fromBase }) =>
113
+ request(resolveUrl(`rpc/${from}`, fromBase), getFetchOptions({
114
+ method: 'POST',
115
+ headers: getJsonHeaders(headers),
116
+ body: JSON.stringify(params)
117
+ })),
118
+
119
+ insert: ({ from, data, headers, baseUrl: fromBase }) =>
120
+ request(resolveUrl(from, fromBase), getFetchOptions({
121
+ method: 'POST',
122
+ headers: getJsonHeaders(headers),
123
+ body: JSON.stringify(data)
124
+ })),
125
+
126
+ update: ({ from, data, params, method, headers, baseUrl: fromBase }) => {
127
+ const id = params?.id || data?.id
128
+ const path = id ? `${from}/${id}` : from
129
+ return request(resolveUrl(path, fromBase), getFetchOptions({
130
+ method: method || 'PATCH',
131
+ headers: getJsonHeaders(headers),
132
+ body: JSON.stringify(data)
133
+ }))
134
+ },
135
+
136
+ delete: ({ from, params, headers, baseUrl: fromBase }) => {
137
+ const id = params?.id
138
+ const path = id ? `${from}/${id}` : from
139
+ return request(resolveUrl(path, fromBase), getFetchOptions({
140
+ method: 'DELETE',
141
+ headers: getHeaders(headers)
142
+ }))
143
+ }
144
+ }
145
+
146
+ return adapter
147
+ }
@@ -0,0 +1,153 @@
1
+ 'use strict'
2
+
3
+ export const setup = async ({ url, key, projectId, createClient, ...options }) => {
4
+ const supabaseUrl = url || (projectId && `https://${projectId}.supabase.co`)
5
+ if (!supabaseUrl || !key) {
6
+ throw new Error('@symbo.ls/fetch supabase: url (or projectId) and key are required')
7
+ }
8
+ if (!createClient) {
9
+ const pkg = '@supabase/' + 'supabase-js'
10
+ const mod = await import(/* webpackIgnore: true */ pkg)
11
+ createClient = mod.createClient
12
+ }
13
+ return supabaseAdapter(createClient(supabaseUrl, key, options))
14
+ }
15
+
16
+ const applyFilters = (query, params) => {
17
+ if (!params) return query
18
+ for (const key in params) {
19
+ const val = params[key]
20
+ if (val === null) {
21
+ query = query.is(key, null)
22
+ } else if (Array.isArray(val)) {
23
+ query = query.in(key, val)
24
+ } else if (typeof val === 'object') {
25
+ for (const op in val) {
26
+ if (op === 'gt') query = query.gt(key, val[op])
27
+ else if (op === 'gte') query = query.gte(key, val[op])
28
+ else if (op === 'lt') query = query.lt(key, val[op])
29
+ else if (op === 'lte') query = query.lte(key, val[op])
30
+ else if (op === 'neq') query = query.neq(key, val[op])
31
+ else if (op === 'like') query = query.like(key, val[op])
32
+ else if (op === 'ilike') query = query.ilike(key, val[op])
33
+ else if (op === 'in') query = query.in(key, val[op])
34
+ else if (op === 'is') query = query.is(key, val[op])
35
+ else if (op === 'contains') query = query.contains(key, val[op])
36
+ else if (op === 'containedBy') query = query.containedBy(key, val[op])
37
+ else if (op === 'textSearch') query = query.textSearch(key, val[op])
38
+ }
39
+ } else {
40
+ query = query.eq(key, val)
41
+ }
42
+ }
43
+ return query
44
+ }
45
+
46
+ const applyModifiers = (query, { limit, offset, order, single } = {}) => {
47
+ if (order) {
48
+ if (Array.isArray(order)) {
49
+ for (const o of order) {
50
+ const by = typeof o === 'string' ? o : o.by
51
+ query = query.order(by, { ascending: o.asc !== false })
52
+ }
53
+ } else {
54
+ const orderBy = typeof order === 'string' ? order : order.by
55
+ query = query.order(orderBy, { ascending: order.asc !== false })
56
+ }
57
+ }
58
+ if (limit) query = query.limit(limit)
59
+ if (offset) query = query.range(offset, offset + (limit || 20) - 1)
60
+ if (single) query = query.single()
61
+ return query
62
+ }
63
+
64
+ export const supabaseAdapter = (client) => ({
65
+ name: 'supabase',
66
+ client,
67
+
68
+ // Auth
69
+ getSession: async () => {
70
+ const { data } = await client.auth.getSession()
71
+ return data?.session || null
72
+ },
73
+
74
+ getUser: async () => {
75
+ const { data } = await client.auth.getUser()
76
+ return data?.user || null
77
+ },
78
+
79
+ signIn: (credentials) => {
80
+ if (credentials.provider) {
81
+ return client.auth.signInWithOAuth({ provider: credentials.provider })
82
+ }
83
+ if (credentials.token) {
84
+ return client.auth.signInWithIdToken(credentials)
85
+ }
86
+ return client.auth.signInWithPassword(credentials)
87
+ },
88
+
89
+ signUp: (credentials) => client.auth.signUp(credentials),
90
+
91
+ signOut: () => client.auth.signOut(),
92
+
93
+ onAuthStateChange: (callback) => {
94
+ const { data: { subscription } } = client.auth.onAuthStateChange(
95
+ (event, session) => callback(event, session)
96
+ )
97
+ return () => subscription.unsubscribe()
98
+ },
99
+
100
+ // CRUD
101
+ select: async ({ from, select, params, ...modifiers }) => {
102
+ let q = client.from(from).select(select || '*')
103
+ q = applyFilters(q, params)
104
+ q = applyModifiers(q, modifiers)
105
+ return q
106
+ },
107
+
108
+ rpc: ({ from, params }) => client.rpc(from, params),
109
+
110
+ insert: ({ from, data, select }) =>
111
+ client.from(from).insert(data).select(select || '*'),
112
+
113
+ update: ({ from, data, params, select }) => {
114
+ let q = client.from(from).update(data)
115
+ q = applyFilters(q, params)
116
+ return q.select(select || '*')
117
+ },
118
+
119
+ upsert: ({ from, data, select }) =>
120
+ client.from(from).upsert(data).select(select || '*'),
121
+
122
+ delete: ({ from, params }) => {
123
+ let q = client.from(from).delete()
124
+ q = applyFilters(q, params)
125
+ return q
126
+ },
127
+
128
+ // Realtime
129
+ subscribe: ({ from, params, on }, callback) => {
130
+ const event = on || '*'
131
+ const channel = client
132
+ .channel(`db-${from}-${Date.now()}`)
133
+ .on('postgres_changes', {
134
+ event,
135
+ schema: 'public',
136
+ table: from,
137
+ ...(params?.id ? { filter: `id=eq.${params.id}` } : {})
138
+ }, (payload) => callback(payload.new, payload.old, payload))
139
+ .subscribe()
140
+
141
+ return () => client.removeChannel(channel)
142
+ },
143
+
144
+ // Storage
145
+ upload: ({ bucket, path, file, options }) =>
146
+ client.storage.from(bucket).upload(path, file, options),
147
+
148
+ download: ({ bucket, path }) =>
149
+ client.storage.from(bucket).download(path),
150
+
151
+ getPublicUrl: ({ bucket, path }) =>
152
+ client.storage.from(bucket).getPublicUrl(path)
153
+ })