@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.js
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
BasicProvider: () => BasicProvider,
|
|
34
|
+
sc: () => sc,
|
|
34
35
|
useBasic: () => useBasic,
|
|
35
36
|
useQuery: () => useQuery
|
|
36
37
|
});
|
|
@@ -50,7 +51,6 @@ var import_dexie_observable = require("dexie-observable");
|
|
|
50
51
|
var import_dexie = require("dexie");
|
|
51
52
|
|
|
52
53
|
// src/config.ts
|
|
53
|
-
var import_ajv = __toESM(require("ajv"));
|
|
54
54
|
var SERVER_URL = "https://api.basic.tech";
|
|
55
55
|
var log = (...args) => {
|
|
56
56
|
try {
|
|
@@ -60,65 +60,6 @@ var log = (...args) => {
|
|
|
60
60
|
} catch (e) {
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
-
var basicJsonSchema = {
|
|
64
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
65
|
-
"type": "object",
|
|
66
|
-
"properties": {
|
|
67
|
-
"project_id": {
|
|
68
|
-
"type": "string"
|
|
69
|
-
},
|
|
70
|
-
"namespace": {
|
|
71
|
-
"type": "string"
|
|
72
|
-
},
|
|
73
|
-
"version": {
|
|
74
|
-
"type": "integer",
|
|
75
|
-
"minimum": 0
|
|
76
|
-
},
|
|
77
|
-
"tables": {
|
|
78
|
-
"type": "object",
|
|
79
|
-
"patternProperties": {
|
|
80
|
-
"^[a-zA-Z0-9_]+$": {
|
|
81
|
-
"type": "object",
|
|
82
|
-
"properties": {
|
|
83
|
-
"name": {
|
|
84
|
-
"type": "string"
|
|
85
|
-
},
|
|
86
|
-
"type": {
|
|
87
|
-
"type": "string",
|
|
88
|
-
"enum": ["collection"]
|
|
89
|
-
},
|
|
90
|
-
"fields": {
|
|
91
|
-
"type": "object",
|
|
92
|
-
"patternProperties": {
|
|
93
|
-
"^[a-zA-Z0-9_]+$": {
|
|
94
|
-
"type": "object",
|
|
95
|
-
"properties": {
|
|
96
|
-
"type": {
|
|
97
|
-
"type": "string"
|
|
98
|
-
},
|
|
99
|
-
"primary": {
|
|
100
|
-
"type": "boolean"
|
|
101
|
-
},
|
|
102
|
-
"indexed": {
|
|
103
|
-
"type": "boolean"
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
"required": ["type"]
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
"additionalProperties": true
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"required": ["fields"]
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
"additionalProperties": true
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
"required": ["project_id", "version", "tables"]
|
|
119
|
-
};
|
|
120
|
-
var ajv = new import_ajv.default();
|
|
121
|
-
var validator = ajv.compile(basicJsonSchema);
|
|
122
63
|
|
|
123
64
|
// src/sync/syncProtocol.js
|
|
124
65
|
var syncProtocol = function() {
|
|
@@ -176,7 +117,6 @@ var syncProtocol = function() {
|
|
|
176
117
|
syncedRevision
|
|
177
118
|
})
|
|
178
119
|
);
|
|
179
|
-
} else if (requestFromServer.type == "error") {
|
|
180
120
|
} else if (requestFromServer.type == "changes") {
|
|
181
121
|
applyRemoteChanges(
|
|
182
122
|
requestFromServer.changes,
|
|
@@ -224,6 +164,117 @@ var syncProtocol = function() {
|
|
|
224
164
|
});
|
|
225
165
|
};
|
|
226
166
|
|
|
167
|
+
// src/schema.ts
|
|
168
|
+
var import_ajv = __toESM(require("ajv"));
|
|
169
|
+
var basicJsonSchema = {
|
|
170
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {
|
|
173
|
+
"project_id": {
|
|
174
|
+
"type": "string"
|
|
175
|
+
},
|
|
176
|
+
"namespace": {
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
"version": {
|
|
180
|
+
"type": "integer",
|
|
181
|
+
"minimum": 0
|
|
182
|
+
},
|
|
183
|
+
"tables": {
|
|
184
|
+
"type": "object",
|
|
185
|
+
"patternProperties": {
|
|
186
|
+
"^[a-zA-Z0-9_]+$": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"properties": {
|
|
189
|
+
"name": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
},
|
|
192
|
+
"type": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"enum": ["collection"]
|
|
195
|
+
},
|
|
196
|
+
"fields": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"patternProperties": {
|
|
199
|
+
"^[a-zA-Z0-9_]+$": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"properties": {
|
|
202
|
+
"type": {
|
|
203
|
+
"type": "string",
|
|
204
|
+
"enum": ["string", "boolean", "number", "json"]
|
|
205
|
+
},
|
|
206
|
+
"indexed": {
|
|
207
|
+
"type": "boolean"
|
|
208
|
+
},
|
|
209
|
+
"required": {
|
|
210
|
+
"type": "boolean"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"required": ["type"]
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"additionalProperties": true
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"required": ["fields"]
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"additionalProperties": true
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"required": ["project_id", "version", "tables"]
|
|
226
|
+
};
|
|
227
|
+
var ajv = new import_ajv.default();
|
|
228
|
+
var validator = ajv.compile(basicJsonSchema);
|
|
229
|
+
function generateEmptySchema() {
|
|
230
|
+
}
|
|
231
|
+
function validateSchema(schema) {
|
|
232
|
+
const v = validator(schema);
|
|
233
|
+
return {
|
|
234
|
+
valid: v,
|
|
235
|
+
errors: validator.errors || []
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function validateData(schema, table, data, checkRequired = true) {
|
|
239
|
+
const valid = validateSchema(schema);
|
|
240
|
+
if (!valid.valid) {
|
|
241
|
+
return { valid: false, errors: valid.errors, message: "Schema is invalid" };
|
|
242
|
+
}
|
|
243
|
+
const tableSchema = schema.tables[table];
|
|
244
|
+
if (!tableSchema) {
|
|
245
|
+
return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" };
|
|
246
|
+
}
|
|
247
|
+
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
248
|
+
const fieldSchema = tableSchema.fields[fieldName];
|
|
249
|
+
if (!fieldSchema) {
|
|
250
|
+
return {
|
|
251
|
+
valid: false,
|
|
252
|
+
errors: [{ message: `Field ${fieldName} not found in schema` }],
|
|
253
|
+
message: "Invalid field"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const schemaType = fieldSchema.type;
|
|
257
|
+
const valueType = typeof fieldValue;
|
|
258
|
+
if (schemaType === "string" && valueType !== "string" || schemaType === "number" && valueType !== "number" || schemaType === "boolean" && valueType !== "boolean" || schemaType === "json" && valueType !== "object") {
|
|
259
|
+
return {
|
|
260
|
+
valid: false,
|
|
261
|
+
errors: [{
|
|
262
|
+
message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
|
|
263
|
+
}],
|
|
264
|
+
message: "invalid type"
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (checkRequired) {
|
|
269
|
+
for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
|
|
270
|
+
if (fieldSchema.required && !data[fieldName]) {
|
|
271
|
+
return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return { valid: true, errors: [] };
|
|
276
|
+
}
|
|
277
|
+
|
|
227
278
|
// src/sync/index.ts
|
|
228
279
|
syncProtocol();
|
|
229
280
|
var BasicSync = class extends import_dexie2.Dexie {
|
|
@@ -291,19 +342,33 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
291
342
|
ref: this.table(name),
|
|
292
343
|
// --- WRITE ---- //
|
|
293
344
|
add: (data) => {
|
|
294
|
-
|
|
345
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
346
|
+
if (!valid.valid) {
|
|
347
|
+
log("Invalid data", valid);
|
|
348
|
+
return Promise.reject({ ...valid });
|
|
349
|
+
}
|
|
295
350
|
return this.table(name).add({
|
|
296
351
|
id: (0, import_uuid.v7)(),
|
|
297
352
|
...data
|
|
298
353
|
});
|
|
299
354
|
},
|
|
300
355
|
put: (data) => {
|
|
356
|
+
const valid = validateData(this.basic_schema, name, data);
|
|
357
|
+
if (!valid.valid) {
|
|
358
|
+
log("Invalid data", valid);
|
|
359
|
+
return Promise.reject({ ...valid });
|
|
360
|
+
}
|
|
301
361
|
return this.table(name).put({
|
|
302
362
|
id: (0, import_uuid.v7)(),
|
|
303
363
|
...data
|
|
304
364
|
});
|
|
305
365
|
},
|
|
306
366
|
update: (id, data) => {
|
|
367
|
+
const valid = validateData(this.basic_schema, name, data, false);
|
|
368
|
+
if (!valid.valid) {
|
|
369
|
+
log("Invalid data", valid);
|
|
370
|
+
return Promise.reject({ ...valid });
|
|
371
|
+
}
|
|
307
372
|
return this.table(name).update(id, data);
|
|
308
373
|
},
|
|
309
374
|
delete: (id) => {
|
|
@@ -374,7 +439,7 @@ async function deleteRecord({ projectId, accountId, tableName, id, token }) {
|
|
|
374
439
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
375
440
|
var BasicContext = (0, import_react.createContext)({
|
|
376
441
|
unicorn: "\u{1F984}",
|
|
377
|
-
|
|
442
|
+
isAuthReady: false,
|
|
378
443
|
isSignedIn: false,
|
|
379
444
|
user: null,
|
|
380
445
|
signout: () => {
|
|
@@ -385,7 +450,7 @@ var BasicContext = (0, import_react.createContext)({
|
|
|
385
450
|
}),
|
|
386
451
|
getSignInLink: () => "",
|
|
387
452
|
db: {},
|
|
388
|
-
dbStatus: "
|
|
453
|
+
dbStatus: "LOADING" /* LOADING */
|
|
389
454
|
});
|
|
390
455
|
function getSyncStatus(statusCode) {
|
|
391
456
|
switch (statusCode) {
|
|
@@ -406,21 +471,21 @@ function getSyncStatus(statusCode) {
|
|
|
406
471
|
}
|
|
407
472
|
}
|
|
408
473
|
function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
409
|
-
const [
|
|
474
|
+
const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
|
|
410
475
|
const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
|
|
411
476
|
const [token, setToken] = (0, import_react.useState)(null);
|
|
412
|
-
const [authCode, setAuthCode] = (0, import_react.useState)(null);
|
|
413
477
|
const [user, setUser] = (0, import_react.useState)({});
|
|
414
478
|
const [dbStatus, setDbStatus] = (0, import_react.useState)("LOADING" /* LOADING */);
|
|
415
|
-
const syncRef = (0, import_react.useRef)(null);
|
|
416
479
|
const [error, setError] = (0, import_react.useState)(null);
|
|
480
|
+
const syncRef = (0, import_react.useRef)(null);
|
|
417
481
|
(0, import_react.useEffect)(() => {
|
|
418
482
|
function initDb() {
|
|
419
|
-
|
|
420
|
-
|
|
483
|
+
const valid = validateSchema(schema);
|
|
484
|
+
if (!valid.valid) {
|
|
485
|
+
log("Basic Schema is invalid!", valid.errors);
|
|
421
486
|
console.group("Schema Errors");
|
|
422
487
|
let errorMessage = "";
|
|
423
|
-
|
|
488
|
+
valid.errors.forEach((error2, index) => {
|
|
424
489
|
log(`${index + 1}:`, error2.message, ` - at ${error2.instancePath}`);
|
|
425
490
|
errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
|
|
426
491
|
`;
|
|
@@ -434,20 +499,30 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
434
499
|
return null;
|
|
435
500
|
}
|
|
436
501
|
if (!syncRef.current) {
|
|
502
|
+
log("Initializing BasicDB");
|
|
437
503
|
syncRef.current = new BasicSync("basicdb", { schema });
|
|
438
|
-
syncRef.current.handleStatusChange((status, url) => {
|
|
439
|
-
setDbStatus(getSyncStatus(status));
|
|
440
|
-
});
|
|
441
|
-
syncRef.current.syncable.getStatus().then((status) => {
|
|
442
|
-
log("sync status", getSyncStatus(status));
|
|
443
|
-
});
|
|
444
504
|
}
|
|
445
505
|
}
|
|
446
506
|
initDb();
|
|
447
507
|
}, []);
|
|
508
|
+
(0, import_react.useEffect)(() => {
|
|
509
|
+
if (!syncRef.current) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
syncRef.current.syncable.on("statusChanged", (status, url) => {
|
|
513
|
+
setDbStatus(getSyncStatus(status));
|
|
514
|
+
});
|
|
515
|
+
syncRef.current.syncable.getStatus().then((status) => {
|
|
516
|
+
setDbStatus(getSyncStatus(status));
|
|
517
|
+
});
|
|
518
|
+
}, [syncRef.current]);
|
|
448
519
|
const connectToDb = async () => {
|
|
449
520
|
const tok = await getToken();
|
|
450
|
-
|
|
521
|
+
if (!tok) {
|
|
522
|
+
log("no token found");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
log("connecting to db...");
|
|
451
526
|
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
452
527
|
log("error connecting to db", e);
|
|
453
528
|
});
|
|
@@ -459,13 +534,14 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
459
534
|
}, [token]);
|
|
460
535
|
const getSignInLink = () => {
|
|
461
536
|
log("getting sign in link...");
|
|
462
|
-
const randomState = Math.random().toString(36).substring(
|
|
537
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
538
|
+
localStorage.setItem("basic_auth_state", randomState);
|
|
463
539
|
let baseUrl2 = "https://api.basic.tech/auth/authorize";
|
|
464
540
|
baseUrl2 += `?client_id=${project_id}`;
|
|
465
541
|
baseUrl2 += `&redirect_uri=${encodeURIComponent(window.location.href)}`;
|
|
466
542
|
baseUrl2 += `&response_type=code`;
|
|
467
543
|
baseUrl2 += `&scope=openid`;
|
|
468
|
-
baseUrl2 += `&state
|
|
544
|
+
baseUrl2 += `&state=${randomState}`;
|
|
469
545
|
return baseUrl2;
|
|
470
546
|
};
|
|
471
547
|
const signin = () => {
|
|
@@ -478,8 +554,8 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
478
554
|
setUser({});
|
|
479
555
|
setIsSignedIn(false);
|
|
480
556
|
setToken(null);
|
|
481
|
-
setAuthCode(null);
|
|
482
557
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
558
|
+
localStorage.removeItem("basic_auth_state");
|
|
483
559
|
};
|
|
484
560
|
const getToken = async () => {
|
|
485
561
|
log("getting token...");
|
|
@@ -529,17 +605,25 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
529
605
|
(0, import_react.useEffect)(() => {
|
|
530
606
|
localStorage.setItem("basic_debug", debug ? "true" : "false");
|
|
531
607
|
try {
|
|
532
|
-
let cookie_token = getCookie("basic_token");
|
|
533
|
-
if (cookie_token !== "") {
|
|
534
|
-
setToken(JSON.parse(cookie_token));
|
|
535
|
-
}
|
|
536
608
|
if (window.location.search.includes("code")) {
|
|
537
609
|
let code = window.location?.search?.split("code=")[1].split("&")[0];
|
|
538
|
-
|
|
610
|
+
const state = localStorage.getItem("basic_auth_state");
|
|
611
|
+
if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
|
|
612
|
+
log("error: auth state does not match");
|
|
613
|
+
setIsAuthReady(true);
|
|
614
|
+
localStorage.removeItem("basic_auth_state");
|
|
615
|
+
window.history.pushState({}, document.title, "/");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
localStorage.removeItem("basic_auth_state");
|
|
539
619
|
fetchToken(code);
|
|
540
|
-
window.history.pushState({}, document.title, "/");
|
|
541
620
|
} else {
|
|
542
|
-
|
|
621
|
+
let cookie_token = getCookie("basic_token");
|
|
622
|
+
if (cookie_token !== "") {
|
|
623
|
+
setToken(JSON.parse(cookie_token));
|
|
624
|
+
} else {
|
|
625
|
+
setIsAuthReady(true);
|
|
626
|
+
}
|
|
543
627
|
}
|
|
544
628
|
} catch (e) {
|
|
545
629
|
log("error getting cookie", e);
|
|
@@ -547,6 +631,7 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
547
631
|
}, []);
|
|
548
632
|
(0, import_react.useEffect)(() => {
|
|
549
633
|
async function fetchUser(acc_token) {
|
|
634
|
+
console.info("fetching user");
|
|
550
635
|
const user2 = await fetch("https://api.basic.tech/auth/userInfo", {
|
|
551
636
|
method: "GET",
|
|
552
637
|
headers: {
|
|
@@ -558,14 +643,18 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
558
643
|
return;
|
|
559
644
|
} else {
|
|
560
645
|
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
646
|
+
if (window.location.search.includes("code")) {
|
|
647
|
+
window.history.pushState({}, document.title, "/");
|
|
648
|
+
}
|
|
561
649
|
setUser(user2);
|
|
562
650
|
setIsSignedIn(true);
|
|
563
|
-
|
|
651
|
+
setIsAuthReady(true);
|
|
564
652
|
}
|
|
565
653
|
}
|
|
566
654
|
async function checkToken() {
|
|
567
655
|
if (!token) {
|
|
568
656
|
log("error: no user token found");
|
|
657
|
+
setIsAuthReady(true);
|
|
569
658
|
return;
|
|
570
659
|
}
|
|
571
660
|
const decoded = (0, import_jwt_decode.jwtDecode)(token?.access_token);
|
|
@@ -580,7 +669,6 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
580
669
|
}
|
|
581
670
|
if (token) {
|
|
582
671
|
checkToken();
|
|
583
|
-
setIsLoaded(true);
|
|
584
672
|
}
|
|
585
673
|
}, [token]);
|
|
586
674
|
const db_ = (tableName) => {
|
|
@@ -614,7 +702,7 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
614
702
|
};
|
|
615
703
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(BasicContext.Provider, { value: {
|
|
616
704
|
unicorn: "\u{1F984}",
|
|
617
|
-
|
|
705
|
+
isAuthReady,
|
|
618
706
|
isSignedIn,
|
|
619
707
|
user,
|
|
620
708
|
signout,
|
|
@@ -665,9 +753,15 @@ function useQuery(queryable) {
|
|
|
665
753
|
return queryable;
|
|
666
754
|
}, [queryable], []);
|
|
667
755
|
}
|
|
756
|
+
var sc = {
|
|
757
|
+
validateSchema,
|
|
758
|
+
validateData,
|
|
759
|
+
generateEmptySchema
|
|
760
|
+
};
|
|
668
761
|
// Annotate the CommonJS export names for ESM import in node:
|
|
669
762
|
0 && (module.exports = {
|
|
670
763
|
BasicProvider,
|
|
764
|
+
sc,
|
|
671
765
|
useBasic,
|
|
672
766
|
useQuery
|
|
673
767
|
});
|