@miguelmorales13/nestkit 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +250 -6
  2. package/dist/auth/index.cjs +12 -70
  3. package/dist/auth/index.js +11 -69
  4. package/dist/auth/oauth/http.d.ts +38 -0
  5. package/dist/auth/oauth/index.cjs +610 -0
  6. package/dist/auth/oauth/index.d.ts +12 -0
  7. package/dist/auth/oauth/index.js +610 -0
  8. package/dist/auth/oauth/oauth-auth.module.d.ts +28 -0
  9. package/dist/auth/oauth/oauth-auth.service.d.ts +101 -0
  10. package/dist/auth/oauth/oauth-callback.filter.d.ts +20 -0
  11. package/dist/auth/oauth/oauth-provider.d.ts +73 -0
  12. package/dist/auth/oauth/oauth.controller.d.ts +38 -0
  13. package/dist/auth/oauth/oauth.options.d.ts +89 -0
  14. package/dist/auth/oauth/oauth.ports.d.ts +86 -0
  15. package/dist/bootstrap/index.cjs +3 -3
  16. package/dist/bootstrap/index.js +2 -2
  17. package/dist/chunk-A3B2EY4V.js +75 -0
  18. package/dist/chunk-NVCI3CQI.cjs +75 -0
  19. package/dist/database/typeorm/index.cjs +79 -0
  20. package/dist/database/typeorm/index.d.ts +3 -0
  21. package/dist/database/typeorm/index.js +79 -0
  22. package/dist/database/typeorm/tenant-scope.d.ts +18 -0
  23. package/dist/database/typeorm/typeorm.module.d.ts +35 -0
  24. package/dist/index.cjs +17 -17
  25. package/dist/index.js +24 -24
  26. package/dist/umami/index.cjs +109 -0
  27. package/dist/umami/index.d.ts +4 -0
  28. package/dist/umami/index.js +109 -0
  29. package/dist/umami/umami.module.d.ts +6 -0
  30. package/dist/umami/umami.options.d.ts +20 -0
  31. package/dist/umami/umami.service.d.ts +40 -0
  32. package/dist/umami/umami.types.d.ts +24 -0
  33. package/package.json +22 -2
  34. package/dist/{chunk-AOCF5QCZ.js → chunk-ANQ3YPDI.js} +3 -3
  35. package/dist/{chunk-54ZXIB5T.cjs → chunk-YARLPYG5.cjs} +2 -2
@@ -0,0 +1,79 @@
1
+ import {
2
+ __decorateClass
3
+ } from "../../chunk-4MGIQFAJ.js";
4
+
5
+ // src/database/typeorm/typeorm.module.ts
6
+ import { Global, Module } from "@nestjs/common";
7
+ import { DataSource } from "typeorm";
8
+ var DATA_SOURCE = /* @__PURE__ */ Symbol("DATA_SOURCE");
9
+ function buildDataSource(options) {
10
+ const url = options.url ?? process.env.DATABASE_URL;
11
+ const hasHost = Boolean(options.host);
12
+ if (!url && !hasHost) {
13
+ throw new Error("TypeOrmDbModule: DATABASE_URL is not set and no host was provided.");
14
+ }
15
+ return new DataSource({
16
+ type: "postgres",
17
+ url,
18
+ synchronize: false,
19
+ ...options
20
+ });
21
+ }
22
+ var TypeOrmDbModule = class {
23
+ static forRoot(options = {}) {
24
+ const provider = {
25
+ provide: DATA_SOURCE,
26
+ useFactory: async () => {
27
+ const dataSource = buildDataSource(options);
28
+ return dataSource.isInitialized ? dataSource : dataSource.initialize();
29
+ }
30
+ };
31
+ return { module: TypeOrmDbModule, providers: [provider], exports: [DATA_SOURCE] };
32
+ }
33
+ static forRootAsync(config) {
34
+ const provider = {
35
+ provide: DATA_SOURCE,
36
+ useFactory: async (...args) => {
37
+ const options = await config.useFactory(...args);
38
+ const dataSource = buildDataSource(options);
39
+ return dataSource.isInitialized ? dataSource : dataSource.initialize();
40
+ },
41
+ inject: config.inject ?? []
42
+ };
43
+ return {
44
+ module: TypeOrmDbModule,
45
+ imports: config.imports ?? [],
46
+ providers: [provider],
47
+ exports: [DATA_SOURCE]
48
+ };
49
+ }
50
+ };
51
+ TypeOrmDbModule = __decorateClass([
52
+ Global(),
53
+ Module({})
54
+ ], TypeOrmDbModule);
55
+
56
+ // src/database/typeorm/tenant-scope.ts
57
+ async function withTenantScope(dataSource, session, fn) {
58
+ const runner = dataSource.createQueryRunner();
59
+ await runner.connect();
60
+ await runner.startTransaction();
61
+ try {
62
+ for (const [name, value] of Object.entries(session)) {
63
+ await runner.query("SELECT set_config($1, $2, true)", [name, value]);
64
+ }
65
+ const result = await fn(runner.manager);
66
+ await runner.commitTransaction();
67
+ return result;
68
+ } catch (error) {
69
+ await runner.rollbackTransaction();
70
+ throw error;
71
+ } finally {
72
+ await runner.release();
73
+ }
74
+ }
75
+ export {
76
+ DATA_SOURCE,
77
+ TypeOrmDbModule,
78
+ withTenantScope
79
+ };
@@ -0,0 +1,18 @@
1
+ import type { DataSource, EntityManager } from 'typeorm';
2
+ /**
3
+ * Runs `fn` inside a transaction whose Postgres session variables are set first,
4
+ * so Row-Level-Security policies keyed on those variables see the right tenant.
5
+ * This is the TypeORM equivalent of the `pg` `withTenantScope`: the ORM's own
6
+ * pool would otherwise run each query on an arbitrary connection with no session
7
+ * context, and RLS would see nothing (or the wrong rows).
8
+ *
9
+ * The `EntityManager` handed to `fn` is bound to that one connection and
10
+ * transaction — so `manager.getRepository(X).find()` inside runs scoped, and
11
+ * anything outside `fn` does NOT. Never leak the manager past the callback.
12
+ *
13
+ * `session` maps GUC name → value, e.g.
14
+ * `{ 'request.jwt.claim.sub': userId, 'request.jwt.claim.tenant': tenantId }`.
15
+ * Each is applied with `set_config(name, value, true)` (transaction-local), which
16
+ * is why the whole thing has to be one transaction on one connection.
17
+ */
18
+ export declare function withTenantScope<T>(dataSource: DataSource, session: Record<string, string>, fn: (manager: EntityManager) => Promise<T>): Promise<T>;
@@ -0,0 +1,35 @@
1
+ import { type DynamicModule } from '@nestjs/common';
2
+ import { type DataSourceOptions } from 'typeorm';
3
+ /** DI token for the shared TypeORM {@link DataSource}. Inject with `@Inject(DATA_SOURCE)`. */
4
+ export declare const DATA_SOURCE: unique symbol;
5
+ /**
6
+ * Options for the DataSource. Everything TypeORM's Postgres options accept, minus
7
+ * the bits this module fixes: `type` is always `postgres`, and `synchronize` is
8
+ * forced off (schema changes go through migrations, never an auto-sync that would
9
+ * happily drop a column in production). `url` defaults to `DATABASE_URL`.
10
+ */
11
+ export type TypeOrmDbOptions = Partial<Omit<DataSourceOptions, 'type' | 'synchronize'>>;
12
+ /**
13
+ * Provides a single, initialized TypeORM {@link DataSource} for the app, on
14
+ * Postgres (works with any Postgres, including Neon). Register once in the root
15
+ * module; inject the DataSource with `@Inject(DATA_SOURCE)`.
16
+ *
17
+ * For multi-tenant apps that rely on Postgres RLS, pair it with `withTenantScope`
18
+ * (same folder): the ORM pool alone has no per-request session, so tenant-scoped
19
+ * work must go through that helper.
20
+ *
21
+ * ```ts
22
+ * TypeOrmDbModule.forRoot({ entities: [User, Order], migrations: ['dist/migrations/*.js'] })
23
+ * ```
24
+ *
25
+ * `synchronize` is always off and `type` is always `postgres` — schema changes go
26
+ * through migrations. Install `typeorm` (optional peer dep) to use this module.
27
+ */
28
+ export declare class TypeOrmDbModule {
29
+ static forRoot(options?: TypeOrmDbOptions): DynamicModule;
30
+ static forRootAsync(config: {
31
+ imports?: any[];
32
+ inject?: any[];
33
+ useFactory: (...args: any[]) => TypeOrmDbOptions | Promise<TypeOrmDbOptions>;
34
+ }): DynamicModule;
35
+ }
package/dist/index.cjs CHANGED
@@ -1,54 +1,54 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('./chunk-KVBQBT3D.cjs');
2
2
 
3
3
 
4
4
 
5
- var _chunkRF75KC63cjs = require('./chunk-RF75KC63.cjs');
6
5
 
6
+ var _chunkSPTDXUEDcjs = require('./chunk-SPTDXUED.cjs');
7
7
 
8
8
 
9
9
 
10
- var _chunkSPTDXUEDcjs = require('./chunk-SPTDXUED.cjs');
11
10
 
11
+ var _chunkRF75KC63cjs = require('./chunk-RF75KC63.cjs');
12
+ require('./chunk-7SOM7EZP.cjs');
13
+ require('./chunk-MR2IFCZE.cjs');
12
14
 
13
15
 
14
- var _chunkM3EL5O6Tcjs = require('./chunk-M3EL5O6T.cjs');
15
16
 
16
17
 
18
+ var _chunkV2M75FN3cjs = require('./chunk-V2M75FN3.cjs');
17
19
 
18
20
 
19
- var _chunkV2M75FN3cjs = require('./chunk-V2M75FN3.cjs');
20
- require('./chunk-MR2IFCZE.cjs');
21
- require('./chunk-KVBQBT3D.cjs');
22
21
 
23
22
 
24
- var _chunk54ZXIB5Tcjs = require('./chunk-54ZXIB5T.cjs');
25
23
 
26
24
 
27
- var _chunkTJHRABMLcjs = require('./chunk-TJHRABML.cjs');
25
+ var _chunkFDNGAYTZcjs = require('./chunk-FDNGAYTZ.cjs');
28
26
 
29
27
 
28
+ var _chunkYARLPYG5cjs = require('./chunk-YARLPYG5.cjs');
30
29
 
31
- var _chunk3CLYZC3Tcjs = require('./chunk-3CLYZC3T.cjs');
32
30
 
31
+ var _chunkJ7TURDALcjs = require('./chunk-J7TURDAL.cjs');
33
32
 
34
- var _chunkQS2W5XCQcjs = require('./chunk-QS2W5XCQ.cjs');
35
- require('./chunk-7SOM7EZP.cjs');
36
33
 
34
+ var _chunkTJHRABMLcjs = require('./chunk-TJHRABML.cjs');
37
35
 
38
36
 
37
+ var _chunkZA56XBCKcjs = require('./chunk-ZA56XBCK.cjs');
39
38
 
40
39
 
40
+ var _chunkR7BVS6CIcjs = require('./chunk-R7BVS6CI.cjs');
41
41
 
42
- var _chunkFDNGAYTZcjs = require('./chunk-FDNGAYTZ.cjs');
43
42
 
44
43
 
45
- var _chunkJ7TURDALcjs = require('./chunk-J7TURDAL.cjs');
44
+ var _chunk3CLYZC3Tcjs = require('./chunk-3CLYZC3T.cjs');
46
45
 
47
46
 
48
- var _chunkZA56XBCKcjs = require('./chunk-ZA56XBCK.cjs');
47
+ var _chunkQS2W5XCQcjs = require('./chunk-QS2W5XCQ.cjs');
49
48
 
50
49
 
51
- var _chunkR7BVS6CIcjs = require('./chunk-R7BVS6CI.cjs');
50
+
51
+ var _chunkM3EL5O6Tcjs = require('./chunk-M3EL5O6T.cjs');
52
52
  require('./chunk-2REOCMUD.cjs');
53
53
 
54
54
 
@@ -75,4 +75,4 @@ require('./chunk-2REOCMUD.cjs');
75
75
 
76
76
 
77
77
 
78
- exports.AppException = _chunkR7BVS6CIcjs.AppException; exports.BaseCrudService = _chunk3CLYZC3Tcjs.BaseCrudService; exports.BaseResponseDto = _chunkQS2W5XCQcjs.BaseResponseDto; exports.ConflictAppException = _chunkFDNGAYTZcjs.ConflictAppException; exports.ForbiddenAppException = _chunkFDNGAYTZcjs.ForbiddenAppException; exports.GlobalExceptionFilter = _chunkJ7TURDALcjs.GlobalExceptionFilter; exports.I18nModule = _chunkM3EL5O6Tcjs.I18nModule; exports.NotFoundAppException = _chunkFDNGAYTZcjs.NotFoundAppException; exports.PG_POOL = _chunkRF75KC63cjs.PG_POOL; exports.PgModule = _chunkRF75KC63cjs.PgModule; exports.REQUEST_ID_HEADER = _chunkV2M75FN3cjs.REQUEST_ID_HEADER; exports.RequestContext = _chunkZA56XBCKcjs.RequestContext; exports.RequestIdMiddleware = _chunkV2M75FN3cjs.RequestIdMiddleware; exports.ResponseInterceptor = _chunkTJHRABMLcjs.ResponseInterceptor; exports.SUPABASE_ANON_CLIENT = _chunkSPTDXUEDcjs.SUPABASE_ANON_CLIENT; exports.SUPABASE_SERVICE_ROLE_CLIENT = _chunkSPTDXUEDcjs.SUPABASE_SERVICE_ROLE_CLIENT; exports.SupabaseModule = _chunkSPTDXUEDcjs.SupabaseModule; exports.TrackingModule = _chunkV2M75FN3cjs.TrackingModule; exports.UnauthorizedAppException = _chunkFDNGAYTZcjs.UnauthorizedAppException; exports.ValidationAppException = _chunkFDNGAYTZcjs.ValidationAppException; exports.applyNestKitDefaults = _chunk54ZXIB5Tcjs.applyNestKitDefaults; exports.createCrudController = _chunk3CLYZC3Tcjs.createCrudController; exports.translateOr = _chunkM3EL5O6Tcjs.translateOr; exports.withTenantScope = _chunkRF75KC63cjs.withTenantScope;
78
+ exports.AppException = _chunkR7BVS6CIcjs.AppException; exports.BaseCrudService = _chunk3CLYZC3Tcjs.BaseCrudService; exports.BaseResponseDto = _chunkQS2W5XCQcjs.BaseResponseDto; exports.ConflictAppException = _chunkFDNGAYTZcjs.ConflictAppException; exports.ForbiddenAppException = _chunkFDNGAYTZcjs.ForbiddenAppException; exports.GlobalExceptionFilter = _chunkJ7TURDALcjs.GlobalExceptionFilter; exports.I18nModule = _chunkM3EL5O6Tcjs.I18nModule; exports.NotFoundAppException = _chunkFDNGAYTZcjs.NotFoundAppException; exports.PG_POOL = _chunkRF75KC63cjs.PG_POOL; exports.PgModule = _chunkRF75KC63cjs.PgModule; exports.REQUEST_ID_HEADER = _chunkV2M75FN3cjs.REQUEST_ID_HEADER; exports.RequestContext = _chunkZA56XBCKcjs.RequestContext; exports.RequestIdMiddleware = _chunkV2M75FN3cjs.RequestIdMiddleware; exports.ResponseInterceptor = _chunkTJHRABMLcjs.ResponseInterceptor; exports.SUPABASE_ANON_CLIENT = _chunkSPTDXUEDcjs.SUPABASE_ANON_CLIENT; exports.SUPABASE_SERVICE_ROLE_CLIENT = _chunkSPTDXUEDcjs.SUPABASE_SERVICE_ROLE_CLIENT; exports.SupabaseModule = _chunkSPTDXUEDcjs.SupabaseModule; exports.TrackingModule = _chunkV2M75FN3cjs.TrackingModule; exports.UnauthorizedAppException = _chunkFDNGAYTZcjs.UnauthorizedAppException; exports.ValidationAppException = _chunkFDNGAYTZcjs.ValidationAppException; exports.applyNestKitDefaults = _chunkYARLPYG5cjs.applyNestKitDefaults; exports.createCrudController = _chunk3CLYZC3Tcjs.createCrudController; exports.translateOr = _chunkM3EL5O6Tcjs.translateOr; exports.withTenantScope = _chunkRF75KC63cjs.withTenantScope;
package/dist/index.js CHANGED
@@ -1,38 +1,21 @@
1
- import {
2
- PG_POOL,
3
- PgModule,
4
- withTenantScope
5
- } from "./chunk-VKOPDDCC.js";
1
+ import "./chunk-EPVKCBPT.js";
6
2
  import {
7
3
  SUPABASE_ANON_CLIENT,
8
4
  SUPABASE_SERVICE_ROLE_CLIENT,
9
5
  SupabaseModule
10
6
  } from "./chunk-PA24P76K.js";
11
7
  import {
12
- I18nModule,
13
- translateOr
14
- } from "./chunk-IYUUYCP5.js";
8
+ PG_POOL,
9
+ PgModule,
10
+ withTenantScope
11
+ } from "./chunk-VKOPDDCC.js";
12
+ import "./chunk-DQYAIQQ5.js";
13
+ import "./chunk-NAK4WDKS.js";
15
14
  import {
16
15
  REQUEST_ID_HEADER,
17
16
  RequestIdMiddleware,
18
17
  TrackingModule
19
18
  } from "./chunk-EYURGACO.js";
20
- import "./chunk-NAK4WDKS.js";
21
- import "./chunk-EPVKCBPT.js";
22
- import {
23
- applyNestKitDefaults
24
- } from "./chunk-AOCF5QCZ.js";
25
- import {
26
- ResponseInterceptor
27
- } from "./chunk-KDAA6GFF.js";
28
- import {
29
- BaseCrudService,
30
- createCrudController
31
- } from "./chunk-JOVBJDJ2.js";
32
- import {
33
- BaseResponseDto
34
- } from "./chunk-XX2HPTRU.js";
35
- import "./chunk-DQYAIQQ5.js";
36
19
  import {
37
20
  ConflictAppException,
38
21
  ForbiddenAppException,
@@ -40,15 +23,32 @@ import {
40
23
  UnauthorizedAppException,
41
24
  ValidationAppException
42
25
  } from "./chunk-ORWJ7LES.js";
26
+ import {
27
+ applyNestKitDefaults
28
+ } from "./chunk-ANQ3YPDI.js";
43
29
  import {
44
30
  GlobalExceptionFilter
45
31
  } from "./chunk-7I2Y7V52.js";
32
+ import {
33
+ ResponseInterceptor
34
+ } from "./chunk-KDAA6GFF.js";
46
35
  import {
47
36
  RequestContext
48
37
  } from "./chunk-EBO6UKHL.js";
49
38
  import {
50
39
  AppException
51
40
  } from "./chunk-YFYHLYHN.js";
41
+ import {
42
+ BaseCrudService,
43
+ createCrudController
44
+ } from "./chunk-JOVBJDJ2.js";
45
+ import {
46
+ BaseResponseDto
47
+ } from "./chunk-XX2HPTRU.js";
48
+ import {
49
+ I18nModule,
50
+ translateOr
51
+ } from "./chunk-IYUUYCP5.js";
52
52
  import "./chunk-4MGIQFAJ.js";
53
53
  export {
54
54
  AppException,
@@ -0,0 +1,109 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+ var _chunk2REOCMUDcjs = require('../chunk-2REOCMUD.cjs');
5
+
6
+ // src/umami/umami.module.ts
7
+ var _common = require('@nestjs/common');
8
+
9
+ // src/umami/umami.service.ts
10
+
11
+
12
+ // src/umami/umami.options.ts
13
+ var UMAMI_OPTIONS = /* @__PURE__ */ Symbol("UMAMI_OPTIONS");
14
+
15
+ // src/umami/umami.service.ts
16
+ var CLOUD = "https://api.umami.is/v1";
17
+ var DAY_MS = 24 * 60 * 60 * 1e3;
18
+ var UmamiService = class {
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.logger = new (0, _common.Logger)(UmamiService.name);
22
+ }
23
+ /** Whether there is enough configuration to make a call at all. */
24
+ get configured() {
25
+ return Boolean(this.options.apiKey && this.options.websiteId);
26
+ }
27
+ /** A range covering the last `days` days, ending now. */
28
+ static lastDays(days) {
29
+ const endAt = Date.now();
30
+ return { startAt: endAt - days * DAY_MS, endAt };
31
+ }
32
+ /** Visitors, pageviews, visits and bounces for a range. */
33
+ stats(range) {
34
+ return this.request("stats", range);
35
+ }
36
+ /**
37
+ * A breakdown by one dimension: top pages, referrers, countries, or —
38
+ * with `'event'` — how many times each custom event fired.
39
+ */
40
+ metrics(type, range, limit) {
41
+ return this.request("metrics", range, {
42
+ type,
43
+ ...limit ? { limit: String(limit) } : {}
44
+ });
45
+ }
46
+ /**
47
+ * How many times one named event fired. Returns 0 when the event exists but
48
+ * never fired, and `null` when the call itself failed — the difference
49
+ * matters: one is "nobody did it", the other is "we don't know".
50
+ */
51
+ async eventCount(name, range) {
52
+ const rows = await this.metrics("event", range);
53
+ if (rows === null) return null;
54
+ return _nullishCoalesce(_optionalChain([rows, 'access', _ => _.find, 'call', _2 => _2((row) => row.x === name), 'optionalAccess', _3 => _3.y]), () => ( 0));
55
+ }
56
+ async request(resource, range, extra = {}) {
57
+ if (!this.configured) {
58
+ this.logger.warn("Umami is not configured: apiKey and websiteId are required.");
59
+ return null;
60
+ }
61
+ const params = new URLSearchParams({
62
+ startAt: String(range.startAt),
63
+ endAt: String(range.endAt),
64
+ ...this.options.timezone ? { timezone: this.options.timezone } : {},
65
+ ...extra
66
+ });
67
+ const base = _nullishCoalesce(this.options.baseUrl, () => ( CLOUD));
68
+ try {
69
+ const response = await fetch(
70
+ `${base}/websites/${this.options.websiteId}/${resource}?${params}`,
71
+ { headers: { "x-umami-api-key": this.options.apiKey } }
72
+ );
73
+ if (!response.ok) {
74
+ this.logger.warn(`Umami ${resource}: HTTP ${response.status}`);
75
+ return null;
76
+ }
77
+ return await response.json();
78
+ } catch (error) {
79
+ this.logger.warn(`Umami ${resource}: ${error.message}`);
80
+ return null;
81
+ }
82
+ }
83
+ };
84
+ UmamiService = exports.UmamiService = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
85
+ _common.Injectable.call(void 0, ),
86
+ _chunk2REOCMUDcjs.__decorateParam.call(void 0, 0, _common.Inject.call(void 0, UMAMI_OPTIONS))
87
+ ], UmamiService);
88
+
89
+ // src/umami/umami.module.ts
90
+ var UmamiModule = class {
91
+ static forRoot(options) {
92
+ return {
93
+ module: UmamiModule,
94
+ providers: [
95
+ { provide: UMAMI_OPTIONS, useValue: options },
96
+ UmamiService
97
+ ],
98
+ exports: [UmamiService]
99
+ };
100
+ }
101
+ };
102
+ UmamiModule = exports.UmamiModule = _chunk2REOCMUDcjs.__decorateClass.call(void 0, [
103
+ _common.Module.call(void 0, {})
104
+ ], UmamiModule);
105
+
106
+
107
+
108
+
109
+ exports.UMAMI_OPTIONS = UMAMI_OPTIONS; exports.UmamiModule = UmamiModule; exports.UmamiService = UmamiService;
@@ -0,0 +1,4 @@
1
+ export { UmamiModule } from './umami.module.js';
2
+ export { UmamiService } from './umami.service.js';
3
+ export { UMAMI_OPTIONS, type UmamiOptions } from './umami.options.js';
4
+ export type { UmamiStats, UmamiMetric, UmamiMetricType, UmamiRange, } from './umami.types.js';
@@ -0,0 +1,109 @@
1
+ import {
2
+ __decorateClass,
3
+ __decorateParam
4
+ } from "../chunk-4MGIQFAJ.js";
5
+
6
+ // src/umami/umami.module.ts
7
+ import { Module } from "@nestjs/common";
8
+
9
+ // src/umami/umami.service.ts
10
+ import { Inject, Injectable, Logger } from "@nestjs/common";
11
+
12
+ // src/umami/umami.options.ts
13
+ var UMAMI_OPTIONS = /* @__PURE__ */ Symbol("UMAMI_OPTIONS");
14
+
15
+ // src/umami/umami.service.ts
16
+ var CLOUD = "https://api.umami.is/v1";
17
+ var DAY_MS = 24 * 60 * 60 * 1e3;
18
+ var UmamiService = class {
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.logger = new Logger(UmamiService.name);
22
+ }
23
+ /** Whether there is enough configuration to make a call at all. */
24
+ get configured() {
25
+ return Boolean(this.options.apiKey && this.options.websiteId);
26
+ }
27
+ /** A range covering the last `days` days, ending now. */
28
+ static lastDays(days) {
29
+ const endAt = Date.now();
30
+ return { startAt: endAt - days * DAY_MS, endAt };
31
+ }
32
+ /** Visitors, pageviews, visits and bounces for a range. */
33
+ stats(range) {
34
+ return this.request("stats", range);
35
+ }
36
+ /**
37
+ * A breakdown by one dimension: top pages, referrers, countries, or —
38
+ * with `'event'` — how many times each custom event fired.
39
+ */
40
+ metrics(type, range, limit) {
41
+ return this.request("metrics", range, {
42
+ type,
43
+ ...limit ? { limit: String(limit) } : {}
44
+ });
45
+ }
46
+ /**
47
+ * How many times one named event fired. Returns 0 when the event exists but
48
+ * never fired, and `null` when the call itself failed — the difference
49
+ * matters: one is "nobody did it", the other is "we don't know".
50
+ */
51
+ async eventCount(name, range) {
52
+ const rows = await this.metrics("event", range);
53
+ if (rows === null) return null;
54
+ return rows.find((row) => row.x === name)?.y ?? 0;
55
+ }
56
+ async request(resource, range, extra = {}) {
57
+ if (!this.configured) {
58
+ this.logger.warn("Umami is not configured: apiKey and websiteId are required.");
59
+ return null;
60
+ }
61
+ const params = new URLSearchParams({
62
+ startAt: String(range.startAt),
63
+ endAt: String(range.endAt),
64
+ ...this.options.timezone ? { timezone: this.options.timezone } : {},
65
+ ...extra
66
+ });
67
+ const base = this.options.baseUrl ?? CLOUD;
68
+ try {
69
+ const response = await fetch(
70
+ `${base}/websites/${this.options.websiteId}/${resource}?${params}`,
71
+ { headers: { "x-umami-api-key": this.options.apiKey } }
72
+ );
73
+ if (!response.ok) {
74
+ this.logger.warn(`Umami ${resource}: HTTP ${response.status}`);
75
+ return null;
76
+ }
77
+ return await response.json();
78
+ } catch (error) {
79
+ this.logger.warn(`Umami ${resource}: ${error.message}`);
80
+ return null;
81
+ }
82
+ }
83
+ };
84
+ UmamiService = __decorateClass([
85
+ Injectable(),
86
+ __decorateParam(0, Inject(UMAMI_OPTIONS))
87
+ ], UmamiService);
88
+
89
+ // src/umami/umami.module.ts
90
+ var UmamiModule = class {
91
+ static forRoot(options) {
92
+ return {
93
+ module: UmamiModule,
94
+ providers: [
95
+ { provide: UMAMI_OPTIONS, useValue: options },
96
+ UmamiService
97
+ ],
98
+ exports: [UmamiService]
99
+ };
100
+ }
101
+ };
102
+ UmamiModule = __decorateClass([
103
+ Module({})
104
+ ], UmamiModule);
105
+ export {
106
+ UMAMI_OPTIONS,
107
+ UmamiModule,
108
+ UmamiService
109
+ };
@@ -0,0 +1,6 @@
1
+ import { type DynamicModule } from '@nestjs/common';
2
+ import { type UmamiOptions } from './umami.options.js';
3
+ /** Read-only access to the Umami analytics API. See `UmamiService`. */
4
+ export declare class UmamiModule {
5
+ static forRoot(options: UmamiOptions): DynamicModule;
6
+ }
@@ -0,0 +1,20 @@
1
+ export declare const UMAMI_OPTIONS: unique symbol;
2
+ export interface UmamiOptions {
3
+ /** API key from Umami Cloud (Settings → API Keys), or a self-hosted token. */
4
+ apiKey?: string;
5
+ /** The website's UUID, shown next to its name in the dashboard. */
6
+ websiteId?: string;
7
+ /**
8
+ * API base URL. Defaults to Umami Cloud.
9
+ *
10
+ * Self-hosted instances serve the same endpoints under `/api`, so pass
11
+ * `https://your-host/api` there.
12
+ */
13
+ baseUrl?: string;
14
+ /**
15
+ * Timezone for the queries, as an IANA name. Umami buckets by day in this
16
+ * zone, so leaving it unset silently reports UTC days — which for anything
17
+ * outside UTC means a "day" that starts in the middle of the afternoon.
18
+ */
19
+ timezone?: string;
20
+ }
@@ -0,0 +1,40 @@
1
+ import { type UmamiOptions } from './umami.options.js';
2
+ import type { UmamiMetric, UmamiMetricType, UmamiRange, UmamiStats } from './umami.types.js';
3
+ /**
4
+ * Read-only client for the Umami analytics API.
5
+ *
6
+ * Only reads. Events are sent by the browser script; a backend writing them
7
+ * would be reporting traffic that never happened.
8
+ *
9
+ * Every method returns `null` instead of throwing when a call fails. A summary
10
+ * usually asks for several things at once, and one endpoint being down should
11
+ * not take the rest of the report with it — the caller decides what a missing
12
+ * block means. Failures are logged, so a persistent one is still visible.
13
+ *
14
+ * Deliberately has no opinion about what the numbers mean. Which events form
15
+ * a funnel, what to call them and how to phrase a report are product
16
+ * decisions that differ per app; this only fetches.
17
+ */
18
+ export declare class UmamiService {
19
+ private readonly options;
20
+ private readonly logger;
21
+ constructor(options: UmamiOptions);
22
+ /** Whether there is enough configuration to make a call at all. */
23
+ get configured(): boolean;
24
+ /** A range covering the last `days` days, ending now. */
25
+ static lastDays(days: number): UmamiRange;
26
+ /** Visitors, pageviews, visits and bounces for a range. */
27
+ stats(range: UmamiRange): Promise<UmamiStats | null>;
28
+ /**
29
+ * A breakdown by one dimension: top pages, referrers, countries, or —
30
+ * with `'event'` — how many times each custom event fired.
31
+ */
32
+ metrics(type: UmamiMetricType, range: UmamiRange, limit?: number): Promise<UmamiMetric[] | null>;
33
+ /**
34
+ * How many times one named event fired. Returns 0 when the event exists but
35
+ * never fired, and `null` when the call itself failed — the difference
36
+ * matters: one is "nobody did it", the other is "we don't know".
37
+ */
38
+ eventCount(name: string, range: UmamiRange): Promise<number | null>;
39
+ private request;
40
+ }
@@ -0,0 +1,24 @@
1
+ /** What `GET /websites/:id/stats` returns. */
2
+ export interface UmamiStats {
3
+ pageviews: number;
4
+ visitors: number;
5
+ visits: number;
6
+ bounces: number;
7
+ totaltime: number;
8
+ }
9
+ /**
10
+ * A single metric row. Umami names these `x` and `y` across every metric type,
11
+ * so the meaning of `x` depends on what was asked for: a URL, a referrer, a
12
+ * country, an event name.
13
+ */
14
+ export interface UmamiMetric {
15
+ x: string | null;
16
+ y: number;
17
+ }
18
+ /** What `type=` accepts on the metrics endpoint. */
19
+ export type UmamiMetricType = 'url' | 'path' | 'entry' | 'exit' | 'referrer' | 'title' | 'query' | 'browser' | 'os' | 'device' | 'country' | 'region' | 'city' | 'language' | 'event';
20
+ /** A window of time, as milliseconds since the epoch. */
21
+ export interface UmamiRange {
22
+ startAt: number;
23
+ endAt: number;
24
+ }