@rpcbase/client 0.60.0 → 0.62.0
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/package.json +9 -6
- package/rts/boot.js +0 -2
- package/rts/getUseDocument.js +23 -0
- package/rts/getUseQuery.js +10 -4
- package/rts/index.js +3 -0
- package/rts/store/debug.js +1 -1
- package/rts/store/get_collection.js +1 -1
- package/rts/store/index.js +23 -100
- package/rts/store/satisfies_projection.js +32 -0
- package/rts/store/satisfies_projection.test.js +42 -0
package/package.json
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpcbase/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.0",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"test": "
|
|
5
|
+
"test": "jest"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"axios": "1.4.0",
|
|
9
|
-
"i18next": "23.
|
|
9
|
+
"i18next": "23.4.1",
|
|
10
10
|
"i18next-chained-backend": "4.4.0",
|
|
11
11
|
"i18next-resources-to-backend": "1.1.4",
|
|
12
12
|
"lodash": "4.17.21",
|
|
13
|
+
"posthog-js": "1.75.3",
|
|
13
14
|
"pouchdb-adapter-indexeddb": "8.0.1",
|
|
14
15
|
"pouchdb-core": "8.0.1",
|
|
15
16
|
"pouchdb-find": "8.0.1",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
17
|
+
"react-i18next": "13.0.3",
|
|
18
|
+
"socket.io-client": "4.7.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"jest": "29.6.2"
|
|
19
22
|
}
|
|
20
23
|
}
|
package/rts/boot.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import getUseQuery from "./getUseQuery"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const getUseDocument = (register_query) => (...args) => {
|
|
6
|
+
|
|
7
|
+
const useQuery = getUseQuery(register_query)
|
|
8
|
+
|
|
9
|
+
const res = useQuery(...args)
|
|
10
|
+
|
|
11
|
+
let data
|
|
12
|
+
// cache will always return [] when there are no matching documents, but we want null instead
|
|
13
|
+
if (Array.isArray(res.data) && res.data.length === 0 && res.source === "cache") {
|
|
14
|
+
data = null
|
|
15
|
+
} else if (Array.isArray(res.data) && res.data.length > 0) {
|
|
16
|
+
// TODO: should we throw if res.data.length > 1 ? ie: there are more than one matching document
|
|
17
|
+
data = res.data[0]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {...res, data}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default getUseDocument
|
package/rts/getUseQuery.js
CHANGED
|
@@ -11,7 +11,8 @@ import get_uid from "../auth/get_uid"
|
|
|
11
11
|
import cacheStorage from "./cacheStorage"
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
const log = debug("rb:useQuery")
|
|
14
|
+
const log = debug("rb:rts:useQuery")
|
|
15
|
+
|
|
15
16
|
|
|
16
17
|
const getUseQuery = (register_query) => (
|
|
17
18
|
model_name,
|
|
@@ -44,10 +45,8 @@ const getUseQuery = (register_query) => (
|
|
|
44
45
|
sort = {},
|
|
45
46
|
} = options
|
|
46
47
|
|
|
47
|
-
|
|
48
48
|
const storageKey = `${uid}.${key}.${model_name}.${JSON.stringify(query)}.${JSON.stringify(projection)}.${JSON.stringify(sort)}`
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
useEffect(() => {
|
|
52
51
|
if (options.debug) {
|
|
53
52
|
console.log("use query", model_name, query, options)
|
|
@@ -57,9 +56,10 @@ const getUseQuery = (register_query) => (
|
|
|
57
56
|
useEffect(() => {
|
|
58
57
|
const load = async() => {
|
|
59
58
|
const val = await cacheStorage.get(storageKey)
|
|
60
|
-
//
|
|
59
|
+
// TODO: rm this
|
|
61
60
|
// always initially apply when first load here
|
|
62
61
|
if (val) {
|
|
62
|
+
console.log("Will set val from cache storage")
|
|
63
63
|
setData(val)
|
|
64
64
|
setLoading(false)
|
|
65
65
|
}
|
|
@@ -94,6 +94,7 @@ const getUseQuery = (register_query) => (
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const start = Date.now()
|
|
97
|
+
log("will register query", model_name, query)
|
|
97
98
|
const unsubscribe = register_query(model_name, query, {...options, key: queryKey}, (err, queryResult, context) => {
|
|
98
99
|
log("callback answer with context", context, queryResult?.length)
|
|
99
100
|
|
|
@@ -170,6 +171,11 @@ const getUseQuery = (register_query) => (
|
|
|
170
171
|
|
|
171
172
|
const result = useMemo(() => ({data, source, error, loading}), [data, source, error, loading])
|
|
172
173
|
|
|
174
|
+
// TODO: investigate this one
|
|
175
|
+
// if (Array.isArray(result.data) && !result.source) {
|
|
176
|
+
// console.warn("RESULT HAS NO SOURCE", {data, error, loading, source})
|
|
177
|
+
// }
|
|
178
|
+
|
|
173
179
|
return result
|
|
174
180
|
}
|
|
175
181
|
|
package/rts/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import BASE_URL from "../base_url"
|
|
|
9
9
|
import {get_tenant_id} from "../auth"
|
|
10
10
|
|
|
11
11
|
import store from "./store"
|
|
12
|
+
import getUseDocument from "./getUseDocument"
|
|
12
13
|
import getUseQuery from "./getUseQuery"
|
|
13
14
|
|
|
14
15
|
const log = debug("rb:socket")
|
|
@@ -219,3 +220,5 @@ export const register_query = (model_name, query, _options, _callback) => {
|
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
export const useQuery = getUseQuery(register_query)
|
|
223
|
+
|
|
224
|
+
export const useDocument = getUseDocument(register_query)
|
package/rts/store/debug.js
CHANGED
package/rts/store/index.js
CHANGED
|
@@ -5,36 +5,15 @@ import "./debug"
|
|
|
5
5
|
|
|
6
6
|
import get_collection from "./get_collection"
|
|
7
7
|
import update_docs from "./update_docs"
|
|
8
|
+
import satisfies_projection from "./satisfies_projection"
|
|
9
|
+
|
|
10
|
+
const log = debug("rb:rts:store")
|
|
8
11
|
|
|
9
|
-
const log = debug("rb:store")
|
|
10
12
|
|
|
11
|
-
// import updateDocument from "./updateDocument"
|
|
12
|
-
// import {DATABASE_NAME} from "env"
|
|
13
|
-
// let prefix = "rb/"
|
|
14
|
-
// if (DATABASE_NAME) prefix += `${DATABASE_NAME}/`
|
|
15
|
-
//
|
|
16
|
-
// PouchDB.prefix = prefix
|
|
17
|
-
//
|
|
18
|
-
// PouchDB.plugin(IndexedDBAdapter)
|
|
19
|
-
// PouchDB.plugin(FindPlugin)
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// let db = new PouchDB(`db/items`, { adapter: "indexeddb" });
|
|
23
|
-
//
|
|
24
|
-
// // Create a new document
|
|
25
|
-
// let doc = {
|
|
26
|
-
// _id: "001",
|
|
27
|
-
// message: "Hello, World!"
|
|
28
|
-
// }
|
|
29
|
-
// //
|
|
30
|
-
//
|
|
31
|
-
// const run = async() => {
|
|
32
13
|
// // https://github.com/pouchdb/pouchdb/tree/master/packages/node_modules/pouchdb-find#dbcreateindexindex--callback
|
|
33
14
|
// const res = await db.createIndex({
|
|
34
15
|
// index: {fields: ["name"]}
|
|
35
16
|
// })
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
17
|
// // Listen for changes on the database
|
|
39
18
|
// const fn = db.changes({
|
|
40
19
|
// since: "now",
|
|
@@ -53,78 +32,18 @@ const log = debug("rb:store")
|
|
|
53
32
|
// console.log("GOT FN", fn)
|
|
54
33
|
//
|
|
55
34
|
// console.log("got res", res)
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// selector: {name: "Mario"},
|
|
59
|
-
// sort: ["name"]
|
|
60
|
-
// })
|
|
61
|
-
//
|
|
62
|
-
// console.log("GT doc", doc)
|
|
63
|
-
//
|
|
64
|
-
// const {docs} = await db.find({selector: {}})
|
|
65
|
-
//
|
|
66
|
-
// console.log("got all docs", docs)
|
|
67
|
-
//
|
|
68
|
-
// db.put({
|
|
69
|
-
// _id: "001-" + Date.now(),
|
|
70
|
-
// message: "Hello, World!"
|
|
71
|
-
// })
|
|
72
|
-
// }
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// run()
|
|
76
|
-
|
|
77
|
-
// // Insert the document into the database
|
|
78
|
-
// db.put(doc, function(err, response) {
|
|
79
|
-
// if (err) {
|
|
80
|
-
// return console.log(err);
|
|
81
|
-
// } else {
|
|
82
|
-
// console.log("Document created successfully");
|
|
83
|
-
// }
|
|
35
|
+
// _cols_store[col_name].getIndexes().then(function (result) {
|
|
36
|
+
// console.log("got indexes", result)
|
|
84
37
|
// })
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// // Insert the document into the database
|
|
88
|
-
// db.put({
|
|
89
|
-
// _id: "hello world",
|
|
90
|
-
// fieldVal: 12,
|
|
91
|
-
// }, {force: true}, async function(err, response) {
|
|
92
|
-
// if (err) {
|
|
93
|
-
// console.log("errrro", err);
|
|
94
|
-
// console.log("Stt", JSON.stringify(err))
|
|
95
|
-
// return
|
|
96
|
-
// } else {
|
|
97
|
-
// console.log("Document created successfully", response);
|
|
98
|
-
// }
|
|
99
|
-
//
|
|
100
|
-
// console.log("will try to find:")
|
|
101
|
-
// const doc = await db.find({
|
|
102
|
-
// selector: {
|
|
103
|
-
// fieldVal: 10
|
|
104
|
-
// }
|
|
105
|
-
// })
|
|
106
|
-
// console.log("LEDOC", doc)
|
|
107
|
-
//
|
|
108
|
-
// });
|
|
109
|
-
//
|
|
110
|
-
// setInterval(() => {
|
|
111
|
-
// // Fetch the document
|
|
112
|
-
// db.get("001", function(err, doc) {
|
|
113
|
-
// if (err) {
|
|
114
|
-
// return console.log(err);
|
|
115
|
-
// } else {
|
|
116
|
-
// console.log(doc);
|
|
117
|
-
// }
|
|
118
|
-
// });
|
|
119
|
-
// }, 2000)
|
|
120
|
-
//
|
|
121
38
|
|
|
122
|
-
// TODO: should the store be in a worker or the main thread ?
|
|
123
39
|
|
|
40
|
+
// TODO: implement store in a shared worker
|
|
41
|
+
// TODO: should we filter all docs by projection ? or just the ones where the projection isn't complete ?
|
|
124
42
|
const run_query = async({model_name, query, query_key, options}, callback) => {
|
|
125
|
-
// console.log("ALAAARM")
|
|
126
43
|
// console.log("run_query", {model_name, query, query_key, options})
|
|
127
44
|
// console.time("store run_query")
|
|
45
|
+
|
|
46
|
+
// TODO: we should prefix model_name with tenant_prefix + env_id
|
|
128
47
|
const col = get_collection(model_name)
|
|
129
48
|
|
|
130
49
|
// https://github.com/pouchdb/pouchdb/tree/master/packages/node_modules/pouchdb-find#dbcreateindexindex--callback
|
|
@@ -136,18 +55,22 @@ const run_query = async({model_name, query, query_key, options}, callback) => {
|
|
|
136
55
|
// fields: [""]
|
|
137
56
|
})
|
|
138
57
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
58
|
+
let mapped_docs = docs
|
|
59
|
+
.map(({_rev, ...doc}) => {
|
|
60
|
+
// TODO: handle projections here
|
|
61
|
+
const remapped_doc = Object.entries(doc).reduce((new_doc, [key, value]) => {
|
|
62
|
+
let new_key = key.startsWith('$_') ? key.replace(/^\$_/, "") : key
|
|
63
|
+
new_doc[new_key] = value
|
|
64
|
+
return new_doc
|
|
65
|
+
}, {})
|
|
66
|
+
|
|
67
|
+
return remapped_doc
|
|
68
|
+
})
|
|
146
69
|
|
|
147
|
-
return remapped_doc
|
|
148
|
-
})
|
|
149
70
|
|
|
150
|
-
|
|
71
|
+
if (options.projection) {
|
|
72
|
+
mapped_docs = mapped_docs.filter((doc) => satisfies_projection(doc, options.projection))
|
|
73
|
+
}
|
|
151
74
|
|
|
152
75
|
callback(null, mapped_docs, {source: "cache"})
|
|
153
76
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
const get_keys = (obj, parent_key = '') => {
|
|
4
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
5
|
+
const new_key = parent_key ? `${parent_key}.${key}` : key
|
|
6
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
7
|
+
acc.push(...get_keys(obj[key], new_key))
|
|
8
|
+
} else {
|
|
9
|
+
acc.push(new_key)
|
|
10
|
+
}
|
|
11
|
+
return acc
|
|
12
|
+
}, [])
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const set_contains_set = (set_a, set_b) => {
|
|
16
|
+
if (set_b.size > set_a.size) return false
|
|
17
|
+
for (const b of set_b) if (!set_a.has(b)) return false
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const satisfies_projection = (doc, projection) => {
|
|
22
|
+
const doc_keys = new Set(get_keys(doc))
|
|
23
|
+
const projection_keys = new Set(Object.keys(projection).filter(k => projection[k] === 1))
|
|
24
|
+
|
|
25
|
+
if (!projection_keys.has('_id')) {
|
|
26
|
+
doc_keys.delete('_id')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return set_contains_set(doc_keys, projection_keys)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = satisfies_projection
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
const satisfies_projection = require("./satisfies_projection")
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let doc = {
|
|
6
|
+
_id: '1',
|
|
7
|
+
field1: 'value1',
|
|
8
|
+
field2: {
|
|
9
|
+
subField1: 'value2',
|
|
10
|
+
subField2: 'value3'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let projection = {
|
|
15
|
+
field1: 1,
|
|
16
|
+
'field2.subField1': 1,
|
|
17
|
+
'field2.subField2': 1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
test("simple", () => {
|
|
23
|
+
expect(satisfies_projection(doc, projection)).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("missing field", () => {
|
|
27
|
+
expect(satisfies_projection(doc, {
|
|
28
|
+
missing_field: 1,
|
|
29
|
+
...projection
|
|
30
|
+
})).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("missing nested field", () => {
|
|
34
|
+
expect(satisfies_projection(doc, {
|
|
35
|
+
"missing_field.missing_nested": 1,
|
|
36
|
+
...projection
|
|
37
|
+
})).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("empty", () => {
|
|
41
|
+
expect(satisfies_projection(doc, {field1: 1})).toBe(true)
|
|
42
|
+
})
|