@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/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
- log("Adding data to", name, data);
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
- isLoaded: false,
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: "OFFLINE" /* OFFLINE */
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 [isLoaded, setIsLoaded] = useState(false);
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
- if (!validator(schema)) {
382
- log("Basic Schema is invalid!", validator.errors);
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
- validator.errors.forEach((error2, index) => {
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
- log("connecting to db...", tok.substring(0, 10));
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(7);
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=1234zyx`;
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
- setAuthCode(code);
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
- setIsLoaded(true);
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
- setIsLoaded(true);
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
- isLoaded,
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
  };