@basictech/react 0.2.0-beta.3 → 0.2.0-beta.5
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 +12 -0
- package/dist/index.js +137 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/readme.md +23 -1
- package/src/AuthContext.tsx +112 -26
- package/src/config.ts +73 -0
- package/src/sync/index.ts +18 -11
package/src/AuthContext.tsx
CHANGED
|
@@ -3,23 +3,53 @@
|
|
|
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
|
+
// }
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
|
|
11
41
|
type BasicSyncType = {
|
|
12
42
|
basic_schema: any;
|
|
13
43
|
connect: (options: { access_token: string }) => void;
|
|
14
44
|
debugeroo: () => void;
|
|
15
45
|
collection: (name: string) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
46
|
+
ref: {
|
|
47
|
+
toArray: () => Promise<any[]>;
|
|
48
|
+
count: () => Promise<number>;
|
|
49
|
+
};
|
|
20
50
|
};
|
|
21
51
|
[key: string]: any; // For other potential methods and properties
|
|
22
|
-
|
|
52
|
+
};
|
|
23
53
|
|
|
24
54
|
|
|
25
55
|
enum DBStatus {
|
|
@@ -56,7 +86,7 @@ export const BasicContext = createContext<{
|
|
|
56
86
|
signin: () => void,
|
|
57
87
|
getToken: () => Promise<string>,
|
|
58
88
|
getSignInLink: () => string,
|
|
59
|
-
db: any,
|
|
89
|
+
db: any,
|
|
60
90
|
dbStatus: DBStatus
|
|
61
91
|
}>({
|
|
62
92
|
unicorn: "🦄",
|
|
@@ -103,6 +133,12 @@ function getSyncStatus(statusCode: number): string {
|
|
|
103
133
|
}
|
|
104
134
|
}
|
|
105
135
|
|
|
136
|
+
type ErrorObject = {
|
|
137
|
+
code: string;
|
|
138
|
+
title: string;
|
|
139
|
+
message: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
106
142
|
export function BasicProvider({ children, project_id, schema }: { children: React.ReactNode, project_id: string, schema: any }) {
|
|
107
143
|
const [isLoaded, setIsLoaded] = useState(false)
|
|
108
144
|
const [isSignedIn, setIsSignedIn] = useState(false)
|
|
@@ -110,30 +146,53 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
110
146
|
const [authCode, setAuthCode] = useState<string | null>(null)
|
|
111
147
|
const [user, setUser] = useState<User>({})
|
|
112
148
|
|
|
113
|
-
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.
|
|
114
|
-
|
|
149
|
+
const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.LOADING)
|
|
115
150
|
|
|
116
151
|
const syncRef = useRef<BasicSync | null>(null);
|
|
117
152
|
|
|
153
|
+
const [error, setError] = useState<ErrorObject | null>(null)
|
|
154
|
+
|
|
118
155
|
useEffect(() => {
|
|
119
|
-
|
|
120
|
-
|
|
156
|
+
function initDb() {
|
|
157
|
+
if (!validator(schema)) {
|
|
158
|
+
console.error('Basic Schema is invalid!', validator.errors)
|
|
159
|
+
console.group('Schema Errors')
|
|
160
|
+
let errorMessage = ''
|
|
161
|
+
validator.errors.forEach((error, index) => {
|
|
162
|
+
console.log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`)
|
|
163
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
|
|
164
|
+
})
|
|
165
|
+
console.groupEnd('Schema Errors')
|
|
166
|
+
setError({
|
|
167
|
+
code: 'schema_invalid',
|
|
168
|
+
title: 'Basic Schema is invalid!',
|
|
169
|
+
message: errorMessage
|
|
170
|
+
})
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
121
173
|
|
|
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
174
|
|
|
128
|
-
syncRef.current
|
|
129
|
-
|
|
130
|
-
})
|
|
175
|
+
if (!syncRef.current) {
|
|
176
|
+
syncRef.current = new BasicSync('basicdb', { schema: schema });
|
|
131
177
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
178
|
+
// console.log('db is open', syncRef.current.isOpen())
|
|
179
|
+
// syncRef.current.open()
|
|
180
|
+
// .then(() => {
|
|
181
|
+
// console.log("is open now:", syncRef.current.isOpen())
|
|
182
|
+
// })
|
|
183
|
+
|
|
184
|
+
syncRef.current.handleStatusChange((status: number, url: string) => {
|
|
185
|
+
setDbStatus(getSyncStatus(status))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
syncRef.current.syncable.getStatus().then((status) => {
|
|
189
|
+
console.log('sync status', getSyncStatus(status))
|
|
190
|
+
})
|
|
191
|
+
}
|
|
135
192
|
|
|
136
193
|
}
|
|
194
|
+
|
|
195
|
+
initDb()
|
|
137
196
|
}, []);
|
|
138
197
|
|
|
139
198
|
|
|
@@ -153,7 +212,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
153
212
|
}
|
|
154
213
|
|
|
155
214
|
useEffect(() => {
|
|
156
|
-
if (token) {
|
|
215
|
+
if (token && syncRef.current) {
|
|
157
216
|
connectToDb()
|
|
158
217
|
}
|
|
159
218
|
}, [token])
|
|
@@ -163,8 +222,7 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
163
222
|
|
|
164
223
|
const randomState = Math.random().toString(36).substring(7);
|
|
165
224
|
|
|
166
|
-
|
|
167
|
-
let baseUrl = "http://localhost:3003/auth/authorize"
|
|
225
|
+
let baseUrl = "https://api.basic.tech/auth/authorize"
|
|
168
226
|
baseUrl += `?client_id=${project_id}`
|
|
169
227
|
baseUrl += `&redirect_uri=${encodeURIComponent(window.location.href)}`
|
|
170
228
|
baseUrl += `&response_type=code`
|
|
@@ -366,11 +424,39 @@ export function BasicProvider({ children, project_id, schema }: { children: Reac
|
|
|
366
424
|
db: syncRef.current,
|
|
367
425
|
dbStatus
|
|
368
426
|
}}>
|
|
427
|
+
{error && <ErrorDisplay error={error} />}
|
|
369
428
|
{syncRef.current ? children : null}
|
|
370
429
|
</BasicContext.Provider>
|
|
371
430
|
)
|
|
372
431
|
}
|
|
373
432
|
|
|
433
|
+
function ErrorDisplay({ error }: { error: ErrorObject }) {
|
|
434
|
+
return <div style={{
|
|
435
|
+
position: 'absolute',
|
|
436
|
+
top: 20,
|
|
437
|
+
left: 20,
|
|
438
|
+
color: 'black',
|
|
439
|
+
backgroundColor: '#f8d7da',
|
|
440
|
+
border: '1px solid #f5c6cb',
|
|
441
|
+
borderRadius: '4px',
|
|
442
|
+
padding: '20px',
|
|
443
|
+
maxWidth: '400px',
|
|
444
|
+
margin: '20px auto',
|
|
445
|
+
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
|
446
|
+
fontFamily: 'monospace',
|
|
447
|
+
}}>
|
|
448
|
+
<h3 style={{fontSize: '0.8rem', opacity: 0.8}}>code: {error.code}</h3>
|
|
449
|
+
<h1 style={{fontSize: '1.2rem', lineHeight: '1.5'}}>{error.title}</h1>
|
|
450
|
+
<p>{error.message}</p>
|
|
451
|
+
</div>
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/*
|
|
455
|
+
possible errors:
|
|
456
|
+
- projectid missing / invalid
|
|
457
|
+
- schema missing / invalid
|
|
458
|
+
*/
|
|
459
|
+
|
|
374
460
|
export function useBasic() {
|
|
375
461
|
return useContext(BasicContext);
|
|
376
462
|
}
|
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
|