@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.
- package/README.md +250 -6
- package/dist/auth/index.cjs +12 -70
- package/dist/auth/index.js +11 -69
- package/dist/auth/oauth/http.d.ts +38 -0
- package/dist/auth/oauth/index.cjs +610 -0
- package/dist/auth/oauth/index.d.ts +12 -0
- package/dist/auth/oauth/index.js +610 -0
- package/dist/auth/oauth/oauth-auth.module.d.ts +28 -0
- package/dist/auth/oauth/oauth-auth.service.d.ts +101 -0
- package/dist/auth/oauth/oauth-callback.filter.d.ts +20 -0
- package/dist/auth/oauth/oauth-provider.d.ts +73 -0
- package/dist/auth/oauth/oauth.controller.d.ts +38 -0
- package/dist/auth/oauth/oauth.options.d.ts +89 -0
- package/dist/auth/oauth/oauth.ports.d.ts +86 -0
- package/dist/bootstrap/index.cjs +3 -3
- package/dist/bootstrap/index.js +2 -2
- package/dist/chunk-A3B2EY4V.js +75 -0
- package/dist/chunk-NVCI3CQI.cjs +75 -0
- package/dist/database/typeorm/index.cjs +79 -0
- package/dist/database/typeorm/index.d.ts +3 -0
- package/dist/database/typeorm/index.js +79 -0
- package/dist/database/typeorm/tenant-scope.d.ts +18 -0
- package/dist/database/typeorm/typeorm.module.d.ts +35 -0
- package/dist/index.cjs +17 -17
- package/dist/index.js +24 -24
- package/dist/umami/index.cjs +109 -0
- package/dist/umami/index.d.ts +4 -0
- package/dist/umami/index.js +109 -0
- package/dist/umami/umami.module.d.ts +6 -0
- package/dist/umami/umami.options.d.ts +20 -0
- package/dist/umami/umami.service.d.ts +40 -0
- package/dist/umami/umami.types.d.ts +24 -0
- package/package.json +22 -2
- package/dist/{chunk-AOCF5QCZ.js → chunk-ANQ3YPDI.js} +3 -3
- 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
|
|
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
|
|
44
|
+
var _chunk3CLYZC3Tcjs = require('./chunk-3CLYZC3T.cjs');
|
|
46
45
|
|
|
47
46
|
|
|
48
|
-
var
|
|
47
|
+
var _chunkQS2W5XCQcjs = require('./chunk-QS2W5XCQ.cjs');
|
|
49
48
|
|
|
50
49
|
|
|
51
|
-
|
|
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 =
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,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
|
+
}
|