@hashsome/runtime 0.1.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.
Files changed (69) hide show
  1. package/LICENSE +674 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/app/entry.client.d.ts +1 -0
  4. package/dist/app/entry.client.js +7 -0
  5. package/dist/app/entry.client.js.map +1 -0
  6. package/dist/app/entry.server.d.ts +2 -0
  7. package/dist/app/entry.server.js +20 -0
  8. package/dist/app/entry.server.js.map +1 -0
  9. package/dist/app/index.d.ts +1 -0
  10. package/dist/app/index.js +1 -0
  11. package/dist/app/index.js.map +1 -0
  12. package/dist/app/root.d.ts +18 -0
  13. package/dist/app/root.js +36 -0
  14. package/dist/app/root.js.map +1 -0
  15. package/dist/bin/hashsome.d.ts +2 -0
  16. package/dist/bin/hashsome.js +17 -0
  17. package/dist/bin/hashsome.js.map +1 -0
  18. package/dist/src/cli/build.d.ts +4 -0
  19. package/dist/src/cli/build.js +18 -0
  20. package/dist/src/cli/build.js.map +1 -0
  21. package/dist/src/cli/dev.d.ts +1 -0
  22. package/dist/src/cli/dev.js +19 -0
  23. package/dist/src/cli/dev.js.map +1 -0
  24. package/dist/src/cli/package.d.ts +13 -0
  25. package/dist/src/cli/package.js +115 -0
  26. package/dist/src/cli/package.js.map +1 -0
  27. package/dist/src/cli/start.d.ts +1 -0
  28. package/dist/src/cli/start.js +12 -0
  29. package/dist/src/cli/start.js.map +1 -0
  30. package/dist/src/config.d.ts +36 -0
  31. package/dist/src/config.js +54 -0
  32. package/dist/src/config.js.map +1 -0
  33. package/dist/src/index.d.ts +1 -0
  34. package/dist/src/index.js +8 -0
  35. package/dist/src/index.js.map +1 -0
  36. package/dist/src/release/bundle.d.ts +10 -0
  37. package/dist/src/release/bundle.js +50 -0
  38. package/dist/src/release/bundle.js.map +1 -0
  39. package/dist/src/release/package.d.ts +26 -0
  40. package/dist/src/release/package.js +102 -0
  41. package/dist/src/release/package.js.map +1 -0
  42. package/dist/src/release/render.d.ts +32 -0
  43. package/dist/src/release/render.js +335 -0
  44. package/dist/src/release/render.js.map +1 -0
  45. package/dist/src/serve.d.ts +8 -0
  46. package/dist/src/serve.js +14 -0
  47. package/dist/src/serve.js.map +1 -0
  48. package/dist/src/server/assets.d.ts +10 -0
  49. package/dist/src/server/assets.js +46 -0
  50. package/dist/src/server/assets.js.map +1 -0
  51. package/dist/src/server/integrations.d.ts +12 -0
  52. package/dist/src/server/integrations.js +39 -0
  53. package/dist/src/server/integrations.js.map +1 -0
  54. package/dist/src/server/production.d.ts +7 -0
  55. package/dist/src/server/production.js +45 -0
  56. package/dist/src/server/production.js.map +1 -0
  57. package/dist/src/server/proxy.d.ts +34 -0
  58. package/dist/src/server/proxy.js +165 -0
  59. package/dist/src/server/proxy.js.map +1 -0
  60. package/dist/src/server/websocket.d.ts +5 -0
  61. package/dist/src/server/websocket.js +17 -0
  62. package/dist/src/server/websocket.js.map +1 -0
  63. package/dist/src/vite-config.d.ts +12 -0
  64. package/dist/src/vite-config.js +35 -0
  65. package/dist/src/vite-config.js.map +1 -0
  66. package/dist/src/vite.d.ts +5 -0
  67. package/dist/src/vite.js +42 -0
  68. package/dist/src/vite.js.map +1 -0
  69. package/package.json +73 -0
@@ -0,0 +1,165 @@
1
+ import { encodeMessage, parseClientMessage, parseEntityRef, UnknownEntityError, } from '@hashsome/core';
2
+ const OPEN = 1;
3
+ const RANGES = ['1h', '1d', '1w', '1m'];
4
+ const BUCKETS = ['5m', '1h', '1d'];
5
+ /** A history query's fields, kept only when they are ones it has: the rest came from a browser. A
6
+ * missing or unknown range is a request that cannot be answered. */
7
+ function historyArgs(args) {
8
+ const { range, bucket } = args ?? {};
9
+ if (typeof range !== 'string' || !RANGES.includes(range)) {
10
+ return undefined;
11
+ }
12
+ return {
13
+ range: range,
14
+ ...(typeof bucket === 'string' && BUCKETS.includes(bucket)
15
+ ? { bucket: bucket }
16
+ : {}),
17
+ };
18
+ }
19
+ /** The only fields a browse query has, kept only when they are strings: the rest came from a browser. */
20
+ function browseArgs(args) {
21
+ return {
22
+ ...(typeof args?.path === 'string' ? { path: args.path } : {}),
23
+ ...(typeof args?.search === 'string' ? { search: args.search } : {}),
24
+ };
25
+ }
26
+ /**
27
+ * Multiplexes browser connections onto server-held integrations. Tokens never
28
+ * reach the browser; current entities are replayed from the integrations' stores
29
+ * so a waking tablet gets a full picture immediately.
30
+ *
31
+ * The proxy does all the addressing: it parses `<integration>:<id>` and hands the integration only
32
+ * its local id. A subscription is resolved once the owning integration is connected — until then
33
+ * the browser has no answer (a component shows its loading state) — and an id the integration does
34
+ * not have is answered with `entity: null` and logged.
35
+ */
36
+ export class Proxy {
37
+ #integrations = new Map();
38
+ #log;
39
+ constructor(integrations, options = {}) {
40
+ for (const integration of integrations) {
41
+ this.#integrations.set(integration.id, integration);
42
+ }
43
+ this.#log = options.log ?? (() => { });
44
+ }
45
+ handleConnection(socket) {
46
+ const send = (message) => {
47
+ if (socket.readyState === OPEN) {
48
+ socket.send(encodeMessage(message));
49
+ }
50
+ };
51
+ const subscriptions = new Map();
52
+ const cleanups = [];
53
+ for (const integration of this.#integrations.values()) {
54
+ const report = (status) => send({ type: 'status', integration: integration.id, status });
55
+ report(integration.status);
56
+ cleanups.push(integration.onStatusChange(report));
57
+ }
58
+ const answer = (id, outcome) => {
59
+ outcome.then((data) => send({ type: 'result', id, ok: true, ...(data !== undefined ? { data } : {}) }), (error) => send({
60
+ type: 'result',
61
+ id,
62
+ ok: false,
63
+ error: error instanceof Error ? error.message : String(error),
64
+ }));
65
+ };
66
+ const subscribe = (ref) => {
67
+ const { integration: integrationId, id } = parseEntityRef(ref);
68
+ const integration = this.#integrations.get(integrationId);
69
+ if (!integration) {
70
+ this.#log(`[proxy] no integration "${integrationId}" for ${ref}`);
71
+ send({ type: 'entity', ref, entity: null });
72
+ return () => { };
73
+ }
74
+ const start = () => {
75
+ try {
76
+ return integration.subscribe(id, (entity) => send({ type: 'entity', ref, entity: entity ?? null }));
77
+ }
78
+ catch (error) {
79
+ if (!(error instanceof UnknownEntityError)) {
80
+ throw error;
81
+ }
82
+ this.#log(`[proxy] ${error.message}`);
83
+ send({ type: 'entity', ref, entity: null });
84
+ return () => { };
85
+ }
86
+ };
87
+ if (integration.status === 'connected') {
88
+ return start();
89
+ }
90
+ let stop;
91
+ const off = integration.onStatusChange((status) => {
92
+ if (status === 'connected' && !stop) {
93
+ stop = start();
94
+ }
95
+ });
96
+ return () => {
97
+ off();
98
+ stop?.();
99
+ };
100
+ };
101
+ socket.on('message', (data) => {
102
+ const message = parseClientMessage(data.toString());
103
+ if (!message) {
104
+ return;
105
+ }
106
+ switch (message.type) {
107
+ case 'subscribe':
108
+ if (!subscriptions.has(message.ref)) {
109
+ subscriptions.set(message.ref, subscribe(message.ref));
110
+ }
111
+ return;
112
+ case 'unsubscribe':
113
+ subscriptions.get(message.ref)?.();
114
+ subscriptions.delete(message.ref);
115
+ return;
116
+ case 'command': {
117
+ const { integration: integrationId, id } = parseEntityRef(message.ref);
118
+ const integration = this.#integrations.get(integrationId);
119
+ answer(message.id, integration
120
+ ? integration.command(id, message.command, message.args)
121
+ : Promise.reject(new Error(`Unknown integration "${integrationId}"`)));
122
+ return;
123
+ }
124
+ case 'query': {
125
+ const { integration: integrationId, id } = parseEntityRef(message.ref);
126
+ const integration = this.#integrations.get(integrationId);
127
+ const history = message.query === 'history' ? historyArgs(message.args) : undefined;
128
+ answer(message.id, !integration
129
+ ? Promise.reject(new Error(`Unknown integration "${integrationId}"`))
130
+ : message.query === 'history'
131
+ ? integration.history
132
+ ? history
133
+ ? integration.history(id, history)
134
+ : Promise.reject(new Error('A history query needs a range'))
135
+ : Promise.reject(new Error(`"${integrationId}" keeps no history`))
136
+ : integration.browse
137
+ ? integration.browse(id, browseArgs(message.args))
138
+ : Promise.reject(new Error(`"${integrationId}" has no media library`)));
139
+ return;
140
+ }
141
+ case 'raw': {
142
+ const integration = this.#integrations.get(message.integration);
143
+ answer(message.id, !integration
144
+ ? Promise.reject(new Error(`Unknown integration "${message.integration}"`))
145
+ : integration.callRaw
146
+ ? integration.callRaw(message.request)
147
+ : Promise.reject(new Error(`"${message.integration}" has no raw requests`)));
148
+ return;
149
+ }
150
+ }
151
+ });
152
+ const dispose = () => {
153
+ for (const off of subscriptions.values()) {
154
+ off();
155
+ }
156
+ subscriptions.clear();
157
+ for (const off of cleanups) {
158
+ off();
159
+ }
160
+ cleanups.length = 0;
161
+ };
162
+ socket.on('close', dispose);
163
+ socket.on('error', dispose);
164
+ }
165
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../../src/server/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EAUd,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAUxB,MAAM,IAAI,GAAG,CAAC,CAAC;AAEf,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACxC,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEnC;oEACoE;AACpE,SAAS,WAAW,CAAC,IAAyC;IAC5D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAqB;QAC5B,GAAG,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YACxD,CAAC,CAAC,EAAE,MAAM,EAAE,MAAuB,EAAE;YACrC,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED,yGAAyG;AACzG,SAAS,UAAU,CAAC,IAAyC;IAC3D,OAAO;QACL,GAAG,CAAC,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,OAAO,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC;AAYD;;;;;;;;;GASG;AACH,MAAM,OAAO,KAAK;IAChB,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,IAAI,CAA4B;IAEhC,YAAY,YAA2B,EAAE,OAAO,GAAiB,EAAE;QACjE,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACvC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,gBAAgB,CAAC,MAAmB;QAClC,MAAM,IAAI,GAAG,CAAC,OAAsB,EAAE,EAAE;YACtC,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;YACtC,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,aAAa,GAAG,IAAI,GAAG,EAA0B,CAAC;QACxD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QAEnC,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,CAAC,MAAwB,EAAE,EAAE,CAC1C,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAEhE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,OAAyB,EAAE,EAAE;YACvD,OAAO,CAAC,IAAI,CACV,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACzF,CAAC,KAAc,EAAE,EAAE,CACjB,IAAI,CAAC;gBACH,IAAI,EAAE,QAAQ;gBACd,EAAE;gBACF,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CACL,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,CAAC,GAAc,EAAe,EAAE;YAChD,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC1D,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,2BAA2B,aAAa,SAAS,GAAG,EAAE,CAAC,CAAC;gBAClE,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5C,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;YAClB,CAAC;YAED,MAAM,KAAK,GAAG,GAAgB,EAAE;gBAC9B,IAAI,CAAC;oBACH,OAAO,WAAW,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAC1C,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI,EAAE,CAAC,CACtD,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,CAAC,KAAK,YAAY,kBAAkB,CAAC,EAAE,CAAC;wBAC3C,MAAM,KAAK,CAAC;oBACd,CAAC;oBAED,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;oBACtC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC5C,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC,CAAC;YAEF,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACvC,OAAO,KAAK,EAAE,CAAC;YACjB,CAAC;YAED,IAAI,IAA6B,CAAC;YAClC,MAAM,GAAG,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC,MAAM,EAAE,EAAE;gBAChD,IAAI,MAAM,KAAK,WAAW,IAAI,CAAC,IAAI,EAAE,CAAC;oBACpC,IAAI,GAAG,KAAK,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,OAAO,GAAG,EAAE;gBACV,GAAG,EAAE,CAAC;gBACN,IAAI,EAAE,EAAE,CAAC;YACX,CAAC,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5B,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACpD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;YACT,CAAC;YAED,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,WAAW;oBACd,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;wBACpC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;oBACzD,CAAC;oBAED,OAAO;gBACT,KAAK,aAAa;oBAChB,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACnC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBAClC,OAAO;gBACT,KAAK,SAAS,EAAE,CAAC;oBACf,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBACvE,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBAC1D,MAAM,CACJ,OAAO,CAAC,EAAE,EACV,WAAW;wBACT,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC;wBACxD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,aAAa,GAAG,CAAC,CAAC,CACxE,CAAC;oBAEF,OAAO;gBACT,CAAC;gBAED,KAAK,OAAO,EAAE,CAAC;oBACb,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBACvE,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;oBACpF,MAAM,CACJ,OAAO,CAAC,EAAE,EACV,CAAC,WAAW;wBACV,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,aAAa,GAAG,CAAC,CAAC;wBACrE,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS;4BAC3B,CAAC,CAAC,WAAW,CAAC,OAAO;gCACnB,CAAC,CAAC,OAAO;oCACP,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC;oCAClC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;gCAC9D,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,aAAa,oBAAoB,CAAC,CAAC;4BACpE,CAAC,CAAC,WAAW,CAAC,MAAM;gCAClB,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gCAClD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,aAAa,wBAAwB,CAAC,CAAC,CAC7E,CAAC;oBAEF,OAAO;gBACT,CAAC;gBAED,KAAK,KAAK,EAAE,CAAC;oBACX,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;oBAChE,MAAM,CACJ,OAAO,CAAC,EAAE,EACV,CAAC,WAAW;wBACV,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;wBAC3E,CAAC,CAAC,WAAW,CAAC,OAAO;4BACnB,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;4BACtC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,OAAO,CAAC,WAAW,uBAAuB,CAAC,CAAC,CAChF,CAAC;oBAEF,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzC,GAAG,EAAE,CAAC;YACR,CAAC;YAED,aAAa,CAAC,KAAK,EAAE,CAAC;YACtB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,GAAG,EAAE,CAAC;YACR,CAAC;YAED,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QACtB,CAAC,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import type { Proxy } from './proxy.ts';
3
+ export declare const WS_PATH = "/ws";
4
+ /** Attaches the proxy to `/ws` on an http server, leaving other upgrades (Vite HMR) alone. */
5
+ export declare function attachWebSocket(server: HttpServer, proxy: Proxy): () => void;
@@ -0,0 +1,17 @@
1
+ import { WebSocketServer } from 'ws';
2
+ export const WS_PATH = '/ws';
3
+ /** Attaches the proxy to `/ws` on an http server, leaving other upgrades (Vite HMR) alone. */
4
+ export function attachWebSocket(server, proxy) {
5
+ const wss = new WebSocketServer({ noServer: true });
6
+ const onUpgrade = (request, socket, head) => {
7
+ if (new URL(request.url ?? '/', 'http://localhost').pathname !== WS_PATH) {
8
+ return;
9
+ }
10
+ wss.handleUpgrade(request, socket, head, (ws) => proxy.handleConnection(ws));
11
+ };
12
+ server.on('upgrade', onUpgrade);
13
+ return () => {
14
+ server.off('upgrade', onUpgrade);
15
+ wss.close();
16
+ };
17
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/server/websocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAGrC,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC;AAE7B,8FAA8F;AAC9F,MAAM,UAAU,eAAe,CAAC,MAAkB,EAAE,KAAY;IAC9D,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,CAChB,OAA4C,EAC5C,MAAoC,EACpC,IAAY,EACZ,EAAE;QACF,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzE,OAAO;QACT,CAAC;QAED,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC;IAEF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACV,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACjC,GAAG,CAAC,KAAK,EAAE,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Sensible default, re-exported by the project's own `vite.config.ts` (the React Router plugin
3
+ * requires a config file at the project root). Server settings and the dev proxy are supplied
4
+ * inline by the CLI.
5
+ *
6
+ * A function, so every load of the config gets its own React Router plugin. The SPA build starts a
7
+ * preview server that loads this config again in the same process; a plugin created once at import
8
+ * would be shared, and the preview's `serve` would overwrite the build's state in it
9
+ * ("Expected build manifest").
10
+ */
11
+ declare const _default: import("vite").UserConfigFnObject;
12
+ export default _default;
@@ -0,0 +1,35 @@
1
+ import { reactRouter } from '@react-router/dev/vite';
2
+ import { defineConfig } from 'vite';
3
+ /**
4
+ * Sensible default, re-exported by the project's own `vite.config.ts` (the React Router plugin
5
+ * requires a config file at the project root). Server settings and the dev proxy are supplied
6
+ * inline by the CLI.
7
+ *
8
+ * A function, so every load of the config gets its own React Router plugin. The SPA build starts a
9
+ * preview server that loads this config again in the same process; a plugin created once at import
10
+ * would be shared, and the preview's `serve` would overwrite the build's state in it
11
+ * ("Expected build manifest").
12
+ */
13
+ export default defineConfig(() => ({
14
+ plugins: [reactRouter()],
15
+ // React Router's SPA prerender starts a private preview server; pin it to IPv4 so it is
16
+ // reachable in containers where `localhost` resolves to ::1.
17
+ preview: { host: '127.0.0.1' },
18
+ resolve: {
19
+ // One copy of these across the runtime app and the instance's dashboards.
20
+ dedupe: ['react', 'react-dom', 'react-router', '@hashsome/core', '@hashsome/ui'],
21
+ },
22
+ optimizeDeps: {
23
+ // Vite's dependency scanner only follows *static* imports reachable from the HTML entry —
24
+ // route modules are loaded dynamically by React Router's own router, not a plain `import`
25
+ // statement it can see, so a dashboard's dependencies (e.g. the energy chart's `recharts`)
26
+ // only surface once that route is actually visited. Without this, navigating to a dashboard
27
+ // not yet visited in the current dev session triggers a fresh dependency pre-bundle mid-visit
28
+ // — the page's own module graph update races a React Router navigation already in flight, and
29
+ // the view can appear stuck (URL changed, nothing rendered) until the resulting full reload,
30
+ // sometimes several seconds later. Pointing the scanner at every route module upfront (plus
31
+ // `app/root.tsx`, which pulls in `HashsomeProvider`) lets it discover all of this at
32
+ // server startup instead.
33
+ entries: ['app/root.tsx', 'dashboards/**/*.{ts,tsx}'],
34
+ },
35
+ }));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-config.js","sourceRoot":"","sources":["../../src/vite-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;GASG;AACH,eAAe,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;IACxB,wFAAwF;IACxF,6DAA6D;IAC7D,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;IAC9B,OAAO,EAAE;QACP,0EAA0E;QAC1E,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,cAAc,CAAC;KACjF;IACD,YAAY,EAAE;QACZ,0FAA0F;QAC1F,0FAA0F;QAC1F,2FAA2F;QAC3F,4FAA4F;QAC5F,8FAA8F;QAC9F,8FAA8F;QAC9F,6FAA6F;QAC7F,4FAA4F;QAC5F,qFAAqF;QACrF,0BAA0B;QAC1B,OAAO,EAAE,CAAC,cAAc,EAAE,0BAA0B,CAAC;KACtD;CACF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type InlineConfig } from 'vite';
2
+ import type { ResolvedConfig } from './config.ts';
3
+ import type { Proxy } from './server/proxy.ts';
4
+ /** `proxy` is only passed for `dev`; `build` needs no server settings (its prerender starts a private preview server). */
5
+ export declare function createViteConfig(config: ResolvedConfig, proxy?: Proxy): InlineConfig;
@@ -0,0 +1,42 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { searchForWorkspaceRoot } from 'vite';
4
+ import { serveAsset } from './server/assets.js';
5
+ import { attachWebSocket } from './server/websocket.js';
6
+ const runtimeRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
7
+ /** Dev only: `/healthz`, the `/ws` proxy and the asset route on Vite's own server. */
8
+ function hashsomeDevServer(proxy, config) {
9
+ return {
10
+ name: 'hashsome:dev-server',
11
+ configureServer(server) {
12
+ server.middlewares.use('/healthz', (_req, res) => {
13
+ res.setHeader('content-type', 'application/json');
14
+ res.end('{"status":"ok"}');
15
+ });
16
+ server.middlewares.use((req, res, next) => {
17
+ serveAsset(config.integrations, req, res).then((served) => (served ? undefined : next()), next);
18
+ });
19
+ if (server.httpServer) {
20
+ attachWebSocket(server.httpServer, proxy);
21
+ }
22
+ },
23
+ };
24
+ }
25
+ /** `proxy` is only passed for `dev`; `build` needs no server settings (its prerender starts a private preview server). */
26
+ export function createViteConfig(config, proxy) {
27
+ return {
28
+ root: config.root,
29
+ configFile: join(config.root, 'vite.config.ts'),
30
+ ...(proxy
31
+ ? {
32
+ plugins: [hashsomeDevServer(proxy, config)],
33
+ server: {
34
+ host: config.host,
35
+ port: config.port,
36
+ strictPort: true,
37
+ fs: { allow: [searchForWorkspaceRoot(config.root), runtimeRoot] },
38
+ },
39
+ }
40
+ : {}),
41
+ };
42
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/vite.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAkC,MAAM,MAAM,CAAC;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAExE,sFAAsF;AACtF,SAAS,iBAAiB,CAAC,KAAY,EAAE,MAAsB;IAC7D,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,eAAe,CAAC,MAAM;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBAC/C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;gBAClD,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAC7B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,UAAU,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAC5C,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EACzC,IAAI,CACL,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBACtB,eAAe,CAAC,MAAM,CAAC,UAAwB,EAAE,KAAK,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,0HAA0H;AAC1H,MAAM,UAAU,gBAAgB,CAAC,MAAsB,EAAE,KAAa;IACpE,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC;QAC/C,GAAG,CAAC,KAAK;YACP,CAAC,CAAC;gBACE,OAAO,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBAC3C,MAAM,EAAE;oBACN,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,UAAU,EAAE,IAAI;oBAChB,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,sBAAsB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,EAAE;iBAClE;aACF;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@hashsome/runtime",
3
+ "version": "0.1.0",
4
+ "description": "Server + CLI that serves a project's own dashboards",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/src/index.d.ts",
9
+ "default": "./dist/src/index.js"
10
+ },
11
+ "./app": {
12
+ "types": "./dist/app/index.d.ts",
13
+ "default": "./dist/app/index.js"
14
+ },
15
+ "./entry-client": {
16
+ "types": "./dist/app/entry.client.d.ts",
17
+ "default": "./dist/app/entry.client.js"
18
+ },
19
+ "./entry-server": {
20
+ "types": "./dist/app/entry.server.d.ts",
21
+ "default": "./dist/app/entry.server.js"
22
+ },
23
+ "./vite-config": {
24
+ "types": "./dist/src/vite-config.d.ts",
25
+ "default": "./dist/src/vite-config.js"
26
+ },
27
+ "./serve": {
28
+ "types": "./dist/src/serve.d.ts",
29
+ "default": "./dist/src/serve.js"
30
+ }
31
+ },
32
+ "devDependencies": {
33
+ "@testing-library/react": "16.3.3",
34
+ "@types/express": "5.0.6",
35
+ "@types/react": "19.3.0",
36
+ "@types/react-dom": "19.3.0",
37
+ "@types/ws": "8.18.1",
38
+ "jsdom": "30.1.1"
39
+ },
40
+ "dependencies": {
41
+ "@hashsome/core": "0.1.0",
42
+ "@hashsome/ui": "0.1.0",
43
+ "@react-router/dev": "8.4.0",
44
+ "express": "5.2.1",
45
+ "react": "19.3.0",
46
+ "react-dom": "19.3.0",
47
+ "react-router": "8.4.0",
48
+ "vite": "8.3.1",
49
+ "ws": "8.21.3"
50
+ },
51
+ "bin": {
52
+ "hashsome": "./dist/bin/hashsome.js"
53
+ },
54
+ "license": "GPL-3.0-only",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/dstoyanoff/hashsome.git",
58
+ "directory": "packages/runtime"
59
+ },
60
+ "homepage": "https://github.com/dstoyanoff/hashsome#readme",
61
+ "bugs": "https://github.com/dstoyanoff/hashsome/issues",
62
+ "files": [
63
+ "dist"
64
+ ],
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "provenance": true
68
+ },
69
+ "scripts": {
70
+ "typecheck": "tsc --noEmit",
71
+ "build": "tsc -b tsconfig.build.json"
72
+ }
73
+ }