@h3ravel/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,776 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // ../http/src/Middleware.ts
35
+ var Middleware = class {
36
+ static {
37
+ __name(this, "Middleware");
38
+ }
39
+ };
40
+
41
+ // ../http/src/Request.ts
42
+ import { getQuery, getRouterParams, readBody } from "h3";
43
+
44
+ // ../support/src/Helpers/Obj.ts
45
+ function safeDot(data, key) {
46
+ if (!key) return data;
47
+ return key.split(".").reduce((acc, k) => acc?.[k], data);
48
+ }
49
+ __name(safeDot, "safeDot");
50
+ var setNested = /* @__PURE__ */ __name((obj, key, value) => {
51
+ if (!key.includes(".")) {
52
+ obj[key] = value;
53
+ return;
54
+ }
55
+ const parts = key.split(".");
56
+ let current = obj;
57
+ for (let i = 0; i < parts.length; i++) {
58
+ const part = parts[i];
59
+ if (i === parts.length - 1) {
60
+ current[part] = value;
61
+ } else {
62
+ if (typeof current[part] !== "object" || current[part] === null) {
63
+ current[part] = {};
64
+ }
65
+ current = current[part];
66
+ }
67
+ }
68
+ }, "setNested");
69
+
70
+ // ../support/src/Helpers/Str.ts
71
+ var afterLast = /* @__PURE__ */ __name((value, search) => {
72
+ if (!search) return value;
73
+ const lastIndex = value.lastIndexOf(search);
74
+ return lastIndex !== -1 ? value.slice(lastIndex + search.length) : value;
75
+ }, "afterLast");
76
+ var before = /* @__PURE__ */ __name((value, search) => {
77
+ if (!search) return value;
78
+ const index = value.indexOf(search);
79
+ return index !== -1 ? value.slice(0, index) : value;
80
+ }, "before");
81
+
82
+ // ../http/src/Request.ts
83
+ var Request = class {
84
+ static {
85
+ __name(this, "Request");
86
+ }
87
+ event;
88
+ constructor(event) {
89
+ this.event = event;
90
+ }
91
+ /**
92
+ * Get all input data (query + body).
93
+ */
94
+ async all() {
95
+ let data = {
96
+ ...getRouterParams(this.event),
97
+ ...getQuery(this.event)
98
+ };
99
+ if (this.event.req.method === "POST") {
100
+ data = Object.assign({}, data, Object.fromEntries((await this.event.req.formData()).entries()));
101
+ } else if (this.event.req.method === "PUT") {
102
+ data = Object.fromEntries(Object.entries(await readBody(this.event)));
103
+ }
104
+ return data;
105
+ }
106
+ /**
107
+ * Get a single input field from query or body.
108
+ */
109
+ async input(key, defaultValue) {
110
+ const data = await this.all();
111
+ return data[key] ?? defaultValue;
112
+ }
113
+ /**
114
+ * Get route parameters.
115
+ */
116
+ params() {
117
+ return getRouterParams(this.event);
118
+ }
119
+ /**
120
+ * Get query parameters.
121
+ */
122
+ query() {
123
+ return getQuery(this.event);
124
+ }
125
+ getEvent(key) {
126
+ return safeDot(this.event, key);
127
+ }
128
+ };
129
+
130
+ // ../http/src/Response.ts
131
+ import { html, redirect } from "h3";
132
+ var Response = class {
133
+ static {
134
+ __name(this, "Response");
135
+ }
136
+ event;
137
+ statusCode = 200;
138
+ headers = {};
139
+ constructor(event) {
140
+ this.event = event;
141
+ }
142
+ /**
143
+ * Set HTTP status code.
144
+ */
145
+ setStatusCode(code) {
146
+ this.statusCode = code;
147
+ this.event.res.status = code;
148
+ return this;
149
+ }
150
+ /**
151
+ * Set a header.
152
+ */
153
+ setHeader(name, value) {
154
+ this.headers[name] = value;
155
+ return this;
156
+ }
157
+ html(content) {
158
+ this.applyHeaders();
159
+ return html(this.event, content);
160
+ }
161
+ /**
162
+ * Send a JSON response.
163
+ */
164
+ json(data) {
165
+ this.setHeader("content-type", "application/json; charset=utf-8");
166
+ this.applyHeaders();
167
+ return data;
168
+ }
169
+ /**
170
+ * Send plain text.
171
+ */
172
+ text(data) {
173
+ this.setHeader("content-type", "text/plain; charset=utf-8");
174
+ this.applyHeaders();
175
+ return data;
176
+ }
177
+ /**
178
+ * Redirect to another URL.
179
+ */
180
+ redirect(url, status = 302) {
181
+ this.setStatusCode(status);
182
+ return redirect(this.event, url, this.statusCode);
183
+ }
184
+ /**
185
+ * Apply headers before sending response.
186
+ */
187
+ applyHeaders() {
188
+ Object.entries(this.headers).forEach(([key, value]) => {
189
+ this.event.res.headers.set(key, value);
190
+ });
191
+ }
192
+ getEvent(key) {
193
+ return safeDot(this.event, key);
194
+ }
195
+ };
196
+
197
+ // ../http/src/Middleware/LogRequests.ts
198
+ var LogRequests = class extends Middleware {
199
+ static {
200
+ __name(this, "LogRequests");
201
+ }
202
+ async handle({ request }, next) {
203
+ const url = request.getEvent("url");
204
+ console.log(`[${request.getEvent("method")}] ${url.pathname + url.search}`);
205
+ return next();
206
+ }
207
+ };
208
+
209
+ // ../http/src/Providers/HttpServiceProvider.ts
210
+ import { H3, serve } from "h3";
211
+
212
+ // src/Container.ts
213
+ var Container = class {
214
+ static {
215
+ __name(this, "Container");
216
+ }
217
+ bindings = /* @__PURE__ */ new Map();
218
+ singletons = /* @__PURE__ */ new Map();
219
+ bind(key, factory) {
220
+ this.bindings.set(key, factory);
221
+ }
222
+ /**
223
+ * Bind a singleton service to the container
224
+ */
225
+ singleton(key, factory) {
226
+ this.bindings.set(key, () => {
227
+ if (!this.singletons.has(key)) {
228
+ this.singletons.set(key, factory());
229
+ }
230
+ return this.singletons.get(key);
231
+ });
232
+ }
233
+ /**
234
+ * Resolve a service from the container
235
+ */
236
+ make(key) {
237
+ if (this.bindings.has(key)) {
238
+ return this.bindings.get(key)();
239
+ }
240
+ if (typeof key === "function") {
241
+ return this.build(key);
242
+ }
243
+ throw new Error(`No binding found for key: ${typeof key === "string" ? key : key?.name}`);
244
+ }
245
+ /**
246
+ * Automatically build a class with constructor dependency injection
247
+ */
248
+ build(ClassType) {
249
+ const paramTypes = Reflect.getMetadata("design:paramtypes", ClassType) || [];
250
+ const dependencies = paramTypes.map((dep) => this.make(dep));
251
+ return new ClassType(...dependencies);
252
+ }
253
+ /**
254
+ * Check if a service is registered
255
+ */
256
+ has(key) {
257
+ return this.bindings.has(key);
258
+ }
259
+ };
260
+
261
+ // src/Utils/PathLoader.ts
262
+ import nodepath from "path";
263
+ var PathLoader = class {
264
+ static {
265
+ __name(this, "PathLoader");
266
+ }
267
+ paths = {
268
+ base: "",
269
+ views: "/src/resources/views",
270
+ assets: "/public/assets",
271
+ routes: "/src/routes",
272
+ config: "/src/config",
273
+ public: "/public",
274
+ storage: "/src/storage"
275
+ };
276
+ /**
277
+ * Dynamically retrieves a path property from the class.
278
+ * Any property ending with "Path" is accessible automatically.
279
+ *
280
+ * @param name - The base name of the path property
281
+ * @param base - The base path to include to the path
282
+ * @returns
283
+ */
284
+ getPath(name, base) {
285
+ if (base && name !== "base") {
286
+ return nodepath.join(base, this.paths[name]);
287
+ }
288
+ return this.paths[name];
289
+ }
290
+ /**
291
+ * Programatically set the paths.
292
+ *
293
+ * @param name - The base name of the path property
294
+ * @param path - The new path
295
+ * @param base - The base path to include to the path
296
+ */
297
+ setPath(name, path2, base) {
298
+ if (base && name !== "base") {
299
+ this.paths[name] = nodepath.join(base, path2);
300
+ }
301
+ this.paths[name] = path2;
302
+ }
303
+ };
304
+
305
+ // src/Application.ts
306
+ import path from "path";
307
+ var Application = class _Application extends Container {
308
+ static {
309
+ __name(this, "Application");
310
+ }
311
+ paths = new PathLoader();
312
+ booted = false;
313
+ versions = {
314
+ app: "0",
315
+ ts: "0"
316
+ };
317
+ basePath;
318
+ providers = [];
319
+ externalProviders = [];
320
+ constructor(basePath) {
321
+ super();
322
+ this.basePath = basePath;
323
+ this.setPath("base", basePath);
324
+ this.loadOptions();
325
+ this.registerBaseBindings();
326
+ }
327
+ /**
328
+ * Register core bindings into the container
329
+ */
330
+ registerBaseBindings() {
331
+ this.bind(_Application, () => this);
332
+ this.bind("path.base", () => this.basePath);
333
+ this.bind("app.paths", () => this.paths);
334
+ }
335
+ /**
336
+ * Dynamically register all configured providers
337
+ */
338
+ async registerConfiguredProviders() {
339
+ const providers = await this.getAllProviders();
340
+ for (const ProviderClass of providers) {
341
+ if (!ProviderClass) continue;
342
+ const provider = new ProviderClass(this);
343
+ await this.register(provider);
344
+ }
345
+ }
346
+ async loadOptions() {
347
+ const app = await this.safeImport(this.getPath("base", "package.json"));
348
+ const core = await this.safeImport("../package.json");
349
+ if (app && app.dependencies) {
350
+ this.versions.app = app.dependencies["@h3ravel/core"];
351
+ }
352
+ if (core && core.devDependencies) {
353
+ this.versions.ts = app.devDependencies.typescript;
354
+ }
355
+ }
356
+ /**
357
+ * Load default and optional providers dynamically
358
+ *
359
+ * Auto-Registration Behavior
360
+ *
361
+ * Minimal App: Loads only core, config, http, router by default.
362
+ * Full-Stack App: Installs database, mail, queue, cache → they self-register via their providers.
363
+ */
364
+ async getConfiguredProviders() {
365
+ return [
366
+ (await import("./index.js")).AppServiceProvider,
367
+ (await import("./src-HATPJSC4.js")).HttpServiceProvider,
368
+ (await import("./src-KWAEFHNO.js")).ConfigServiceProvider,
369
+ (await import("./src-E5IZSMT7.js")).RouteServiceProvider,
370
+ (await import("./src-E5IZSMT7.js")).AssetsServiceProvider,
371
+ (await import("./index.js")).ViewServiceProvider,
372
+ (await this.safeImport("@h3ravel/database"))?.DatabaseServiceProvider,
373
+ (await this.safeImport("@h3ravel/cache"))?.CacheServiceProvider,
374
+ (await this.safeImport("@h3ravel/console"))?.ConsoleServiceProvider,
375
+ (await this.safeImport("@h3ravel/queue"))?.QueueServiceProvider,
376
+ (await this.safeImport("@h3ravel/mail"))?.MailServiceProvider
377
+ ];
378
+ }
379
+ async getAllProviders() {
380
+ const coreProviders = await this.getConfiguredProviders();
381
+ return [
382
+ ...coreProviders,
383
+ ...this.externalProviders
384
+ ];
385
+ }
386
+ registerProviders(providers) {
387
+ this.externalProviders.push(...providers);
388
+ }
389
+ /**
390
+ * Register a provider
391
+ */
392
+ async register(provider) {
393
+ await provider.register();
394
+ this.providers.push(provider);
395
+ }
396
+ /**
397
+ * Boot all providers after registration
398
+ */
399
+ async boot() {
400
+ if (this.booted) return;
401
+ for (const provider of this.providers) {
402
+ if (provider.boot) {
403
+ await provider.boot();
404
+ }
405
+ }
406
+ this.booted = true;
407
+ }
408
+ /**
409
+ * Attempt to dynamically import an optional module
410
+ */
411
+ async safeImport(moduleName) {
412
+ try {
413
+ const mod = await import(moduleName);
414
+ return mod.default ?? mod;
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ /**
420
+ * Get the base path of the app
421
+ *
422
+ * @returns
423
+ */
424
+ getBasePath() {
425
+ return this.basePath;
426
+ }
427
+ /**
428
+ * Dynamically retrieves a path property from the class.
429
+ * Any property ending with "Path" is accessible automatically.
430
+ *
431
+ * @param name - The base name of the path property
432
+ * @returns
433
+ */
434
+ getPath(name, pth) {
435
+ return path.join(this.paths.getPath(name, this.basePath), pth ?? "");
436
+ }
437
+ /**
438
+ * Programatically set the paths.
439
+ *
440
+ * @param name - The base name of the path property
441
+ * @param path - The new path
442
+ * @returns
443
+ */
444
+ setPath(name, path2) {
445
+ return this.paths.setPath(name, path2, this.basePath);
446
+ }
447
+ /**
448
+ * Returns the installed version of the system core and typescript.
449
+ *
450
+ * @returns
451
+ */
452
+ getVersion(key) {
453
+ return this.versions[key]?.replaceAll(/\^|\~/g, "");
454
+ }
455
+ };
456
+
457
+ // src/Controller.ts
458
+ var Controller = class {
459
+ static {
460
+ __name(this, "Controller");
461
+ }
462
+ app;
463
+ constructor(app) {
464
+ this.app = app;
465
+ }
466
+ show(_ctx) {
467
+ return;
468
+ }
469
+ index(_ctx) {
470
+ return;
471
+ }
472
+ store(_ctx) {
473
+ return;
474
+ }
475
+ update(_ctx) {
476
+ return;
477
+ }
478
+ destroy(_ctx) {
479
+ return;
480
+ }
481
+ };
482
+
483
+ // src/ServiceProvider.ts
484
+ var ServiceProvider = class {
485
+ static {
486
+ __name(this, "ServiceProvider");
487
+ }
488
+ app;
489
+ constructor(app) {
490
+ this.app = app;
491
+ }
492
+ };
493
+
494
+ // src/Http/Kernel.ts
495
+ var Kernel = class {
496
+ static {
497
+ __name(this, "Kernel");
498
+ }
499
+ middleware;
500
+ constructor(middleware = []) {
501
+ this.middleware = middleware;
502
+ }
503
+ async handle(event, next) {
504
+ const context = {
505
+ request: new Request(event),
506
+ response: new Response(event)
507
+ };
508
+ const result = await this.runMiddleware(context, () => next(context));
509
+ if (result !== void 0 && this.isPlainObject(result)) {
510
+ event.res.headers.set("Content-Type", "application/json; charset=UTF-8");
511
+ }
512
+ return result;
513
+ }
514
+ async runMiddleware(context, next) {
515
+ let index = -1;
516
+ const runner = /* @__PURE__ */ __name(async (i) => {
517
+ if (i <= index) throw new Error("next() called multiple times");
518
+ index = i;
519
+ const middleware = this.middleware[i];
520
+ if (middleware) {
521
+ return middleware.handle(context, () => runner(i + 1));
522
+ } else {
523
+ return next(context);
524
+ }
525
+ }, "runner");
526
+ return runner(0);
527
+ }
528
+ isPlainObject(value) {
529
+ return typeof value === "object" && value !== null && (value.constructor === Object || value.constructor === Array);
530
+ }
531
+ };
532
+
533
+ // src/Providers/AppServiceProvider.ts
534
+ import "reflect-metadata";
535
+ var AppServiceProvider = class extends ServiceProvider {
536
+ static {
537
+ __name(this, "AppServiceProvider");
538
+ }
539
+ register() {
540
+ }
541
+ };
542
+
543
+ // src/Providers/ViewServiceProvider.ts
544
+ import { Edge } from "edge.js";
545
+ var ViewServiceProvider = class extends ServiceProvider {
546
+ static {
547
+ __name(this, "ViewServiceProvider");
548
+ }
549
+ register() {
550
+ const config = this.app.make("config");
551
+ const edge = Edge.create({
552
+ cache: process.env.NODE_ENV === "production"
553
+ });
554
+ edge.mount(this.app.getPath("views"));
555
+ edge.global("asset", this.app.make("asset"));
556
+ edge.global("config", config.get);
557
+ edge.global("app", this.app);
558
+ this.app.bind("view", () => edge);
559
+ }
560
+ };
561
+
562
+ // ../http/src/Providers/HttpServiceProvider.ts
563
+ var HttpServiceProvider = class extends ServiceProvider {
564
+ static {
565
+ __name(this, "HttpServiceProvider");
566
+ }
567
+ register() {
568
+ this.app.singleton("http.app", () => {
569
+ return new H3();
570
+ });
571
+ this.app.singleton("http.serve", () => serve);
572
+ }
573
+ };
574
+
575
+ // ../http/src/Resources/JsonResource.ts
576
+ var JsonResource = class {
577
+ static {
578
+ __name(this, "JsonResource");
579
+ }
580
+ event;
581
+ /**
582
+ * The request instance
583
+ */
584
+ request;
585
+ /**
586
+ * The response instance
587
+ */
588
+ response;
589
+ /**
590
+ * The data to send to the client
591
+ */
592
+ resource;
593
+ /**
594
+ * The final response data object
595
+ */
596
+ body = {
597
+ data: {}
598
+ };
599
+ /**
600
+ * Flag to track if response should be sent automatically
601
+ */
602
+ shouldSend = false;
603
+ /**
604
+ * Flag to track if response has been sent
605
+ */
606
+ responseSent = false;
607
+ /**
608
+ * @param req The request instance
609
+ * @param res The response instance
610
+ * @param rsc The data to send to the client
611
+ */
612
+ constructor(event, rsc) {
613
+ this.event = event;
614
+ this.request = event.req;
615
+ this.response = event.res;
616
+ this.resource = rsc;
617
+ for (const key of Object.keys(rsc)) {
618
+ if (!(key in this)) {
619
+ Object.defineProperty(this, key, {
620
+ enumerable: true,
621
+ configurable: true,
622
+ get: /* @__PURE__ */ __name(() => this.resource[key], "get"),
623
+ set: /* @__PURE__ */ __name((value) => {
624
+ this.resource[key] = value;
625
+ }, "set")
626
+ });
627
+ }
628
+ }
629
+ }
630
+ /**
631
+ * Return the data in the expected format
632
+ *
633
+ * @returns
634
+ */
635
+ data() {
636
+ return this.resource;
637
+ }
638
+ /**
639
+ * Build the response object
640
+ * @returns this
641
+ */
642
+ json() {
643
+ this.shouldSend = true;
644
+ this.response.status = 200;
645
+ const resource = this.data();
646
+ let data = Array.isArray(resource) ? [
647
+ ...resource
648
+ ] : {
649
+ ...resource
650
+ };
651
+ if (typeof data.data !== "undefined") {
652
+ data = data.data;
653
+ }
654
+ if (!Array.isArray(resource)) {
655
+ delete data.pagination;
656
+ }
657
+ this.body = {
658
+ data
659
+ };
660
+ if (!Array.isArray(resource) && resource.pagination) {
661
+ const meta = this.body.meta ?? {};
662
+ meta.pagination = resource.pagination;
663
+ this.body.meta = meta;
664
+ }
665
+ if (this.resource.pagination && !this.body.meta?.pagination) {
666
+ const meta = this.body.meta ?? {};
667
+ meta.pagination = this.resource.pagination;
668
+ this.body.meta = meta;
669
+ }
670
+ return this;
671
+ }
672
+ /**
673
+ * Add context data to the response object
674
+ * @param data Context data
675
+ * @returns this
676
+ */
677
+ additional(data) {
678
+ this.shouldSend = true;
679
+ delete data.data;
680
+ delete data.pagination;
681
+ this.body = {
682
+ ...this.body,
683
+ ...data
684
+ };
685
+ return this;
686
+ }
687
+ /**
688
+ * Send the output to the client
689
+ * @returns this
690
+ */
691
+ send() {
692
+ this.shouldSend = false;
693
+ if (!this.responseSent) {
694
+ this.#send();
695
+ }
696
+ return this;
697
+ }
698
+ /**
699
+ * Set the status code for this response
700
+ * @param code Status code
701
+ * @returns this
702
+ */
703
+ status(code) {
704
+ this.response.status = code;
705
+ return this;
706
+ }
707
+ /**
708
+ * Private method to send the response
709
+ */
710
+ #send() {
711
+ if (!this.responseSent) {
712
+ this.event.context.this.response.json(this.body);
713
+ this.responseSent = true;
714
+ }
715
+ }
716
+ /**
717
+ * Check if send should be triggered automatically
718
+ */
719
+ checkSend() {
720
+ if (this.shouldSend && !this.responseSent) {
721
+ this.#send();
722
+ }
723
+ }
724
+ };
725
+
726
+ // ../http/src/Resources/ApiResource.ts
727
+ function ApiResource(instance) {
728
+ return new Proxy(instance, {
729
+ get(target, prop, receiver) {
730
+ const value = Reflect.get(target, prop, receiver);
731
+ if (typeof value === "function") {
732
+ if (prop === "json" || prop === "additional") {
733
+ return (...args) => {
734
+ const result = value.apply(target, args);
735
+ setImmediate(() => target["checkSend"]());
736
+ return result;
737
+ };
738
+ } else if (prop === "send") {
739
+ return (...args) => {
740
+ target["shouldSend"] = false;
741
+ return value.apply(target, args);
742
+ };
743
+ }
744
+ }
745
+ return value;
746
+ }
747
+ });
748
+ }
749
+ __name(ApiResource, "ApiResource");
750
+
751
+ export {
752
+ __name,
753
+ __require,
754
+ __commonJS,
755
+ __toESM,
756
+ Container,
757
+ PathLoader,
758
+ Middleware,
759
+ safeDot,
760
+ setNested,
761
+ afterLast,
762
+ before,
763
+ Request,
764
+ Response,
765
+ LogRequests,
766
+ HttpServiceProvider,
767
+ JsonResource,
768
+ ApiResource,
769
+ Application,
770
+ Controller,
771
+ ServiceProvider,
772
+ Kernel,
773
+ AppServiceProvider,
774
+ ViewServiceProvider
775
+ };
776
+ //# sourceMappingURL=chunk-UZGBH6AC.js.map