@esmalley/ts-utils 1.0.1 → 2.0.1

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 (99) hide show
  1. package/dist/cjs/Arithmetic.d.ts.map +1 -0
  2. package/dist/cjs/Arithmetic.js +9 -0
  3. package/dist/cjs/Arrayifier.d.ts.map +1 -0
  4. package/dist/cjs/Arrayifier.js +69 -0
  5. package/dist/cjs/CSV.d.ts.map +1 -0
  6. package/dist/cjs/CSV.js +39 -0
  7. package/dist/cjs/Color.d.ts.map +1 -0
  8. package/dist/cjs/Color.js +348 -0
  9. package/dist/cjs/Dates.d.ts.map +1 -0
  10. package/dist/cjs/Dates.js +358 -0
  11. package/dist/cjs/Kontororu/Socket.d.ts.map +1 -0
  12. package/dist/cjs/Kontororu/Socket.js +221 -0
  13. package/dist/cjs/Kontororu.d.ts.map +1 -0
  14. package/dist/cjs/Kontororu.js +27 -0
  15. package/dist/cjs/Objector.d.ts.map +1 -0
  16. package/dist/cjs/Objector.js +124 -0
  17. package/dist/cjs/Sorter.d.ts.map +1 -0
  18. package/dist/cjs/Sorter.js +32 -0
  19. package/dist/cjs/Style.d.ts.map +1 -0
  20. package/dist/cjs/Style.js +475 -0
  21. package/dist/cjs/Text.d.ts.map +1 -0
  22. package/dist/cjs/Text.js +39 -0
  23. package/dist/{Theme.d.ts.map → cjs/Theme.d.ts.map} +1 -1
  24. package/dist/cjs/Theme.js +507 -0
  25. package/dist/cjs/Toaster.d.ts.map +1 -0
  26. package/dist/cjs/Toaster.js +49 -0
  27. package/dist/cjs/index.d.ts.map +1 -0
  28. package/dist/cjs/index.js +27 -0
  29. package/dist/cjs/package.json +1 -0
  30. package/dist/esm/Arithmetic.d.ts +4 -0
  31. package/dist/esm/Arithmetic.d.ts.map +1 -0
  32. package/dist/esm/Arrayifier.d.ts +29 -0
  33. package/dist/esm/Arrayifier.d.ts.map +1 -0
  34. package/dist/esm/CSV.d.ts +10 -0
  35. package/dist/esm/CSV.d.ts.map +1 -0
  36. package/dist/esm/Color.d.ts +93 -0
  37. package/dist/esm/Color.d.ts.map +1 -0
  38. package/dist/esm/Dates.d.ts +51 -0
  39. package/dist/esm/Dates.d.ts.map +1 -0
  40. package/dist/esm/Kontororu/Socket.d.ts +44 -0
  41. package/dist/esm/Kontororu/Socket.d.ts.map +1 -0
  42. package/dist/esm/Kontororu.d.ts +8 -0
  43. package/dist/esm/Kontororu.d.ts.map +1 -0
  44. package/dist/esm/Objector.d.ts +32 -0
  45. package/dist/esm/Objector.d.ts.map +1 -0
  46. package/dist/esm/Sorter.d.ts +8 -0
  47. package/dist/esm/Sorter.d.ts.map +1 -0
  48. package/dist/esm/Style.d.ts +63 -0
  49. package/dist/esm/Style.d.ts.map +1 -0
  50. package/dist/esm/Text.d.ts +5 -0
  51. package/dist/esm/Text.d.ts.map +1 -0
  52. package/dist/esm/Theme.d.ts +1413 -0
  53. package/dist/esm/Theme.d.ts.map +1 -0
  54. package/dist/esm/Toaster.d.ts +24 -0
  55. package/dist/esm/Toaster.d.ts.map +1 -0
  56. package/dist/esm/index.d.ts +12 -0
  57. package/dist/esm/index.d.ts.map +1 -0
  58. package/package.json +8 -5
  59. package/dist/Arithmetic.d.ts.map +0 -1
  60. package/dist/Arrayifier.d.ts.map +0 -1
  61. package/dist/CSV.d.ts.map +0 -1
  62. package/dist/Color.d.ts.map +0 -1
  63. package/dist/Dates.d.ts.map +0 -1
  64. package/dist/Kontororu/Socket.d.ts.map +0 -1
  65. package/dist/Kontororu.d.ts.map +0 -1
  66. package/dist/Objector.d.ts.map +0 -1
  67. package/dist/Sorter.d.ts.map +0 -1
  68. package/dist/Style.d.ts.map +0 -1
  69. package/dist/Text.d.ts.map +0 -1
  70. package/dist/Toaster.d.ts.map +0 -1
  71. package/dist/index.d.ts.map +0 -1
  72. /package/dist/{Arithmetic.d.ts → cjs/Arithmetic.d.ts} +0 -0
  73. /package/dist/{Arrayifier.d.ts → cjs/Arrayifier.d.ts} +0 -0
  74. /package/dist/{CSV.d.ts → cjs/CSV.d.ts} +0 -0
  75. /package/dist/{Color.d.ts → cjs/Color.d.ts} +0 -0
  76. /package/dist/{Dates.d.ts → cjs/Dates.d.ts} +0 -0
  77. /package/dist/{Kontororu → cjs/Kontororu}/Socket.d.ts +0 -0
  78. /package/dist/{Kontororu.d.ts → cjs/Kontororu.d.ts} +0 -0
  79. /package/dist/{Objector.d.ts → cjs/Objector.d.ts} +0 -0
  80. /package/dist/{Sorter.d.ts → cjs/Sorter.d.ts} +0 -0
  81. /package/dist/{Style.d.ts → cjs/Style.d.ts} +0 -0
  82. /package/dist/{Text.d.ts → cjs/Text.d.ts} +0 -0
  83. /package/dist/{Theme.d.ts → cjs/Theme.d.ts} +0 -0
  84. /package/dist/{Toaster.d.ts → cjs/Toaster.d.ts} +0 -0
  85. /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  86. /package/dist/{Arithmetic.js → esm/Arithmetic.js} +0 -0
  87. /package/dist/{Arrayifier.js → esm/Arrayifier.js} +0 -0
  88. /package/dist/{CSV.js → esm/CSV.js} +0 -0
  89. /package/dist/{Color.js → esm/Color.js} +0 -0
  90. /package/dist/{Dates.js → esm/Dates.js} +0 -0
  91. /package/dist/{Kontororu → esm/Kontororu}/Socket.js +0 -0
  92. /package/dist/{Kontororu.js → esm/Kontororu.js} +0 -0
  93. /package/dist/{Objector.js → esm/Objector.js} +0 -0
  94. /package/dist/{Sorter.js → esm/Sorter.js} +0 -0
  95. /package/dist/{Style.js → esm/Style.js} +0 -0
  96. /package/dist/{Text.js → esm/Text.js} +0 -0
  97. /package/dist/{Theme.js → esm/Theme.js} +0 -0
  98. /package/dist/{Toaster.js → esm/Toaster.js} +0 -0
  99. /package/dist/{index.js → esm/index.js} +0 -0
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ /* eslint-disable one-var-declaration-per-line */
3
+ /* eslint-disable one-var */
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Dates = void 0;
6
+ class Dates {
7
+ // constructor() {
8
+ // }
9
+ /*
10
+ * Robust Date Parsing
11
+ * Handles:
12
+ * - Date Objects / Numbers (Timestamps)
13
+ * - ISO Strings (2025-01-01) -> Forces Local Midnight
14
+ * - US Formats (01/05/2026)
15
+ * - Mixed Time Formats (23:00 pm, 5:00pm, 14:30:00)
16
+ */
17
+ static parse(str, utc = false) {
18
+ // 1. Handle Null / Undefined -> Return Now
19
+ if (!str) {
20
+ return new Date();
21
+ }
22
+ // 2. Handle Existing Date Objects -> Return Copy
23
+ if (str instanceof Date) {
24
+ return new Date(str.getTime());
25
+ }
26
+ // 3. Handle Timestamps (Numbers)
27
+ if (typeof str === 'number') {
28
+ return new Date(str);
29
+ }
30
+ // 4. Handle Strings
31
+ if (typeof str === 'string') {
32
+ const input = str.trim();
33
+ // CASE A: Strict ISO Date "YYYY-MM-DD"
34
+ // Native JS parses this as UTC Midnight, which often shows as
35
+ // previous day 7pm EST. We force "T00:00:00" to make it Local Midnight.
36
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
37
+ return new Date(`${input}T00:00:00`);
38
+ }
39
+ // CASE B: Manual Parsing for Complex Strings
40
+ // This handles "2026-01-05 23:03:19 pm", "01/05/2026", etc.
41
+ // Step 1: Extract Date Part (YYYY-MM-DD or MM/DD/YYYY)
42
+ // Regex looks for: (Group 1: Year/Month) -or/ (Group 2: Month/Day) -or/ (Group 3: Day/Year)
43
+ let year, month, day, timePart = '';
44
+ // Match YYYY-MM-DD or YYYY/MM/DD
45
+ const isoMatch = input.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(.*)$/);
46
+ // Match MM/DD/YYYY or MM-DD-YYYY
47
+ const usMatch = input.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})(.*)$/);
48
+ if (isoMatch) {
49
+ year = parseInt(isoMatch[1], 10);
50
+ month = parseInt(isoMatch[2], 10) - 1; // JS Months are 0-11
51
+ day = parseInt(isoMatch[3], 10);
52
+ timePart = isoMatch[4];
53
+ }
54
+ else if (usMatch) {
55
+ year = parseInt(usMatch[3], 10);
56
+ month = parseInt(usMatch[1], 10) - 1;
57
+ day = parseInt(usMatch[2], 10);
58
+ timePart = usMatch[4];
59
+ }
60
+ else {
61
+ // Fallback: Let the browser try its best if our regex fails
62
+ const d = new Date(input);
63
+ return isNaN(d.getTime()) ? new Date() : d;
64
+ }
65
+ // Step 2: Extract Time Part
66
+ let hours = 0;
67
+ let minutes = 0;
68
+ let seconds = 0;
69
+ // Look for HH:MM(:SS) and optional AM/PM in the remaining string
70
+ if (timePart && timePart.trim().length > 0) {
71
+ // Matches: 23:03, 23:03:19, 5:00pm, 5:00 pm
72
+ const timeMatch = timePart.match(/(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)?/);
73
+ if (timeMatch) {
74
+ hours = parseInt(timeMatch[1], 10);
75
+ minutes = parseInt(timeMatch[2], 10);
76
+ seconds = timeMatch[3] ? parseInt(timeMatch[3], 10) : 0;
77
+ const meridiem = timeMatch[4] ? timeMatch[4].toLowerCase() : null;
78
+ // Step 3: Normalize Hours (12h to 24h)
79
+ if (meridiem === 'pm' && hours < 12) {
80
+ hours += 12;
81
+ }
82
+ if (meridiem === 'am' && hours === 12) {
83
+ hours = 0;
84
+ }
85
+ // Note: If input is "23:00 pm", we ignore the 'pm' because 23 > 12.
86
+ }
87
+ }
88
+ if (utc) {
89
+ return new Date(Date.UTC(year, month, day, hours, minutes, seconds));
90
+ }
91
+ // Step 4: Construct Date in Local Time
92
+ return new Date(year, month, day, hours, minutes, seconds);
93
+ }
94
+ return new Date();
95
+ }
96
+ static utc(date) {
97
+ const d = this.parse(date);
98
+ return new Date(d.getTime() + (d.getTimezoneOffset() * 60000));
99
+ }
100
+ static getMonthsShort() {
101
+ return ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
102
+ }
103
+ static getMonths() {
104
+ return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
105
+ }
106
+ static getDaysShort() {
107
+ return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
108
+ }
109
+ static getDays() {
110
+ return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
111
+ }
112
+ /**
113
+ * Format a date using php syntax
114
+ *
115
+ | Token | Meaning | Example |
116
+ | ----- | ------------------------- | ------- |
117
+ | `Y` | 4-digit year | 2025 |
118
+ | `y` | 2-digit year | 25 |
119
+ | `m` | 2-digit month | 03 |
120
+ | `n` | month (no leading zero) | 3 |
121
+ | `d` | day (2-digit) | 09 |
122
+ | `j` | day (no leading zero) | 9 |
123
+ | `S` | Ordinal suffix | th |
124
+ | `H` | 24-hour | 14 |
125
+ | `G` | 24-hour (no leading zero) | 14 |
126
+ | `h` | 12-hour | 02 |
127
+ | `g` | 12-hour (no leading zero) | 2 |
128
+ | `i` | minutes | 05 |
129
+ | `s` | seconds | 09 |
130
+ | `A` | AM/PM | PM |
131
+ | `a` | am/pm | pm |
132
+ | `w` | day of week (0–6) | 1 |
133
+ | `N` | day of week (1–7) | 2 |
134
+ | `M` | short month name | Mar |
135
+ | `F` | full month name | March |
136
+ | `D` | short weekday | Mon |
137
+ | `l` | full weekday | Monday |
138
+ */
139
+ static format(dateInput, format) {
140
+ const date = this.parse(dateInput);
141
+ const pad = (n) => String(n).padStart(2, '0');
142
+ const monthsShort = this.getMonthsShort();
143
+ const monthsLong = this.getMonths();
144
+ const daysShort = this.getDaysShort();
145
+ const daysLong = this.getDays();
146
+ /*
147
+ const Y = date.getUTCFullYear();
148
+ const y = String(Y).slice(-2);
149
+ const month = date.getUTCMonth(); // 0-11
150
+ const dateNum = date.getUTCDate(); // 1-31
151
+ const day = date.getUTCDay(); // 0-6, Sun = 0
152
+ const hours = date.getUTCHours(); // 0-23
153
+ const minutes = date.getUTCMinutes();
154
+ const seconds = date.getUTCSeconds();
155
+ */
156
+ const Y = date.getFullYear();
157
+ const y = String(Y).slice(-2);
158
+ const month = date.getMonth(); // 0-11
159
+ const dateNum = date.getDate(); // 1-31
160
+ const day = date.getDay(); // 0-6, Sun = 0
161
+ const hours = date.getHours(); // 0-23
162
+ const minutes = date.getMinutes();
163
+ const seconds = date.getSeconds();
164
+ // Logic for ordinal suffix (st, nd, rd, th)
165
+ const getOrdinalSuffix = (n) => {
166
+ const v = n % 100;
167
+ // 11th, 12th, 13th are exceptions to the 1st, 2nd, 3rd rule
168
+ if (v >= 11 && v <= 13) {
169
+ return 'th';
170
+ }
171
+ switch (n % 10) {
172
+ case 1: {
173
+ return 'st';
174
+ }
175
+ case 2: {
176
+ return 'nd';
177
+ }
178
+ case 3: {
179
+ return 'rd';
180
+ }
181
+ default: {
182
+ return 'th';
183
+ }
184
+ }
185
+ };
186
+ const tokens = {
187
+ /// Year
188
+ Y: String(Y),
189
+ y,
190
+ // Month
191
+ m: pad(month + 1),
192
+ n: String(month + 1),
193
+ M: monthsShort[month],
194
+ F: monthsLong[month],
195
+ // Day
196
+ d: pad(dateNum),
197
+ j: String(dateNum),
198
+ D: daysShort[day],
199
+ l: daysLong[day],
200
+ w: String(day), // 0 (Sun) - 6
201
+ N: String(day === 0 ? 7 : day), // 1 (Mon) - 7 (Sun)
202
+ S: getOrdinalSuffix(dateNum),
203
+ // Time
204
+ H: pad(hours),
205
+ G: String(hours),
206
+ h: pad(((hours + 11) % 12) + 1),
207
+ g: String(((hours + 11) % 12) + 1),
208
+ i: pad(minutes),
209
+ s: pad(seconds),
210
+ // AM/PM
211
+ A: hours < 12 ? 'AM' : 'PM',
212
+ a: hours < 12 ? 'am' : 'pm',
213
+ };
214
+ // Replace tokens using regex
215
+ return format.replace(/\\(.)|([a-zA-Z])/g, (_, esc, token) => {
216
+ if (esc) {
217
+ // literal escaped char like \H or \Y → return the raw letter
218
+ return esc;
219
+ }
220
+ return tokens[token] ?? token;
221
+ });
222
+ }
223
+ static add(date, amount, unit) {
224
+ const d = this.parse(date);
225
+ if (unit === 'years') {
226
+ const originalDay = d.getDate();
227
+ d.setFullYear(d.getFullYear() + amount);
228
+ // Handle Leap Year Rollover:
229
+ // Feb 29, 2024 + 1 year -> Mar 1, 2025 (Standard JS behavior)
230
+ // If strict "same day or last day of month" logic is desired (turning it into Feb 28):
231
+ if (d.getDate() !== originalDay) {
232
+ d.setDate(0); // Set to last day of previous month (Feb 28)
233
+ }
234
+ }
235
+ else if (unit === 'months') {
236
+ const originalDay = d.getDate();
237
+ d.setMonth(d.getMonth() + amount);
238
+ // Handle rollover: Jan 31 + 1 month -> Feb 28/29
239
+ if (d.getDate() !== originalDay) {
240
+ d.setDate(0); // Set to last day of previous month
241
+ }
242
+ }
243
+ else if (unit === 'days') {
244
+ // Use setDate to be DST safe (24h addition via ms is unsafe across DST)
245
+ d.setDate(d.getDate() + amount);
246
+ }
247
+ else {
248
+ const map = {
249
+ hours: amount * 60 * 60 * 1000,
250
+ minutes: amount * 60 * 1000,
251
+ };
252
+ // Use getTime() for day/hour/minute units for simple millisecond addition
253
+ d.setTime(d.getTime() + map[unit]);
254
+ }
255
+ return d;
256
+ }
257
+ static subtract(date, amount, unit) {
258
+ return this.add(date, -amount, unit);
259
+ }
260
+ static fromNow(date) {
261
+ const d = this.parse(date);
262
+ const diff = Date.now() - d.getTime();
263
+ const mins = Math.floor(diff / 60000);
264
+ if (Math.abs(mins) < 1) {
265
+ return 'just now';
266
+ }
267
+ // Handle future dates roughly
268
+ if (mins < 0) {
269
+ return 'in the future';
270
+ }
271
+ if (mins < 60) {
272
+ return `${mins}m ago`;
273
+ }
274
+ const hours = Math.floor(mins / 60);
275
+ if (hours < 24) {
276
+ return `${hours}h ago`;
277
+ }
278
+ const days = Math.floor(hours / 24);
279
+ return `${days}d ago`;
280
+ }
281
+ /**
282
+ * Find the closest date in an array of dates
283
+ */
284
+ static getClosestDate(dateToMatch, datesArray) {
285
+ if (!datesArray.length) {
286
+ return null;
287
+ }
288
+ const matchDate = this.parse(dateToMatch).getTime();
289
+ let closestDate = null;
290
+ let closestDist = Infinity;
291
+ // eslint-disable-next-line no-restricted-syntax
292
+ for (const dateStr of datesArray) {
293
+ const currDate = this.parse(dateStr).getTime();
294
+ const dist = Math.abs(currDate - matchDate);
295
+ if (dist < closestDist) {
296
+ closestDist = dist;
297
+ closestDate = dateStr;
298
+ }
299
+ else if (dist === closestDist) {
300
+ // Tie-breaker: Prefer the date that is in the future relative to the matchDate
301
+ // Or if both are same direction, just keep the current one (or implementation defined)
302
+ // Requirement: "Both 17th and 19th have same dist. It should pick 19th"
303
+ if (currDate > matchDate) {
304
+ closestDate = dateStr;
305
+ }
306
+ }
307
+ }
308
+ return closestDate;
309
+ }
310
+ static getTodayEST() {
311
+ return this.format(new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }), 'Y-m-d');
312
+ }
313
+ static getStartOfDay(date) {
314
+ const d = this.parse(date);
315
+ d.setHours(0, 0, 0, 0);
316
+ return d;
317
+ }
318
+ static getStartOfMonth(date) {
319
+ const d = this.parse(date);
320
+ d.setDate(1);
321
+ d.setHours(0, 0, 0, 0);
322
+ return d;
323
+ }
324
+ static getStartOfGrid(date) {
325
+ const d = this.getStartOfMonth(date);
326
+ const dayOfWeek = d.getDay(); // 0 (Sunday) is the start in standard JS
327
+ // Move back to the beginning of the week
328
+ const result = this.parse(d);
329
+ // Subtract days to get to the start of the week (Sunday)
330
+ result.setDate(d.getDate() - dayOfWeek);
331
+ return result;
332
+ }
333
+ static isSameDay(date1, date2) {
334
+ if (!date1 || !date2) {
335
+ return false;
336
+ }
337
+ const d1 = this.parse(date1);
338
+ const d2 = this.parse(date2);
339
+ return (d1.getFullYear() === d2.getFullYear() &&
340
+ d1.getMonth() === d2.getMonth() &&
341
+ d1.getDate() === d2.getDate());
342
+ }
343
+ // Helper to check if one date is before another (ignoring time)
344
+ static isBeforeDay(date1, date2) {
345
+ if (!date1 || !date2) {
346
+ return false;
347
+ }
348
+ return this.getStartOfDay(date1).getTime() < this.getStartOfDay(date2).getTime();
349
+ }
350
+ // Helper to check if one date is after another (ignoring time)
351
+ static isAfterDay(date1, date2) {
352
+ if (!date1 || !date2) {
353
+ return false;
354
+ }
355
+ return this.getStartOfDay(date1).getTime() > this.getStartOfDay(date2).getTime();
356
+ }
357
+ }
358
+ exports.Dates = Dates;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Socket.d.ts","sourceRoot":"","sources":["../../../src/Kontororu/Socket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAS3C,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,WAAW,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAUD,cAAM,MAAO,SAAQ,SAAS;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAY;IAEvB,OAAO,CAAC,gBAAgB,CAAgC;IAExD,OAAO,CAAC,QAAQ,CAA4I;IAE5J,OAAO,CAAC,GAAG,CAAoE;IAE/E,OAAO,CAAC,UAAU,CAAC,CAAS;IAE5B,OAAO,CAAC,aAAa,CAAuB;IAG5C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,iBAAiB,CAAC,CAAiB;IAG3C,OAAO,CAAC,wBAAwB,CAAsB;IAEtD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAQ;IAE9C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA2C;IAEnF,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA2C;;IAyC5E,OAAO,CAAC,UAAU,EAAE,MAAM;IA+BjC;;;OAGG;IACI,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa;IAa1C,UAAU;IAYjB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,WAAW;IA+BnB,OAAO,CAAC,cAAc;IA+BtB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,YAAY;CAMrB;AAED,eAAO,MAAM,MAAM,QAAe,CAAC"}
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.socket = void 0;
4
+ const Kontororu_js_1 = require("@/Kontororu.js");
5
+ const hostname = process.env.NEXT_PUBLIC_WS_HOST;
6
+ const port = process.env.NEXT_PUBLIC_WS_PORT;
7
+ const path = process.env.NEXT_PUBLIC_WS_PATH;
8
+ const debug = false;
9
+ class Socket extends Kontororu_js_1.Kontororu {
10
+ constructor() {
11
+ super();
12
+ this.connection_state = 'connected';
13
+ this.protocol = (typeof window !== 'undefined' && window.location && window.location.protocol && window.location.protocol === 'https:' ? 'wss:' : 'ws:');
14
+ this.url = `${this.protocol}//${hostname}${port ? `:${port}` : ''}/${path}`;
15
+ this.message_queue = [];
16
+ // Reconnection logic
17
+ this.reconnect_attempts = 0;
18
+ this.should_reconnect = true;
19
+ // Heartbeat / Sleep Detection Logic
20
+ this.last_heartbeat_timestamp = Date.now();
21
+ // How often the server pings THIS specific client (5 seconds)
22
+ this.HEARTBEAT_INTERVAL_MS = 5000;
23
+ // Calculate threshold: 2 missed beats + 1 second of network jitter buffer
24
+ this.SUSPENSION_THRESHOLD_MS = (this.HEARTBEAT_INTERVAL_MS * 2) + 1000;
25
+ // 3 Heartbeats + 1s buffer = 16 seconds
26
+ this.DISCONNECT_THRESHOLD_MS = (this.HEARTBEAT_INTERVAL_MS * 3) + 1000;
27
+ if (typeof document !== 'undefined') {
28
+ document.addEventListener('visibilitychange', (event) => {
29
+ if (document.visibilityState === 'visible') {
30
+ this.check_staleness('tab_switch');
31
+ }
32
+ });
33
+ }
34
+ if (typeof window !== 'undefined') {
35
+ const offlineChecker = () => {
36
+ const check = 3;
37
+ let checked = 1;
38
+ const checker = () => {
39
+ if (checked > check) {
40
+ return;
41
+ }
42
+ setTimeout(() => {
43
+ this.check_staleness('offline');
44
+ checked++;
45
+ checker();
46
+ }, (this.HEARTBEAT_INTERVAL_MS) + 500);
47
+ };
48
+ checker();
49
+ };
50
+ window.addEventListener('online', () => {
51
+ window.removeEventListener('offline', offlineChecker);
52
+ });
53
+ window.addEventListener('offline', offlineChecker);
54
+ }
55
+ }
56
+ connect(session_id) {
57
+ if (!session_id) {
58
+ console.warn('session_id required to open ws');
59
+ return;
60
+ }
61
+ if (this.ws &&
62
+ (this.ws.readyState === WebSocket.OPEN ||
63
+ this.ws.readyState === WebSocket.CONNECTING)) {
64
+ return;
65
+ }
66
+ this.ws = new WebSocket(this.url);
67
+ if (debug)
68
+ console.log('new websocket');
69
+ this.session_id = session_id;
70
+ // Reset heartbeat timer on new connection
71
+ this.last_heartbeat_timestamp = Date.now();
72
+ this.ws.addEventListener('open', (event) => this.handle_open(event));
73
+ this.ws.addEventListener('message', (event) => this.handle_message(event));
74
+ this.ws.addEventListener('close', (event) => this.handle_close(event));
75
+ this.ws.addEventListener('error', (event) => this.handle_error(event));
76
+ }
77
+ /**
78
+ * Send message if the websocket is open,
79
+ * otherwise add it to the message queue
80
+ */
81
+ message({ type, table, id }) {
82
+ const payload = { type, table, id };
83
+ if (this.ws &&
84
+ this.ws.readyState === WebSocket.OPEN) {
85
+ this.ws.send(JSON.stringify(payload));
86
+ }
87
+ else {
88
+ this.message_queue.push(payload);
89
+ }
90
+ }
91
+ disconnect() {
92
+ if (debug)
93
+ console.log('websocket disconnect()');
94
+ this.update_connection_state('disconnected');
95
+ // if we are manually disconnecting we do not want an auto reconnect
96
+ this.should_reconnect = false;
97
+ this.ws?.close();
98
+ this.ws = undefined;
99
+ if (this.reconnect_timeout) {
100
+ clearTimeout(this.reconnect_timeout);
101
+ }
102
+ }
103
+ /**
104
+ * Update the connection state, dispatch an event if it actually changed.
105
+ */
106
+ update_connection_state(new_connection_state) {
107
+ const old_connection_state = this.connection_state;
108
+ if (this.connection_state !== new_connection_state) {
109
+ if (debug)
110
+ console.log('update_connection_state', new_connection_state);
111
+ this.connection_state = new_connection_state;
112
+ if (debug)
113
+ console.warn(`[Socket] State changed to: ${this.connection_state}`);
114
+ this.dispatchEvent(new CustomEvent('connection_state', { detail: this.connection_state }));
115
+ if ((new_connection_state === 'connected' || new_connection_state === 'reconnected') &&
116
+ (old_connection_state === 'stale' || old_connection_state === 'disconnected')) {
117
+ this.dispatchEvent(new CustomEvent('refresh', { bubbles: true }));
118
+ }
119
+ }
120
+ }
121
+ /**
122
+ * Helper to determine if we need to fetch missing data
123
+ */
124
+ check_staleness(source) {
125
+ const now = Date.now();
126
+ const time_since_last = now - this.last_heartbeat_timestamp;
127
+ if (debug)
128
+ console.log('websocket check_staleness()', source, time_since_last);
129
+ if (!this.ws ||
130
+ this.ws.readyState === this.ws.CLOSED ||
131
+ time_since_last > this.DISCONNECT_THRESHOLD_MS) {
132
+ this.update_connection_state('disconnected');
133
+ return;
134
+ }
135
+ // If gap is larger than threshold, we missed messages
136
+ if (time_since_last > this.SUSPENSION_THRESHOLD_MS) {
137
+ this.update_connection_state('stale');
138
+ }
139
+ else {
140
+ this.update_connection_state('connected');
141
+ }
142
+ }
143
+ handle_open(event) {
144
+ if (debug)
145
+ console.log('websocket handle open()');
146
+ if (this.ws &&
147
+ this.ws.readyState === this.ws.OPEN) {
148
+ this.update_connection_state('connected');
149
+ }
150
+ // clear reconnect timeout if we connected
151
+ if (this.reconnect_timeout) {
152
+ clearTimeout(this.reconnect_timeout);
153
+ }
154
+ // If we are reconnecting (attempts > 0), we definitely missed data.
155
+ if (this.reconnect_attempts > 0) {
156
+ if (debug)
157
+ console.log('[Socket] Reconnected. Triggering refresh.');
158
+ this.dispatchEvent(new CustomEvent('refresh', { bubbles: true }));
159
+ }
160
+ // reset the reconnect attempts
161
+ this.reconnect_attempts = 0;
162
+ this.ws?.send(JSON.stringify({ type: 'session', table: 'session', id: this.session_id }));
163
+ while (this.message_queue.length > 0) {
164
+ const queuedMsg = this.message_queue.shift();
165
+ this.ws?.send(JSON.stringify(queuedMsg));
166
+ }
167
+ }
168
+ handle_message(event) {
169
+ if (debug)
170
+ console.log('websocket handle message()');
171
+ try {
172
+ const data = JSON.parse(event.data);
173
+ if (debug)
174
+ console.log('data', data);
175
+ // HEARTBEAT CHECK: Intercept heartbeat messages
176
+ if (data.type === 'heartbeat') {
177
+ this.check_staleness('heartbeat');
178
+ this.last_heartbeat_timestamp = Date.now();
179
+ return; // Do not bubble 'heartbeat' to the UI
180
+ }
181
+ // todo not sure I like this
182
+ const messageEvent = new CustomEvent('message', {
183
+ detail: JSON.parse(event.data),
184
+ bubbles: true,
185
+ });
186
+ this.dispatchEvent(messageEvent);
187
+ }
188
+ catch (e) {
189
+ const messageEvent = new CustomEvent('message', {
190
+ detail: event.data,
191
+ bubbles: true,
192
+ });
193
+ this.dispatchEvent(messageEvent);
194
+ }
195
+ }
196
+ handle_close(event) {
197
+ if (debug)
198
+ console.log('websocket handle close()');
199
+ this.update_connection_state('disconnected');
200
+ if (this.should_reconnect) {
201
+ const delay = Math.min(1000 * Math.pow(2, this.reconnect_attempts), 30000);
202
+ if (debug)
203
+ console.log(`Connection lost. Retrying in ${delay}ms... (Attempt ${this.reconnect_attempts + 1})`);
204
+ this.reconnect_timeout = setTimeout(() => {
205
+ this.reconnect_attempts++;
206
+ if (this.session_id) {
207
+ this.connect(this.session_id);
208
+ }
209
+ }, delay);
210
+ }
211
+ this.ws = undefined;
212
+ }
213
+ handle_error(event) {
214
+ if (debug)
215
+ console.log('websocket handle error()');
216
+ this.update_connection_state('disconnected');
217
+ console.error('WebSocket Error:', event);
218
+ this.ws?.close();
219
+ }
220
+ }
221
+ exports.socket = new Socket();
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Kontororu.d.ts","sourceRoot":"","sources":["../../src/Kontororu.ts"],"names":[],"mappings":"AAGA,qBAAa,SAAU,SAAQ,WAAW;;IAMxC,OAAO,CAAC,SAAS,CAEf;IAEF,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;IASrE,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;IAQxE,YAAY,CAAC,IAAI,EAAE,MAAM,cApBS,OAAO,EAAE,KAAK,IAAI;CAuBrD"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ // コントロール
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Kontororu = void 0;
5
+ class Kontororu extends EventTarget {
6
+ constructor() {
7
+ super();
8
+ this.listeners = {};
9
+ }
10
+ addEventListener(type, listener) {
11
+ super.addEventListener(type, listener);
12
+ if (!this.listeners[type]) {
13
+ this.listeners[type] = [];
14
+ }
15
+ this.listeners[type].push(listener);
16
+ }
17
+ removeEventListener(type, listener) {
18
+ super.removeEventListener(type, listener);
19
+ if (this.listeners[type]) {
20
+ this.listeners[type] = this.listeners[type].filter((l) => l !== listener);
21
+ }
22
+ }
23
+ getListeners(type) {
24
+ return this.listeners[type] || [];
25
+ }
26
+ }
27
+ exports.Kontororu = Kontororu;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Objector.d.ts","sourceRoot":"","sources":["../../src/Objector.ts"],"names":[],"mappings":"AAEA,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAExC,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,IAC9B,CAAC,SAAS,CAAC,MAAM,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,GAClC,KAAK,SAAS,MAAM,GAClB,IAAI,SAAS,MAAM,EAAE,GACnB,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,GAC5B,KAAK,GACP,OAAO,GACT,OAAO,CAAC;AAEd;;GAEG;AACH,qBAAa,QAAQ;WAKL,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,oBAA0B,GAAG,CAAC;IAwErE;;;;;;;;;;;;;;;;;;;;OAoBG;WACW,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAAE,EACzD,MAAM,EAAE,CAAC,EACT,GAAG,OAAO,EAAE,CAAC,GACZ,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;CA+BvB"}