tina4-nodejs 3.10.93 → 3.10.97

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/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.70)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.97)
2
2
 
3
3
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
4
4
 
5
5
  ## What This Project Is
6
6
 
7
- Tina4 for Node.js/TypeScript v3.10.70 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.10.95 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
8
8
 
9
9
  The philosophy: zero ceremony, batteries included, file system as source of truth.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.93",
3
+ "version": "3.10.97",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript — 54 built-in features, zero dependencies",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -47,14 +47,14 @@ export async function migrateStatus(migrationDir?: string): Promise<void> {
47
47
  if (result.completed.length > 0) {
48
48
  console.log(` Completed (${result.completed.length}):`);
49
49
  for (const file of result.completed) {
50
- console.log(` ${file}`);
50
+ console.log(` [OK] ${file}`);
51
51
  }
52
52
  }
53
53
 
54
54
  if (result.pending.length > 0) {
55
55
  console.log(` Pending (${result.pending.length}):`);
56
56
  for (const file of result.pending) {
57
- console.log(` ${file}`);
57
+ console.log(` [ ] ${file}`);
58
58
  }
59
59
  }
60
60
 
@@ -1,420 +1,2 @@
1
- (function (global, factory) {
2
- if (typeof define === 'function' && define.amd) {
3
- define([], factory);
4
- } else if (typeof module !== 'undefined' && module.exports){
5
- module.exports = factory();
6
- } else {
7
- global.ReconnectingWebSocket = factory();
8
- }
9
- })(this, function () {
10
- if (!('WebSocket' in window)) {
11
- return;
12
- }
13
- function ReconnectingWebSocket(url, protocols, options) {
14
- var settings = {
15
- debug: false,
16
- automaticOpen: true,
17
- reconnectInterval: 1000,
18
- maxReconnectInterval: 30000,
19
- reconnectDecay: 1.5,
20
- timeoutInterval: 2000,
21
- maxReconnectAttempts: null,
22
- binaryType: 'blob'
23
- }
24
- if (!options) { options = {}; }
25
- for (var key in settings) {
26
- if (typeof options[key] !== 'undefined') {
27
- this[key] = options[key];
28
- } else {
29
- this[key] = settings[key];
30
- }
31
- }
32
- this.url = url;
33
- this.reconnectAttempts = 0;
34
- this.readyState = WebSocket.CONNECTING;
35
- this.protocol = null;
36
- var self = this;
37
- var ws;
38
- var forcedClose = false;
39
- var timedOut = false;
40
- var eventTarget = document.createElement('div');
41
- eventTarget.addEventListener('open', function(event) { self.onopen(event); });
42
- eventTarget.addEventListener('close', function(event) { self.onclose(event); });
43
- eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
44
- eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
45
- eventTarget.addEventListener('error', function(event) { self.onerror(event); });
46
- this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
47
- this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
48
- this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
49
- function generateEvent(s, args) {
50
- var evt = document.createEvent("CustomEvent");
51
- evt.initCustomEvent(s, false, false, args);
52
- return evt;
53
- };
54
- this.open = function (reconnectAttempt) {
55
- ws = new WebSocket(self.url, protocols || []);
56
- ws.binaryType = this.binaryType;
57
- if (reconnectAttempt) {
58
- if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
59
- return;
60
- }
61
- } else {
62
- eventTarget.dispatchEvent(generateEvent('connecting'));
63
- this.reconnectAttempts = 0;
64
- }
65
- if (self.debug || ReconnectingWebSocket.debugAll) {
66
- console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
67
- }
68
- var localWs = ws;
69
- var timeout = setTimeout(function() {
70
- if (self.debug || ReconnectingWebSocket.debugAll) {
71
- console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
72
- }
73
- timedOut = true;
74
- localWs.close();
75
- timedOut = false;
76
- }, self.timeoutInterval);
77
- ws.onopen = function(event) {
78
- clearTimeout(timeout);
79
- if (self.debug || ReconnectingWebSocket.debugAll) {
80
- console.debug('ReconnectingWebSocket', 'onopen', self.url);
81
- }
82
- self.protocol = ws.protocol;
83
- self.readyState = WebSocket.OPEN;
84
- self.reconnectAttempts = 0;
85
- var e = generateEvent('open');
86
- e.isReconnect = reconnectAttempt;
87
- reconnectAttempt = false;
88
- eventTarget.dispatchEvent(e);
89
- };
90
- ws.onclose = function(event) {
91
- clearTimeout(timeout);
92
- ws = null;
93
- if (forcedClose) {
94
- self.readyState = WebSocket.CLOSED;
95
- eventTarget.dispatchEvent(generateEvent('close'));
96
- } else {
97
- self.readyState = WebSocket.CONNECTING;
98
- var e = generateEvent('connecting');
99
- e.code = event.code;
100
- e.reason = event.reason;
101
- e.wasClean = event.wasClean;
102
- eventTarget.dispatchEvent(e);
103
- if (!reconnectAttempt && !timedOut) {
104
- if (self.debug || ReconnectingWebSocket.debugAll) {
105
- console.debug('ReconnectingWebSocket', 'onclose', self.url);
106
- }
107
- eventTarget.dispatchEvent(generateEvent('close'));
108
- }
109
- var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
110
- setTimeout(function() {
111
- self.reconnectAttempts++;
112
- self.open(true);
113
- }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
114
- }
115
- };
116
- ws.onmessage = function(event) {
117
- if (self.debug || ReconnectingWebSocket.debugAll) {
118
- console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
119
- }
120
- var e = generateEvent('message');
121
- e.data = event.data;
122
- eventTarget.dispatchEvent(e);
123
- };
124
- ws.onerror = function(event) {
125
- if (self.debug || ReconnectingWebSocket.debugAll) {
126
- console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
127
- }
128
- eventTarget.dispatchEvent(generateEvent('error'));
129
- };
130
- }
131
- if (this.automaticOpen == true) {
132
- this.open(false);
133
- }
134
- this.send = function(data) {
135
- if (ws) {
136
- if (self.debug || ReconnectingWebSocket.debugAll) {
137
- console.debug('ReconnectingWebSocket', 'send', self.url, data);
138
- }
139
- return ws.send(data);
140
- } else {
141
- throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
142
- }
143
- };
144
- this.close = function(code, reason) {
145
- if (typeof code == 'undefined') {
146
- code = 1000;
147
- }
148
- forcedClose = true;
149
- if (ws) {
150
- ws.close(code, reason);
151
- }
152
- };
153
- this.refresh = function() {
154
- if (ws) {
155
- ws.close();
156
- }
157
- };
158
- }
159
- ReconnectingWebSocket.prototype.onopen = function(event) {};
160
- ReconnectingWebSocket.prototype.onclose = function(event) {};
161
- ReconnectingWebSocket.prototype.onconnecting = function(event) {};
162
- ReconnectingWebSocket.prototype.onmessage = function(event) {};
163
- ReconnectingWebSocket.prototype.onerror = function(event) {};
164
- ReconnectingWebSocket.debugAll = false;
165
- ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
166
- ReconnectingWebSocket.OPEN = WebSocket.OPEN;
167
- ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
168
- ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
169
- return ReconnectingWebSocket;
170
- });
171
- var formToken = null;
172
- function sendRequest(url, request, method, callback) {
173
- if (url === undefined) url = "";
174
- if (request === undefined) request = null;
175
- if (method === undefined) method = 'GET';
176
- const xhr = new XMLHttpRequest();
177
- xhr.open(method, url, true);
178
- if (formToken !== null) {
179
- xhr.setRequestHeader('Authorization', 'Bearer ' + formToken);
180
- }
181
- let isFormData = request instanceof FormData;
182
- if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH') {
183
- if (isFormData) {
184
- } else if (typeof request === 'object' && request !== null) {
185
- request = JSON.stringify(request);
186
- xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
187
- } else if (typeof request === 'string') {
188
- xhr.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
189
- }
190
- }
191
- xhr.onload = function () {
192
- let content = xhr.response;
193
- const freshToken = xhr.getResponseHeader('FreshToken');
194
- if (freshToken && freshToken !== '') {
195
- formToken = freshToken;
196
- }
197
- try {
198
- content = JSON.parse(content);
199
- } catch (e) {
200
- }
201
- if (typeof callback === 'function') {
202
- callback(content, xhr.status, xhr);
203
- }
204
- };
205
- xhr.onerror = function () {
206
- if (typeof callback === 'function') {
207
- callback(null, xhr.status, xhr);
208
- }
209
- };
210
- xhr.send(request);
211
- }
212
- function getFormData(formId) {
213
- let data = new FormData();
214
- let elements = document.querySelectorAll("#" + formId + " select, #" + formId + " input, #" + formId + " textarea");
215
- for (let ie = 0; ie < elements.length; ie++ )
216
- {
217
- let element = elements[ie];
218
- if (element.name === 'formToken' && formToken !== null) {
219
- element.value = formToken;
220
- }
221
- if (element.name) {
222
- if (element.type === 'file') {
223
- for (let i = 0; i < element.files.length; i++) {
224
- let fileData = element.files[i];
225
- let elementName = element.name;
226
- if (fileData !== undefined) {
227
- if (element.files.length > 1 && !elementName.includes('[')) {
228
- elementName = elementName + '[]';
229
- }
230
- data.append(elementName, fileData, fileData.name);
231
- }
232
- }
233
- } else if (element.type === 'checkbox' || element.type === 'radio') {
234
- if (element.checked) {
235
- data.append(element.name, element.value)
236
- } else {
237
- if (element.type !== 'radio') {
238
- data.append(element.name, "0")
239
- }
240
- }
241
- } else {
242
- if (element.value === '') {
243
- element.value = null;
244
- }
245
- data.append(element.name, element.value);
246
- }
247
- }
248
- }
249
- return data;
250
- }
251
- function handleHtmlData(data, targetElement) {
252
- if (data === "") return '';
253
- const parser = new DOMParser();
254
- const htmlData = parser.parseFromString(data.includes !== undefined && data.includes('<html>') ? data : '<body>'+data+'</body></html>', 'text/html');
255
- const body = htmlData.querySelector('body');
256
- const scripts = body.querySelectorAll('script');
257
- body.querySelectorAll('script').forEach(script => script.remove());
258
- if (targetElement !== null) {
259
- if (body.children.length > 0) {
260
- document.getElementById(targetElement).replaceChildren(...body.children);
261
- } else {
262
- document.getElementById(targetElement).replaceChildren(body.innerHTML);
263
- }
264
- if (scripts) {
265
- scripts.forEach(script => {
266
- const newScript = document.createElement("script");
267
- newScript.type = 'text/javascript';
268
- newScript.async = true;
269
- newScript.textContent = script.innerText;
270
- document.getElementById(targetElement).append(newScript);
271
- });
272
- }
273
- } else {
274
- if (scripts) {
275
- scripts.forEach(script => {
276
- const newScript = document.createElement("script");
277
- newScript.type = 'text/javascript';
278
- newScript.async = true;
279
- newScript.textContent = script.innerText;
280
- document.body.append(newScript);
281
- console.log(newScript);
282
- });
283
- }
284
- return body.innerHTML;
285
- }
286
- return '';
287
- }
288
- function loadPage(loadURL, targetElement, callback = null) {
289
- if (targetElement === undefined) targetElement = 'content';
290
- sendRequest(loadURL, null, "GET", function(data) {
291
- let processedHTML = '';
292
- if (document.getElementById(targetElement) !== null) {
293
- processedHTML = handleHtmlData(data, targetElement);
294
- } else {
295
- if (callback) {
296
- callback(data);
297
- } else {
298
- console.log('TINA4 - define targetElement or callback for loadPage', data);
299
- }
300
- return;
301
- }
302
- if (callback) {
303
- callback(processedHTML, data);
304
- }
305
- });
306
- }
307
- function showForm(action, loadURL, targetElement, callback = null) {
308
- if (targetElement === undefined) targetElement = 'form';
309
- if (action === 'create') action = 'GET';
310
- if (action === 'edit') action = 'GET';
311
- if (action === 'delete') action = 'DELETE';
312
- sendRequest(loadURL, null, action, function(data) {
313
- let processedHTML = '';
314
- if (data.message !== undefined) {
315
- processedHTML = handleHtmlData ((data.message), targetElement);
316
- } else {
317
- if (document.getElementById(targetElement) !== null) {
318
- processedHTML = handleHtmlData (data, targetElement);
319
- } else {
320
- if (callback) {
321
- callback(data);
322
- } else {
323
- console.log('TINA4 - define targetElement or callback for showForm', data);
324
- }
325
- return;
326
- }
327
- }
328
- if (callback) {
329
- callback(processedHTML);
330
- }
331
- });
332
- }
333
- function postUrl(url, data, targetElement, callback= null) {
334
- sendRequest(url, data, 'POST', function(data) {
335
- let processedHTML = '';
336
- if (data.message !== undefined) {
337
- processedHTML = handleHtmlData ((data.message), targetElement);
338
- } else {
339
- if (document.getElementById(targetElement) !== null) {
340
- processedHTML = handleHtmlData (data, targetElement);
341
- } else {
342
- if (callback) {
343
- callback(data);
344
- } else {
345
- console.log('TINA4 - define targetElement or callback for postUrl', data);
346
- }
347
- return;
348
- }
349
- }
350
- if (callback) {
351
- callback(processedHTML,data)
352
- }
353
- });
354
- }
355
- function saveForm(formId, targetURL, targetElement, callback = null) {
356
- if (targetElement === undefined) targetElement = 'message';
357
- let data = getFormData(formId);
358
- postUrl(targetURL, data, targetElement, callback);
359
- }
360
- function postForm(formId, targetURL, targetElement, callback = null){
361
- saveForm(formId, targetURL, targetElement, callback)
362
- }
363
- function submitForm(formId, targetURL, targetElement, callback = null){
364
- saveForm(formId, targetURL, targetElement, callback)
365
- }
366
- function showMessage(message) {
367
- document.getElementById('message').innerHTML = '<div class="alert alert-info alert-dismissible fade show"><strong>Info</strong> ' + message + '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
368
- }
369
- function setCookie(name, value, days) {
370
- let expires = "";
371
- if (days) {
372
- let date = new Date();
373
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
374
- expires = "; expires=" + date.toUTCString();
375
- }
376
- document.cookie = name + "=" + (value || "") + expires + "; path=/";
377
- }
378
- function getCookie(name) {
379
- let nameEQ = name + "=";
380
- let ca = document.cookie.split(';');
381
- for (let i = 0; i < ca.length; i++) {
382
- var c = ca[i];
383
- while (c.charAt(0) == ' ') c = c.substring(1, c.length);
384
- if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
385
- }
386
- return null;
387
- }
388
- const popupCenter = ({url, title, w, h}) => {
389
- const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
390
- const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
391
- const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
392
- const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
393
- const systemZoom = width / window.screen.availWidth;
394
- const left = (width - w) / 2 / systemZoom + dualScreenLeft
395
- const top = (height - h) / 2 / systemZoom + dualScreenTop
396
- const newWindow = window.open(url, title,
397
- `
398
- directories=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,
399
- width=${w / systemZoom},
400
- height=${h / systemZoom},
401
- top=${top},
402
- left=${left}
403
- `
404
- )
405
- if (window.focus) newWindow.focus();
406
- return newWindow;
407
- }
408
- function openReport(pdfReportPath){
409
- if (pdfReportPath.indexOf("No data available") < 0){
410
- open(pdfReportPath, "content", "target=_blank, toolbar=no, scrollbars=yes, resizable=yes, width=800, height=600, top=0, left=0");
411
- }
412
- else {
413
- window.alert("Sorry , unable to print a report according to your selection!");
414
- }
415
- }
416
- function getRoute(loadURL, callback) {
417
- sendRequest(loadURL, null, 'GET', function(data) {
418
- callback(handleHtmlData (data, null));
419
- });
420
- }
1
+ var _frondModule=(()=>{var b=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var O=(o,s)=>{for(var e in s)b(o,e,{get:s[e],enumerable:!0})},M=(o,s,e,t)=>{if(s&&typeof s=="object"||typeof s=="function")for(let n of x(s))!C.call(o,n)&&n!==e&&b(o,n,{get:()=>s[n],enumerable:!(t=k(s,n))||t.enumerable});return o};var q=o=>M(b({},"__esModule",{value:!0}),o);var j={};O(j,{frond:()=>R});var g=null;function w(o,s){let e;typeof s=="function"?e={onSuccess:s}:e=s||{};let t=(e.method||"GET").toUpperCase(),n=new XMLHttpRequest;if(n.open(t,o,!0),g!==null&&n.setRequestHeader("Authorization","Bearer "+g),e.headers)for(let r in e.headers)Object.prototype.hasOwnProperty.call(e.headers,r)&&n.setRequestHeader(r,e.headers[r]);let i=null;e.body!==void 0&&e.body!==null&&(e.body instanceof FormData?i=e.body:typeof e.body=="object"?(i=JSON.stringify(e.body),n.setRequestHeader("Content-Type","application/json; charset=UTF-8")):typeof e.body=="string"&&(i=e.body,n.setRequestHeader("Content-Type","text/plain; charset=UTF-8"))),n.onload=function(){let r=n.getResponseHeader("FreshToken");r&&r!==""&&(g=r);let u=n.response;try{u=JSON.parse(u)}catch{}if(n.responseURL){let c=new URL(o,window.location.href).href;if(n.responseURL!==c){window.location.href=n.responseURL;return}}n.status>=200&&n.status<400?e.onSuccess&&e.onSuccess(u,n.status,n):e.onError&&e.onError(n.status,n)},n.onerror=function(){e.onError&&e.onError(n.status,n)},n.send(i)}function h(o,s){if(!o)return"";let e=new DOMParser,t=o.includes("<html>")?o:"<body>"+o+"</body></html>",i=e.parseFromString(t,"text/html").querySelector("body"),r=i.querySelectorAll("script");if(r.forEach(function(u){u.remove()}),s!==null){let u=document.getElementById(s);return u&&(i.children.length>0?u.replaceChildren.apply(u,Array.from(i.children)):u.innerHTML=i.innerHTML,r.forEach(function(c){let d=document.createElement("script");d.type="text/javascript",d.async=!0,c.src?d.src=c.src:d.textContent=c.textContent,u.appendChild(d)})),""}return r.forEach(function(u){let c=document.createElement("script");c.type="text/javascript",c.async=!0,c.textContent=u.textContent,document.body.appendChild(c)}),i.innerHTML}function H(o,s,e){let t=s||"content";w(o,{method:"GET",onSuccess:function(n,i){if(document.getElementById(t)){let r=h(n,t);e&&e(r,n)}else e&&e(n)}})}function S(o,s,e,t){let n=e||"content";w(o,{method:"POST",body:s,onSuccess:function(i){let r="";if(i&&i.message!==void 0)r=h(i.message,n);else if(document.getElementById(n))r=h(i,n);else{t&&t(i);return}t&&t(r,i)}})}var T={collect:function(o){let s=new FormData,e=document.querySelectorAll("#"+o+" select, #"+o+" input, #"+o+" textarea");for(let t=0;t<e.length;t++){let n=e[t];if(n.name==="formToken"&&g!==null&&(n.value=g),!!n.name)if(n.type==="file"){let i=n.files;if(i)for(let r=0;r<i.length;r++){let u=i[r];if(u!==void 0){let c=n.name;i.length>1&&!c.includes("[")&&(c=c+"[]"),s.append(c,u,u.name)}}}else n.type==="checkbox"||n.type==="radio"?n.checked?s.append(n.name,n.value):n.type!=="radio"&&s.append(n.name,"0"):s.append(n.name,n.value===""?"":n.value)}return s},submit:function(o,s,e,t){let n=T.collect(o);S(s,n,e||"message",t)},show:function(o,s,e,t){let n=o.toUpperCase();(o==="create"||o==="edit")&&(n="GET"),o==="delete"&&(n="DELETE");let i=e||"form";w(s,{method:n,onSuccess:function(r){let u="";if(r&&r.message!==void 0)u=h(r.message,i);else if(document.getElementById(i))u=h(r,i);else{t&&t(r);return}t&&t(u)}})}};function L(o,s){let e={reconnect:!0,reconnectDelay:1e3,maxReconnectDelay:3e4,maxReconnectAttempts:1/0,protocols:[],onOpen:function(){},onClose:function(){},onError:function(){},...s||{}},t=null,n=!1,i=e.reconnectDelay,r=0,u=null,c={message:[],open:[],close:[],error:[]},d={status:"connecting",send:function(l){if(!t||t.readyState!==WebSocket.OPEN)throw new Error("[frond] WebSocket is not connected");t.send(typeof l=="string"?l:JSON.stringify(l))},on:function(l,a){return c[l]||(c[l]=[]),c[l].push(a),function(){let f=c[l],m=f.indexOf(a);m>=0&&f.splice(m,1)}},close:function(l,a){n=!0,u&&(clearTimeout(u),u=null),t&&t.close(l||1e3,a||""),d.status="closed"}};function y(l){if(typeof l!="string")return l;try{return JSON.parse(l)}catch{return l}}function p(){!e.reconnect||r>=e.maxReconnectAttempts||(r++,d.status="reconnecting",u=setTimeout(function(){u=null,v()},i),i=Math.min(i*2,e.maxReconnectDelay))}function v(){d.status=r>0?"reconnecting":"connecting";try{t=new WebSocket(o,e.protocols)}catch{d.status="closed";return}t.onopen=function(){d.status="open",r=0,i=e.reconnectDelay,e.onOpen();for(let l of c.open)l()},t.onmessage=function(l){let a=y(l.data);for(let f of c.message)f(a)},t.onclose=function(l){d.status="closed",e.onClose(l.code,l.reason);for(let a of c.close)a(l.code,l.reason);n||p()},t.onerror=function(l){e.onError(l);for(let a of c.error)a(l)}}return v(),d}function D(o,s){let e={reconnect:!0,reconnectDelay:1e3,maxReconnectDelay:3e4,maxReconnectAttempts:1/0,events:[],json:!0,onOpen:function(){},onClose:function(){},onError:function(){},...s||{}},t=null,n=!1,i=e.reconnectDelay,r=0,u=null,c={message:[],open:[],close:[],error:[]},d={status:"connecting",on:function(a,f){return c[a]||(c[a]=[]),c[a].push(f),function(){let m=c[a],E=m.indexOf(f);E>=0&&m.splice(E,1)}},close:function(){n=!0,u&&(clearTimeout(u),u=null),t&&(t.close(),t=null),d.status="closed"}};function y(a){if(!e.json)return a;try{return JSON.parse(a)}catch{return a}}function p(a,f){for(let m of c.message)m(a,f||void 0)}function v(){!e.reconnect||r>=e.maxReconnectAttempts||(r++,d.status="reconnecting",u=setTimeout(function(){u=null,l()},i),i=Math.min(i*2,e.maxReconnectDelay))}function l(){d.status=r>0?"reconnecting":"connecting";try{t=new EventSource(o)}catch{d.status="closed";return}t.onopen=function(){d.status="open",r=0,i=e.reconnectDelay,e.onOpen();for(let a of c.open)a(null)},t.onmessage=function(a){p(y(a.data),null)};for(let a of e.events)t.addEventListener(a,function(f){p(y(f.data),a)});t.onerror=function(a){e.onError(a);for(let f of c.error)f(a);if(t&&t.readyState===2){t=null,d.status="closed",e.onClose();for(let f of c.close)f(null);n||v()}}}return l(),d}var W={set:function(o,s,e){let t="";if(e){let n=new Date;n.setTime(n.getTime()+e*24*60*60*1e3),t="; expires="+n.toUTCString()}document.cookie=o+"="+(s||"")+t+"; path=/"},get:function(o){let s=o+"=",e=document.cookie.split(";");for(let t=0;t<e.length;t++){let n=e[t];for(;n.charAt(0)===" ";)n=n.substring(1);if(n.indexOf(s)===0)return n.substring(s.length)}return null},remove:function(o){document.cookie=o+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/"}};function A(o,s){let e=document.getElementById("message");if(!e)return;let t=s||"info";e.innerHTML='<div class="alert alert-'+t+' alert-dismissible">'+o+'<button type="button" class="btn-close" data-t4-dismiss="alert">&times;</button></div>'}function I(o,s,e,t){let n=window.screenLeft!==void 0?window.screenLeft:window.screenX,i=window.screenTop!==void 0?window.screenTop:window.screenY,r=window.innerWidth||document.documentElement.clientWidth||screen.width,u=window.innerHeight||document.documentElement.clientHeight||screen.height,c=r/window.screen.availWidth,d=(r-e)/2/c+n,y=(u-t)/2/c+i,p=window.open(o,s,"directories=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width="+e/c+",height="+t/c+",top="+y+",left="+d);return window.focus&&p&&p.focus(),p}function N(o){if(o.indexOf("No data available")>=0){window.alert("No data available for this report.");return}window.open(o,"_blank","toolbar=no,scrollbars=yes,resizable=yes,width=800,height=600,top=0,left=0")}function U(o,s,e,t){w(o,{method:"POST",body:{query:s,variables:e||{}},onSuccess:function(n){t&&t(n.data||null,n.errors||void 0)},onError:function(n){t&&t(null,[{message:"GraphQL request failed with status "+n}])}})}var R={request:w,load:H,post:S,inject:h,form:T,ws:L,sse:D,cookie:W,message:A,popup:I,report:N,graphql:U,get token(){return g},set token(o){g=o}};typeof window<"u"&&(window.frond=R);return q(j);})();
2
+ /* Frond v2 tina4.com */
@@ -1,6 +1,6 @@
1
- "use strict";var Tina4=(()=>{var H=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var me=(e,n)=>{for(var t in n)H(e,t,{get:n[t],enumerable:!0})},ye=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of ge(n))!he.call(e,r)&&r!==t&&H(e,r,{get:()=>n[r],enumerable:!(o=pe(n,r))||o.enumerable});return e};var ve=e=>ye(H({},"__esModule",{value:!0}),e);var Ae={};me(Ae,{Tina4Element:()=>I,api:()=>ie,batch:()=>q,computed:()=>z,effect:()=>m,html:()=>X,isSignal:()=>w,navigate:()=>L,pwa:()=>le,route:()=>oe,router:()=>re,signal:()=>h,ws:()=>ue});var C=null,_=null,M=null;function b(e){M=e}function J(){return M}var B=null,V=null;var N=0,P=new Set;function h(e,n){let t=e,o=new Set,r={_t4:!0,get value(){if(C&&(o.add(C),_)){let a=C;_.push(()=>o.delete(a))}return t},set value(a){if(Object.is(a,t))return;let i=t;if(t=a,r._debugInfo&&r._debugInfo.updateCount++,V&&V(r,i,a),N>0)for(let s of o)P.add(s);else{let s;for(let c of[...o])try{c()}catch(l){s===void 0&&(s=l)}if(s!==void 0)throw s}},_subscribe(a){return o.add(a),()=>{o.delete(a)}},peek(){return t}};return B&&(r._debugInfo={label:n,createdAt:Date.now(),updateCount:0,subs:o},B(r,n)),r}function z(e){let n=h(void 0);return m(()=>{n.value=e()}),{_t4:!0,get value(){return n.value},set value(t){throw new Error("[tina4] computed signals are read-only")},_subscribe(t){return n._subscribe(t)},peek(){return n.peek()}}}function m(e){let n=!1,t=[],o=()=>{if(n)return;for(let s of t)s();t=[];let a=C,i=_;C=o,_=t;try{e()}finally{C=a,_=i}};o();let r=()=>{n=!0;for(let a of t)a();t=[]};return M&&M.push(r),r}function q(e){N++;try{e()}finally{if(N--,N===0){let n=[...P];P.clear();let t;for(let o of n)try{o()}catch(r){t===void 0&&(t=r)}if(t!==void 0)throw t}}}function w(e){return e!==null&&typeof e=="object"&&e._t4===!0}var Q=new WeakMap,D="t4:";function X(e,...n){let t=Q.get(e);if(!t){t=document.createElement("template");let i="";for(let s=0;s<e.length;s++)i+=e[s],s<n.length&&(Te(i)?i+=`__t4_${s}__`:i+=`<!--${D}${s}-->`);t.innerHTML=i,Q.set(e,t)}let o=t.content.cloneNode(!0),r=be(o);for(let{marker:i,index:s}of r)ke(i,n[s]);let a=we(o);for(let i of a)Ce(i,n);return o}function be(e){let n=[];return W(e,t=>{if(t.nodeType===8){let o=t.data;if(o&&o.startsWith(D)){let r=parseInt(o.slice(D.length),10);n.push({marker:t,index:r})}}}),n}function we(e){let n=[];return W(e,t=>{t.nodeType===1&&n.push(t)}),n}function W(e,n){let t=e.childNodes;for(let o=0;o<t.length;o++){let r=t[o];n(r),W(r,n)}}function ke(e,n){let t=e.parentNode;if(t)if(w(n)){let o=document.createTextNode("");t.replaceChild(o,e),m(()=>{o.data=String(n.value??"")})}else if(typeof n=="function"){let o=document.createComment("");t.replaceChild(o,e);let r=[],a=[];m(()=>{for(let d of a)d();a=[];let i=[],s=J();b(i);let c=n();b(s),a=i;for(let d of r)d.parentNode?.removeChild(d);r=[];let l=F(c),f=o.parentNode;if(f)for(let d of l)f.insertBefore(d,o),r.push(d)})}else if(Y(n))t.replaceChild(n,e);else if(n instanceof Node)t.replaceChild(n,e);else if(Array.isArray(n)){let o=document.createDocumentFragment();for(let r of n){let a=F(r);for(let i of a)o.appendChild(i)}t.replaceChild(o,e)}else{let o=document.createTextNode(String(n??""));t.replaceChild(o,e)}}function Ce(e,n){let t=[];for(let o of Array.from(e.attributes)){let r=o.name,a=o.value;if(r.startsWith("@")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];typeof l=="function"&&e.addEventListener(s,f=>q(()=>l(f)))}t.push(r);continue}if(r.startsWith("?")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];if(w(l)){let f=l;m(()=>{f.value?e.setAttribute(s,""):e.removeAttribute(s)})}else typeof l=="function"?m(()=>{l()?e.setAttribute(s,""):e.removeAttribute(s)}):l&&e.setAttribute(s,"")}t.push(r);continue}if(r.startsWith(".")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];w(l)?m(()=>{e[s]=l.value}):e[s]=l}t.push(r);continue}let i=a.match(/__t4_(\d+)__/);if(i){let s=n[parseInt(i[1],10)];if(w(s)){let c=s;m(()=>{e.setAttribute(r,String(c.value??""))})}else typeof s=="function"?m(()=>{e.setAttribute(r,String(s()??""))}):e.setAttribute(r,String(s??""))}}for(let o of t)e.removeAttribute(o)}function F(e){if(e==null||e===!1)return[];if(Y(e))return Array.from(e.childNodes);if(e instanceof Node)return[e];if(Array.isArray(e)){let n=[];for(let t of e)n.push(...F(t));return n}return[document.createTextNode(String(e))]}function Y(e){return e!=null&&typeof e=="object"&&e.nodeType===11}function Te(e){let n=!1,t=!1,o=!1;for(let r=0;r<e.length;r++){let a=e[r];a==="<"&&!n&&!t&&(o=!0),a===">"&&!n&&!t&&(o=!1),o&&(a==='"'&&!n&&(t=!t),a==="'"&&!t&&(n=!n))}return o}var Z=null,ee=null;var I=class extends HTMLElement{constructor(){super();this._props={};this._rendered=!1;let t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(let[o,r]of Object.entries(t.props))this._props[o]=h(this._coerce(this.getAttribute(o),r))}static{this.props={}}static{this.styles=""}static{this.shadow=!0}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;let t=this.constructor;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){let r=document.createElement("style");r.textContent=t.styles,this._root.appendChild(r)}let o=this.render();o&&this._root.appendChild(o),this.onMount(),Z&&Z(this)}disconnectedCallback(){this.onUnmount(),ee&&ee(this)}attributeChangedCallback(t,o,r){let i=this.constructor.props[t];i&&this._props[t]&&(this._props[t].value=this._coerce(r,i))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,o){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...o}))}onMount(){}onUnmount(){}_coerce(t,o){return o===Boolean?t!==null:o===Number?t!==null?Number(t):0:t??""}};var U=[],T=null,S="history",_e=!1,E=[],O=[],te=0;function oe(e,n){let t=[],o;e==="*"?o=".*":o=e.replace(/\{(\w+)\}/g,(a,i)=>(t.push(i),"([^/]+)"));let r=new RegExp(`^${o}$`);typeof n=="function"?U.push({pattern:e,regex:r,paramNames:t,handler:n}):U.push({pattern:e,regex:r,paramNames:t,handler:n.handler,guard:n.guard})}function L(e,n){if(S==="hash")if(n?.replace){let t=new URL(location.href);t.hash="#"+e,history.replaceState(null,"",t.toString()),R()}else location.hash="#"+e;else n?.replace?history.replaceState(null,"",e):history.pushState(null,"",e),R()}function R(){if(!T)return;let e=performance.now(),n=++te,t=S==="hash"?location.hash.slice(1)||"/":location.pathname;for(let o of U){let r=t.match(o.regex);if(!r)continue;let a={};if(o.paramNames.forEach((c,l)=>{a[c]=decodeURIComponent(r[l+1])}),o.guard){let c=o.guard();if(c===!1)return;if(typeof c=="string"){L(c,{replace:!0});return}}for(let c of O)c();O=[],T.innerHTML="";let i=[];b(i);let s=o.handler(a);if(s instanceof Promise)s.then(c=>{if(b(null),n!==te){for(let f of i)f();return}ne(T,c),O=i;let l=performance.now()-e;for(let f of E)f({path:t,params:a,pattern:o.pattern,durationMs:l})});else{b(null),ne(T,s),O=i;let c=performance.now()-e;for(let l of E)l({path:t,params:a,pattern:o.pattern,durationMs:c})}return}}function ne(e,n){n instanceof DocumentFragment||n instanceof Node?e.replaceChildren(n):typeof n=="string"?e.innerHTML=n:n!=null&&e.replaceChildren(document.createTextNode(String(n)))}var re={start(e){if(T=document.querySelector(e.target),!T)throw new Error(`[tina4] Router target '${e.target}' not found in DOM`);S=e.mode??"history",_e=!0,window.addEventListener("popstate",R),S==="hash"&&window.addEventListener("hashchange",R),document.addEventListener("click",n=>{if(n.metaKey||n.ctrlKey||n.shiftKey||n.altKey)return;let t=n.target.closest("a[href]");if(!t||t.origin!==location.origin||t.hasAttribute("target")||t.hasAttribute("download")||t.getAttribute("rel")?.includes("external"))return;n.preventDefault();let o=S==="hash"?t.getAttribute("href"):t.pathname;L(o)}),R()},on(e,n){return E.push(n),()=>{let t=E.indexOf(n);t>=0&&E.splice(t,1)}}};var y={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},j=[],K=[],Se=0;function se(){try{return localStorage.getItem(y.tokenKey)}catch{return null}}function Ee(e){try{localStorage.setItem(y.tokenKey,e)}catch{}}async function x(e,n,t,o){let r={method:e,headers:{"Content-Type":"application/json",...y.headers}};if(y.auth){let d=se();d&&(r.headers.Authorization=`Bearer ${d}`)}if(t!==void 0&&e!=="GET"){let d=typeof t=="object"&&t!==null?{...t}:t;if(y.auth&&typeof d=="object"&&d!==null){let p=se();p&&(d.formToken=p)}r.body=JSON.stringify(d)}if(o?.headers&&Object.assign(r.headers,o.headers),o?.params){let d=Object.entries(o.params).map(([p,v])=>`${encodeURIComponent(p)}=${encodeURIComponent(String(v))}`).join("&");n+=(n.includes("?")?"&":"?")+d}let a=y.baseUrl+n;r._url=a,r._requestId=++Se;for(let d of j){let p=d(r);p&&(r=p)}let i=await fetch(a,r),s=i.headers.get("FreshToken");s&&Ee(s);let c=i.headers.get("Content-Type")??"",l;c.includes("json")?l=await i.json():l=await i.text();let f={status:i.status,data:l,ok:i.ok,headers:i.headers,_requestId:r._requestId};for(let d of K){let p=d(f);p&&(f=p)}if(!i.ok)throw f;return f.data}var ie={configure(e){Object.assign(y,e)},get(e,n){return x("GET",e,void 0,n)},post(e,n,t){return x("POST",e,n,t)},put(e,n,t){return x("PUT",e,n,t)},patch(e,n,t){return x("PATCH",e,n,t)},delete(e,n){return x("DELETE",e,void 0,n)},intercept(e,n){e==="request"?j.push(n):K.push(n)},_reset(){y.baseUrl="",y.auth=!1,y.tokenKey="tina4_token",y.headers={},j.length=0,K.length=0}};function ae(e){let n=e.cacheStrategy??"network-first",t=JSON.stringify(e.precache??[]),o=e.offlineRoute?`'${e.offlineRoute}'`:"null";return`
1
+ "use strict";var Tina4=(()=>{var B=Object.defineProperty;var Me=Object.getOwnPropertyDescriptor;var qe=Object.getOwnPropertyNames;var Ne=Object.prototype.hasOwnProperty;var Oe=(t,n)=>{for(var e in n)B(t,e,{get:n[e],enumerable:!0})},Ie=(t,n,e,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of qe(n))!Ne.call(t,r)&&r!==e&&B(t,r,{get:()=>n[r],enumerable:!(o=Me(n,r))||o.enumerable});return t};var De=t=>Ie(B({},"__esModule",{value:!0}),t);var Ve={};Oe(Ve,{Tina4Element:()=>W,api:()=>we,batch:()=>F,computed:()=>ce,effect:()=>b,html:()=>ue,isSignal:()=>R,navigate:()=>G,pwa:()=>Se,route:()=>me,router:()=>ve,signal:()=>v,sse:()=>Te,ws:()=>Ee});var q=null,I=null,j=null;function S(t){j=t}function U(){return j}var ae=null,ie=null;var K=0,z=new Set;function v(t,n){let e=t,o=new Set,r={_t4:!0,get value(){if(q&&(o.add(q),I)){let s=q;I.push(()=>o.delete(s))}return e},set value(s){if(Object.is(s,e))return;let i=e;if(e=s,r._debugInfo&&r._debugInfo.updateCount++,ie&&ie(r,i,s),K>0)for(let a of o)z.add(a);else{let a;for(let c of[...o])try{c()}catch(l){a===void 0&&(a=l)}if(a!==void 0)throw a}},_subscribe(s){return o.add(s),()=>{o.delete(s)}},peek(){return e}};return ae&&(r._debugInfo={label:n,createdAt:Date.now(),updateCount:0,subs:o},ae(r,n)),r}function ce(t){let n=v(void 0);return b(()=>{n.value=t()}),{_t4:!0,get value(){return n.value},set value(e){throw new Error("[tina4] computed signals are read-only")},_subscribe(e){return n._subscribe(e)},peek(){return n.peek()}}}function b(t){let n=!1,e=[],o=()=>{if(n)return;for(let a of e)a();e=[];let s=q,i=I;q=o,I=e;try{t()}finally{q=s,I=i}};o();let r=()=>{n=!0;for(let s of e)s();e=[]};return j&&j.push(r),r}function F(t){K++;try{t()}finally{if(K--,K===0){let n=[...z];z.clear();let e;for(let o of n)try{o()}catch(r){e===void 0&&(e=r)}if(e!==void 0)throw e}}}function R(t){return t!==null&&typeof t=="object"&&t._t4===!0}var le=new WeakMap,Q="t4:";function ue(t,...n){let e=le.get(t);if(!e){e=document.createElement("template");let i="";for(let a=0;a<t.length;a++)i+=t[a],a<n.length&&(je(i)?i+=`__t4_${a}__`:i+=`<!--${Q}${a}-->`);e.innerHTML=i,le.set(t,e)}let o=e.content.cloneNode(!0),r=He(o);for(let{marker:i,index:a}of r)Pe(i,n[a]);let s=Le(o);for(let i of s)Ke(i,n);return o}function He(t){let n=[];return Y(t,e=>{if(e.nodeType===8){let o=e.data;if(o&&o.startsWith(Q)){let r=parseInt(o.slice(Q.length),10);n.push({marker:e,index:r})}}}),n}function Le(t){let n=[];return Y(t,e=>{e.nodeType===1&&n.push(e)}),n}function Y(t,n){let e=t.childNodes;for(let o=0;o<e.length;o++){let r=e[o];n(r),Y(r,n)}}function Pe(t,n){let e=t.parentNode;if(e)if(R(n)){let o=document.createTextNode("");e.replaceChild(o,t),b(()=>{o.data=String(n.value??"")})}else if(typeof n=="function"){let o=document.createComment("");e.replaceChild(o,t);let r=[],s=[];b(()=>{for(let u of s)u();s=[];let i=[],a=U();S(i);let c=n();S(a),s=i;for(let u of r)u.parentNode?.removeChild(u);r=[];let l=X(c),d=o.parentNode;if(d)for(let u of l)d.insertBefore(u,o),r.push(u)})}else if(de(n))e.replaceChild(n,t);else if(n instanceof Node)e.replaceChild(n,t);else if(Array.isArray(n)){let o=document.createDocumentFragment();for(let r of n){let s=X(r);for(let i of s)o.appendChild(i)}e.replaceChild(o,t)}else{let o=document.createTextNode(String(n??""));e.replaceChild(o,t)}}function Ke(t,n){let e=[];for(let o of Array.from(t.attributes)){let r=o.name,s=o.value;if(r.startsWith("@")){let a=r.slice(1),c=s.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];typeof l=="function"&&t.addEventListener(a,d=>F(()=>l(d)))}e.push(r);continue}if(r.startsWith("?")){let a=r.slice(1),c=s.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];if(R(l)){let d=l;b(()=>{d.value?t.setAttribute(a,""):t.removeAttribute(a)})}else typeof l=="function"?b(()=>{l()?t.setAttribute(a,""):t.removeAttribute(a)}):l&&t.setAttribute(a,"")}e.push(r);continue}if(r.startsWith(".")){let a=r.slice(1),c=s.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];R(l)?b(()=>{t[a]=l.value}):t[a]=l}e.push(r);continue}let i=s.match(/__t4_(\d+)__/);if(i){let a=n[parseInt(i[1],10)];if(R(a)){let c=a;b(()=>{t.setAttribute(r,String(c.value??""))})}else typeof a=="function"?b(()=>{t.setAttribute(r,String(a()??""))}):t.setAttribute(r,String(a??""))}}for(let o of e)t.removeAttribute(o)}function X(t){if(t==null||t===!1)return[];if(de(t))return Array.from(t.childNodes);if(t instanceof Node)return[t];if(Array.isArray(t)){let n=[];for(let e of t)n.push(...X(e));return n}return[document.createTextNode(String(t))]}function de(t){return t!=null&&typeof t=="object"&&t.nodeType===11}function je(t){let n=!1,e=!1,o=!1;for(let r=0;r<t.length;r++){let s=t[r];s==="<"&&!n&&!e&&(o=!0),s===">"&&!n&&!e&&(o=!1),o&&(s==='"'&&!n&&(e=!e),s==="'"&&!e&&(n=!n))}return o}var fe=null,pe=null;var W=class extends HTMLElement{constructor(){super();this._props={};this._rendered=!1;this._disposeRender=null;this._innerDisposers=[];let e=this.constructor;this._root=e.shadow?this.attachShadow({mode:"open"}):this;for(let[o,r]of Object.entries(e.props))this._props[o]=v(this._coerce(this.getAttribute(o),r))}static{this.props={}}static{this.styles=""}static{this.shadow=!0}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;let e=this.constructor,o=null;if(e.styles&&e.shadow&&this._root instanceof ShadowRoot){let r=document.createElement("style");r.textContent=e.styles,this._root.appendChild(r),o=r}this._disposeRender=b(()=>{for(let c of this._innerDisposers)c();this._innerDisposers=[];let r=[],s=U();S(r);let i=this.render();S(s),this._innerDisposers=r;let a=Array.from(this._root.childNodes);for(let c of a)c!==o&&this._root.removeChild(c);i&&this._root.appendChild(i)}),this.onMount(),fe&&fe(this)}disconnectedCallback(){this._disposeRender&&(this._disposeRender(),this._disposeRender=null);for(let e of this._innerDisposers)e();this._innerDisposers=[],this.onUnmount(),pe&&pe(this)}attributeChangedCallback(e,o,r){let i=this.constructor.props[e];i&&this._props[e]&&(this._props[e].value=this._coerce(r,i))}prop(e){if(!this._props[e])throw new Error(`[tina4] Prop '${e}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[e]}emit(e,o){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,...o}))}onMount(){}onUnmount(){}_coerce(e,o){return o===Boolean?e!==null:o===Number?e!==null?Number(e):0:e??""}};var Z=[],N=null,D="history",Ue=!1,H=[],$=[],ge=0;function me(t,n){let e=[],o;t==="*"?o=".*":o=t.replace(/\{(\w+)\}/g,(s,i)=>(e.push(i),"([^/]+)"));let r=new RegExp(`^${o}$`);typeof n=="function"?Z.push({pattern:t,regex:r,paramNames:e,handler:n}):Z.push({pattern:t,regex:r,paramNames:e,handler:n.handler,guard:n.guard})}function G(t,n){if(D==="hash")if(n?.replace){let e=new URL(location.href);e.hash="#"+t,history.replaceState(null,"",e.toString()),L()}else location.hash="#"+t;else n?.replace?history.replaceState(null,"",t):history.pushState(null,"",t),L()}function L(){if(!N)return;let t=performance.now(),n=++ge,e=D==="hash"?location.hash.slice(1)||"/":location.pathname;for(let o of Z){let r=e.match(o.regex);if(!r)continue;let s={};if(o.paramNames.forEach((c,l)=>{s[c]=decodeURIComponent(r[l+1])}),o.guard){let c=o.guard();if(c===!1)return;if(typeof c=="string"){G(c,{replace:!0});return}}for(let c of $)c();$=[],N.innerHTML="";let i=[];S(i);let a=o.handler(s);if(a instanceof Promise)a.then(c=>{if(S(null),n!==ge){for(let d of i)d();return}he(N,c),$=i;let l=performance.now()-t;for(let d of H)d({path:e,params:s,pattern:o.pattern,durationMs:l})});else{S(null),he(N,a),$=i;let c=performance.now()-t;for(let l of H)l({path:e,params:s,pattern:o.pattern,durationMs:c})}return}}function he(t,n){n instanceof DocumentFragment||n instanceof Node?t.replaceChildren(n):typeof n=="string"?t.innerHTML=n:n!=null&&t.replaceChildren(document.createTextNode(String(n)))}var ve={start(t){if(N=document.querySelector(t.target),!N)throw new Error(`[tina4] Router target '${t.target}' not found in DOM`);D=t.mode??"history",Ue=!0,window.addEventListener("popstate",L),D==="hash"&&window.addEventListener("hashchange",L),document.addEventListener("click",n=>{if(n.metaKey||n.ctrlKey||n.shiftKey||n.altKey)return;let e=n.target.closest("a[href]");if(!e||e.origin!==location.origin||e.hasAttribute("target")||e.hasAttribute("download")||e.getAttribute("rel")?.includes("external"))return;n.preventDefault();let o=D==="hash"?e.getAttribute("href"):e.pathname;G(o)}),L()},on(t,n){return H.push(n),()=>{let e=H.indexOf(n);e>=0&&H.splice(e,1)}}};var y={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},J=[],V=[],ye=0;function ee(){try{return localStorage.getItem(y.tokenKey)}catch{return null}}function be(t){try{localStorage.setItem(y.tokenKey,t)}catch{}}async function O(t,n,e,o){let r={method:t,headers:{"Content-Type":"application/json",...y.headers}};if(y.auth){let u=ee();u&&(r.headers.Authorization=`Bearer ${u}`)}if(e!==void 0&&t!=="GET"){let u=typeof e=="object"&&e!==null?{...e}:e;if(y.auth&&typeof u=="object"&&u!==null){let g=ee();g&&(u.formToken=g)}r.body=JSON.stringify(u)}if(o?.headers&&Object.assign(r.headers,o.headers),o?.params){let u=Object.entries(o.params).map(([g,w])=>`${encodeURIComponent(g)}=${encodeURIComponent(String(w))}`).join("&");n+=(n.includes("?")?"&":"?")+u}let s=y.baseUrl+n;r._url=s,r._requestId=++ye;for(let u of J){let g=u(r);g&&(r=g)}let i=await fetch(s,r),a=i.headers.get("FreshToken");a&&be(a);let c=i.headers.get("Content-Type")??"",l;c.includes("json")?l=await i.json():l=await i.text();let d={status:i.status,data:l,ok:i.ok,headers:i.headers,_requestId:r._requestId};for(let u of V){let g=u(d);g&&(d=g)}if(!i.ok)throw d;return d.data}var we={configure(t){Object.assign(y,t)},get(t,n){return O("GET",t,void 0,n)},post(t,n,e){return O("POST",t,n,e)},put(t,n,e){return O("PUT",t,n,e)},patch(t,n,e){return O("PATCH",t,n,e)},delete(t,n){return O("DELETE",t,void 0,n)},async graphql(t,n,e,o){return O("POST",t,{query:n,variables:e||{}},o)},async upload(t,n,e){let o={method:"POST",headers:{...y.headers},body:n};if(delete o.headers["Content-Type"],delete o.headers["content-type"],y.auth){let d=ee();d&&(o.headers.Authorization=`Bearer ${d}`)}if(e?.headers&&Object.assign(o.headers,e.headers),e?.params){let d=Object.entries(e.params).map(([u,g])=>`${encodeURIComponent(u)}=${encodeURIComponent(String(g))}`).join("&");t+=(t.includes("?")?"&":"?")+d}let r=y.baseUrl+t;o._url=r,o._requestId=++ye;for(let d of J){let u=d(o);u&&(o=u)}let s=await fetch(r,o),i=s.headers.get("FreshToken");i&&be(i);let a=s.headers.get("Content-Type")??"",c;a.includes("json")?c=await s.json():c=await s.text();let l={status:s.status,data:c,ok:s.ok,headers:s.headers,_requestId:o._requestId};for(let d of V){let u=d(l);u&&(l=u)}if(!s.ok)throw l;return l.data},intercept(t,n){t==="request"?J.push(n):V.push(n)},_reset(){y.baseUrl="",y.auth=!1,y.tokenKey="tina4_token",y.headers={},J.length=0,V.length=0}};function Fe(t){let n=t.cacheStrategy??"network-first",e=JSON.stringify(t.precache??[]),o=t.offlineRoute?`'${t.offlineRoute}'`:"null";return`
2
2
  const CACHE = 'tina4-v1';
3
- const PRECACHE = ${t};
3
+ const PRECACHE = ${e};
4
4
  const OFFLINE = ${o};
5
5
 
6
6
  self.addEventListener('install', (e) => {
@@ -44,4 +44,5 @@ self.addEventListener('fetch', (e) => {
44
44
  ))
45
45
  );`}
46
46
  });
47
- `.trim()}function ce(e){let n={name:e.name,short_name:e.shortName??e.name,start_url:"/",display:e.display??"standalone",background_color:e.backgroundColor??"#ffffff",theme_color:e.themeColor??"#000000"};return e.icon&&(n.icons=[{src:e.icon,sizes:"192x192",type:"image/png"},{src:e.icon,sizes:"512x512",type:"image/png"}]),n}var le={register(e){let n=ce(e),t=new Blob([JSON.stringify(n)],{type:"application/json"}),o=document.createElement("link");o.rel="manifest",o.href=URL.createObjectURL(t),document.head.appendChild(o);let r=document.querySelector('meta[name="theme-color"]');if(r||(r=document.createElement("meta"),r.name="theme-color",document.head.appendChild(r)),r.content=e.themeColor??"#000000","serviceWorker"in navigator){let a=ae(e),i=new Blob([a],{type:"text/javascript"}),s=URL.createObjectURL(i);navigator.serviceWorker.register(s).catch(c=>{console.warn("[tina4] Service worker registration failed:",c)})}},generateServiceWorker(e){return ae(e)},generateManifest(e){return ce(e)}};var Re={reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,protocols:[]};function xe(e,n={}){let t={...Re,...n},o=h("connecting"),r=h(!1),a=h(null),i=h(null),s=h(0),c={message:[],open:[],close:[],error:[]},l=null,f=!1,d=t.reconnectDelay,p=null,v=0;function de(u){if(typeof u!="string")return u;try{return JSON.parse(u)}catch{return u}}function $(){o.value=v>0?"reconnecting":"connecting";try{l=new WebSocket(e,t.protocols)}catch{o.value="closed",r.value=!1;return}l.onopen=()=>{o.value="open",r.value=!0,i.value=null,v=0,d=t.reconnectDelay,s.value=0;for(let u of c.open)u()},l.onmessage=u=>{let g=de(u.data);a.value=g;for(let k of c.message)k(g)},l.onclose=u=>{o.value="closed",r.value=!1;for(let g of c.close)g(u.code,u.reason);!f&&t.reconnect&&v<t.reconnectAttempts&&fe()},l.onerror=u=>{i.value=u;for(let g of c.error)g(u)}}function fe(){v++,s.value=v,o.value="reconnecting",p=setTimeout(()=>{p=null,$()},d),d=Math.min(d*2,t.reconnectMaxDelay)}let G={status:o,connected:r,lastMessage:a,error:i,reconnectCount:s,send(u){if(!l||l.readyState!==WebSocket.OPEN)throw new Error("[tina4] WebSocket is not connected");let g=typeof u=="string"?u:JSON.stringify(u);l.send(g)},on(u,g){return c[u].push(g),()=>{let k=c[u],A=k.indexOf(g);A>=0&&k.splice(A,1)}},pipe(u,g){let k=A=>{u.value=g(A,u.value)};return G.on("message",k)},close(u,g){f=!0,p&&(clearTimeout(p),p=null),l&&l.close(u??1e3,g??""),o.value="closed",r.value=!1}};return $(),G}var ue={connect:xe};return ve(Ae);})();
47
+ `.trim()}function ke(t){let n={name:t.name,short_name:t.shortName??t.name,start_url:"/",display:t.display??"standalone",background_color:t.backgroundColor??"#ffffff",theme_color:t.themeColor??"#000000"};return t.icon&&(n.icons=[{src:t.icon,sizes:"192x192",type:"image/png"},{src:t.icon,sizes:"512x512",type:"image/png"}]),n}var Se={register(t){let n=ke(t),e=new Blob([JSON.stringify(n)],{type:"application/json"}),o=document.createElement("link");o.rel="manifest",o.href=URL.createObjectURL(e),document.head.appendChild(o);let r=document.querySelector('meta[name="theme-color"]');r||(r=document.createElement("meta"),r.name="theme-color",document.head.appendChild(r)),r.content=t.themeColor??"#000000","serviceWorker"in navigator&&(t.swUrl?navigator.serviceWorker.register(t.swUrl).catch(s=>{console.warn("[tina4] Service worker registration failed:",s)}):navigator.serviceWorker.register("/sw.js").catch(()=>{console.info("[tina4] No service worker at /sw.js. Use pwa.generateServiceWorker() to create one, or pass swUrl in config.")}))},generateServiceWorker(t){return Fe(t)},generateManifest(t){return ke(t)}};var We={reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,protocols:[]};function $e(t,n={}){let e={...We,...n},o=v("connecting"),r=v(!1),s=v(null),i=v(null),a=v(0),c={message:[],open:[],close:[],error:[]},l=null,d=!1,u=e.reconnectDelay,g=null,w=0;function C(p){if(typeof p!="string")return p;try{return JSON.parse(p)}catch{return p}}function E(){o.value=w>0?"reconnecting":"connecting";try{l=new WebSocket(t,e.protocols)}catch{o.value="closed",r.value=!1;return}l.onopen=()=>{o.value="open",r.value=!0,i.value=null,w=0,u=e.reconnectDelay,a.value=0;for(let p of c.open)p()},l.onmessage=p=>{let h=C(p.data);s.value=h;for(let k of c.message)k(h)},l.onclose=p=>{o.value="closed",r.value=!1;for(let h of c.close)h(p.code,p.reason);!d&&e.reconnect&&w<e.reconnectAttempts&&x()},l.onerror=p=>{i.value=p;for(let h of c.error)h(p)}}function x(){w++,a.value=w,o.value="reconnecting",g=setTimeout(()=>{g=null,E()},u),u=Math.min(u*2,e.reconnectMaxDelay)}let _={status:o,connected:r,lastMessage:s,error:i,reconnectCount:a,send(p){if(!l||l.readyState!==WebSocket.OPEN)throw new Error("[tina4] WebSocket is not connected");let h=typeof p=="string"?p:JSON.stringify(p);l.send(h)},on(p,h){return c[p].push(h),()=>{let k=c[p],A=k.indexOf(h);A>=0&&k.splice(A,1)}},pipe(p,h){let k=A=>{p.value=h(A,p.value)};return _.on("message",k)},close(p,h){d=!0,g&&(clearTimeout(g),g=null),l&&l.close(p??1e3,h??""),o.value="closed",r.value=!1}};return E(),_}var Ee={connect:$e};var Ge={mode:"eventsource",method:"GET",headers:{},body:void 0,reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,events:[],json:!0};function Je(t,n={}){let e={...Ge,...n},o=v("connecting"),r=v(!1),s=v(null),i=v(null),a=v(null),c=v(0),l={message:[],open:[],close:[],error:[]},d=null,u=null,g=!1,w=e.reconnectDelay,C=null,E=0;function x(f){if(!e.json||typeof f!="string")return f;try{return JSON.parse(f)}catch{return f}}function _(f,m){s.value=f,i.value=m;for(let T of l.message)T(f,m??void 0)}function p(){o.value="open",r.value=!0,a.value=null,E=0,w=e.reconnectDelay,c.value=0;for(let f of l.open)f()}function h(){o.value="closed",r.value=!1;for(let f of l.close)f();!g&&e.reconnect&&E<e.reconnectAttempts&&_e()}function k(f){a.value=f;for(let m of l.error)m(f)}function A(){o.value=E>0?"reconnecting":"connecting";try{d=new EventSource(t)}catch{o.value="closed",r.value=!1;return}d.onopen=()=>p(),d.onmessage=f=>{_(x(f.data),null)};for(let f of e.events)d.addEventListener(f,m=>{_(x(m.data),f)});d.onerror=f=>{k(f),d&&d.readyState===2&&(d=null,h())}}function Ce(){o.value=E>0?"reconnecting":"connecting",u=new AbortController;let f={method:e.method,headers:e.headers,signal:u.signal};e.body!==void 0&&(f.body=typeof e.body=="string"?e.body:JSON.stringify(e.body)),fetch(t,f).then(async m=>{if(!m.ok){k(new Error(`[tina4] SSE fetch ${m.status}`)),h();return}p();let T=m.body.getReader(),M=new TextDecoder,P="";for(;;){let{done:Re,value:xe}=await T.read();if(Re)break;P+=M.decode(xe,{stream:!0});let re=P.split(`
48
+ `);P=re.pop();for(let Ae of re){let se=Ae.trim();se&&_(x(se),null)}}let oe=P.trim();oe&&_(x(oe),null),u=null,h()}).catch(m=>{m.name!=="AbortError"&&(u=null,k(m),h())})}function _e(){E++,c.value=E,o.value="reconnecting",C=setTimeout(()=>{C=null,te()},w),w=Math.min(w*2,e.reconnectMaxDelay)}function te(){e.mode==="fetch"?Ce():A()}let ne={status:o,connected:r,lastMessage:s,lastEvent:i,error:a,reconnectCount:c,on(f,m){return l[f].push(m),()=>{let T=l[f],M=T.indexOf(m);M>=0&&T.splice(M,1)}},pipe(f,m){let T=M=>{f.value=m(M,f.value)};return ne.on("message",T)},close(){g=!0,C&&(clearTimeout(C),C=null),d&&(d.close(),d=null),u&&(u.abort(),u=null),o.value="closed",r.value=!1}};return te(),ne}var Te={connect:Je};return De(Ve);})();
@@ -448,6 +448,27 @@ let _serverHandle: { close: () => void; router: Router; port: number } | null =
448
448
  * Thin wrapper around startServer() for cross-framework parity with PHP and Ruby.
449
449
  */
450
450
  export async function start(config?: Tina4Config): Promise<{ close: () => void; router: Router; port: number }> {
451
+ const isManaged = process.argv.includes('--managed');
452
+ if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== 'true') {
453
+ console.log();
454
+ console.log('='.repeat(60));
455
+ console.log();
456
+ console.log(' Tina4 must be started with the tina4 CLI:');
457
+ console.log();
458
+ console.log(' tina4 serve (development)');
459
+ console.log(' tina4 serve --production (production)');
460
+ console.log();
461
+ console.log(' Install: cargo install tina4');
462
+ console.log(' Docs: https://tina4.com');
463
+ console.log();
464
+ console.log(' To run directly, add to .env:');
465
+ console.log(' TINA4_OVERRIDE_CLIENT=true');
466
+ console.log();
467
+ console.log('='.repeat(60));
468
+ console.log();
469
+ process.exit(1);
470
+ }
471
+
451
472
  _serverHandle = await startServer(config);
452
473
  return _serverHandle;
453
474
  }
@@ -26,6 +26,9 @@ import { createHash } from "node:crypto";
26
26
  import { randomUUID } from "node:crypto";
27
27
  import type { Socket } from "node:net";
28
28
  import type { Server } from "node:http";
29
+ import type { WebSocketConnection } from "./websocketConnection.js";
30
+ import type { WebSocketRouteHandler } from "./types.js";
31
+ import { Router } from "./router.js";
29
32
 
30
33
  // ── Constants ────────────────────────────────────────────────
31
34
 
@@ -174,6 +177,8 @@ export class WebSocketServer {
174
177
  private rooms: Map<string, Set<string>> = new Map();
175
178
  /** clientRooms[clientId] = Set of roomNames */
176
179
  private clientRooms: Map<string, Set<string>> = new Map();
180
+ /** Route-style handlers registered via route(), keyed by path */
181
+ private _routeHandlers: Map<string, (conn: WebSocketConnection) => void | Promise<void>> = new Map();
177
182
 
178
183
  constructor(options?: { port?: number }) {
179
184
  this.port = options?.port ?? parseInt(process.env.TINA4_WS_PORT ?? "8080", 10);
@@ -189,6 +194,46 @@ export class WebSocketServer {
189
194
  return this;
190
195
  }
191
196
 
197
+ /**
198
+ * Register a WebSocket handler for a path (decorator style, matches Python).
199
+ *
200
+ * The handler receives a WebSocketConnection and sets up callbacks via
201
+ * `conn.onMessage(handler)` and `conn.onClose(handler)`.
202
+ *
203
+ * Internally this creates an adapter that converts from the decorator style
204
+ * to the Router's `(conn, event, data)` style and registers it via
205
+ * `Router.websocket()`.
206
+ */
207
+ route(path: string, handler: (conn: WebSocketConnection) => void | Promise<void>): void {
208
+ this._routeHandlers.set(path, handler);
209
+
210
+ // Adapt to Router's (conn, event, data) style
211
+ const adapter: WebSocketRouteHandler = async (conn, event, data) => {
212
+ if (event === "open") {
213
+ const result = handler(conn);
214
+ if (result instanceof Promise) {
215
+ await result;
216
+ }
217
+ } else if (event === "message") {
218
+ if (conn._onMessage) {
219
+ const result = conn._onMessage(data);
220
+ if (result instanceof Promise) {
221
+ await result;
222
+ }
223
+ }
224
+ } else if (event === "close") {
225
+ if (conn._onClose) {
226
+ const result = conn._onClose();
227
+ if (result instanceof Promise) {
228
+ await result;
229
+ }
230
+ }
231
+ }
232
+ };
233
+
234
+ Router.websocket(path, adapter);
235
+ }
236
+
192
237
  /**
193
238
  * Broadcast a message to all connected clients.
194
239
  *
@@ -23,4 +23,16 @@ export interface WebSocketConnection {
23
23
  leaveRoom(roomName: string): void;
24
24
  /** Close this connection */
25
25
  close(): void;
26
+
27
+ // ── Callback properties (used by WebSocketServer.route() adapter) ──
28
+
29
+ /** Internal message callback, set via onMessage(). */
30
+ _onMessage: ((data: string) => void | Promise<void>) | null;
31
+ /** Internal close callback, set via onClose(). */
32
+ _onClose: (() => void | Promise<void>) | null;
33
+
34
+ /** Register a message handler (decorator style, matches Python). */
35
+ onMessage(handler: (data: string) => void | Promise<void>): void;
36
+ /** Register a close handler (decorator style, matches Python). */
37
+ onClose(handler: () => void | Promise<void>): void;
26
38
  }