@based/react 0.6.1 → 0.7.2

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/src/clients.ts ADDED
@@ -0,0 +1,160 @@
1
+ import React, {
2
+ createContext,
3
+ FunctionComponent,
4
+ ReactNode,
5
+ useContext,
6
+ useEffect,
7
+ useReducer,
8
+ } from 'react'
9
+
10
+ import based, { Based, BasedOpts } from '@based/client'
11
+
12
+ import { genOptsId } from './genOptsId'
13
+
14
+ export type CreateClient = (
15
+ selector: string | (BasedOpts & { key?: string })
16
+ ) => Based
17
+
18
+ const newClientListeners: Set<() => void> = new Set()
19
+
20
+ interface BasedContextType {
21
+ clients: { [key: string]: Based }
22
+ createClient?: CreateClient
23
+ removeClient: (
24
+ selector: string | (BasedOpts & { key?: string }) | Based
25
+ ) => void
26
+ }
27
+
28
+ export const BasedContext = createContext<BasedContextType>({
29
+ clients: {},
30
+ // eslint-disable-next-line
31
+ removeClient: (...args: any) => {},
32
+ })
33
+
34
+ export const defaultCreateClient: CreateClient = (selector) => {
35
+ if (typeof selector === 'object') {
36
+ if (
37
+ process.env.CLUSTER &&
38
+ process.env.CLUSTER.startsWith('local') &&
39
+ !selector.cluster
40
+ ) {
41
+ selector.cluster = process.env.CLUSTER
42
+ }
43
+ return based(selector)
44
+ } else {
45
+ // default
46
+ console.error('Cannot create client from ' + selector)
47
+ }
48
+ }
49
+
50
+ export const Provider: FunctionComponent<{
51
+ client?: Based
52
+ clients?: { [key: string]: Based }
53
+ children: ReactNode
54
+ createClient?: CreateClient
55
+ }> = ({ client, children, clients, createClient }) => {
56
+ if (!clients && client) {
57
+ clients = {
58
+ default: client,
59
+ }
60
+ } else if (clients && client) {
61
+ clients.default = client
62
+ }
63
+
64
+ const ctx = React.createElement(
65
+ BasedContext.Provider,
66
+ {
67
+ value: {
68
+ clients,
69
+ createClient: createClient || defaultCreateClient,
70
+ removeClient: (selector) => {
71
+ if (selector instanceof Based) {
72
+ for (const cl in clients) {
73
+ if (clients[cl] === selector) {
74
+ selector = cl
75
+ break
76
+ }
77
+ }
78
+ if (typeof selector !== 'string') {
79
+ console.error('Cannot find client to remove from ctx', selector)
80
+ return
81
+ }
82
+ } else if (typeof selector !== 'string') {
83
+ selector = genOptsId(selector)
84
+ }
85
+ // @ts-ignore
86
+ if (clients[selector]) {
87
+ // @ts-ignore
88
+ clients[selector].disconnect()
89
+ // @ts-ignore
90
+ delete clients[selector]
91
+ newClientListeners.forEach((fn) => fn())
92
+ }
93
+ },
94
+ },
95
+ },
96
+ children
97
+ )
98
+
99
+ return ctx
100
+ }
101
+
102
+ export const useBasedContext = () => {
103
+ return useContext(BasedContext)
104
+ }
105
+
106
+ function forceUpdate(state: number) {
107
+ return state + 1
108
+ }
109
+
110
+ export const useClients = (): Based[] => {
111
+ const ctx = useBasedContext()
112
+ const [, update] = useReducer(forceUpdate, 0)
113
+
114
+ useEffect(() => {
115
+ let timer
116
+ const fn = () => {
117
+ timer = setTimeout(update, 0)
118
+ }
119
+ newClientListeners.add(fn)
120
+ return () => {
121
+ newClientListeners.delete(fn)
122
+ clearTimeout(timer)
123
+ }
124
+ }, [])
125
+
126
+ return Object.values(ctx.clients)
127
+ }
128
+
129
+ export const useClient = (
130
+ selector: string | (BasedOpts & { key?: string }) = 'default'
131
+ ) => {
132
+ const basedCtx = useContext(BasedContext)
133
+
134
+ if (typeof selector === 'object') {
135
+ if (!(selector.env && selector.project && selector.org)) {
136
+ return
137
+ }
138
+ }
139
+
140
+ let key: string
141
+
142
+ if (typeof selector === 'string') {
143
+ key = selector
144
+ } else {
145
+ key = selector.key || genOptsId(selector)
146
+ }
147
+
148
+ let client: Based = basedCtx.clients[key]
149
+
150
+ if (!client && basedCtx.createClient) {
151
+ client = basedCtx.createClient(selector)
152
+
153
+ if (client) {
154
+ basedCtx.clients[key] = client
155
+ newClientListeners.forEach((fn) => fn())
156
+ }
157
+ }
158
+
159
+ return client
160
+ }
@@ -0,0 +1,13 @@
1
+ import { BasedOpts } from '@based/client'
2
+
3
+ export const genOptsId = (opts: BasedOpts & { key?: string }): string => {
4
+ if (!opts) {
5
+ return
6
+ }
7
+ if (opts.key) {
8
+ return opts.key
9
+ }
10
+ return `${opts.env}_${opts.project}_${opts.org}_${opts.cluster || ''}_${
11
+ opts.name || ''
12
+ }`
13
+ }
package/src/gql.ts ADDED
@@ -0,0 +1,210 @@
1
+ import { useEffect, useMemo, useReducer, useRef, useState } from 'react'
2
+ import {
3
+ addSubscriber,
4
+ removeSubscriber,
5
+ handleGraphqlVariables,
6
+ BasedGraphQL,
7
+ generateSubscriptionId,
8
+ BasedOpts,
9
+ } from '@based/client'
10
+ import { Loading, Data } from './types'
11
+ import { resultReducer } from './reducer'
12
+ import { useClient } from './clients'
13
+ import { genOptsId } from './genOptsId'
14
+ import { updateMeta } from './meta'
15
+ import { hashObjectIgnoreKeyOrder } from '@saulx/hash'
16
+
17
+ const schemaSubId = generateSubscriptionId({ $subscribe_schema: 'default' })
18
+
19
+ // step one make 1 useEffect
20
+ // - and the nessecary if
21
+
22
+ export function useQuery(
23
+ query?: string | BasedGraphQL,
24
+ variables: Record<string, any> = {},
25
+ clientSelector?: string | (BasedOpts & { key?: string })
26
+ ): {
27
+ data: Data
28
+ error?: Error
29
+ loading: Loading
30
+ } {
31
+ const [result, dispatch] = useReducer(resultReducer, {
32
+ loading: true,
33
+ data: {},
34
+ checksum: 0,
35
+ })
36
+
37
+ const r = useRef({ checksum: 0, fns: {} })
38
+ const selector = clientSelector || 'default'
39
+ const client = useClient(selector)
40
+
41
+ if (query) {
42
+ const [configState, updateConfigState] = useState(0)
43
+
44
+ useEffect(() => {
45
+ const [, subscriberId] = addSubscriber(
46
+ client.client,
47
+ // FIXME dont want too many updates plz this has to become 1 use effect
48
+ { $subscribe_schema: 'default' },
49
+ (d, checksum) => {
50
+ if (!client.client.configuration) {
51
+ client.client.configuration = { dbs: [], schema: {}, functions: {} }
52
+ }
53
+ client.client.configuration.schema.default = d
54
+ updateConfigState(checksum)
55
+ },
56
+ (err) => {
57
+ if (err) {
58
+ console.error(err)
59
+ }
60
+ },
61
+ (err) => {
62
+ console.error(err)
63
+ },
64
+ schemaSubId
65
+ )
66
+ return () => {
67
+ removeSubscriber(client.client, schemaSubId, subscriberId)
68
+ }
69
+ return () => {}
70
+ }, [])
71
+
72
+ if (configState) {
73
+ let op: BasedGraphQL
74
+ if (typeof query === 'string') {
75
+ op = client.gql(query)
76
+ } else {
77
+ op = query
78
+ }
79
+
80
+ op = handleGraphqlVariables(op, op, variables)
81
+
82
+ const fns: {
83
+ [key: string]: { name: string; payload: any; key: string }
84
+ } = {}
85
+ const queryObj: any = {}
86
+ for (const key in op.ops) {
87
+ if (op.ops[key].fnObserve) {
88
+ const { name, payload } = op.ops[key].fnObserve
89
+ fns[key] = { name: <string>name, payload, key }
90
+ continue
91
+ }
92
+ queryObj[key] = op.ops[key].get
93
+ }
94
+
95
+ const fnHash = useMemo(() => {
96
+ return hashObjectIgnoreKeyOrder(fns)
97
+ }, [fns])
98
+
99
+ const subId = useMemo(() => {
100
+ // fn?
101
+ return generateSubscriptionId(queryObj)
102
+ }, [queryObj])
103
+
104
+ const clientKey =
105
+ typeof selector === 'string' ? selector : genOptsId(selector)
106
+
107
+ if (client) {
108
+ const subKey = clientKey + subId
109
+
110
+ useEffect(() => {
111
+ const subs = []
112
+
113
+ for (const key in fns) {
114
+ subs.push(
115
+ addSubscriber(
116
+ client.client,
117
+ fns[key].payload,
118
+ (d, checksum) => {
119
+ updateMeta(subKey, false, false)
120
+ if (r.current.fns[key] !== checksum) {
121
+ r.current.fns[key] = checksum
122
+ dispatch({
123
+ merge: { [key]: d },
124
+ checksum: hashObjectIgnoreKeyOrder(r.current),
125
+ })
126
+ }
127
+ },
128
+ (err) => {
129
+ if (err) {
130
+ console.error(err)
131
+ dispatch({ error: err, loading: false })
132
+ }
133
+ },
134
+ (err) => {
135
+ console.error(err)
136
+ updateMeta(subKey, false, err)
137
+ dispatch({ error: err })
138
+ },
139
+ undefined,
140
+ fns[key].name
141
+ )
142
+ )
143
+ }
144
+
145
+ return () => {
146
+ for (const [subId, subscriberId] of subs) {
147
+ removeSubscriber(client.client, subId, subscriberId)
148
+ }
149
+ }
150
+ }, [fnHash])
151
+
152
+ useEffect(() => {
153
+ if (!configState) {
154
+ return
155
+ }
156
+ updateMeta(subKey, true, false)
157
+ const [, subscriberId] = addSubscriber(
158
+ client.client,
159
+ queryObj,
160
+ (d, checksum) => {
161
+ updateMeta(subKey, false, false)
162
+ if (r.current.checksum !== checksum) {
163
+ r.current.checksum = checksum
164
+ dispatch({
165
+ merge: d,
166
+ checksum: hashObjectIgnoreKeyOrder(r.current),
167
+ })
168
+ }
169
+ },
170
+ (err) => {
171
+ if (err) {
172
+ console.error(err)
173
+ updateMeta(subKey, false, err)
174
+ dispatch({ error: err, loading: false })
175
+ }
176
+ },
177
+ (err) => {
178
+ console.error(err)
179
+ updateMeta(subKey, false, err)
180
+ dispatch({ error: err })
181
+ },
182
+ subId
183
+ )
184
+ return () => {
185
+ updateMeta(subKey, false, false)
186
+ removeSubscriber(client.client, subId, subscriberId)
187
+ }
188
+ }, [subId, clientKey, configState])
189
+ } else {
190
+ useEffect(stubFn, [null, null])
191
+ }
192
+ } else {
193
+ useMemo(stubFn, [null])
194
+ useMemo(stubFn, [null])
195
+ useEffect(stubFn, [null])
196
+ useEffect(stubFn, [null, null, null])
197
+ }
198
+ } else {
199
+ useState(null)
200
+ useEffect(stubFn, [null])
201
+ useMemo(stubFn, [null])
202
+ useMemo(stubFn, [null])
203
+ useEffect(stubFn, [null])
204
+ useEffect(stubFn, [null, null, null])
205
+ }
206
+
207
+ return result
208
+ }
209
+
210
+ function stubFn() {}
package/src/index.ts CHANGED
@@ -1,16 +1,6 @@
1
- import React, {
2
- createContext,
3
- FunctionComponent,
4
- ReactNode,
5
- useContext,
6
- useEffect,
7
- useMemo,
8
- useReducer,
9
- useState,
10
- } from 'react'
11
- import based, {
12
- Based,
13
- GenericObject,
1
+ import { useEffect, useMemo, useReducer, useState } from 'react'
2
+
3
+ import {
14
4
  Query,
15
5
  addSubscriber,
16
6
  removeSubscriber,
@@ -18,200 +8,19 @@ import based, {
18
8
  BasedOpts,
19
9
  } from '@based/client'
20
10
 
21
- const genOptsId = (opts: BasedOpts & { key?: string }): string => {
22
- if (!opts) {
23
- return
24
- }
25
-
26
- if (opts.key) {
27
- return opts.key
28
- }
29
-
30
- return `${opts.env}_${opts.project}_${opts.org}_${opts.cluster || ''}_${
31
- opts.name || ''
32
- }`
33
- }
34
-
35
- export type CreateClient = (
36
- selector: string | (BasedOpts & { key?: string })
37
- ) => Based
38
-
39
- interface BasedContextType {
40
- clients: { [key: string]: Based }
41
- createClient?: CreateClient
42
- removeClient: (
43
- selector: string | (BasedOpts & { key?: string }) | Based
44
- ) => void
45
- }
46
-
47
- export const BasedContext = createContext<BasedContextType>({
48
- clients: {},
49
- removeClient: (...args: any) => {},
50
- })
51
-
52
- const errors = {}
53
- const errorListeners = new Set()
54
- const loadings = new Set()
55
- const loadingListeners = new Set()
56
- const newClientListeners: Set<() => void> = new Set()
57
-
58
- let isLoading = false
59
- let lastError = ''
60
- let errorCnt = 0
61
- let errorKey = errorCnt + lastError
62
-
63
- export const defaultCreateClient: CreateClient = (selector) => {
64
- if (typeof selector === 'object') {
65
- if (
66
- process.env.CLUSTER &&
67
- process.env.CLUSTER.startsWith('local') &&
68
- !selector.cluster
69
- ) {
70
- selector.cluster = process.env.CLUSTER
71
- }
72
- return based(selector)
73
- } else {
74
- // default
75
- console.error('Cannot create client from ' + selector)
76
- }
77
- }
78
-
79
- export const Provider: FunctionComponent<{
80
- client?: Based
81
- clients?: { [key: string]: Based }
82
- children: ReactNode
83
- createClient?: CreateClient
84
- }> = ({ client, children, clients, createClient }) => {
85
- if (!clients && client) {
86
- clients = {
87
- default: client,
88
- }
89
- } else if (clients && client) {
90
- clients.default = client
91
- }
92
-
93
- const ctx = React.createElement(
94
- BasedContext.Provider,
95
- {
96
- value: {
97
- clients,
98
- createClient: createClient || defaultCreateClient,
99
- removeClient: (selector) => {
100
- if (selector instanceof Based) {
101
- for (const cl in clients) {
102
- if (clients[cl] === selector) {
103
- selector = cl
104
- break
105
- }
106
- }
107
- if (typeof selector !== 'string') {
108
- console.error('Cannot find client to remove from ctx', selector)
109
- return
110
- }
111
- } else if (typeof selector !== 'string') {
112
- selector = genOptsId(selector)
113
- }
114
- // @ts-ignore
115
- if (clients[selector]) {
116
- // @ts-ignore
117
- clients[selector].disconnect()
118
- // @ts-ignore
119
- delete clients[selector]
120
- newClientListeners.forEach((fn) => fn())
121
- }
122
- },
123
- },
124
- },
125
- children
126
- )
127
-
128
- return ctx
129
- }
130
-
131
- export const useBasedContext = () => {
132
- return useContext(BasedContext)
133
- }
134
-
135
- function forceUpdate(state: number) {
136
- return state + 1
137
- }
138
-
139
- export const useClients = (): Based[] => {
140
- const ctx = useBasedContext()
141
- const [, update] = useReducer(forceUpdate, 0)
142
-
143
- useEffect(() => {
144
- let timer
145
- const fn = () => {
146
- timer = setTimeout(update, 0)
147
- }
148
- newClientListeners.add(fn)
149
- return () => {
150
- newClientListeners.delete(fn)
151
- clearTimeout(timer)
152
- }
153
- }, [])
154
-
155
- return Object.values(ctx.clients)
156
- }
157
-
158
- export const useClient = (
159
- selector: string | (BasedOpts & { key?: string }) = 'default'
160
- ) => {
161
- const basedCtx = useContext(BasedContext)
162
-
163
- if (typeof selector === 'object') {
164
- if (!(selector.env && selector.project && selector.org)) {
165
- return
166
- }
167
- }
168
-
169
- let key: string
170
-
171
- if (typeof selector === 'string') {
172
- key = selector
173
- } else {
174
- key = selector.key || genOptsId(selector)
175
- }
176
-
177
- let client: Based = basedCtx.clients[key]
11
+ import { resultReducer } from './reducer'
178
12
 
179
- if (!client && basedCtx.createClient) {
180
- client = basedCtx.createClient(selector)
13
+ import { Loading, Data } from './types'
181
14
 
182
- if (client) {
183
- basedCtx.clients[key] = client
184
- newClientListeners.forEach((fn) => fn())
185
- }
186
- }
15
+ import { genOptsId } from './genOptsId'
187
16
 
188
- return client
189
- }
17
+ import { useClient } from './clients'
190
18
 
191
- type Data = GenericObject
19
+ import { updateMeta } from './meta'
192
20
 
193
- type Loading = boolean
194
-
195
- function resultReducer(
196
- state: { data: Data; error?: Error; loading: Loading; checksum: number },
197
- action: { data?: Data; error?: Error; loading?: Loading; checksum?: number }
198
- ) {
199
- if (action.error) {
200
- state.error = action.error
201
- }
202
- if (action.data) {
203
- state.checksum = action.checksum || 0
204
- state.data = action.data
205
- state.loading = false
206
- if (state.error) {
207
- delete state.error
208
- }
209
- }
210
- if (action.loading) {
211
- state.loading = action.loading
212
- }
213
- return { ...state }
214
- }
21
+ export * from './meta'
22
+ export * from './clients'
23
+ export * from './gql'
215
24
 
216
25
  export function useAuth(
217
26
  clientSelector?: string | (BasedOpts & { key?: string })
@@ -219,7 +28,7 @@ export function useAuth(
219
28
  const client = useClient(clientSelector)
220
29
  const [token, setToken] = useState<false | string>(false)
221
30
  useEffect(() => {
222
- const t = (v) => setToken(client.getToken())
31
+ const t = () => setToken(client.getToken())
223
32
  client.on('auth', t)
224
33
  return () => {
225
34
  client.removeListener('auth', t)
@@ -373,67 +182,4 @@ export function useData(
373
182
  return result
374
183
  }
375
184
 
376
- export function useLoading() {
377
- const [, setLoading] = useState(isLoading)
378
-
379
- loadingListeners.add(setLoading)
380
-
381
- useEffect(() => {
382
- return () => {
383
- loadingListeners.delete(setLoading)
384
- }
385
- }, [])
386
-
387
- return { loading: isLoading }
388
- }
389
-
390
- export function useError() {
391
- const [, setError] = useState(errorKey)
392
-
393
- errorListeners.add(setError)
394
-
395
- useEffect(() => {
396
- return () => {
397
- errorListeners.delete(setError)
398
- }
399
- }, [])
400
-
401
- return { error: errorCnt ? lastError : null, errors: Object.values(errors) }
402
- }
403
-
404
- function updateMeta(subKey, loading, error) {
405
- if (error) {
406
- lastError = error
407
- if (subKey in errors) {
408
- errors[subKey] = error
409
- } else {
410
- errors[subKey] = error
411
- errorCnt++
412
- }
413
- } else {
414
- if (subKey in errors) {
415
- errorCnt--
416
- delete errors[subKey]
417
- }
418
- }
419
-
420
- const newErrorKey = errorCnt + lastError
421
- if (newErrorKey !== errorKey) {
422
- errorKey = newErrorKey
423
- errorListeners.forEach((fn: Function) => fn(errorKey))
424
- }
425
-
426
- if (loading) {
427
- loadings.add(subKey)
428
- } else {
429
- loadings.delete(subKey)
430
- }
431
-
432
- const newLoading = !!loadings.size
433
- if (newLoading !== isLoading) {
434
- isLoading = newLoading
435
- loadingListeners.forEach((fn: Function) => fn(isLoading))
436
- }
437
- }
438
-
439
185
  function stubFn() {}