@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/src/schema.ts ADDED
@@ -0,0 +1,159 @@
1
+ // Basic Schema Library
2
+ // utils for validating and interacting with Basic schemas
3
+ import Ajv, { ErrorObject } from 'ajv'
4
+
5
+ const basicJsonSchema = {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "type": "object",
8
+ "properties": {
9
+ "project_id": {
10
+ "type": "string"
11
+ },
12
+ "namespace": {
13
+ "type": "string",
14
+ },
15
+ "version": {
16
+ "type": "integer",
17
+ "minimum": 0
18
+ },
19
+ "tables": {
20
+ "type": "object",
21
+ "patternProperties": {
22
+ "^[a-zA-Z0-9_]+$": {
23
+ "type": "object",
24
+ "properties": {
25
+ "name": {
26
+ "type": "string"
27
+ },
28
+ "type": {
29
+ "type": "string",
30
+ "enum": ["collection"]
31
+ },
32
+ "fields": {
33
+ "type": "object",
34
+ "patternProperties": {
35
+ "^[a-zA-Z0-9_]+$": {
36
+ "type": "object",
37
+ "properties": {
38
+ "type": {
39
+ "type": "string",
40
+ "enum": ["string", "boolean", "number", "json"]
41
+ },
42
+ "indexed": {
43
+ "type": "boolean"
44
+ },
45
+ "required": {
46
+ "type": "boolean"
47
+ }
48
+ },
49
+ "required": ["type"]
50
+ }
51
+ },
52
+ "additionalProperties": true
53
+ }
54
+ },
55
+ "required": ["fields"]
56
+ }
57
+ },
58
+ "additionalProperties": true
59
+ }
60
+ },
61
+ "required": ["project_id", "version", "tables"]
62
+ }
63
+ const ajv = new Ajv()
64
+ const validator = ajv.compile(basicJsonSchema)
65
+
66
+ type Schema = typeof basicJsonSchema
67
+
68
+ function generateEmptySchema() {
69
+
70
+ }
71
+
72
+
73
+ /**
74
+ * Validate a schema
75
+ * only checks if the schema is formatted correctly, not if can be published
76
+ * @param schema - The schema to validate
77
+ * @returns {valid: boolean, errors: any[]} - The validation result
78
+ */
79
+ function validateSchema(schema: Schema) : {valid: boolean, errors: ErrorObject[]} {
80
+ const v = validator(schema)
81
+ return {
82
+ valid: v,
83
+ errors: validator.errors || []
84
+ }
85
+ }
86
+
87
+ // type ErrorObject = {
88
+ // keyword: string;
89
+ // instancePath: string;
90
+ // schemaPath: string;
91
+ // params: Record<string, any>;
92
+ // propertyName?: string;
93
+ // message?: string;
94
+ // schema?: any;
95
+ // parentSchema?: any;
96
+ // data?: any;
97
+ // }
98
+
99
+
100
+ function validateData(schema: any, table: string, data: Record<string, any>, checkRequired: boolean = true) {
101
+ const valid = validateSchema(schema)
102
+ if (!valid.valid) {
103
+ return { valid: false, errors: valid.errors, message: "Schema is invalid" }
104
+ }
105
+
106
+ const tableSchema = schema.tables[table]
107
+
108
+ if (!tableSchema) {
109
+ return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" }
110
+ }
111
+
112
+ for (const [fieldName, fieldValue] of Object.entries(data)) {
113
+ const fieldSchema = tableSchema.fields[fieldName]
114
+
115
+ if (!fieldSchema) {
116
+ return {
117
+ valid: false,
118
+ errors: [{ message: `Field ${fieldName} not found in schema` }],
119
+ message: "Invalid field"
120
+ }
121
+ }
122
+
123
+ const schemaType = fieldSchema.type
124
+ const valueType = typeof fieldValue
125
+
126
+ if (
127
+ (schemaType === 'string' && valueType !== 'string') ||
128
+ (schemaType === 'number' && valueType !== 'number') ||
129
+ (schemaType === 'boolean' && valueType !== 'boolean') ||
130
+ (schemaType === 'json' && valueType !== 'object')
131
+ ) {
132
+ return {
133
+ valid: false,
134
+ errors: [{
135
+ message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
136
+ }],
137
+ message: "invalid type"
138
+ }
139
+ }
140
+ }
141
+
142
+ if (checkRequired) {
143
+ for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
144
+ if ((fieldSchema as { required?: boolean }).required && !data[fieldName]) {
145
+ return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" }
146
+ }
147
+ }
148
+ }
149
+
150
+ return { valid: true, errors: [] }
151
+ }
152
+
153
+
154
+ export {
155
+ validateSchema,
156
+ validateData,
157
+ generateEmptySchema
158
+ }
159
+
package/src/sync/index.ts CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  import { v7 as uuidv7 } from 'uuid';
4
4
  import { Dexie, PromiseExtended } from 'dexie';
5
- // if (typeof window !== 'undefined') {
6
- // import('dexie-observable');
7
- // }
8
5
  import 'dexie-syncable';
9
6
  import 'dexie-observable';
10
7
 
11
8
  import { syncProtocol } from './syncProtocol'
12
9
  import { SERVER_URL, log } from '../config'
10
+
11
+ import { validateSchema, validateData } from '../schema'
13
12
  syncProtocol()
14
13
 
15
14
 
@@ -149,14 +148,28 @@ export class BasicSync extends Dexie {
149
148
 
150
149
  // --- WRITE ---- //
151
150
  add: (data: any) => {
152
- log("Adding data to", name, data)
151
+ // log("Adding data to", name, data)
152
+
153
+ const valid = validateData(this.basic_schema, name, data)
154
+ if (!valid.valid) {
155
+ log('Invalid data', valid)
156
+ return Promise.reject({ ... valid })
157
+ }
158
+
153
159
  return this.table(name).add({
154
160
  id: uuidv7(),
155
161
  ...data
156
162
  })
163
+
157
164
  },
158
165
 
159
166
  put: (data: any) => {
167
+ const valid = validateData(this.basic_schema, name, data)
168
+ if (!valid.valid) {
169
+ log('Invalid data', valid)
170
+ return Promise.reject({ ... valid })
171
+ }
172
+
160
173
  return this.table(name).put({
161
174
  id: uuidv7(),
162
175
  ...data
@@ -164,6 +177,12 @@ export class BasicSync extends Dexie {
164
177
  },
165
178
 
166
179
  update: (id: string, data: any) => {
180
+ const valid = validateData(this.basic_schema, name, data, false)
181
+ if (!valid.valid) {
182
+ log('Invalid data', valid)
183
+ return Promise.reject({ ... valid })
184
+ }
185
+
167
186
  return this.table(name).update(id, data)
168
187
  },
169
188
 
@@ -86,6 +86,7 @@ export const syncProtocol = function () {
86
86
 
87
87
  // If socket is closed (network disconnected), inform framework and make it reconnect
88
88
  ws.onclose = function (event) {
89
+ // console.log('🙅 ws.onclose', event)
89
90
  onError("Socket closed: " + event.reason, RECONNECT_DELAY);
90
91
  };
91
92
 
@@ -124,7 +125,6 @@ export const syncProtocol = function () {
124
125
  syncedRevision: syncedRevision,
125
126
  }),
126
127
  );
127
- } else if (requestFromServer.type == "error") {
128
128
  } else if (requestFromServer.type == "changes") {
129
129
  applyRemoteChanges(
130
130
  requestFromServer.changes,