@based/react 0.7.1 → 2.2.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.
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "@based/react",
3
- "version": "0.7.1",
3
+ "version": "2.2.0",
4
+ "license": "MIT",
4
5
  "source": "src/index.tsx",
5
6
  "main": "dist/index.js",
6
7
  "scripts": {
7
- "build": "npx tsc",
8
- "watch": "npm run build -- --watch"
8
+ "buildBundle": "node ./build/index.js",
9
+ "build": "npx tsc && npm run buildBundle",
10
+ "watch": "npm run build -- --watch",
11
+ "clean": "rimraf {.turbo,dist,node_modules}"
9
12
  },
10
13
  "sideEffects": false,
11
14
  "peerDependencies": {
12
- "@based/client": "^1.1.0",
13
15
  "react": "^17.0.2"
14
16
  },
17
+ "dependencies": {
18
+ "@based/client": "*",
19
+ "@saulx/hash": "^1.1.0"
20
+ },
15
21
  "devDependencies": {
16
22
  "@types/react": "^17.0.14",
23
+ "esbuild": "^0.14.29",
17
24
  "ts-node": "^10.1.0",
18
- "typescript": "^4.3.5"
25
+ "typescript": "^4.3.5",
26
+ "rimraf": "^3.0.2"
19
27
  }
20
28
  }
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,214 @@
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 = {
52
+ dbs: [],
53
+ schema: {},
54
+ functions: {},
55
+ } as any // TODO: FIX
56
+ }
57
+ client.client.configuration.schema.default = d
58
+ updateConfigState(checksum)
59
+ },
60
+ (err) => {
61
+ if (err) {
62
+ console.error(err)
63
+ }
64
+ },
65
+ (err) => {
66
+ console.error(err)
67
+ },
68
+ schemaSubId
69
+ )
70
+ return () => {
71
+ removeSubscriber(client.client, schemaSubId, subscriberId)
72
+ }
73
+ return () => {}
74
+ }, [])
75
+
76
+ if (configState) {
77
+ let op: BasedGraphQL
78
+ if (typeof query === 'string') {
79
+ op = client.gql(query)
80
+ } else {
81
+ op = query
82
+ }
83
+
84
+ op = handleGraphqlVariables(op, op, variables)
85
+
86
+ const fns: {
87
+ [key: string]: { name: string; payload: any; key: string }
88
+ } = {}
89
+ const queryObj: any = {}
90
+ for (const key in op.ops) {
91
+ if (op.ops[key].fnObserve) {
92
+ const { name, payload } = op.ops[key].fnObserve
93
+ fns[key] = { name: <string>name, payload, key }
94
+ continue
95
+ }
96
+ queryObj[key] = op.ops[key].get
97
+ }
98
+
99
+ const fnHash = useMemo(() => {
100
+ return hashObjectIgnoreKeyOrder(fns)
101
+ }, [fns])
102
+
103
+ const subId = useMemo(() => {
104
+ // fn?
105
+ return generateSubscriptionId(queryObj)
106
+ }, [queryObj])
107
+
108
+ const clientKey =
109
+ typeof selector === 'string' ? selector : genOptsId(selector)
110
+
111
+ if (client) {
112
+ const subKey = clientKey + subId
113
+
114
+ useEffect(() => {
115
+ const subs = []
116
+
117
+ for (const key in fns) {
118
+ subs.push(
119
+ addSubscriber(
120
+ client.client,
121
+ fns[key].payload,
122
+ (d, checksum) => {
123
+ updateMeta(subKey, false, false)
124
+ if (r.current.fns[key] !== checksum) {
125
+ r.current.fns[key] = checksum
126
+ dispatch({
127
+ merge: { [key]: d },
128
+ checksum: hashObjectIgnoreKeyOrder(r.current),
129
+ })
130
+ }
131
+ },
132
+ (err) => {
133
+ if (err) {
134
+ console.error(err)
135
+ dispatch({ error: err, loading: false })
136
+ }
137
+ },
138
+ (err) => {
139
+ console.error(err)
140
+ updateMeta(subKey, false, err)
141
+ dispatch({ error: err })
142
+ },
143
+ undefined,
144
+ fns[key].name
145
+ )
146
+ )
147
+ }
148
+
149
+ return () => {
150
+ for (const [subId, subscriberId] of subs) {
151
+ removeSubscriber(client.client, subId, subscriberId)
152
+ }
153
+ }
154
+ }, [fnHash])
155
+
156
+ useEffect(() => {
157
+ if (!configState) {
158
+ return
159
+ }
160
+ updateMeta(subKey, true, false)
161
+ const [, subscriberId] = addSubscriber(
162
+ client.client,
163
+ queryObj,
164
+ (d, checksum) => {
165
+ updateMeta(subKey, false, false)
166
+ if (r.current.checksum !== checksum) {
167
+ r.current.checksum = checksum
168
+ dispatch({
169
+ merge: d,
170
+ checksum: hashObjectIgnoreKeyOrder(r.current),
171
+ })
172
+ }
173
+ },
174
+ (err) => {
175
+ if (err) {
176
+ console.error(err)
177
+ updateMeta(subKey, false, err)
178
+ dispatch({ error: err, loading: false })
179
+ }
180
+ },
181
+ (err) => {
182
+ console.error(err)
183
+ updateMeta(subKey, false, err)
184
+ dispatch({ error: err })
185
+ },
186
+ subId
187
+ )
188
+ return () => {
189
+ updateMeta(subKey, false, false)
190
+ removeSubscriber(client.client, subId, subscriberId)
191
+ }
192
+ }, [subId, clientKey, configState])
193
+ } else {
194
+ useEffect(stubFn, [null, null])
195
+ }
196
+ } else {
197
+ useMemo(stubFn, [null])
198
+ useMemo(stubFn, [null])
199
+ useEffect(stubFn, [null])
200
+ useEffect(stubFn, [null, null, null])
201
+ }
202
+ } else {
203
+ useState(null)
204
+ useEffect(stubFn, [null])
205
+ useMemo(stubFn, [null])
206
+ useMemo(stubFn, [null])
207
+ useEffect(stubFn, [null])
208
+ useEffect(stubFn, [null, null, null])
209
+ }
210
+
211
+ return result
212
+ }
213
+
214
+ function stubFn() {}