nesthub 1.0.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 +108 -0
- package/dist/cache/README.md +91 -0
- package/dist/cache/index.d.ts +10 -0
- package/dist/cache/index.js +75 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/index.spec.d.ts +1 -0
- package/dist/cache/index.spec.js +61 -0
- package/dist/cache/index.spec.js.map +1 -0
- package/dist/excel/README.md +132 -0
- package/dist/excel/excel.module.d.ts +2 -0
- package/dist/excel/excel.module.js +21 -0
- package/dist/excel/excel.module.js.map +1 -0
- package/dist/excel/excel.service.d.ts +23 -0
- package/dist/excel/excel.service.js +124 -0
- package/dist/excel/excel.service.js.map +1 -0
- package/dist/excel/index.d.ts +2 -0
- package/dist/excel/index.js +8 -0
- package/dist/excel/index.js.map +1 -0
- package/dist/excel/interfaces.d.ts +19 -0
- package/dist/excel/interfaces.js +3 -0
- package/dist/excel/interfaces.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/index.spec.d.ts +1 -0
- package/dist/index.spec.js +11 -0
- package/dist/index.spec.js.map +1 -0
- package/dist/notification/README.md +237 -0
- package/dist/notification/channels/channel.interface.d.ts +10 -0
- package/dist/notification/channels/channel.interface.js +3 -0
- package/dist/notification/channels/channel.interface.js.map +1 -0
- package/dist/notification/channels/email.channel.d.ts +11 -0
- package/dist/notification/channels/email.channel.js +58 -0
- package/dist/notification/channels/email.channel.js.map +1 -0
- package/dist/notification/channels/firebase.channel.d.ts +11 -0
- package/dist/notification/channels/firebase.channel.js +69 -0
- package/dist/notification/channels/firebase.channel.js.map +1 -0
- package/dist/notification/channels/index.d.ts +5 -0
- package/dist/notification/channels/index.js +12 -0
- package/dist/notification/channels/index.js.map +1 -0
- package/dist/notification/channels/sms.channel.d.ts +8 -0
- package/dist/notification/channels/sms.channel.js +90 -0
- package/dist/notification/channels/sms.channel.js.map +1 -0
- package/dist/notification/channels/telegram.channel.d.ts +10 -0
- package/dist/notification/channels/telegram.channel.js +59 -0
- package/dist/notification/channels/telegram.channel.js.map +1 -0
- package/dist/notification/email/index.d.ts +62 -0
- package/dist/notification/email/index.js +253 -0
- package/dist/notification/email/index.js.map +1 -0
- package/dist/notification/email/index.spec.d.ts +1 -0
- package/dist/notification/email/index.spec.js +121 -0
- package/dist/notification/email/index.spec.js.map +1 -0
- package/dist/notification/entities/notification-log.entity.d.ts +15 -0
- package/dist/notification/entities/notification-log.entity.js +82 -0
- package/dist/notification/entities/notification-log.entity.js.map +1 -0
- package/dist/notification/firebase/index.d.ts +52 -0
- package/dist/notification/firebase/index.js +261 -0
- package/dist/notification/firebase/index.js.map +1 -0
- package/dist/notification/firebase/index.spec.d.ts +1 -0
- package/dist/notification/firebase/index.spec.js +114 -0
- package/dist/notification/firebase/index.spec.js.map +1 -0
- package/dist/notification/index.d.ts +12 -0
- package/dist/notification/index.js +26 -0
- package/dist/notification/index.js.map +1 -0
- package/dist/notification/index.spec.d.ts +1 -0
- package/dist/notification/index.spec.js +336 -0
- package/dist/notification/index.spec.js.map +1 -0
- package/dist/notification/interfaces.d.ts +98 -0
- package/dist/notification/interfaces.js +3 -0
- package/dist/notification/interfaces.js.map +1 -0
- package/dist/notification/notification.constants.d.ts +4 -0
- package/dist/notification/notification.constants.js +8 -0
- package/dist/notification/notification.constants.js.map +1 -0
- package/dist/notification/notification.module.d.ts +10 -0
- package/dist/notification/notification.module.js +160 -0
- package/dist/notification/notification.module.js.map +1 -0
- package/dist/notification/notification.service.d.ts +14 -0
- package/dist/notification/notification.service.js +184 -0
- package/dist/notification/notification.service.js.map +1 -0
- package/dist/notification/queue/index.d.ts +2 -0
- package/dist/notification/queue/index.js +6 -0
- package/dist/notification/queue/index.js.map +1 -0
- package/dist/notification/queue/notification-queue.service.d.ts +31 -0
- package/dist/notification/queue/notification-queue.service.js +134 -0
- package/dist/notification/queue/notification-queue.service.js.map +1 -0
- package/dist/notification/services/index.d.ts +1 -0
- package/dist/notification/services/index.js +6 -0
- package/dist/notification/services/index.js.map +1 -0
- package/dist/notification/services/template.service.d.ts +13 -0
- package/dist/notification/services/template.service.js +75 -0
- package/dist/notification/services/template.service.js.map +1 -0
- package/dist/notification/shared.d.ts +48 -0
- package/dist/notification/shared.js +95 -0
- package/dist/notification/shared.js.map +1 -0
- package/dist/notification/sms/index.d.ts +52 -0
- package/dist/notification/sms/index.js +234 -0
- package/dist/notification/sms/index.js.map +1 -0
- package/dist/notification/sms/index.spec.d.ts +1 -0
- package/dist/notification/sms/index.spec.js +123 -0
- package/dist/notification/sms/index.spec.js.map +1 -0
- package/dist/notification/telegram/index.d.ts +50 -0
- package/dist/notification/telegram/index.js +248 -0
- package/dist/notification/telegram/index.js.map +1 -0
- package/dist/notification/telegram/index.spec.d.ts +1 -0
- package/dist/notification/telegram/index.spec.js +108 -0
- package/dist/notification/telegram/index.spec.js.map +1 -0
- package/dist/notification/typeorm-storage.d.ts +28 -0
- package/dist/notification/typeorm-storage.js +56 -0
- package/dist/notification/typeorm-storage.js.map +1 -0
- package/dist/notification/unified.d.ts +47 -0
- package/dist/notification/unified.js +207 -0
- package/dist/notification/unified.js.map +1 -0
- package/dist/queue/README.md +82 -0
- package/dist/queue/index.d.ts +14 -0
- package/dist/queue/index.js +17 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/index.spec.d.ts +1 -0
- package/dist/queue/index.spec.js +76 -0
- package/dist/queue/index.spec.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/typeorm/README.md +197 -0
- package/dist/typeorm/crud-controller.d.ts +4 -0
- package/dist/typeorm/crud-controller.js +81 -0
- package/dist/typeorm/crud-controller.js.map +1 -0
- package/dist/typeorm/crud-service.d.ts +6 -0
- package/dist/typeorm/crud-service.js +53 -0
- package/dist/typeorm/crud-service.js.map +1 -0
- package/dist/typeorm/crud.interface.d.ts +9 -0
- package/dist/typeorm/crud.interface.js +3 -0
- package/dist/typeorm/crud.interface.js.map +1 -0
- package/dist/typeorm/index.d.ts +23 -0
- package/dist/typeorm/index.js +66 -0
- package/dist/typeorm/index.js.map +1 -0
- package/dist/typeorm/index.spec.d.ts +1 -0
- package/dist/typeorm/index.spec.js +109 -0
- package/dist/typeorm/index.spec.js.map +1 -0
- package/package.json +229 -0
- package/src/cache/README.md +91 -0
- package/src/excel/README.md +132 -0
- package/src/notification/README.md +237 -0
- package/src/queue/README.md +82 -0
- package/src/typeorm/README.md +197 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# nesthub
|
|
2
|
+
|
|
3
|
+
A collection of modular NestJS utility packages. Each module can be imported independently via sub-path exports so you only install what you need.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install nesthub
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Available modules
|
|
12
|
+
|
|
13
|
+
| Import path | Description | README |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `nesthub` | Version constant (`VERSION`) | — |
|
|
16
|
+
| `nesthub/typeorm` | TypeORM configuration helpers (RDS PostgreSQL, MySQL) | [README](src/typeorm/README.md) |
|
|
17
|
+
| `nesthub/cache` | Global cache module with Valkey / Redis backend | [README](src/cache/README.md) |
|
|
18
|
+
| `nesthub/queue` | BullMQ config factory for Valkey / Redis backend | [README](src/queue/README.md) |
|
|
19
|
+
| `nesthub/notification` | Multi-channel notification module (email, SMS, Firebase, Telegram) with templates, queue, TypeORM persistence | [README](src/notification/README.md) |
|
|
20
|
+
| `nesthub/excel` | Export JSON data to Excel (.xlsx) — fast, zero boilerplate | [README](src/excel/README.md) |
|
|
21
|
+
|
|
22
|
+
Click each README link above for detailed usage, environment variables, and options specific to that module.
|
|
23
|
+
|
|
24
|
+
## Quick examples
|
|
25
|
+
|
|
26
|
+
### TypeORM
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { TypeOrmModule } from '@nestjs/typeorm'
|
|
30
|
+
import { ConfigService } from '@nestjs/config'
|
|
31
|
+
import { configTypeOrmRDSPostgres } from 'nesthub/typeorm'
|
|
32
|
+
|
|
33
|
+
TypeOrmModule.forRootAsync({
|
|
34
|
+
inject: [ConfigService],
|
|
35
|
+
useFactory: (config: ConfigService) =>
|
|
36
|
+
configTypeOrmRDSPostgres(config, { poolSize: 20 }),
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Cache
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { CacheModule } from 'nesthub/cache'
|
|
44
|
+
|
|
45
|
+
@Module({
|
|
46
|
+
imports: [CacheModule.forRoot({ store: 'valkey' })],
|
|
47
|
+
})
|
|
48
|
+
export class AppModule {}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Queue
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { BullModule } from '@nestjs/bullmq'
|
|
55
|
+
import { configBullMQ } from 'nesthub/queue'
|
|
56
|
+
|
|
57
|
+
BullModule.forRootAsync({
|
|
58
|
+
inject: [ConfigService],
|
|
59
|
+
useFactory: configBullMQ,
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Notification
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { Module } from '@nestjs/common'
|
|
67
|
+
import { NotificationModule } from 'nesthub/notification'
|
|
68
|
+
|
|
69
|
+
@Module({
|
|
70
|
+
imports: [
|
|
71
|
+
NotificationModule.forRoot({
|
|
72
|
+
channels: {
|
|
73
|
+
email: { smtp: { host: 'smtp.example.com', port: 587 } },
|
|
74
|
+
sms: { provider: 'twilio', credentials: { accountSid: '...', authToken: '...' }, from: '+123' },
|
|
75
|
+
},
|
|
76
|
+
templates: { dir: './templates' },
|
|
77
|
+
queue: { enabled: true, connection: { url: 'valkey://localhost:6379' } },
|
|
78
|
+
storage: { enabled: true },
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
export class AppModule {}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Excel
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { exportToBuffer, exportToFile, exportToResponse } from 'nesthub/excel'
|
|
89
|
+
|
|
90
|
+
// Get buffer
|
|
91
|
+
const buffer = await exportToBuffer(users, {
|
|
92
|
+
columns: ['name', 'email'],
|
|
93
|
+
formatters: { active: (v) => (v ? 'Yes' : 'No') },
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Write to file
|
|
97
|
+
await exportToFile(users, './reports/users.xlsx')
|
|
98
|
+
|
|
99
|
+
// Send as download in controller
|
|
100
|
+
@Get('download')
|
|
101
|
+
async download(@Res() res: any) {
|
|
102
|
+
await exportToResponse(users, res, 'users.xlsx')
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# nesthub/cache
|
|
2
|
+
|
|
3
|
+
A global NestJS cache module using [Keyv](https://keyv.org/) + [Cacheable](https://github.com/jaredwray/cacheable) with Valkey or Redis backend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install nesthub keyv cacheable
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Depending on your store:
|
|
12
|
+
|
|
13
|
+
- **Valkey**: `npm install @keyv/valkey`
|
|
14
|
+
- **Redis**: `npm install @keyv/redis`
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
### Valkey (reads `VALKEY_URL` from env)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// app.module.ts
|
|
22
|
+
import { CacheModule } from 'nesthub/cache'
|
|
23
|
+
|
|
24
|
+
@Module({
|
|
25
|
+
imports: [
|
|
26
|
+
CacheModule.forRoot({ store: 'valkey' }),
|
|
27
|
+
// Requires VALKEY_URL in your .env:
|
|
28
|
+
// VALKEY_URL=valkey://localhost:6379
|
|
29
|
+
],
|
|
30
|
+
})
|
|
31
|
+
export class AppModule {}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Redis (reads `REDIS_URL` from env)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
CacheModule.forRoot({ store: 'redis' })
|
|
38
|
+
// Requires REDIS_URL in .env:
|
|
39
|
+
// REDIS_URL=redis://localhost:6379
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Direct URL (no env vars needed)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
CacheModule.forRoot({
|
|
46
|
+
store: 'valkey',
|
|
47
|
+
url: 'valkey://user:pass@host:6379',
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage in a service
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Inject } from '@nestjs/common'
|
|
55
|
+
import { CACHE_MANAGER } from 'nesthub/cache'
|
|
56
|
+
import { Cacheable } from 'cacheable'
|
|
57
|
+
|
|
58
|
+
@Injectable()
|
|
59
|
+
export class MyService {
|
|
60
|
+
constructor(
|
|
61
|
+
@Inject(CACHE_MANAGER) private readonly cache: Cacheable,
|
|
62
|
+
) {}
|
|
63
|
+
|
|
64
|
+
async getData(key: string) {
|
|
65
|
+
return this.cache.get(key)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async setData(key: string, value: unknown, ttl?: number) {
|
|
69
|
+
await this.cache.set(key, value, ttl)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## CacheModuleOptions
|
|
75
|
+
|
|
76
|
+
| Option | Default | Description |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `store` | `'valkey'` | Backend store: `'valkey'` or `'redis'` |
|
|
79
|
+
| `namespace` | `'{default}'` | Cache namespace prefix |
|
|
80
|
+
| `url` | — | Connection URL. If omitted, reads `VALKEY_URL` or `REDIS_URL` from env |
|
|
81
|
+
|
|
82
|
+
## How it works
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Cacheable → Keyv → KeyvValkey / KeyvRedis → Valkey / Redis
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `KeyvValkey` / `KeyvRedis` connects to the backend
|
|
89
|
+
- `Keyv` wraps it with a uniform API
|
|
90
|
+
- `Cacheable` adds TTL, namespace, and caching logic
|
|
91
|
+
- The module is `@Global()`, so `CACHE_MANAGER` is available everywhere
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
export declare const CACHE_MANAGER = "CACHE_MANAGER";
|
|
3
|
+
export interface CacheModuleOptions {
|
|
4
|
+
namespace?: string;
|
|
5
|
+
store?: 'valkey' | 'redis';
|
|
6
|
+
url?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class CacheModule {
|
|
9
|
+
static forRoot(options?: CacheModuleOptions): DynamicModule;
|
|
10
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
var CacheModule_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CacheModule = exports.CACHE_MANAGER = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const config_1 = require("@nestjs/config");
|
|
16
|
+
const keyv_1 = __importDefault(require("keyv"));
|
|
17
|
+
const cacheable_1 = require("cacheable");
|
|
18
|
+
exports.CACHE_MANAGER = 'CACHE_MANAGER';
|
|
19
|
+
async function loadAdapter(store, url) {
|
|
20
|
+
if (store === 'valkey') {
|
|
21
|
+
const { default: KeyvValkey } = await import('@keyv/valkey');
|
|
22
|
+
return new KeyvValkey(url);
|
|
23
|
+
}
|
|
24
|
+
const { default: KeyvRedis } = await import('@keyv/redis');
|
|
25
|
+
return new KeyvRedis(url);
|
|
26
|
+
}
|
|
27
|
+
function buildCacheable(adapter, namespace) {
|
|
28
|
+
const keyv = new keyv_1.default({ store: adapter });
|
|
29
|
+
return new cacheable_1.Cacheable({ primary: keyv, namespace });
|
|
30
|
+
}
|
|
31
|
+
let CacheModule = CacheModule_1 = class CacheModule {
|
|
32
|
+
static forRoot(options = {}) {
|
|
33
|
+
const { namespace = '{default}', store = 'valkey' } = options;
|
|
34
|
+
if (options.url) {
|
|
35
|
+
return {
|
|
36
|
+
module: CacheModule_1,
|
|
37
|
+
providers: [
|
|
38
|
+
{
|
|
39
|
+
provide: exports.CACHE_MANAGER,
|
|
40
|
+
useFactory: async () => {
|
|
41
|
+
const adapter = await loadAdapter(store, options.url);
|
|
42
|
+
return buildCacheable(adapter, namespace);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
exports: [exports.CACHE_MANAGER],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
module: CacheModule_1,
|
|
51
|
+
providers: [
|
|
52
|
+
{
|
|
53
|
+
provide: exports.CACHE_MANAGER,
|
|
54
|
+
inject: [config_1.ConfigService],
|
|
55
|
+
useFactory: async (configService) => {
|
|
56
|
+
const envKey = store === 'valkey' ? 'VALKEY_URL' : 'REDIS_URL';
|
|
57
|
+
const connectionUrl = configService.get(envKey);
|
|
58
|
+
if (!connectionUrl) {
|
|
59
|
+
throw new Error(`Missing ${envKey} environment variable. Set it or pass "url" to forRoot().`);
|
|
60
|
+
}
|
|
61
|
+
const adapter = await loadAdapter(store, connectionUrl);
|
|
62
|
+
return buildCacheable(adapter, namespace);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
exports: [exports.CACHE_MANAGER],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.CacheModule = CacheModule;
|
|
71
|
+
exports.CacheModule = CacheModule = CacheModule_1 = __decorate([
|
|
72
|
+
(0, common_1.Global)(),
|
|
73
|
+
(0, common_1.Module)({})
|
|
74
|
+
], CacheModule);
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA+D;AAC/D,2CAA+C;AAC/C,gDAAwB;AACxB,yCAAsC;AAEzB,QAAA,aAAa,GAAG,eAAe,CAAC;AAQ7C,KAAK,UAAU,WAAW,CAAC,KAAyB,EAAE,GAAW;IAC/D,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QAC7D,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC3D,OAAO,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,cAAc,CAAC,OAAgB,EAAE,SAAiB;IACzD,MAAM,IAAI,GAAG,IAAI,cAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,OAAO,IAAI,qBAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;AACrD,CAAC;AAIM,IAAM,WAAW,mBAAjB,MAAM,WAAW;IACtB,MAAM,CAAC,OAAO,CAAC,UAA8B,EAAE;QAC7C,MAAM,EAAE,SAAS,GAAG,WAAW,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC;QAE9D,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,OAAO;gBACL,MAAM,EAAE,aAAW;gBACnB,SAAS,EAAE;oBACT;wBACE,OAAO,EAAE,qBAAa;wBACtB,UAAU,EAAE,KAAK,IAAI,EAAE;4BACrB,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,GAAI,CAAC,CAAC;4BACvD,OAAO,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;wBAC5C,CAAC;qBACF;iBACF;gBACD,OAAO,EAAE,CAAC,qBAAa,CAAC;aACzB,CAAC;QACJ,CAAC;QAED,OAAO;YACL,MAAM,EAAE,aAAW;YACnB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,qBAAa;oBACtB,MAAM,EAAE,CAAC,sBAAa,CAAC;oBACvB,UAAU,EAAE,KAAK,EAAE,aAA4B,EAAE,EAAE;wBACjD,MAAM,MAAM,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;wBAC/D,MAAM,aAAa,GAAG,aAAa,CAAC,GAAG,CAAS,MAAM,CAAC,CAAC;wBACxD,IAAI,CAAC,aAAa,EAAE,CAAC;4BACnB,MAAM,IAAI,KAAK,CACb,WAAW,MAAM,2DAA2D,CAC7E,CAAC;wBACJ,CAAC;wBACD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;wBACxD,OAAO,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;oBAC5C,CAAC;iBACF;aACF;YACD,OAAO,EAAE,CAAC,qBAAa,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAA;AA1CY,kCAAW;sBAAX,WAAW;IAFvB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,WAAW,CA0CvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
describe('@vn.chemgio/nestjs-utilities/cache', () => {
|
|
5
|
+
describe('CACHE_MANAGER', () => {
|
|
6
|
+
it('should be the string CACHE_MANAGER', () => {
|
|
7
|
+
expect(index_1.CACHE_MANAGER).toBe('CACHE_MANAGER');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe('CacheModule.forRoot', () => {
|
|
11
|
+
it('should return a DynamicModule', () => {
|
|
12
|
+
const mod = index_1.CacheModule.forRoot();
|
|
13
|
+
expect(mod.module).toBe(index_1.CacheModule);
|
|
14
|
+
expect(mod.exports).toContain(index_1.CACHE_MANAGER);
|
|
15
|
+
expect(mod.providers).toHaveLength(1);
|
|
16
|
+
});
|
|
17
|
+
describe('with direct URL', () => {
|
|
18
|
+
it('should omit inject array', () => {
|
|
19
|
+
const mod = index_1.CacheModule.forRoot({ url: 'valkey://localhost:6379' });
|
|
20
|
+
const provider = mod.providers[0];
|
|
21
|
+
expect(provider.provide).toBe(index_1.CACHE_MANAGER);
|
|
22
|
+
expect(provider.inject).toBeUndefined();
|
|
23
|
+
expect(provider.useFactory).toBeInstanceOf(Function);
|
|
24
|
+
});
|
|
25
|
+
it('should accept redis store with URL', () => {
|
|
26
|
+
const mod = index_1.CacheModule.forRoot({
|
|
27
|
+
store: 'redis',
|
|
28
|
+
url: 'redis://localhost:6379',
|
|
29
|
+
namespace: 'myapp',
|
|
30
|
+
});
|
|
31
|
+
const provider = mod.providers[0];
|
|
32
|
+
expect(provider.provide).toBe(index_1.CACHE_MANAGER);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('without URL (env-based)', () => {
|
|
36
|
+
it('should include ConfigService in inject array', () => {
|
|
37
|
+
const mod = index_1.CacheModule.forRoot({ store: 'valkey' });
|
|
38
|
+
const provider = mod.providers[0];
|
|
39
|
+
expect(provider.provide).toBe(index_1.CACHE_MANAGER);
|
|
40
|
+
expect(provider.inject).toBeDefined();
|
|
41
|
+
expect(provider.inject).toHaveLength(1);
|
|
42
|
+
expect(provider.useFactory).toBeInstanceOf(Function);
|
|
43
|
+
});
|
|
44
|
+
it('should throw on missing env var', async () => {
|
|
45
|
+
const mod = index_1.CacheModule.forRoot({ store: 'valkey' });
|
|
46
|
+
const provider = mod.providers[0];
|
|
47
|
+
const mockConfig = { get: jest.fn().mockReturnValue(undefined) };
|
|
48
|
+
await expect(provider.useFactory(mockConfig)).rejects.toThrow('Missing VALKEY_URL environment variable');
|
|
49
|
+
expect(mockConfig.get).toHaveBeenCalledWith('VALKEY_URL');
|
|
50
|
+
});
|
|
51
|
+
it('should read REDIS_URL for redis store', async () => {
|
|
52
|
+
const mod = index_1.CacheModule.forRoot({ store: 'redis' });
|
|
53
|
+
const provider = mod.providers[0];
|
|
54
|
+
const mockConfig = { get: jest.fn().mockReturnValue(undefined) };
|
|
55
|
+
await expect(provider.useFactory(mockConfig)).rejects.toThrow('REDIS_URL');
|
|
56
|
+
expect(mockConfig.get).toHaveBeenCalledWith('REDIS_URL');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=index.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.spec.js","sourceRoot":"","sources":["../../src/cache/index.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# nesthub/excel
|
|
2
|
+
|
|
3
|
+
Export JSON data to Excel (.xlsx) — fast, zero boilerplate.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install nesthub exceljs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Functions
|
|
12
|
+
|
|
13
|
+
### `exportToBuffer(data, options?)`
|
|
14
|
+
|
|
15
|
+
Returns a `Buffer` of the .xlsx file.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { exportToBuffer } from 'nesthub/excel';
|
|
19
|
+
|
|
20
|
+
const data = [
|
|
21
|
+
{ name: 'Alice', age: 30 },
|
|
22
|
+
{ name: 'Bob', age: 25 },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const buffer = await exportToBuffer(data);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `exportToFile(data, filePath, options?)`
|
|
29
|
+
|
|
30
|
+
Writes the .xlsx directly to disk.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { exportToFile } from 'nesthub/excel';
|
|
34
|
+
|
|
35
|
+
await exportToFile(data, './reports/users.xlsx');
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `exportToResponse(data, res, filename?, options?)`
|
|
39
|
+
|
|
40
|
+
Sends the .xlsx as a download response in a NestJS/Express controller.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { exportToResponse } from 'nesthub/excel';
|
|
44
|
+
import { Response } from 'express';
|
|
45
|
+
import { Controller, Get, Res } from '@nestjs/common';
|
|
46
|
+
|
|
47
|
+
@Controller('reports')
|
|
48
|
+
export class ReportController {
|
|
49
|
+
@Get('users')
|
|
50
|
+
async downloadUsers(@Res() res: Response) {
|
|
51
|
+
const data = await this.userService.findAll();
|
|
52
|
+
await exportToResponse(data, res, 'users.xlsx');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
| Param | Type | Default |
|
|
60
|
+
|-------|------|---------|
|
|
61
|
+
| `data` | `Record<string, unknown>[]` | — |
|
|
62
|
+
| `options.sheetName` | `string` | `'Sheet1'` |
|
|
63
|
+
| `options.columns` | `(string \| ExcelColumn)[]` | All keys from the first data object |
|
|
64
|
+
| `options.useAutoFilter` | `boolean` | `true` |
|
|
65
|
+
| `options.headerFont` | `{ name, size, bold }` | `{ name: 'Calibri', size: 11, bold: true }` |
|
|
66
|
+
| `options.headerBg` | `string` (ARGB hex) | `'4472C4'` |
|
|
67
|
+
| `options.borderColor` | `string` (ARGB hex) | `'B0B0B0'` |
|
|
68
|
+
| `options.formatters` | `Record<string, (value, row) => any>` | — |
|
|
69
|
+
|
|
70
|
+
### `ExcelColumn`
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface ExcelColumn {
|
|
74
|
+
key: string; // field name in data
|
|
75
|
+
header?: string; // default: capitalize(key)
|
|
76
|
+
width?: number; // default: 18
|
|
77
|
+
formatter?: (value: unknown, row: object) => unknown; // transform cell value
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Pick fields to export
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
await exportToBuffer(data, {
|
|
87
|
+
columns: ['name', 'email'],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Custom header + column width
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
await exportToBuffer(data, {
|
|
95
|
+
columns: [
|
|
96
|
+
{ key: 'name', header: 'Full Name', width: 25 },
|
|
97
|
+
{ key: 'salary', header: 'Salary', width: 15 },
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Format displayed values
|
|
103
|
+
|
|
104
|
+
**Per-column formatter:**
|
|
105
|
+
```typescript
|
|
106
|
+
await exportToBuffer(data, {
|
|
107
|
+
columns: [
|
|
108
|
+
{ key: 'name' },
|
|
109
|
+
{
|
|
110
|
+
key: 'salary',
|
|
111
|
+
header: 'Salary',
|
|
112
|
+
formatter: (v) => `$${(v as number).toLocaleString()}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Global formatters:**
|
|
119
|
+
```typescript
|
|
120
|
+
await exportToBuffer(data, {
|
|
121
|
+
columns: ['name', 'active'],
|
|
122
|
+
formatters: {
|
|
123
|
+
active: (v) => (v ? 'Active' : 'Inactive'),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Disable auto filter
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
await exportToBuffer(data, { useAutoFilter: false });
|
|
132
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ExcelModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const excel_service_1 = require("./excel.service");
|
|
12
|
+
let ExcelModule = class ExcelModule {
|
|
13
|
+
};
|
|
14
|
+
exports.ExcelModule = ExcelModule;
|
|
15
|
+
exports.ExcelModule = ExcelModule = __decorate([
|
|
16
|
+
(0, common_1.Module)({
|
|
17
|
+
providers: [excel_service_1.ExcelService],
|
|
18
|
+
exports: [excel_service_1.ExcelService],
|
|
19
|
+
})
|
|
20
|
+
], ExcelModule);
|
|
21
|
+
//# sourceMappingURL=excel.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excel.module.js","sourceRoot":"","sources":["../../src/excel/excel.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,mDAA+C;AAMxC,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IAJvB,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,4BAAY,CAAC;QACzB,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,WAAW,CAAG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http';
|
|
2
|
+
export interface ExcelColumn {
|
|
3
|
+
key: string;
|
|
4
|
+
header?: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
formatter?: (value: unknown, row: Record<string, unknown>) => unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface ExcelExportOptions {
|
|
9
|
+
sheetName?: string;
|
|
10
|
+
columns?: (string | ExcelColumn)[];
|
|
11
|
+
useAutoFilter?: boolean;
|
|
12
|
+
headerFont?: {
|
|
13
|
+
name?: string;
|
|
14
|
+
size?: number;
|
|
15
|
+
bold?: boolean;
|
|
16
|
+
};
|
|
17
|
+
headerBg?: string;
|
|
18
|
+
borderColor?: string;
|
|
19
|
+
formatters?: Record<string, (value: unknown, row: Record<string, unknown>) => unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare function exportToBuffer(data: Record<string, unknown>[], options?: ExcelExportOptions): Promise<Buffer>;
|
|
22
|
+
export declare function exportToFile(data: Record<string, unknown>[], filePath: string, options?: ExcelExportOptions): Promise<void>;
|
|
23
|
+
export declare function exportToResponse(data: Record<string, unknown>[], res: ServerResponse, filename?: string, options?: ExcelExportOptions): Promise<void>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportToBuffer = exportToBuffer;
|
|
4
|
+
exports.exportToFile = exportToFile;
|
|
5
|
+
exports.exportToResponse = exportToResponse;
|
|
6
|
+
const exceljs_1 = require("exceljs");
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const DEFAULTS = {
|
|
9
|
+
sheetName: 'Sheet1',
|
|
10
|
+
useAutoFilter: true,
|
|
11
|
+
headerFont: { name: 'Calibri', size: 11, bold: true },
|
|
12
|
+
headerBg: '4472C4',
|
|
13
|
+
borderColor: 'B0B0B0',
|
|
14
|
+
};
|
|
15
|
+
function resolveCols(data, columns) {
|
|
16
|
+
if (!columns || columns.length === 0) {
|
|
17
|
+
if (data.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
return Object.keys(data[0]).map((key) => ({
|
|
20
|
+
key,
|
|
21
|
+
header: key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' '),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
return columns.map((c) => {
|
|
25
|
+
if (typeof c === 'string')
|
|
26
|
+
return { key: c, header: c };
|
|
27
|
+
return { ...c, header: c.header ?? c.key };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function pick(obj, cols) {
|
|
31
|
+
if (cols.length === 0)
|
|
32
|
+
return obj;
|
|
33
|
+
const result = {};
|
|
34
|
+
for (const col of cols) {
|
|
35
|
+
if (col.key in obj)
|
|
36
|
+
result[col.key] = obj[col.key];
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function applyFormatters(row, cols, globalFormatters) {
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const col of cols) {
|
|
43
|
+
const value = row[col.key];
|
|
44
|
+
const fn = col.formatter ?? globalFormatters?.[col.key];
|
|
45
|
+
result[col.key] = fn ? fn(value, row) : value;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function styleHeader(ws, colCount, font, bg, borderColor) {
|
|
50
|
+
const row = ws.getRow(1);
|
|
51
|
+
row.font = font;
|
|
52
|
+
row.alignment = { vertical: 'middle', horizontal: 'center' };
|
|
53
|
+
for (let c = 1; c <= colCount; c++) {
|
|
54
|
+
const cell = row.getCell(c);
|
|
55
|
+
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: bg } };
|
|
56
|
+
cell.font = { ...font, color: { argb: 'FFFFFFFF' } };
|
|
57
|
+
cell.border = {
|
|
58
|
+
top: { style: 'thin', color: { argb: borderColor } },
|
|
59
|
+
left: { style: 'thin', color: { argb: borderColor } },
|
|
60
|
+
bottom: { style: 'thin', color: { argb: borderColor } },
|
|
61
|
+
right: { style: 'thin', color: { argb: borderColor } },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function styleRows(ws, rowCount, colCount, borderColor) {
|
|
66
|
+
for (let r = 2; r <= rowCount + 1; r++) {
|
|
67
|
+
const row = ws.getRow(r);
|
|
68
|
+
for (let c = 1; c <= colCount; c++) {
|
|
69
|
+
const cell = row.getCell(c);
|
|
70
|
+
cell.alignment = { vertical: 'middle', horizontal: 'left' };
|
|
71
|
+
cell.border = {
|
|
72
|
+
top: { style: 'thin', color: { argb: borderColor } },
|
|
73
|
+
left: { style: 'thin', color: { argb: borderColor } },
|
|
74
|
+
bottom: { style: 'thin', color: { argb: borderColor } },
|
|
75
|
+
right: { style: 'thin', color: { argb: borderColor } },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function exportToBuffer(data, options) {
|
|
81
|
+
const workbook = new exceljs_1.Workbook();
|
|
82
|
+
const ws = workbook.addWorksheet(options?.sheetName ?? DEFAULTS.sheetName);
|
|
83
|
+
const cols = resolveCols(data, options?.columns);
|
|
84
|
+
if (cols.length === 0) {
|
|
85
|
+
const buf = await workbook.xlsx.writeBuffer();
|
|
86
|
+
return Buffer.from(buf);
|
|
87
|
+
}
|
|
88
|
+
ws.columns = cols.map((col) => ({
|
|
89
|
+
header: col.header,
|
|
90
|
+
key: col.key,
|
|
91
|
+
width: col.width ?? 18,
|
|
92
|
+
}));
|
|
93
|
+
const rows = data.map((item) => {
|
|
94
|
+
const picked = pick(item, cols);
|
|
95
|
+
return applyFormatters(picked, cols, options?.formatters);
|
|
96
|
+
});
|
|
97
|
+
ws.addRows(rows);
|
|
98
|
+
const headerFont = options?.headerFont ?? DEFAULTS.headerFont;
|
|
99
|
+
const headerBg = options?.headerBg ?? DEFAULTS.headerBg;
|
|
100
|
+
const borderColor = options?.borderColor ?? DEFAULTS.borderColor;
|
|
101
|
+
styleHeader(ws, cols.length, headerFont, headerBg, borderColor);
|
|
102
|
+
styleRows(ws, rows.length, cols.length, borderColor);
|
|
103
|
+
if (options?.useAutoFilter ?? DEFAULTS.useAutoFilter) {
|
|
104
|
+
ws.autoFilter = {
|
|
105
|
+
from: { row: 1, column: 1 },
|
|
106
|
+
to: { row: rows.length + 1, column: cols.length },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const buf = await workbook.xlsx.writeBuffer();
|
|
110
|
+
return Buffer.from(buf);
|
|
111
|
+
}
|
|
112
|
+
async function exportToFile(data, filePath, options) {
|
|
113
|
+
const buffer = await exportToBuffer(data, options);
|
|
114
|
+
await (0, promises_1.writeFile)(filePath, buffer);
|
|
115
|
+
}
|
|
116
|
+
async function exportToResponse(data, res, filename, options) {
|
|
117
|
+
const buffer = await exportToBuffer(data, options);
|
|
118
|
+
const name = filename ?? 'export.xlsx';
|
|
119
|
+
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
120
|
+
res.setHeader('Content-Disposition', `attachment; filename="${name}"`);
|
|
121
|
+
res.setHeader('Content-Length', buffer.length);
|
|
122
|
+
res.end(buffer);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=excel.service.js.map
|