@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/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
|
|
package/src/sync/syncProtocol.js
CHANGED
|
@@ -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,
|