@h3ravel/session 0.1.0-alpha.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1434 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let __h3ravel_support = require("@h3ravel/support");
29
+ let __h3ravel_database = require("@h3ravel/database");
30
+ let crypto$1 = require("crypto");
31
+ crypto$1 = __toESM(crypto$1);
32
+ let __h3ravel_foundation = require("@h3ravel/foundation");
33
+ let __h3ravel_contracts = require("@h3ravel/contracts");
34
+ let fs = require("fs");
35
+ let path = require("path");
36
+ path = __toESM(path);
37
+ let __h3ravel_musket = require("@h3ravel/musket");
38
+
39
+ //#region src/Encryption.ts
40
+ var Encryption = class {
41
+ key;
42
+ constructor() {
43
+ const appKey = process.env.APP_KEY;
44
+ if (!appKey) throw new __h3ravel_foundation.ConfigException("APP_KEY not set in env");
45
+ this.key = (0, crypto$1.createHash)("sha256").update(Buffer.from(appKey, "base64")).digest();
46
+ }
47
+ /**
48
+ * Encrypt session data using AES-256-CBC and the APP_KEY.
49
+ */
50
+ encrypt(value) {
51
+ value = typeof value === "string" ? value : JSON.stringify(value);
52
+ const iv = crypto$1.default.randomBytes(16);
53
+ const cipher = crypto$1.default.createCipheriv("aes-256-cbc", this.key, iv);
54
+ const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
55
+ return iv.toString("hex") + ":" + encrypted.toString("hex");
56
+ }
57
+ /**
58
+ * Decrypt session data.
59
+ */
60
+ decrypt(value) {
61
+ const [ivHex, encryptedHex] = value.split(":");
62
+ const iv = Buffer.from(ivHex, "hex");
63
+ const encrypted = Buffer.from(encryptedHex, "hex");
64
+ const decipher = crypto$1.default.createDecipheriv("aes-256-cbc", this.key, iv);
65
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
66
+ try {
67
+ return JSON.parse(decrypted);
68
+ } catch {
69
+ return decrypted;
70
+ }
71
+ }
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/FlashBag.ts
76
+ /**
77
+ * FlashBag
78
+ *
79
+ * Manages flash data for session management, handling temporary data
80
+ * that persists for one request cycle.
81
+ */
82
+ var FlashBag = class {
83
+ /**
84
+ * Storage for flash data
85
+ *
86
+ * Structure:
87
+ * {
88
+ * new: { key1: value1, key2: value2 },
89
+ * old: { key3: value3, key4: value4 }
90
+ * }
91
+ */
92
+ flashData = {
93
+ new: {},
94
+ old: {}
95
+ };
96
+ /**
97
+ * Flash a value for the next request
98
+ *
99
+ * @param key Key to store in flash
100
+ * @param value Value to be flashed
101
+ */
102
+ flash(key, value) {
103
+ this.flashData.new[key] = value;
104
+ }
105
+ /**
106
+ * Store a temporary value for the current request only
107
+ *
108
+ * @param key Key to store
109
+ * @param value Value to store
110
+ */
111
+ now(key, value) {
112
+ this.flashData.new[key] = value;
113
+ }
114
+ /**
115
+ * Reflash all current flash data for another request cycle
116
+ */
117
+ reflash() {
118
+ this.flashData.old = { ...this.flashData.new };
119
+ }
120
+ /**
121
+ * Keep only specific flash keys for the next request
122
+ *
123
+ * @param keys Keys to keep
124
+ */
125
+ keep(keys) {
126
+ const keptNew = {};
127
+ const keptOld = {};
128
+ keys.forEach((key) => {
129
+ if (this.flashData.new[key] !== void 0) keptNew[key] = this.flashData.new[key];
130
+ if (this.flashData.old[key] !== void 0) keptOld[key] = this.flashData.old[key];
131
+ });
132
+ this.flashData.new = keptNew;
133
+ this.flashData.old = keptOld;
134
+ }
135
+ /**
136
+ * Age flash data at the end of the request
137
+ *
138
+ * - Removes old flash data
139
+ * - Moves new flash data to old
140
+ * - Clears new flash data
141
+ */
142
+ ageFlashData() {
143
+ this.flashData.old = {};
144
+ this.flashData.old = { ...this.flashData.new };
145
+ this.flashData.new = {};
146
+ }
147
+ /**
148
+ * Get a flash value
149
+ *
150
+ * @param key Key to retrieve
151
+ * @param defaultValue Default value if key doesn't exist
152
+ * @returns Flash value or default
153
+ */
154
+ get(key, defaultValue) {
155
+ return this.flashData.new[key] ?? this.flashData.old[key] ?? defaultValue;
156
+ }
157
+ /**
158
+ * Check if a flash key exists
159
+ *
160
+ * @param key Key to check
161
+ * @returns Boolean indicating existence
162
+ */
163
+ has(key) {
164
+ return key in this.flashData.new || key in this.flashData.old;
165
+ }
166
+ /**
167
+ * Get all flash data
168
+ *
169
+ * @returns Combined flash data
170
+ */
171
+ all() {
172
+ return {
173
+ ...this.flashData.old,
174
+ ...this.flashData.new
175
+ };
176
+ }
177
+ /**
178
+ * Get all flash data keys
179
+ *
180
+ * @returns Combined flash data
181
+ */
182
+ keys() {
183
+ return Object.keys({
184
+ ...this.flashData.old,
185
+ ...this.flashData.new
186
+ });
187
+ }
188
+ /**
189
+ * Get the raww flash data
190
+ *
191
+ * @returns raw flash data
192
+ */
193
+ raw() {
194
+ return this.flashData;
195
+ }
196
+ /**
197
+ * Clear all flash data
198
+ */
199
+ clear() {
200
+ this.flashData.new = {};
201
+ this.flashData.old = {};
202
+ }
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/drivers/Driver.ts
207
+ /**
208
+ * Driver
209
+ *
210
+ * Base Session driver.
211
+ */
212
+ var Driver = class extends __h3ravel_contracts.ISessionDriver {
213
+ encryptor = new Encryption();
214
+ sessionId;
215
+ flashBag = new FlashBag();
216
+ /**
217
+ * Save the raw session payload (session + flash)
218
+ */
219
+ saveRawPayload() {
220
+ this.savePayload(Object.assign({}, this.fetchPayload(), { _flash: this.flashBag.raw() }));
221
+ }
222
+ /**
223
+ * Retrieve all data from the session including flash
224
+ *
225
+ * @returns
226
+ */
227
+ getAll() {
228
+ const payload = this.fetchPayload();
229
+ const flash = payload._flash ?? {
230
+ old: {},
231
+ new: {}
232
+ };
233
+ return {
234
+ ...payload,
235
+ ...flash.old,
236
+ ...flash.new
237
+ };
238
+ }
239
+ /**
240
+ * Retrieve a value from the session
241
+ *
242
+ * @param key
243
+ * @param defaultValue
244
+ * @returns
245
+ */
246
+ get(key, defaultValue) {
247
+ return (0, __h3ravel_support.safeDot)(this.getAll(), key) || defaultValue;
248
+ }
249
+ /**
250
+ * Store a value in the session
251
+ *
252
+ * @param key
253
+ * @param value
254
+ */
255
+ set(value) {
256
+ const payload = this.fetchPayload();
257
+ Object.assign(payload, value);
258
+ return this.savePayload(payload);
259
+ }
260
+ /**
261
+ * Store multiple key/value pairs
262
+ *
263
+ * @param values
264
+ */
265
+ put(key, value) {
266
+ const payload = this.fetchPayload();
267
+ (0, __h3ravel_support.setNested)(payload, key, value);
268
+ return this.savePayload(payload);
269
+ }
270
+ /**
271
+ * Append a value to an array key
272
+ *
273
+ * @param key
274
+ * @param value
275
+ */
276
+ push(key, value) {
277
+ const payload = this.fetchPayload();
278
+ if (!Array.isArray(payload[key])) payload[key] = [];
279
+ payload[key].push(value);
280
+ return this.savePayload(payload);
281
+ }
282
+ /**
283
+ * Remove a key from the session
284
+ *
285
+ * @param key
286
+ */
287
+ forget(key) {
288
+ const payload = this.fetchPayload();
289
+ delete payload[key];
290
+ return this.savePayload(payload);
291
+ }
292
+ /**
293
+ * Retrieve all session data
294
+ *
295
+ * @returns
296
+ */
297
+ all() {
298
+ return this.fetchPayload();
299
+ }
300
+ /**
301
+ * Determine if a key exists (even if null).
302
+ *
303
+ * @param key
304
+ * @returns
305
+ */
306
+ exists(key) {
307
+ const data = this.getAll();
308
+ return Object.prototype.hasOwnProperty.call(data, key);
309
+ }
310
+ /**
311
+ * Determine if a key has a non-null value.
312
+ *
313
+ * @param key
314
+ * @returns
315
+ */
316
+ has(key) {
317
+ const data = this.getAll();
318
+ return data[key] !== void 0 && data[key] !== null;
319
+ }
320
+ /**
321
+ * Get only specific keys.
322
+ *
323
+ * @param keys
324
+ * @returns
325
+ */
326
+ only(keys) {
327
+ const data = this.fetchPayload();
328
+ const result = {};
329
+ keys.forEach((k) => {
330
+ if (k in data) result[k] = data[k];
331
+ });
332
+ return result;
333
+ }
334
+ /**
335
+ * Return all keys except the specified ones.
336
+ *
337
+ * @param keys
338
+ * @returns
339
+ */
340
+ except(keys) {
341
+ const data = this.fetchPayload();
342
+ keys.forEach((k) => delete data[k]);
343
+ return data;
344
+ }
345
+ /**
346
+ * Return and delete a key from the session.
347
+ *
348
+ * @param key
349
+ * @param defaultValue
350
+ * @returns
351
+ */
352
+ pull(key, defaultValue = null) {
353
+ const data = this.fetchPayload();
354
+ const value = data[key] ?? defaultValue;
355
+ delete data[key];
356
+ this.savePayload(data);
357
+ return value;
358
+ }
359
+ /**
360
+ * Increment a numeric value by amount (default 1).
361
+ *
362
+ * @param key
363
+ * @param amount
364
+ * @returns
365
+ */
366
+ increment(key, amount = 1) {
367
+ const data = this.fetchPayload();
368
+ const newVal = (parseFloat(data[key]) || 0) + amount;
369
+ data[key] = newVal;
370
+ this.savePayload(data);
371
+ return newVal;
372
+ }
373
+ /**
374
+ * Decrement a numeric value by amount (default 1).
375
+ *
376
+ * @param key
377
+ * @param amount
378
+ * @returns
379
+ */
380
+ decrement(key, amount = 1) {
381
+ return this.increment(key, -amount);
382
+ }
383
+ /**
384
+ * Flash a value for next request only.
385
+ *
386
+ * @param key
387
+ * @param value
388
+ */
389
+ flash(key, value) {
390
+ this.flashBag.flash(key, value);
391
+ this.saveRawPayload();
392
+ }
393
+ /**
394
+ * Reflash all flash data for one more cycle.
395
+ *
396
+ * @returns
397
+ */
398
+ reflash() {
399
+ this.flashBag.reflash();
400
+ this.saveRawPayload();
401
+ }
402
+ /**
403
+ * Keep only selected flash data.
404
+ *
405
+ * @param keys
406
+ * @returns
407
+ */
408
+ keep(keys) {
409
+ this.flashBag.keep(keys);
410
+ this.saveRawPayload();
411
+ }
412
+ /**
413
+ * Store a temporary value (flash) for this request only (not persisted)
414
+ *
415
+ * @param key
416
+ * @param value
417
+ */
418
+ now(key, value) {
419
+ this.flashBag.now(key, value);
420
+ this.saveRawPayload();
421
+ }
422
+ /**
423
+ * Regenerate session ID and persist data under new ID.
424
+ */
425
+ regenerate() {
426
+ const oldData = this.fetchPayload();
427
+ this.sessionId = crypto.randomUUID();
428
+ this.savePayload(oldData);
429
+ }
430
+ /**
431
+ * Age flash data at the end of the request lifecycle.
432
+ */
433
+ ageFlashData() {
434
+ const data = this.flashBag.ageFlashData();
435
+ this.saveRawPayload();
436
+ return data;
437
+ }
438
+ /**
439
+ * Determine if an item is not present in the session.
440
+ *
441
+ * @param key
442
+ * @returns
443
+ */
444
+ missing(key) {
445
+ return !this.exists(key);
446
+ }
447
+ /**
448
+ * Flush all session data
449
+ */
450
+ flush() {
451
+ return this.savePayload({});
452
+ }
453
+ };
454
+
455
+ //#endregion
456
+ //#region src/drivers/DatabaseDriver.ts
457
+ /**
458
+ * DatabaseDriver
459
+ *
460
+ * Stores sessions in a database table. Each session ID maps to a row.
461
+ * The `payload` column contains all session key/value pairs as JSON.
462
+ */
463
+ var DatabaseDriver = class extends Driver {
464
+ /**
465
+ *
466
+ * @param sessionId The current session ID
467
+ * @param table
468
+ */
469
+ constructor(sessionId, table = "sessions") {
470
+ super();
471
+ this.sessionId = sessionId;
472
+ this.table = table;
473
+ }
474
+ /**
475
+ * Get the query builder for this table
476
+ */
477
+ query() {
478
+ return __h3ravel_database.DB.table(this.table).where("id", this.sessionId);
479
+ }
480
+ /**
481
+ * Fetch the session payload
482
+ */
483
+ async fetchPayload() {
484
+ const row = await this.query().first();
485
+ if (!row) return {};
486
+ try {
487
+ const decrypted = this.encryptor.decrypt(row.payload);
488
+ return typeof decrypted === "string" ? JSON.parse(decrypted) : decrypted;
489
+ } catch {
490
+ return {};
491
+ }
492
+ }
493
+ /**
494
+ * Save the session payload back to DB
495
+ */
496
+ async savePayload(payload) {
497
+ const now = Math.floor(Date.now() / 1e3);
498
+ const exists = await this.query().exists();
499
+ const encrypted = this.encryptor.encrypt(JSON.stringify(payload));
500
+ if (exists) await this.query().update({
501
+ payload: encrypted,
502
+ last_activity: now
503
+ });
504
+ else await __h3ravel_database.DB.table(this.table).insert({
505
+ id: this.sessionId,
506
+ payload: encrypted,
507
+ last_activity: now
508
+ });
509
+ }
510
+ /**
511
+ * Retrieve all data from the session including flash
512
+ */
513
+ async getAll() {
514
+ const payload = await this.fetchPayload();
515
+ const flash = payload._flash ?? {
516
+ old: {},
517
+ new: {}
518
+ };
519
+ return {
520
+ ...payload,
521
+ ...flash.old,
522
+ ...flash.new
523
+ };
524
+ }
525
+ /**
526
+ * Get a value from the session
527
+ */
528
+ async get(key, defaultValue) {
529
+ return (0, __h3ravel_support.safeDot)(await this.getAll(), key) || defaultValue;
530
+ }
531
+ /**
532
+ * Set one or multiple session values
533
+ */
534
+ async set(values) {
535
+ const payload = await this.fetchPayload();
536
+ Object.assign(payload, values);
537
+ await this.savePayload(payload);
538
+ }
539
+ /**
540
+ * Store a single key/value pair
541
+ */
542
+ async put(key, value) {
543
+ const payload = await this.fetchPayload();
544
+ (0, __h3ravel_support.setNested)(payload, key, value);
545
+ await this.savePayload(payload);
546
+ }
547
+ /**
548
+ * Append a value to an array key
549
+ */
550
+ async push(key, value) {
551
+ const payload = await this.fetchPayload();
552
+ if (!Array.isArray(payload[key])) payload[key] = [];
553
+ payload[key].push(value);
554
+ await this.savePayload(payload);
555
+ }
556
+ /**
557
+ * Forget a session key
558
+ */
559
+ async forget(key) {
560
+ const payload = await this.fetchPayload();
561
+ delete payload[key];
562
+ await this.savePayload(payload);
563
+ }
564
+ /**
565
+ * Retrieve all session data (excluding flash)
566
+ */
567
+ async all() {
568
+ return this.fetchPayload();
569
+ }
570
+ /**
571
+ * Determine if a key exists (even if null)
572
+ */
573
+ async exists(key) {
574
+ const data = await this.getAll();
575
+ return Object.prototype.hasOwnProperty.call(data, key);
576
+ }
577
+ /**
578
+ * Determine if a key has a non-null value
579
+ */
580
+ async has(key) {
581
+ const data = await this.getAll();
582
+ return data[key] !== void 0 && data[key] !== null;
583
+ }
584
+ /**
585
+ * Get only specific keys
586
+ */
587
+ async only(keys) {
588
+ const data = await this.fetchPayload();
589
+ const result = {};
590
+ keys.forEach((k) => {
591
+ if (k in data) result[k] = data[k];
592
+ });
593
+ return result;
594
+ }
595
+ /**
596
+ * Return all except specific keys
597
+ */
598
+ async except(keys) {
599
+ const data = await this.fetchPayload();
600
+ keys.forEach((k) => delete data[k]);
601
+ return data;
602
+ }
603
+ /**
604
+ * Retrieve and delete a value
605
+ */
606
+ async pull(key, defaultValue = null) {
607
+ const data = await this.fetchPayload();
608
+ const value = data[key] ?? defaultValue;
609
+ delete data[key];
610
+ await this.savePayload(data);
611
+ return value;
612
+ }
613
+ /**
614
+ * Increment a numeric value
615
+ */
616
+ async increment(key, amount = 1) {
617
+ const data = await this.fetchPayload();
618
+ const newVal = (parseFloat(data[key]) || 0) + amount;
619
+ data[key] = newVal;
620
+ await this.savePayload(data);
621
+ return newVal;
622
+ }
623
+ /**
624
+ * Decrement a numeric value
625
+ */
626
+ async decrement(key, amount = 1) {
627
+ return this.increment(key, -amount);
628
+ }
629
+ /**
630
+ * Flash a value for next request only
631
+ */
632
+ async flash(key, value) {
633
+ this.flashBag.flash(key, value);
634
+ }
635
+ /**
636
+ * Reflash all flash data for one more cycle
637
+ */
638
+ async reflash() {
639
+ this.flashBag.reflash();
640
+ }
641
+ /**
642
+ * Keep only specific flash keys
643
+ */
644
+ async keep(keys) {
645
+ this.flashBag.keep(keys);
646
+ }
647
+ /**
648
+ * Store a temporary value (flash) for this request only (not persisted)
649
+ */
650
+ async now(key, value) {
651
+ this.flashBag.now(key, value);
652
+ }
653
+ /**
654
+ * Regenerate session ID with same data
655
+ */
656
+ async regenerate() {
657
+ const oldData = await this.fetchPayload();
658
+ this.sessionId = crypto.randomUUID();
659
+ await this.savePayload(oldData);
660
+ }
661
+ /**
662
+ * Check if a key is missing
663
+ */
664
+ async missing(key) {
665
+ return !await this.exists(key);
666
+ }
667
+ /**
668
+ * Flush all session data
669
+ */
670
+ async flush() {
671
+ await this.savePayload({});
672
+ }
673
+ /**
674
+ * Invalidate the session and regenerate
675
+ */
676
+ async invalidate() {
677
+ await __h3ravel_database.DB.table(this.table).where("id", this.sessionId).delete();
678
+ this.sessionId = crypto.randomUUID();
679
+ this.flashBag = new FlashBag();
680
+ await this.savePayload({});
681
+ }
682
+ /**
683
+ * Age flash data at the end of the request lifecycle.
684
+ */
685
+ async ageFlashData() {
686
+ this.flashBag.ageFlashData();
687
+ }
688
+ };
689
+
690
+ //#endregion
691
+ //#region src/drivers/FileDriver.ts
692
+ /**
693
+ * FileDriver
694
+ *
695
+ * Stores session data as encrypted JSON files.
696
+ * Each session is stored in its own file named after the session ID.
697
+ * Ideal for local development or low-scale deployments.
698
+ */
699
+ var FileDriver = class extends Driver {
700
+ constructor(sessionId, sessionDir = path.default.resolve(".sessions"), cwd = process.cwd()) {
701
+ super();
702
+ this.sessionId = sessionId;
703
+ this.sessionDir = sessionDir;
704
+ this.cwd = cwd;
705
+ this.sessionDir = path.default.join(this.cwd, sessionDir);
706
+ this.sessionId = sessionId;
707
+ if (!(0, fs.existsSync)(this.sessionDir)) (0, fs.mkdirSync)(this.sessionDir, { recursive: true });
708
+ this.ensureSessionFile();
709
+ }
710
+ /**
711
+ * Ensures the session file exists and is initialized.
712
+ */
713
+ ensureSessionFile() {
714
+ if (!(0, fs.existsSync)(this.sessionFilePath())) this.savePayload({});
715
+ }
716
+ /**
717
+ * Get the absolute path for the current session file.
718
+ */
719
+ sessionFilePath() {
720
+ return path.default.join(this.sessionDir, this.sessionId);
721
+ }
722
+ /**
723
+ * Read raw decrypted payload (including _flash).
724
+ */
725
+ readRawPayload() {
726
+ const file = this.sessionFilePath();
727
+ if (!(0, fs.existsSync)(file)) return {};
728
+ const content = (0, fs.readFileSync)(file, "utf8");
729
+ try {
730
+ return this.encryptor.decrypt(content);
731
+ } catch {
732
+ return {};
733
+ }
734
+ }
735
+ /**
736
+ * Fetch decrypted payload and strip out flash metadata.
737
+ */
738
+ fetchPayload() {
739
+ return this.readRawPayload();
740
+ }
741
+ /**
742
+ * Write and encrypt session data to file.
743
+ * Always persists flash state.
744
+ *
745
+ * @param data
746
+ */
747
+ savePayload(payload) {
748
+ (0, fs.writeFileSync)(this.sessionFilePath(), this.encryptor.encrypt(payload), "utf8");
749
+ }
750
+ /**
751
+ * Completely invalidate the current session and regenerate a new one.
752
+ */
753
+ invalidate() {
754
+ const file = this.sessionFilePath();
755
+ if ((0, fs.existsSync)(file)) (0, fs.rmSync)(file, { recursive: true });
756
+ this.sessionId = crypto.randomUUID();
757
+ this.flashBag = new FlashBag();
758
+ this.savePayload({});
759
+ }
760
+ };
761
+
762
+ //#endregion
763
+ //#region src/drivers/MemoryDriver.ts
764
+ /**
765
+ * MemoryDriver
766
+ *
767
+ * Lightweight, ephemeral session storage.
768
+ * Intended for tests, local development, or short-lived apps.
769
+ */
770
+ var MemoryDriver = class MemoryDriver extends Driver {
771
+ static store = {};
772
+ constructor(sessionId) {
773
+ super();
774
+ this.sessionId = sessionId;
775
+ this.sessionId = sessionId;
776
+ if (!MemoryDriver.store[this.sessionId]) MemoryDriver.store[this.sessionId] = {};
777
+ }
778
+ /**
779
+ * Fetch and return session payload.
780
+ *
781
+ * @returns Decrypted and usable payload
782
+ */
783
+ fetchPayload() {
784
+ return { ...MemoryDriver.store[this.sessionId] };
785
+ }
786
+ /**
787
+ * Persist session payload and flash bag state.
788
+ *
789
+ * @param data
790
+ */
791
+ savePayload(payload) {
792
+ MemoryDriver.store[this.sessionId] = { ...payload };
793
+ }
794
+ /**
795
+ * Invalidate current session and regenerate new session ID.
796
+ */
797
+ invalidate() {
798
+ delete MemoryDriver.store[this.sessionId];
799
+ this.sessionId = crypto$1.default.randomUUID();
800
+ this.flashBag = new FlashBag();
801
+ this.savePayload({});
802
+ }
803
+ };
804
+
805
+ //#endregion
806
+ //#region src/drivers/RedisDriver.ts
807
+ /**
808
+ * RedisDriver (placeholder)
809
+ */
810
+ var RedisDriver = class extends Driver {
811
+ static store = {};
812
+ constructor(sessionId, redisClient, prefix) {
813
+ super();
814
+ this.sessionId = sessionId;
815
+ this.redisClient = redisClient;
816
+ this.prefix = prefix;
817
+ }
818
+ /**
819
+ * Fetch and return session payload.
820
+ *
821
+ * @returns Decrypted and usable payload
822
+ */
823
+ fetchPayload() {
824
+ return {};
825
+ }
826
+ /**
827
+ * Persist session payload and flash bag state.
828
+ *
829
+ * @param data
830
+ */
831
+ savePayload(_payload) {}
832
+ /**
833
+ * Invalidate current session and regenerate new session ID.
834
+ */
835
+ invalidate() {
836
+ this.flashBag = new FlashBag();
837
+ this.savePayload({});
838
+ }
839
+ };
840
+
841
+ //#endregion
842
+ //#region src/adapters.ts
843
+ /**
844
+ * FileDriver builder
845
+ * constructor(sessionId: string, sessionDir?: string, cwd?: string)
846
+ */
847
+ const fileBuilder = (sessionId, options = {}) => {
848
+ return new FileDriver(sessionId, options.sessionDir ?? options.dir ?? "./storage/sessions", options.cwd ?? process.cwd());
849
+ };
850
+ /**
851
+ * DatabaseDriver builder
852
+ * constructor(sessionId: string, table?: string)
853
+ */
854
+ const dbBuilder = (sessionId, options = {}) => {
855
+ const table = options.table ?? "sessions";
856
+ return new DatabaseDriver(options.sessionId ?? sessionId, table);
857
+ };
858
+ /**
859
+ * MemoryDriver builder
860
+ * constructor(sessionId: string)
861
+ */
862
+ const memoryBuilder = (sessionId) => {
863
+ return new MemoryDriver(sessionId);
864
+ };
865
+ /**
866
+ * RedisDriver builder
867
+ * constructor(sessionId: string, redisClient?: RedisClient, prefix?: string)
868
+ */
869
+ const redisBuilder = (sessionId, options = {}) => {
870
+ const client = options.client;
871
+ return new RedisDriver(sessionId, client, options.prefix ?? "h3ravel:sessions:");
872
+ };
873
+
874
+ //#endregion
875
+ //#region src/Commands/MakeSessionTableCommand.ts
876
+ var MakeSessionTableCommand = class extends __h3ravel_musket.Command {
877
+ /**
878
+ * The name and signature of the console command.
879
+ *
880
+ * @var string
881
+ */
882
+ signature = "make:session-table";
883
+ /**
884
+ * The console command description.
885
+ *
886
+ * @var string
887
+ */
888
+ description = "Create a migration for the session database table";
889
+ async handle() {
890
+ await __h3ravel_database.DB.instance().schema.hasTable("sessions").then(async function(exists) {
891
+ if (!exists) return __h3ravel_database.DB.instance().schema.createTable("sessions", (table) => {
892
+ table.string("id", 255).primary();
893
+ table.bigInteger("user_id").nullable().index();
894
+ table.string("ip_address", 45).nullable();
895
+ table.text("user_agent").nullable();
896
+ table.text("payload", "longtext").nullable();
897
+ table.integer("last_activity").index();
898
+ });
899
+ });
900
+ this.info("INFO: session table created successfully.");
901
+ }
902
+ };
903
+
904
+ //#endregion
905
+ //#region ../../node_modules/.pnpm/h3@2.0.1-rc.5/node_modules/h3/dist/h3.mjs
906
+ const kEventNS = "h3.internal.event.";
907
+ const kEventRes = /* @__PURE__ */ Symbol.for(`${kEventNS}res`);
908
+ const kEventResHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.headers`);
909
+ function parse(str, options) {
910
+ if (typeof str !== "string") throw new TypeError("argument str must be a string");
911
+ const obj = {};
912
+ const opt = options || {};
913
+ const dec = opt.decode || decode;
914
+ let index = 0;
915
+ while (index < str.length) {
916
+ const eqIdx = str.indexOf("=", index);
917
+ if (eqIdx === -1) break;
918
+ let endIdx = str.indexOf(";", index);
919
+ if (endIdx === -1) endIdx = str.length;
920
+ else if (endIdx < eqIdx) {
921
+ index = str.lastIndexOf(";", eqIdx - 1) + 1;
922
+ continue;
923
+ }
924
+ const key = str.slice(index, eqIdx).trim();
925
+ if (opt?.filter && !opt?.filter(key)) {
926
+ index = endIdx + 1;
927
+ continue;
928
+ }
929
+ if (void 0 === obj[key]) {
930
+ let val = str.slice(eqIdx + 1, endIdx).trim();
931
+ if (val.codePointAt(0) === 34) val = val.slice(1, -1);
932
+ obj[key] = tryDecode(val, dec);
933
+ }
934
+ index = endIdx + 1;
935
+ }
936
+ return obj;
937
+ }
938
+ function decode(str) {
939
+ return str.includes("%") ? decodeURIComponent(str) : str;
940
+ }
941
+ function tryDecode(str, decode2) {
942
+ try {
943
+ return decode2(str);
944
+ } catch {
945
+ return str;
946
+ }
947
+ }
948
+ const fieldContentRegExp = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/;
949
+ function serialize(name, value, options) {
950
+ const opt = options || {};
951
+ const enc = opt.encode || encodeURIComponent;
952
+ if (typeof enc !== "function") throw new TypeError("option encode is invalid");
953
+ if (!fieldContentRegExp.test(name)) throw new TypeError("argument name is invalid");
954
+ const encodedValue = enc(value);
955
+ if (encodedValue && !fieldContentRegExp.test(encodedValue)) throw new TypeError("argument val is invalid");
956
+ let str = name + "=" + encodedValue;
957
+ if (void 0 !== opt.maxAge && opt.maxAge !== null) {
958
+ const maxAge = opt.maxAge - 0;
959
+ if (Number.isNaN(maxAge) || !Number.isFinite(maxAge)) throw new TypeError("option maxAge is invalid");
960
+ str += "; Max-Age=" + Math.floor(maxAge);
961
+ }
962
+ if (opt.domain) {
963
+ if (!fieldContentRegExp.test(opt.domain)) throw new TypeError("option domain is invalid");
964
+ str += "; Domain=" + opt.domain;
965
+ }
966
+ if (opt.path) {
967
+ if (!fieldContentRegExp.test(opt.path)) throw new TypeError("option path is invalid");
968
+ str += "; Path=" + opt.path;
969
+ }
970
+ if (opt.expires) {
971
+ if (!isDate(opt.expires) || Number.isNaN(opt.expires.valueOf())) throw new TypeError("option expires is invalid");
972
+ str += "; Expires=" + opt.expires.toUTCString();
973
+ }
974
+ if (opt.httpOnly) str += "; HttpOnly";
975
+ if (opt.secure) str += "; Secure";
976
+ if (opt.priority) switch (typeof opt.priority === "string" ? opt.priority.toLowerCase() : opt.priority) {
977
+ case "low":
978
+ str += "; Priority=Low";
979
+ break;
980
+ case "medium":
981
+ str += "; Priority=Medium";
982
+ break;
983
+ case "high":
984
+ str += "; Priority=High";
985
+ break;
986
+ default: throw new TypeError("option priority is invalid");
987
+ }
988
+ if (opt.sameSite) switch (typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite) {
989
+ case true:
990
+ str += "; SameSite=Strict";
991
+ break;
992
+ case "lax":
993
+ str += "; SameSite=Lax";
994
+ break;
995
+ case "strict":
996
+ str += "; SameSite=Strict";
997
+ break;
998
+ case "none":
999
+ str += "; SameSite=None";
1000
+ break;
1001
+ default: throw new TypeError("option sameSite is invalid");
1002
+ }
1003
+ if (opt.partitioned) str += "; Partitioned";
1004
+ return str;
1005
+ }
1006
+ function isDate(val) {
1007
+ return Object.prototype.toString.call(val) === "[object Date]" || val instanceof Date;
1008
+ }
1009
+ function parseSetCookie(setCookieValue, options) {
1010
+ const parts = (setCookieValue || "").split(";").filter((str) => typeof str === "string" && !!str.trim());
1011
+ const parsed = _parseNameValuePair(parts.shift() || "");
1012
+ const name = parsed.name;
1013
+ let value = parsed.value;
1014
+ try {
1015
+ value = options?.decode === false ? value : (options?.decode || decodeURIComponent)(value);
1016
+ } catch {}
1017
+ const cookie = {
1018
+ name,
1019
+ value
1020
+ };
1021
+ for (const part of parts) {
1022
+ const sides = part.split("=");
1023
+ const partKey = (sides.shift() || "").trimStart().toLowerCase();
1024
+ const partValue = sides.join("=");
1025
+ switch (partKey) {
1026
+ case "expires":
1027
+ cookie.expires = new Date(partValue);
1028
+ break;
1029
+ case "max-age":
1030
+ cookie.maxAge = Number.parseInt(partValue, 10);
1031
+ break;
1032
+ case "secure":
1033
+ cookie.secure = true;
1034
+ break;
1035
+ case "httponly":
1036
+ cookie.httpOnly = true;
1037
+ break;
1038
+ case "samesite":
1039
+ cookie.sameSite = partValue;
1040
+ break;
1041
+ default: cookie[partKey] = partValue;
1042
+ }
1043
+ }
1044
+ return cookie;
1045
+ }
1046
+ function _parseNameValuePair(nameValuePairStr) {
1047
+ let name = "";
1048
+ let value = "";
1049
+ const nameValueArr = nameValuePairStr.split("=");
1050
+ if (nameValueArr.length > 1) {
1051
+ name = nameValueArr.shift();
1052
+ value = nameValueArr.join("=");
1053
+ } else value = nameValuePairStr;
1054
+ return {
1055
+ name,
1056
+ value
1057
+ };
1058
+ }
1059
+ function parseCookies(event) {
1060
+ return parse(event.req.headers.get("cookie") || "");
1061
+ }
1062
+ function getCookie(event, name) {
1063
+ return parseCookies(event)[name];
1064
+ }
1065
+ function setCookie(event, name, value, options) {
1066
+ const newCookie = serialize(name, value, {
1067
+ path: "/",
1068
+ ...options
1069
+ });
1070
+ const currentCookies = event.res.headers.getSetCookie();
1071
+ if (currentCookies.length === 0) {
1072
+ event.res.headers.set("set-cookie", newCookie);
1073
+ return;
1074
+ }
1075
+ const newCookieKey = _getDistinctCookieKey(name, options || {});
1076
+ event.res.headers.delete("set-cookie");
1077
+ for (const cookie of currentCookies) {
1078
+ if (_getDistinctCookieKey(cookie.split("=")?.[0], parseSetCookie(cookie)) === newCookieKey) continue;
1079
+ event.res.headers.append("set-cookie", cookie);
1080
+ }
1081
+ event.res.headers.append("set-cookie", newCookie);
1082
+ }
1083
+ function _getDistinctCookieKey(name, options) {
1084
+ return [
1085
+ name,
1086
+ options.domain || "",
1087
+ options.path || "/"
1088
+ ].join(";");
1089
+ }
1090
+
1091
+ //#endregion
1092
+ //#region src/SessionStore.ts
1093
+ /**
1094
+ * SessionStore (Driver registry)
1095
+ *
1096
+ * Register driver builders under a name and then create instances using:
1097
+ * SessionStore.make('file', sessionId, options)
1098
+ */
1099
+ var SessionStore = class {
1100
+ static registry = /* @__PURE__ */ new Map();
1101
+ /**
1102
+ * Register a driver builder under a key (e.g. 'file', 'database', 'memory').
1103
+ */
1104
+ static register(name, builder) {
1105
+ this.registry.set(name, builder);
1106
+ }
1107
+ /**
1108
+ * Create a driver instance for the given sessionId using the named builder.
1109
+ *
1110
+ * If driver not found, throws. Options is a simple key/value bag passed to the builder.
1111
+ */
1112
+ static make(name, sessionId, options = {}) {
1113
+ const builder = this.registry.get(name);
1114
+ if (!builder) throw new Error(`Session driver "${name}" is not registered`);
1115
+ return builder(sessionId, options);
1116
+ }
1117
+ };
1118
+
1119
+ //#endregion
1120
+ //#region src/SessionManager.ts
1121
+ /**
1122
+ * SessionManager
1123
+ *
1124
+ * Handles session initialization, ID generation, and encryption.
1125
+ * Each request gets a unique session namespace tied to its ID.
1126
+ */
1127
+ var SessionManager = class SessionManager extends __h3ravel_contracts.ISessionManager {
1128
+ app;
1129
+ ctx;
1130
+ driver;
1131
+ appKey;
1132
+ sessionId;
1133
+ request;
1134
+ flashBag;
1135
+ constructor(app, driverName = "file", driverOptions = {}) {
1136
+ super();
1137
+ this.appKey = process.env.APP_KEY;
1138
+ if (app instanceof __h3ravel_contracts.IHttpContext) {
1139
+ this.request = app.request;
1140
+ this.ctx = app;
1141
+ this.app = app.app;
1142
+ } else {
1143
+ this.app = app;
1144
+ this.ctx = app.make("http.context");
1145
+ this.request = this.ctx.request;
1146
+ }
1147
+ this.sessionId = this.resolveSessionId();
1148
+ this.driver = SessionStore.make(driverName, driverOptions.sessionId ?? this.sessionId, driverOptions);
1149
+ this.flashBag = this.driver.flashBag;
1150
+ }
1151
+ /**
1152
+ * Initialize the Session Manager
1153
+ *
1154
+ * @param ctx
1155
+ * @returns
1156
+ */
1157
+ static init(app) {
1158
+ return new SessionManager(app, config("session.driver", "file"), {
1159
+ cwd: config("session.files"),
1160
+ sessionDir: "/",
1161
+ dir: "/",
1162
+ table: config("session.table"),
1163
+ prefix: config("database.connections.redis.options.prefix"),
1164
+ client: config(`database.connections.${config("session.driver", "file")}.client`)
1165
+ });
1166
+ }
1167
+ /**
1168
+ * Generate a secure session ID unique to the user device.
1169
+ */
1170
+ generateSessionId() {
1171
+ const userAgent = this.request.getHeader("user-agent") || "";
1172
+ const ip = this.request.getHeader("x-forwarded-for") || this.request.ip() || "";
1173
+ const random = (0, crypto$1.randomBytes)(32).toString("hex");
1174
+ const fingerprint = (0, crypto$1.createHash)("sha256").update(`${userAgent}-${ip}`).digest("hex");
1175
+ return (0, crypto$1.createHmac)("sha256", this.appKey).update(`${fingerprint}-${random}`).digest("hex");
1176
+ }
1177
+ /**
1178
+ * Resolve the session ID from cookie, header, or create a new one.
1179
+ */
1180
+ resolveSessionId() {
1181
+ const cookieSession = getCookie(this.ctx.event, "h3ravel_session");
1182
+ if (cookieSession) return cookieSession;
1183
+ const newId = this.generateSessionId();
1184
+ setCookie(this.ctx.event, "h3ravel_session", newId, {
1185
+ httpOnly: true,
1186
+ secure: true,
1187
+ sameSite: "lax",
1188
+ maxAge: 3600 * 24 * 7
1189
+ });
1190
+ return newId;
1191
+ }
1192
+ /**
1193
+ * Access the current session ID.
1194
+ */
1195
+ id() {
1196
+ return this.sessionId;
1197
+ }
1198
+ /**
1199
+ * Get the current session driver
1200
+ */
1201
+ getDriver() {
1202
+ return this.driver;
1203
+ }
1204
+ /**
1205
+ * Retrieve a value from the session
1206
+ *
1207
+ * @param key
1208
+ * @param defaultValue
1209
+ * @returns
1210
+ */
1211
+ get(key, defaultValue) {
1212
+ return this.driver.get(key, defaultValue);
1213
+ }
1214
+ /**
1215
+ * Store a value in the session
1216
+ *
1217
+ * @param key
1218
+ * @param value
1219
+ */
1220
+ set(value) {
1221
+ return this.driver.set(value);
1222
+ }
1223
+ /**
1224
+ * Store multiple key/value pairs
1225
+ *
1226
+ * @param values
1227
+ */
1228
+ put(key, value) {
1229
+ return this.driver.put(key, value);
1230
+ }
1231
+ /**
1232
+ * Append a value to an array key
1233
+ *
1234
+ * @param key
1235
+ * @param value
1236
+ */
1237
+ push(key, value) {
1238
+ return this.driver.push(key, value);
1239
+ }
1240
+ /**
1241
+ * Remove a key from the session
1242
+ *
1243
+ * @param key
1244
+ */
1245
+ forget(key) {
1246
+ return this.driver.forget(key);
1247
+ }
1248
+ /**
1249
+ * Retrieve all session data
1250
+ *
1251
+ * @returns
1252
+ */
1253
+ all() {
1254
+ return this.driver.all();
1255
+ }
1256
+ /**
1257
+ * Determine if a key exists (even if null).
1258
+ *
1259
+ * @param key
1260
+ * @returns
1261
+ */
1262
+ exists(key) {
1263
+ return this.driver.exists(key);
1264
+ }
1265
+ /**
1266
+ * Determine if a key has a non-null value.
1267
+ *
1268
+ * @param key
1269
+ * @returns
1270
+ */
1271
+ has(key) {
1272
+ return this.driver.has(key);
1273
+ }
1274
+ /**
1275
+ * Get only specific keys.
1276
+ *
1277
+ * @param keys
1278
+ * @returns
1279
+ */
1280
+ only(keys) {
1281
+ return this.driver.only(keys);
1282
+ }
1283
+ /**
1284
+ * Return all keys except the specified ones.
1285
+ *
1286
+ * @param keys
1287
+ * @returns
1288
+ */
1289
+ except(keys) {
1290
+ return this.driver.except(keys);
1291
+ }
1292
+ /**
1293
+ * Return and delete a key from the session.
1294
+ *
1295
+ * @param key
1296
+ * @param defaultValue
1297
+ * @returns
1298
+ */
1299
+ pull(key, defaultValue = null) {
1300
+ return this.driver.pull(key, defaultValue);
1301
+ }
1302
+ /**
1303
+ * Increment a numeric value by amount (default 1).
1304
+ *
1305
+ * @param key
1306
+ * @param amount
1307
+ * @returns
1308
+ */
1309
+ increment(key, amount = 1) {
1310
+ return this.driver.increment(key, amount);
1311
+ }
1312
+ /**
1313
+ * Decrement a numeric value by amount (default 1).
1314
+ *
1315
+ * @param key
1316
+ * @param amount
1317
+ * @returns
1318
+ */
1319
+ decrement(key, amount = 1) {
1320
+ return this.driver.decrement(key, amount);
1321
+ }
1322
+ /**
1323
+ * Flash a value for next request only.
1324
+ *
1325
+ * @param key
1326
+ * @param value
1327
+ */
1328
+ flash(key, value) {
1329
+ return this.driver.flash(key, value);
1330
+ }
1331
+ /**
1332
+ * Reflash all flash data for one more cycle.
1333
+ *
1334
+ * @returns
1335
+ */
1336
+ reflash() {
1337
+ return this.driver.reflash();
1338
+ }
1339
+ /**
1340
+ * Keep only selected flash data.
1341
+ *
1342
+ * @param keys
1343
+ * @returns
1344
+ */
1345
+ keep(keys) {
1346
+ return this.driver.keep(keys);
1347
+ }
1348
+ /**
1349
+ * Store data only for current request cycle (not persisted).
1350
+ *
1351
+ * @param key
1352
+ * @param value
1353
+ */
1354
+ now(key, value) {
1355
+ return this.driver.now(key, value);
1356
+ }
1357
+ /**
1358
+ * Regenerate session ID and persist data under new ID.
1359
+ */
1360
+ regenerate() {
1361
+ return this.driver.regenerate();
1362
+ }
1363
+ /**
1364
+ * Determine if an item is not present in the session.
1365
+ *
1366
+ * @param key
1367
+ * @returns
1368
+ */
1369
+ missing(key) {
1370
+ return this.driver.missing(key);
1371
+ }
1372
+ /**
1373
+ * Flush all session data
1374
+ */
1375
+ flush() {
1376
+ return this.driver.flush();
1377
+ }
1378
+ /**
1379
+ * Invalidate the session completely and regenerate ID.
1380
+ *
1381
+ * @returns
1382
+ */
1383
+ invalidate() {
1384
+ return this.driver.invalidate();
1385
+ }
1386
+ /**
1387
+ * Age flash data at the end of the request lifecycle.
1388
+ *
1389
+ * @returns
1390
+ */
1391
+ ageFlashData() {
1392
+ return this.driver.ageFlashData();
1393
+ }
1394
+ };
1395
+
1396
+ //#endregion
1397
+ //#region src/Providers/SessionServiceProvider.ts
1398
+ var SessionServiceProvider = class extends __h3ravel_support.ServiceProvider {
1399
+ static priority = 895;
1400
+ static order = "before:HttpServiceProvider";
1401
+ register() {
1402
+ /**
1403
+ * Register default drivers.
1404
+ */
1405
+ SessionStore.register("file", fileBuilder);
1406
+ SessionStore.register("database", dbBuilder);
1407
+ SessionStore.register("memory", memoryBuilder);
1408
+ SessionStore.register("redis", redisBuilder);
1409
+ this.app.singleton("session", (app) => {
1410
+ return SessionManager.init(app);
1411
+ });
1412
+ this.app.singleton("session.store", (app) => {
1413
+ return app.make("session").getDriver();
1414
+ });
1415
+ this.registerCommands([MakeSessionTableCommand]);
1416
+ }
1417
+ };
1418
+
1419
+ //#endregion
1420
+ exports.DatabaseDriver = DatabaseDriver;
1421
+ exports.Driver = Driver;
1422
+ exports.Encryption = Encryption;
1423
+ exports.FileDriver = FileDriver;
1424
+ exports.FlashBag = FlashBag;
1425
+ exports.MakeSessionTableCommand = MakeSessionTableCommand;
1426
+ exports.MemoryDriver = MemoryDriver;
1427
+ exports.RedisDriver = RedisDriver;
1428
+ exports.SessionManager = SessionManager;
1429
+ exports.SessionServiceProvider = SessionServiceProvider;
1430
+ exports.SessionStore = SessionStore;
1431
+ exports.dbBuilder = dbBuilder;
1432
+ exports.fileBuilder = fileBuilder;
1433
+ exports.memoryBuilder = memoryBuilder;
1434
+ exports.redisBuilder = redisBuilder;