@basictech/react 0.2.0-beta.3 → 0.2.0-beta.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/.turbo/turbo-build.log +6 -6
- package/changelog.md +6 -0
- package/dist/index.js +166 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/readme.md +23 -1
- package/src/AuthContext.tsx +122 -26
- package/src/config.ts +73 -0
- package/src/sync/index.ts +18 -11
package/src/AuthContext.tsx
CHANGED
|
@@ -3,23 +3,61 @@
|
|
|
3
3
|
import React, { createContext, useContext, useEffect, useState, useRef } from 'react'
|
|
4
4
|
import { jwtDecode } from 'jwt-decode'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
6
|
import { BasicSync } from './sync'
|
|
9
7
|
import { get, add, update, deleteRecord } from './db'
|
|
10
8
|
|
|
9
|
+
import { validator, log } from './config'
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
schema todo:
|
|
13
|
+
field types
|
|
14
|
+
array types
|
|
15
|
+
relations
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const example = {
|
|
20
|
+
project_id: '123',
|
|
21
|
+
version: 0,
|
|
22
|
+
tables: {
|
|
23
|
+
example: {
|
|
24
|
+
name: 'example',
|
|
25
|
+
type: 'collection',
|
|
26
|
+
fields: {
|
|
27
|
+
id: {
|
|
28
|
+
type: 'uuid',
|
|
29
|
+
primary: true,
|
|
30
|
+
},
|
|
31
|
+
value: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
indexed: true,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
example2: {
|
|
38
|
+
name: 'example2',
|
|
39
|
+
type: 'collection',
|
|
40
|
+
fields: {
|
|
41
|
+
id: { type: 'string', primary: true },
|
|
42
|
+
id: { type: 'string', primary: true },
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
11
49
|
type BasicSyncType = {
|
|
12
50
|
basic_schema: any;
|
|
13
51
|
connect: (options: { access_token: string }) => void;
|
|
14
52
|
debugeroo: () => void;
|
|
15
53
|
collection: (name: string) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
ref: {
|
|
55
|
+
toArray: () => Promise<any[]>;
|
|
56
|
+
count: () => Promise<number>;
|
|
57
|
+
};
|
|
20
58
|
};
|
|
21
59
|
[key: string]: any; // For other potential methods and properties
|
|
22
|
-
|
|
60
|
+
};
|
|
23
61
|
|
|
24
62
|
|
|
25
63
|
enum DBStatus {
|
|
@@ -56,7 +94,7 @@ export const BasicContext = createContext<{
|
|
|
56
94
|
signin: () => void,
|
|
57
95
|
getToken: () => Promise<string>,
|
|
58
96
|
getSignInLink: () => string,
|
|
59
|
-
db: any,
|
|
97
|
+
db: any,
|
|
60
98
|
dbStatus: DBStatus
|
|
61
99
|
}>({
|
|
62
100
|
unicorn: "🦄",
|
|
@@ -103,6 +141,12 @@ function getSyncStatus(statusCode: number): string {
|
|
|
103
141
|
}
|
|
104
142
|
}
|
|
105
143
|
|
|
144
|
+
type ErrorObject = {
|
|
145
|
+
code: string;
|
|
146
|
+
title: string;
|
|
147
|
+
message: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
106
150
|
export function BasicProvider({ children, project_id, schema }: { children: React.ReactNode, project_id: string, schema: any }) {
|
|
107
151
|
const [isLoaded, setIsLoaded] = useState(false)
|
|
108
152
|
const [isSignedIn, setIsSignedIn] = useState(false)
|
|
@@ -110,30 +154,55 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
110
154
|
const [authCode, setAuthCode] = useState<string | null>(null)
|
|
111
155
|
const [user, setUser] = useState<User>({})
|
|
112
156
|
|
|
113
|
-
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.
|
|
114
|
-
|
|
157
|
+
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.LOADING)
|
|
115
158
|
|
|
116
159
|
const syncRef = useRef<BasicSync | null>(null);
|
|
117
160
|
|
|
161
|
+
const [error, setError] = useState<ErrorObject | null>(null)
|
|
162
|
+
|
|
118
163
|
useEffect(() => {
|
|
119
|
-
|
|
120
|
-
|
|
164
|
+
function initDb() {
|
|
165
|
+
// console.log('S', validator(example))
|
|
166
|
+
if (!validator(example)) {
|
|
167
|
+
console.error('Basic Schema is invalid!', validator.errors)
|
|
168
|
+
console.group('Schema Errors')
|
|
169
|
+
let errorMessage = ''
|
|
170
|
+
validator.errors.forEach((error, index) => {
|
|
171
|
+
console.log('error', error)
|
|
172
|
+
console.log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`)
|
|
173
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
|
|
174
|
+
})
|
|
175
|
+
console.groupEnd('Schema Errors')
|
|
176
|
+
setError({
|
|
177
|
+
code: 'schema_invalid',
|
|
178
|
+
title: 'Basic Schema is invalid!',
|
|
179
|
+
message: errorMessage
|
|
180
|
+
})
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
121
183
|
|
|
122
|
-
// console.log('db is open', syncRef.current.isOpen())
|
|
123
|
-
// syncRef.current.open()
|
|
124
|
-
// .then(() => {
|
|
125
|
-
// console.log("is open now:", syncRef.current.isOpen())
|
|
126
|
-
// })
|
|
127
184
|
|
|
128
|
-
syncRef.current
|
|
129
|
-
|
|
130
|
-
})
|
|
185
|
+
if (!syncRef.current) {
|
|
186
|
+
syncRef.current = new BasicSync('basicdb', { schema: schema });
|
|
131
187
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
188
|
+
// console.log('db is open', syncRef.current.isOpen())
|
|
189
|
+
// syncRef.current.open()
|
|
190
|
+
// .then(() => {
|
|
191
|
+
// console.log("is open now:", syncRef.current.isOpen())
|
|
192
|
+
// })
|
|
193
|
+
|
|
194
|
+
syncRef.current.handleStatusChange((status: number, url: string) => {
|
|
195
|
+
setDbStatus(getSyncStatus(status))
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
syncRef.current.syncable.getStatus().then((status) => {
|
|
199
|
+
console.log('sync status', getSyncStatus(status))
|
|
200
|
+
})
|
|
201
|
+
}
|
|
135
202
|
|
|
136
203
|
}
|
|
204
|
+
|
|
205
|
+
initDb()
|
|
137
206
|
}, []);
|
|
138
207
|
|
|
139
208
|
|
|
@@ -153,7 +222,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
153
222
|
}
|
|
154
223
|
|
|
155
224
|
useEffect(() => {
|
|
156
|
-
if (token) {
|
|
225
|
+
if (token && syncRef.current) {
|
|
157
226
|
connectToDb()
|
|
158
227
|
}
|
|
159
228
|
}, [token])
|
|
@@ -163,8 +232,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
163
232
|
|
|
164
233
|
const randomState = Math.random().toString(36).substring(7);
|
|
165
234
|
|
|
166
|
-
|
|
167
|
-
let baseUrl = "http://localhost:3003/auth/authorize"
|
|
235
|
+
let baseUrl = "https://api.basic.tech/auth/authorize"
|
|
168
236
|
baseUrl += `?client_id=${project_id}`
|
|
169
237
|
baseUrl += `&redirect_uri=${encodeURIComponent(window.location.href)}`
|
|
170
238
|
baseUrl += `&response_type=code`
|
|
@@ -366,11 +434,39 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
366
434
|
db: syncRef.current,
|
|
367
435
|
dbStatus
|
|
368
436
|
}}>
|
|
437
|
+
{error && <ErrorDisplay error={error} />}
|
|
369
438
|
{syncRef.current ? children : null}
|
|
370
439
|
</BasicContext.Provider>
|
|
371
440
|
)
|
|
372
441
|
}
|
|
373
442
|
|
|
443
|
+
function ErrorDisplay({ error }: { error: ErrorObject }) {
|
|
444
|
+
return <div style={{
|
|
445
|
+
position: 'absolute',
|
|
446
|
+
top: 20,
|
|
447
|
+
left: 20,
|
|
448
|
+
color: 'black',
|
|
449
|
+
backgroundColor: '#f8d7da',
|
|
450
|
+
border: '1px solid #f5c6cb',
|
|
451
|
+
borderRadius: '4px',
|
|
452
|
+
padding: '20px',
|
|
453
|
+
maxWidth: '400px',
|
|
454
|
+
margin: '20px auto',
|
|
455
|
+
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
|
456
|
+
fontFamily: 'monospace',
|
|
457
|
+
}}>
|
|
458
|
+
<h3 style={{fontSize: '0.8rem', opacity: 0.8}}>code: {error.code}</h3>
|
|
459
|
+
<h1 style={{fontSize: '1.2rem', lineHeight: '1.5'}}>{error.title}</h1>
|
|
460
|
+
<p>{error.message}</p>
|
|
461
|
+
</div>
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/*
|
|
465
|
+
possible errors:
|
|
466
|
+
- projectid missing / invalid
|
|
467
|
+
- schema missing / invalid
|
|
468
|
+
*/
|
|
469
|
+
|
|
374
470
|
export function useBasic() {
|
|
375
471
|
return useContext(BasicContext);
|
|
376
472
|
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Ajv from 'ajv'
|
|
2
|
+
|
|
3
|
+
export const SERVER_URL = "https://api.basic.tech"
|
|
4
|
+
// export const WS_URL = `${SERVER_URL}/ws`
|
|
5
|
+
|
|
6
|
+
export const log = (...args: any[]) => {
|
|
7
|
+
if (process.env.NODE_ENV === 'development') {
|
|
8
|
+
console.log(...args)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const basicJsonSchema = {
|
|
14
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"project_id": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"namespace": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
},
|
|
23
|
+
"version": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 0
|
|
26
|
+
},
|
|
27
|
+
"tables": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"patternProperties": {
|
|
30
|
+
"^[a-zA-Z0-9_]+$": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"name": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"type": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["collection"]
|
|
39
|
+
},
|
|
40
|
+
"fields": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"patternProperties": {
|
|
43
|
+
"^[a-zA-Z0-9_]+$": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"type": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"primary": {
|
|
50
|
+
"type": "boolean"
|
|
51
|
+
},
|
|
52
|
+
"indexed": {
|
|
53
|
+
"type": "boolean"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": ["type"]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"additionalProperties": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": ["fields"]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"additionalProperties": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"required": ["project_id", "version", "tables"]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const ajv = new Ajv()
|
|
73
|
+
export const validator = ajv.compile(basicJsonSchema)
|
package/src/sync/index.ts
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import { v7 as uuidv7 } from 'uuid';
|
|
2
4
|
import { Dexie, PromiseExtended } from 'dexie';
|
|
3
|
-
|
|
5
|
+
// if (typeof window !== 'undefined') {
|
|
6
|
+
// import('dexie-observable');
|
|
7
|
+
// }
|
|
4
8
|
import 'dexie-syncable';
|
|
9
|
+
import 'dexie-observable';
|
|
5
10
|
|
|
6
11
|
import { syncProtocol } from './syncProtocol'
|
|
7
|
-
|
|
12
|
+
import { SERVER_URL } from '../config'
|
|
8
13
|
syncProtocol()
|
|
9
14
|
|
|
10
15
|
|
|
11
|
-
const DexieSyncStatus = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
16
|
+
// const DexieSyncStatus = {
|
|
17
|
+
// "-1": "ERROR",
|
|
18
|
+
// "0": "OFFLINE",
|
|
19
|
+
// "1": "CONNECTING",
|
|
20
|
+
// "2": "ONLINE",
|
|
21
|
+
// "3": "SYNCING",
|
|
22
|
+
// "4": "ERROR_WILL_RETRY"
|
|
23
|
+
// }
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
|
|
@@ -52,7 +58,8 @@ export class BasicSync extends Dexie {
|
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
async connect({ access_token }: { access_token: string }) {
|
|
55
|
-
const WS_URL = "ws://localhost:3003/ws"
|
|
61
|
+
// const WS_URL = "ws://localhost:3003/ws"
|
|
62
|
+
const WS_URL = `${SERVER_URL}/ws`
|
|
56
63
|
|
|
57
64
|
|
|
58
65
|
// Update sync nodes
|