@nuxodin/item 0.6.4
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/LICENSE +21 -0
- package/README.md +176 -0
- package/item.d.ts +116 -0
- package/item.js +361 -0
- package/package.json +24 -0
- package/src/AsyncDataPoint.d.ts +52 -0
- package/src/AsyncDataPoint.js +280 -0
- package/src/Collection.experiment.js +63 -0
- package/src/Emitter.js +103 -0
- package/src/proxy.js +77 -0
- package/src/signal.js +95 -0
- package/tools/AsyncChild.js +44 -0
- package/tools/access.js +36 -0
- package/tools/beta/collectChanges.js +88 -0
- package/tools/beta/syncWith.js +22 -0
- package/tools/condition.js +65 -0
- package/tools/csvDataItem.js +139 -0
- package/tools/db/connect.js +27 -0
- package/tools/db/dialect/mysql.js +12 -0
- package/tools/db/dialect/pg.js +15 -0
- package/tools/db/dialect/sqlite.js +12 -0
- package/tools/db/shared.js +7 -0
- package/tools/db/sql.d.ts +24 -0
- package/tools/db/sql.js +83 -0
- package/tools/httpRouter.js +66 -0
- package/tools/jsonDataItem.js +29 -0
- package/tools/schema/build/input.js +120 -0
- package/tools/schema/db/indexeddb/from-db.js +29 -0
- package/tools/schema/db/indexeddb/to-db.js +115 -0
- package/tools/schema/db/mysql/from-db.js +16 -0
- package/tools/schema/db/mysql/from-field.js +101 -0
- package/tools/schema/db/mysql/to-db.js +275 -0
- package/tools/schema/db/mysql/to-field.js +60 -0
- package/tools/schema/db/pg/from-db.js +48 -0
- package/tools/schema/db/pg/from-field.js +71 -0
- package/tools/schema/db/pg/to-db.js +223 -0
- package/tools/schema/db/pg/to-field.js +52 -0
- package/tools/schema/db/shared/from-db.js +29 -0
- package/tools/schema/db/shared/sql.js +26 -0
- package/tools/schema/db/sqlite/from-db.js +39 -0
- package/tools/schema/db/sqlite/from-field.js +49 -0
- package/tools/schema/db/sqlite/to-db.js +127 -0
- package/tools/schema/db/sqlite/to-field.js +34 -0
- package/tools/schema/diff.js +90 -0
- package/tools/schema/dom/toInput.js +6 -0
- package/tools/schema/render/dom.js +26 -0
- package/tools/schema/render/html.js +40 -0
- package/tools/schema/utils.js +131 -0
- package/tools/schema/validator.js +92 -0
- package/tools/view/dom.js +96 -0
- package/tools/view/uhtml.js +40 -0
- package/tools/wsDenoRouter.js +108 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// tools/views/uhtml.js
|
|
2
|
+
import { html } from "https://esm.sh/uhtml@4";
|
|
3
|
+
import { field } from './dom.js';
|
|
4
|
+
|
|
5
|
+
const props = (item) => Object.entries(item.schema?.properties ?? {});
|
|
6
|
+
|
|
7
|
+
export function detail(item, { editable = false } = {}) {
|
|
8
|
+
item.read();
|
|
9
|
+
return html`<dl>
|
|
10
|
+
${props(item).map(([name, s]) => html`
|
|
11
|
+
<dt>${s.title ?? name}
|
|
12
|
+
<dd>${field(item.item(name), editable)}
|
|
13
|
+
`)}
|
|
14
|
+
</dl>`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cachedTableFields = new WeakMap();
|
|
18
|
+
|
|
19
|
+
export function table(store, { editable = false, cols } = {}) {
|
|
20
|
+
store.read();
|
|
21
|
+
return html`<table>
|
|
22
|
+
<thead><tr>
|
|
23
|
+
${cols.map(name => html`<th>${store.schema?.additionalProperties?.properties?.[name]?.title ?? name}`)}
|
|
24
|
+
${editable ? html`<th><button onclick="${() => store.add({})}">+</button>` : ''}
|
|
25
|
+
</tr></thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
${[...store].map(row => {
|
|
28
|
+
row.read();
|
|
29
|
+
row.keys;
|
|
30
|
+
return html`<tr>
|
|
31
|
+
${cols.map(name => {
|
|
32
|
+
const cell = row.item(name);
|
|
33
|
+
return html`<td>${cachedTableFields.get(cell) ?? cachedTableFields.set(cell, field(cell, editable)).get(cell)}`
|
|
34
|
+
})}
|
|
35
|
+
${editable ? html`<td width=2><button u1-confirm onclick="${() => row.remove()}">✖</button>` : ''}
|
|
36
|
+
</tr>`;
|
|
37
|
+
})}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket router for Item structures
|
|
3
|
+
* JSON RPC protocol: get/set/delete/subscribe/unsubscribe
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function createItemWsRouter(rootItem, basePath = '/ws') {
|
|
7
|
+
const prefixParts = basePath.replace(/^\/|\/$/g, '').split('/').filter(Boolean);
|
|
8
|
+
|
|
9
|
+
const actions = {
|
|
10
|
+
async get({ path, msg, subscribe }) {
|
|
11
|
+
const item = rootItem.sub(path);
|
|
12
|
+
await item.io.get();
|
|
13
|
+
const data = item.isObject ? item.keys.map(key => ({ key })) : item.value;
|
|
14
|
+
if (msg.subscribe) subscribe(item);
|
|
15
|
+
return data;
|
|
16
|
+
},
|
|
17
|
+
async set({ path, msg }) {
|
|
18
|
+
await rootItem.sub(path).set(msg.value);
|
|
19
|
+
},
|
|
20
|
+
async delete({ path }) {
|
|
21
|
+
await rootItem.has(path)?.remove();
|
|
22
|
+
},
|
|
23
|
+
async post({ path, msg }) {
|
|
24
|
+
const newItem = await rootItem.sub(path).add(msg.value);
|
|
25
|
+
return { key: newItem.key };
|
|
26
|
+
},
|
|
27
|
+
async subscribe({ path, subscribe }) {
|
|
28
|
+
const item = rootItem.sub(path);
|
|
29
|
+
subscribe(item);
|
|
30
|
+
return await item.read(); // needed?
|
|
31
|
+
},
|
|
32
|
+
async unsubscribe({ path, unsubscribe }) {
|
|
33
|
+
const item = rootItem.sub(path);
|
|
34
|
+
unsubscribe(item);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return async (request) => {
|
|
39
|
+
const segments = new URL(request.url).pathname.split('/').filter(Boolean);
|
|
40
|
+
if (prefixParts.some((p, i) => segments[i] !== p)) return null;
|
|
41
|
+
|
|
42
|
+
// Require WebSocket upgrade
|
|
43
|
+
if (!request.headers.get('upgrade')?.toLowerCase().includes('websocket')) {
|
|
44
|
+
return new Response('Expected WebSocket upgrade', { status: 400 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { socket, response } = Deno.upgradeWebSocket(request);
|
|
48
|
+
const subscriptions = new Map(); // key: path string -> { item, listener }
|
|
49
|
+
|
|
50
|
+
// Helper: send JSON message
|
|
51
|
+
const send = (data) => socket.readyState === WebSocket.OPEN && socket.send(JSON.stringify(data));
|
|
52
|
+
|
|
53
|
+
// Helper: send RPC response
|
|
54
|
+
const respond = (id, status, data, error) => send({ id, type: 'response', status, ...data !== undefined && { data }, ...error && { error } });
|
|
55
|
+
|
|
56
|
+
// todo
|
|
57
|
+
// better subscription management: track which clients are subscribed to which items, and only send updates to those clients
|
|
58
|
+
/*
|
|
59
|
+
a.addEventListener('changeIn', (event) => {
|
|
60
|
+
if (event.add) console.log(event.target, 'added property', event.add); // child-item
|
|
61
|
+
if (event.remove) console.log(event.target, 'removed property', event.remove);
|
|
62
|
+
if (event.value !== undefined) console.log(event.target, 'value changed from', event.oldValue, 'to', event.value );
|
|
63
|
+
});
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
// Subscribe to item changes
|
|
67
|
+
const subscribe = (item) => {
|
|
68
|
+
const key = item.path.join('/');
|
|
69
|
+
if (subscriptions.has(key)) return;
|
|
70
|
+
const listener = (e) => {
|
|
71
|
+
if (!e.options?.local) return;
|
|
72
|
+
if (e.pending || e.error) return;
|
|
73
|
+
const payload = e.toJSON();
|
|
74
|
+
payload.type = 'update';
|
|
75
|
+
send(payload);
|
|
76
|
+
};
|
|
77
|
+
item.addEventListener('changeIn', listener);
|
|
78
|
+
subscriptions.set(key, { item, listener });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const unsubscribe = (item) => {
|
|
82
|
+
const key = item.path.join('/');
|
|
83
|
+
const sub = subscriptions.get(key);
|
|
84
|
+
if (!sub) return;
|
|
85
|
+
sub.item.removeEventListener('changeIn', sub.listener);
|
|
86
|
+
subscriptions.delete(key);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
socket.onmessage = async (ev) => { // Handle incoming messages
|
|
90
|
+
let id;
|
|
91
|
+
try {
|
|
92
|
+
const msg = JSON.parse(ev.data);
|
|
93
|
+
id = msg.id;
|
|
94
|
+
const handler = actions[msg.action];
|
|
95
|
+
if (!handler) return respond(id, 'error', undefined, `Unknown action: ${msg.action}`);
|
|
96
|
+
const data = await handler({ path:msg.path, msg, subscribe, unsubscribe });
|
|
97
|
+
respond(id, 'ok', data);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('[ws-router] error', err);
|
|
100
|
+
respond(id, 'error', undefined, err.message || String(err));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
socket.onclose = () => subscriptions.forEach(({ item }) => unsubscribe(item));
|
|
105
|
+
|
|
106
|
+
return response;
|
|
107
|
+
};
|
|
108
|
+
}
|