@basictech/react 0.2.0-beta.7 → 0.2.0-beta.9
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 +10 -10
- package/changelog.md +12 -0
- package/dist/index.d.mts +94 -2
- package/dist/index.d.ts +94 -2
- package/dist/index.js +184 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +73 -44
- package/src/config.ts +2 -64
- package/src/index.ts +7 -1
- package/src/schema.ts +159 -0
- package/src/sync/index.ts +23 -4
- package/src/sync/syncProtocol.js +1 -1
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import "dexie-observable";
|
|
|
12
12
|
import { Dexie } from "dexie";
|
|
13
13
|
|
|
14
14
|
// src/config.ts
|
|
15
|
-
import Ajv from "ajv";
|
|
16
15
|
var SERVER_URL = "https://api.basic.tech";
|
|
17
16
|
var log = (...args) => {
|
|
18
17
|
try {
|
|
@@ -22,65 +21,6 @@ var log = (...args) => {
|
|
|
22
21
|
} catch (e) {
|
|
23
22
|
}
|
|
24
23
|
};
|
|
25
|
-
var basicJsonSchema = {
|
|
26
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
27
|
-
"type": "object",
|
|
28
|
-
"properties": {
|
|
29
|
-
"project_id": {
|
|
30
|
-
"type": "string"
|
|
31
|
-
},
|
|
32
|
-
"namespace": {
|
|
33
|
-
"type": "string"
|
|
34
|
-
},
|
|
35
|
-
"version": {
|
|
36
|
-
"type": "integer",
|
|
37
|
-
"minimum": 0
|
|
38
|
-
},
|
|
39
|
-
"tables": {
|
|
40
|
-
"type": "object",
|
|
41
|
-
"patternProperties": {
|
|
42
|
-
"^[a-zA-Z0-9_]+$": {
|
|
43
|
-
"type": "object",
|
|
44
|
-
"properties": {
|
|
45
|
-
"name": {
|
|
46
|
-
"type": "string"
|
|
47
|
-
},
|
|
48
|
-
"type": {
|
|
49
|
-
"type": "string",
|
|
50
|
-
"enum": ["collection"]
|
|
51
|
-
},
|
|
52
|
-
"fields": {
|
|
53
|
-
"type": "object",
|
|
54
|
-
"patternProperties": {
|
|
55
|
-
"^[a-zA-Z0-9_]+$": {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"properties": {
|
|
58
|
-
"type": {
|
|
59
|
-
"type": "string"
|
|
60
|
-
},
|
|
61
|
-
"primary": {
|
|
62
|
-
"type": "boolean"
|
|
63
|
-
},
|
|
64
|
-
"indexed": {
|
|
65
|
-
"type": "boolean"
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"required": ["type"]
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"additionalProperties": true
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"required": ["fields"]
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
"additionalProperties": true
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"required": ["project_id", "version", "tables"]
|
|
81
|
-
};
|
|
82
|
-
var ajv = new Ajv();
|
|
83
|
-
var validator = ajv.compile(basicJsonSchema);
|
|
84
24
|
|
|
85
25
|
// src/sync/syncProtocol.js
|
|
86
26
|
var syncProtocol = function() {
|
|
@@ -138,7 +78,6 @@ var syncProtocol = function() {
|
|
|
138
78
|
syncedRevision
|
|
139
79
|
})
|
|
140
80
|
);
|
|
141
|
-
} else if (requestFromServer.type == "error") {
|
|
142
81
|
} else if (requestFromServer.type == "changes") {
|
|
143
82
|
applyRemoteChanges(
|
|
144
83
|
requestFromServer.changes,
|
|
@@ -186,6 +125,117 @@ var syncProtocol = function() {
|
|
|
186
125
|
});
|
|
187
126
|
};
|
|
188
127
|
|
|
128
|
+
// src/schema.ts
|
|
129
|
+
import Ajv from "ajv";
|
|
130
|
+
var basicJsonSchema = {
|
|
131
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"project_id": {
|
|
135
|
+
"type": "string"
|
|
136
|
+
},
|
|
137
|
+
"namespace": {
|
|
138
|
+
"type": "string"
|
|
139
|
+
},
|
|
140
|
+
"version": {
|
|
141
|
+
"type": "integer",
|
|
142
|
+
"minimum": 0
|
|
143
|
+
},
|
|
144
|
+
"tables": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"patternProperties": {
|
|
147
|
+
"^[a-zA-Z0-9_]+$": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"name": {
|
|
151
|
+
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
"type": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"enum": ["collection"]
|
|
156
|
+
},
|
|
157
|
+
"fields": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"patternProperties": {
|
|
160
|
+
"^[a-zA-Z0-9_]+$": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"properties": {
|
|
163
|
+
"type": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"enum": ["string", "boolean", "number", "json"]
|
|
166
|
+
},
|
|
167
|
+
"indexed": {
|
|
168
|
+
"type": "boolean"
|
|
169
|
+
},
|
|
170
|
+
"required": {
|
|
171
|
+
"type": "boolean"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"required": ["type"]
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"additionalProperties": true
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"required": ["fields"]
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"additionalProperties": true
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"required": ["project_id", "version", "tables"]
|
|
187
|
+
};
|
|
188
|
+
var ajv = new Ajv();
|
|
189
|
+
var validator = ajv.compile(basicJsonSchema);
|
|
190
|
+
function generateEmptySchema() {
|
|
191
|
+
}
|
|
192
|
+
function validateSchema(schema) {
|
|
193
|
+
const v = validator(schema);
|
|
194
|
+
return {
|
|
195
|
+
valid: v,
|
|
196
|
+
errors: validator.errors || []
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function validateData(schema, table, data, checkRequired = true) {
|
|
200
|
+
const valid = validateSchema(schema);
|
|
201
|
+
if (!valid.valid) {
|
|
202
|
+
return { valid: false, errors: valid.errors, message: "Schema is invalid" };
|
|
203
|
+
}
|
|
204
|
+
const tableSchema = schema.tables[table];
|
|
205
|
+
if (!tableSchema) {
|
|
206
|
+
return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" };
|
|
207
|
+
}
|
|
208
|
+
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
209
|
+
const fieldSchema = tableSchema.fields[fieldName];
|
|
210
|
+
if (!fieldSchema) {
|
|
211
|
+
return {
|
|
212
|
+
valid: false,
|
|
213
|
+
errors: [{ message: `Field ${fieldName} not found in schema` }],
|
|
214
|
+
message: "Invalid field"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const schemaType = fieldSchema.type;
|
|
218
|
+
const valueType = typeof fieldValue;
|
|
219
|
+
if (schemaType === "string" && valueType !== "string" || schemaType === "number" && valueType !== "number" || schemaType === "boolean" && valueType !== "boolean" || schemaType === "json" && valueType !== "object") {
|
|
220
|
+
return {
|
|
221
|
+
valid: false,
|
|
222
|
+
errors: [{
|
|
223
|
+
message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
|
|
224
|
+
}],
|
|
225
|
+
message: "invalid type"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (checkRequired) {
|
|
230
|
+
for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
|
|
231
|
+
if (fieldSchema.required && !data[fieldName]) {
|
|
232
|
+
return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { valid: true, errors: [] };
|
|
237
|
+
}
|
|
238
|
+
|
|
189
239
|
// src/sync/index.ts
|
|
190
240
|
syncProtocol();
|
|
191
241
|
var BasicSync = class extends Dexie2 {
|
|
@@ -253,19 +303,33 @@ var BasicSync = class extends Dexie2 {
|
|
|
253
303
|
ref: this.table(name),
|
|
254
304
|
// --- WRITE ---- //
|
|
255
305
|
add: (data) => {
|
|
256
|
-
|
|
306
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
307
|
+
if (!valid.valid) {
|
|
308
|
+
log("Invalid data", valid);
|
|
309
|
+
return Promise.reject({ ...valid });
|
|
310
|
+
}
|
|
257
311
|
return this.table(name).add({
|
|
258
312
|
id: uuidv7(),
|
|
259
313
|
...data
|
|
260
314
|
});
|
|
261
315
|
},
|
|
262
316
|
put: (data) => {
|
|
317
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
318
|
+
if (!valid.valid) {
|
|
319
|
+
log("Invalid data", valid);
|
|
320
|
+
return Promise.reject({ ...valid });
|
|
321
|
+
}
|
|
263
322
|
return this.table(name).put({
|
|
264
323
|
id: uuidv7(),
|
|
265
324
|
...data
|
|
266
325
|
});
|
|
267
326
|
},
|
|
268
327
|
update: (id, data) => {
|
|
328
|
+
const valid = validateData(this.basic_schema, name, data, false);
|
|
329
|
+
if (!valid.valid) {
|
|
330
|
+
log("Invalid data", valid);
|
|
331
|
+
return Promise.reject({ ...valid });
|
|
332
|
+
}
|
|
269
333
|
return this.table(name).update(id, data);
|
|
270
334
|
},
|
|
271
335
|
delete: (id) => {
|
|
@@ -336,7 +400,7 @@ async function deleteRecord({ projectId, accountId, tableName, id, token }) {
|
|
|
336
400
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
337
401
|
var BasicContext = createContext({
|
|
338
402
|
unicorn: "\u{1F984}",
|
|
339
|
-
|
|
403
|
+
isAuthReady: false,
|
|
340
404
|
isSignedIn: false,
|
|
341
405
|
user: null,
|
|
342
406
|
signout: () => {
|
|
@@ -347,7 +411,7 @@ var BasicContext = createContext({
|
|
|
347
411
|
}),
|
|
348
412
|
getSignInLink: () => "",
|
|
349
413
|
db: {},
|
|
350
|
-
dbStatus: "
|
|
414
|
+
dbStatus: "LOADING" /* LOADING */
|
|
351
415
|
});
|
|
352
416
|
function getSyncStatus(statusCode) {
|
|
353
417
|
switch (statusCode) {
|
|
@@ -368,21 +432,21 @@ function getSyncStatus(statusCode) {
|
|
|
368
432
|
}
|
|
369
433
|
}
|
|
370
434
|
function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
371
|
-
const [
|
|
435
|
+
const [isAuthReady, setIsAuthReady] = useState(false);
|
|
372
436
|
const [isSignedIn, setIsSignedIn] = useState(false);
|
|
373
437
|
const [token, setToken] = useState(null);
|
|
374
|
-
const [authCode, setAuthCode] = useState(null);
|
|
375
438
|
const [user, setUser] = useState({});
|
|
376
439
|
const [dbStatus, setDbStatus] = useState("LOADING" /* LOADING */);
|
|
377
|
-
const syncRef = useRef(null);
|
|
378
440
|
const [error, setError] = useState(null);
|
|
441
|
+
const syncRef = useRef(null);
|
|
379
442
|
useEffect(() => {
|
|
380
443
|
function initDb() {
|
|
381
|
-
|
|
382
|
-
|
|
444
|
+
const valid = validateSchema(schema);
|
|
445
|
+
if (!valid.valid) {
|
|
446
|
+
log("Basic Schema is invalid!", valid.errors);
|
|
383
447
|
console.group("Schema Errors");
|
|
384
448
|
let errorMessage = "";
|
|
385
|
-
|
|
449
|
+
valid.errors.forEach((error2, index) => {
|
|
386
450
|
log(`${index + 1}:`, error2.message, ` - at ${error2.instancePath}`);
|
|
387
451
|
errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
|
|
388
452
|
`;
|
|
@@ -396,20 +460,30 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
396
460
|
return null;
|
|
397
461
|
}
|
|
398
462
|
if (!syncRef.current) {
|
|
463
|
+
log("Initializing BasicDB");
|
|
399
464
|
syncRef.current = new BasicSync("basicdb", { schema });
|
|
400
|
-
syncRef.current.handleStatusChange((status, url) => {
|
|
401
|
-
setDbStatus(getSyncStatus(status));
|
|
402
|
-
});
|
|
403
|
-
syncRef.current.syncable.getStatus().then((status) => {
|
|
404
|
-
log("sync status", getSyncStatus(status));
|
|
405
|
-
});
|
|
406
465
|
}
|
|
407
466
|
}
|
|
408
467
|
initDb();
|
|
409
468
|
}, []);
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (!syncRef.current) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
syncRef.current.syncable.on("statusChanged", (status, url) => {
|
|
474
|
+
setDbStatus(getSyncStatus(status));
|
|
475
|
+
});
|
|
476
|
+
syncRef.current.syncable.getStatus().then((status) => {
|
|
477
|
+
setDbStatus(getSyncStatus(status));
|
|
478
|
+
});
|
|
479
|
+
}, [syncRef.current]);
|
|
410
480
|
const connectToDb = async () => {
|
|
411
481
|
const tok = await getToken();
|
|
412
|
-
|
|
482
|
+
if (!tok) {
|
|
483
|
+
log("no token found");
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
log("connecting to db...");
|
|
413
487
|
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
414
488
|
log("error connecting to db", e);
|
|
415
489
|
});
|
|
@@ -421,13 +495,14 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
421
495
|
}, [token]);
|
|
422
496
|
const getSignInLink = () => {
|
|
423
497
|
log("getting sign in link...");
|
|
424
|
-
const randomState = Math.random().toString(36).substring(
|
|
498
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
499
|
+
localStorage.setItem("basic_auth_state", randomState);
|
|
425
500
|
let baseUrl2 = "https://api.basic.tech/auth/authorize";
|
|
426
501
|
baseUrl2 += `?client_id=${project_id}`;
|
|
427
502
|
baseUrl2 += `&redirect_uri=${encodeURIComponent(window.location.href)}`;
|
|
428
503
|
baseUrl2 += `&response_type=code`;
|
|
429
504
|
baseUrl2 += `&scope=openid`;
|
|
430
|
-
baseUrl2 += `&state
|
|
505
|
+
baseUrl2 += `&state=${randomState}`;
|
|
431
506
|
return baseUrl2;
|
|
432
507
|
};
|
|
433
508
|
const signin = () => {
|
|
@@ -440,8 +515,8 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
440
515
|
setUser({});
|
|
441
516
|
setIsSignedIn(false);
|
|
442
517
|
setToken(null);
|
|
443
|
-
setAuthCode(null);
|
|
444
518
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
519
|
+
localStorage.removeItem("basic_auth_state");
|
|
445
520
|
};
|
|
446
521
|
const getToken = async () => {
|
|
447
522
|
log("getting token...");
|
|
@@ -491,17 +566,25 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
491
566
|
useEffect(() => {
|
|
492
567
|
localStorage.setItem("basic_debug", debug ? "true" : "false");
|
|
493
568
|
try {
|
|
494
|
-
let cookie_token = getCookie("basic_token");
|
|
495
|
-
if (cookie_token !== "") {
|
|
496
|
-
setToken(JSON.parse(cookie_token));
|
|
497
|
-
}
|
|
498
569
|
if (window.location.search.includes("code")) {
|
|
499
570
|
let code = window.location?.search?.split("code=")[1].split("&")[0];
|
|
500
|
-
|
|
571
|
+
const state = localStorage.getItem("basic_auth_state");
|
|
572
|
+
if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
|
|
573
|
+
log("error: auth state does not match");
|
|
574
|
+
setIsAuthReady(true);
|
|
575
|
+
localStorage.removeItem("basic_auth_state");
|
|
576
|
+
window.history.pushState({}, document.title, "/");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
localStorage.removeItem("basic_auth_state");
|
|
501
580
|
fetchToken(code);
|
|
502
|
-
window.history.pushState({}, document.title, "/");
|
|
503
581
|
} else {
|
|
504
|
-
|
|
582
|
+
let cookie_token = getCookie("basic_token");
|
|
583
|
+
if (cookie_token !== "") {
|
|
584
|
+
setToken(JSON.parse(cookie_token));
|
|
585
|
+
} else {
|
|
586
|
+
setIsAuthReady(true);
|
|
587
|
+
}
|
|
505
588
|
}
|
|
506
589
|
} catch (e) {
|
|
507
590
|
log("error getting cookie", e);
|
|
@@ -509,6 +592,7 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
509
592
|
}, []);
|
|
510
593
|
useEffect(() => {
|
|
511
594
|
async function fetchUser(acc_token) {
|
|
595
|
+
console.info("fetching user");
|
|
512
596
|
const user2 = await fetch("https://api.basic.tech/auth/userInfo", {
|
|
513
597
|
method: "GET",
|
|
514
598
|
headers: {
|
|
@@ -520,14 +604,18 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
520
604
|
return;
|
|
521
605
|
} else {
|
|
522
606
|
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
607
|
+
if (window.location.search.includes("code")) {
|
|
608
|
+
window.history.pushState({}, document.title, "/");
|
|
609
|
+
}
|
|
523
610
|
setUser(user2);
|
|
524
611
|
setIsSignedIn(true);
|
|
525
|
-
|
|
612
|
+
setIsAuthReady(true);
|
|
526
613
|
}
|
|
527
614
|
}
|
|
528
615
|
async function checkToken() {
|
|
529
616
|
if (!token) {
|
|
530
617
|
log("error: no user token found");
|
|
618
|
+
setIsAuthReady(true);
|
|
531
619
|
return;
|
|
532
620
|
}
|
|
533
621
|
const decoded = jwtDecode(token?.access_token);
|
|
@@ -542,7 +630,6 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
542
630
|
}
|
|
543
631
|
if (token) {
|
|
544
632
|
checkToken();
|
|
545
|
-
setIsLoaded(true);
|
|
546
633
|
}
|
|
547
634
|
}, [token]);
|
|
548
635
|
const db_ = (tableName) => {
|
|
@@ -576,7 +663,7 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
576
663
|
};
|
|
577
664
|
return /* @__PURE__ */ jsxs(BasicContext.Provider, { value: {
|
|
578
665
|
unicorn: "\u{1F984}",
|
|
579
|
-
|
|
666
|
+
isAuthReady,
|
|
580
667
|
isSignedIn,
|
|
581
668
|
user,
|
|
582
669
|
signout,
|
|
@@ -627,8 +714,14 @@ function useQuery(queryable) {
|
|
|
627
714
|
return queryable;
|
|
628
715
|
}, [queryable], []);
|
|
629
716
|
}
|
|
717
|
+
var sc = {
|
|
718
|
+
validateSchema,
|
|
719
|
+
validateData,
|
|
720
|
+
generateEmptySchema
|
|
721
|
+
};
|
|
630
722
|
export {
|
|
631
723
|
BasicProvider,
|
|
724
|
+
sc,
|
|
632
725
|
useBasic,
|
|
633
726
|
useQuery
|
|
634
727
|
};
|