@reproapp/node-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +2535 -0
- package/dist/integrations/sendgrid.d.ts +20 -0
- package/dist/integrations/sendgrid.js +177 -0
- package/dist/redaction.d.ts +44 -0
- package/dist/redaction.js +167 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +26 -0
- package/package.json +58 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type Ctx = {
|
|
2
|
+
sid?: string;
|
|
3
|
+
aid?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const getCtx: () => Ctx;
|
|
6
|
+
export type SendgridPatchConfig = {
|
|
7
|
+
appId: string;
|
|
8
|
+
appSecret: string;
|
|
9
|
+
appName?: string;
|
|
10
|
+
resolveContext?: () => {
|
|
11
|
+
sid?: string;
|
|
12
|
+
aid?: string;
|
|
13
|
+
} | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Patch @sendgrid/mail's send() and sendMultiple() to capture outbound email.
|
|
17
|
+
* Idempotent. No behavior change for the app.
|
|
18
|
+
*/
|
|
19
|
+
export declare function patchSendgridMail(cfg: SendgridPatchConfig): void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.patchSendgridMail = exports.getCtx = void 0;
|
|
4
|
+
// repro-node/src/integrations/sendgrid.ts
|
|
5
|
+
const async_hooks_1 = require("async_hooks");
|
|
6
|
+
const als = new async_hooks_1.AsyncLocalStorage();
|
|
7
|
+
const getCtx = () => als.getStore() || {};
|
|
8
|
+
exports.getCtx = getCtx;
|
|
9
|
+
// If you already export als/getCtx from repro-node, reuse that instead of re-declaring.
|
|
10
|
+
async function post(cfg, sessionId, body) {
|
|
11
|
+
try {
|
|
12
|
+
const envBase = typeof process !== 'undefined' ? process?.env?.REPRO_API_BASE : undefined;
|
|
13
|
+
const legacyBase = cfg?.apiBase;
|
|
14
|
+
const apiBase = String(envBase || legacyBase || 'https://oozy-loreta-gully.ngrok-free.dev').replace(/\/+$/, '');
|
|
15
|
+
await fetch(`${apiBase}/v1/sessions/${sessionId}/backend`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'X-App-Id': cfg.appId,
|
|
20
|
+
'X-App-Secret': cfg.appSecret,
|
|
21
|
+
...(cfg.appName ? { 'X-App-Name': cfg.appName } : {}),
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch { /* swallow */ }
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Patch @sendgrid/mail's send() and sendMultiple() to capture outbound email.
|
|
30
|
+
* Idempotent. No behavior change for the app.
|
|
31
|
+
*/
|
|
32
|
+
function patchSendgridMail(cfg) {
|
|
33
|
+
let sgMail;
|
|
34
|
+
try {
|
|
35
|
+
sgMail = require('@sendgrid/mail');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
39
|
+
} // not installed → no-op
|
|
40
|
+
if (!sgMail || sgMail.__repro_patched)
|
|
41
|
+
return;
|
|
42
|
+
sgMail.__repro_patched = true;
|
|
43
|
+
const origSend = sgMail.send?.bind(sgMail);
|
|
44
|
+
const origSendMultiple = sgMail.sendMultiple?.bind(sgMail);
|
|
45
|
+
if (origSend) {
|
|
46
|
+
sgMail.send = async function patchedSend(msg, isMultiple) {
|
|
47
|
+
const startedAt = Date.now();
|
|
48
|
+
let statusCode;
|
|
49
|
+
let headers;
|
|
50
|
+
try {
|
|
51
|
+
const res = await origSend(msg, isMultiple);
|
|
52
|
+
// sendgrid returns [response] as array in v7.x
|
|
53
|
+
const r = Array.isArray(res) ? res[0] : res;
|
|
54
|
+
statusCode = r?.statusCode ?? r?.status ?? undefined;
|
|
55
|
+
headers = r?.headers ?? undefined;
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
fireCapture('send', msg, startedAt, statusCode, headers);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (origSendMultiple) {
|
|
64
|
+
sgMail.sendMultiple = async function patchedSendMultiple(msg) {
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
let statusCode;
|
|
67
|
+
let headers;
|
|
68
|
+
try {
|
|
69
|
+
const res = await origSendMultiple(msg);
|
|
70
|
+
const r = Array.isArray(res) ? res[0] : res;
|
|
71
|
+
statusCode = r?.statusCode ?? r?.status ?? undefined;
|
|
72
|
+
headers = r?.headers ?? undefined;
|
|
73
|
+
return res;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
fireCapture('sendMultiple', msg, startedAt, statusCode, headers);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function fireCapture(kind, rawMsg, t0, statusCode, headers) {
|
|
81
|
+
const ctx = (0, exports.getCtx)();
|
|
82
|
+
const sid = ctx.sid ?? cfg.resolveContext?.()?.sid;
|
|
83
|
+
const aid = ctx.aid ?? cfg.resolveContext?.()?.aid;
|
|
84
|
+
if (!sid)
|
|
85
|
+
return; // no active session → skip
|
|
86
|
+
const norm = normalizeSendgridMessage(rawMsg);
|
|
87
|
+
const entry = {
|
|
88
|
+
actionId: aid ?? null,
|
|
89
|
+
email: {
|
|
90
|
+
provider: 'sendgrid',
|
|
91
|
+
kind,
|
|
92
|
+
to: norm.to,
|
|
93
|
+
cc: norm.cc,
|
|
94
|
+
bcc: norm.bcc,
|
|
95
|
+
from: norm.from,
|
|
96
|
+
subject: norm.subject,
|
|
97
|
+
text: norm.text, // you said privacy later → include now
|
|
98
|
+
html: norm.html, // idem
|
|
99
|
+
templateId: norm.templateId,
|
|
100
|
+
dynamicTemplateData: norm.dynamicTemplateData,
|
|
101
|
+
categories: norm.categories,
|
|
102
|
+
customArgs: norm.customArgs,
|
|
103
|
+
attachmentsMeta: norm.attachmentsMeta, // safe metadata only
|
|
104
|
+
statusCode,
|
|
105
|
+
durMs: Date.now() - t0,
|
|
106
|
+
headers: headers ?? {},
|
|
107
|
+
},
|
|
108
|
+
t: Date.now(),
|
|
109
|
+
};
|
|
110
|
+
post(cfg, sid, { entries: [entry] });
|
|
111
|
+
}
|
|
112
|
+
function normalizeAddress(a) {
|
|
113
|
+
if (!a)
|
|
114
|
+
return null;
|
|
115
|
+
if (typeof a === 'string')
|
|
116
|
+
return { email: a };
|
|
117
|
+
if (typeof a === 'object' && a.email)
|
|
118
|
+
return { email: String(a.email), name: a.name ? String(a.name) : undefined };
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function normalizeAddressList(v) {
|
|
122
|
+
if (!v)
|
|
123
|
+
return undefined;
|
|
124
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
125
|
+
const out = arr.map(normalizeAddress).filter(Boolean);
|
|
126
|
+
return out.length ? out : undefined;
|
|
127
|
+
}
|
|
128
|
+
function normalizeSendgridMessage(msg) {
|
|
129
|
+
// sendgrid supports "personalizations" & top-level fields; we’ll flatten the common pieces
|
|
130
|
+
const base = {
|
|
131
|
+
from: normalizeAddress(msg?.from) ?? undefined,
|
|
132
|
+
to: normalizeAddressList(msg?.to),
|
|
133
|
+
cc: normalizeAddressList(msg?.cc),
|
|
134
|
+
bcc: normalizeAddressList(msg?.bcc),
|
|
135
|
+
subject: msg?.subject ? String(msg.subject) : undefined,
|
|
136
|
+
text: typeof msg?.text === 'string' ? msg.text : undefined,
|
|
137
|
+
html: typeof msg?.html === 'string' ? msg.html : undefined,
|
|
138
|
+
templateId: msg?.templateId ? String(msg.templateId) : undefined,
|
|
139
|
+
dynamicTemplateData: msg?.dynamic_template_data ?? msg?.dynamicTemplateData ?? undefined,
|
|
140
|
+
categories: Array.isArray(msg?.categories) ? msg.categories.map(String) : undefined,
|
|
141
|
+
customArgs: msg?.customArgs ?? msg?.custom_args ?? undefined,
|
|
142
|
+
attachmentsMeta: Array.isArray(msg?.attachments)
|
|
143
|
+
? msg.attachments.map((a) => ({
|
|
144
|
+
filename: a?.filename ? String(a.filename) : undefined,
|
|
145
|
+
type: a?.type ? String(a.type) : undefined,
|
|
146
|
+
size: a?.content ? byteLen(a.content) : undefined, // base64 or string → approximate size
|
|
147
|
+
}))
|
|
148
|
+
: undefined,
|
|
149
|
+
};
|
|
150
|
+
// If personalizations exist, pull the FIRST one as representative (keeps MVP simple)
|
|
151
|
+
const p0 = Array.isArray(msg?.personalizations) ? msg.personalizations[0] : undefined;
|
|
152
|
+
if (p0) {
|
|
153
|
+
base.to = normalizeAddressList(p0.to) ?? base.to;
|
|
154
|
+
base.cc = normalizeAddressList(p0.cc) ?? base.cc;
|
|
155
|
+
base.bcc = normalizeAddressList(p0.bcc) ?? base.bcc;
|
|
156
|
+
if (!base.subject && p0.subject)
|
|
157
|
+
base.subject = String(p0.subject);
|
|
158
|
+
// template data can also live inside personalization
|
|
159
|
+
if (!base.dynamicTemplateData && p0.dynamic_template_data)
|
|
160
|
+
base.dynamicTemplateData = p0.dynamic_template_data;
|
|
161
|
+
if (!base.customArgs && p0.custom_args)
|
|
162
|
+
base.customArgs = p0.custom_args;
|
|
163
|
+
}
|
|
164
|
+
return base;
|
|
165
|
+
}
|
|
166
|
+
function byteLen(content) {
|
|
167
|
+
try {
|
|
168
|
+
if (typeof content === 'string')
|
|
169
|
+
return Buffer.byteLength(content, 'utf8');
|
|
170
|
+
if (content && typeof content === 'object' && 'length' in content)
|
|
171
|
+
return Number(content.length);
|
|
172
|
+
}
|
|
173
|
+
catch { }
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.patchSendgridMail = patchSendgridMail;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type RedactionStrategy = 'mask' | 'remove';
|
|
2
|
+
export type RedactionRuleInput = string | RegExp | {
|
|
3
|
+
match?: string | RegExp;
|
|
4
|
+
field?: string | RegExp;
|
|
5
|
+
exact?: boolean;
|
|
6
|
+
strategy?: RedactionStrategy;
|
|
7
|
+
maskWith?: string;
|
|
8
|
+
};
|
|
9
|
+
type RedactionFieldMapKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'response' | 'responseBody' | 'responseHeaders';
|
|
10
|
+
export type TransportRedactionFields = Partial<Record<RedactionFieldMapKey, RedactionRuleInput[]>>;
|
|
11
|
+
export interface TransportRedactionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Custom mask string for masked values. Default: "*****".
|
|
14
|
+
*/
|
|
15
|
+
maskWith?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Additional rules that apply to specific request/response sections.
|
|
18
|
+
* Provide field names (case-insensitive), dot-delimited paths, or
|
|
19
|
+
* regular expressions. Rules declared under `any` apply everywhere.
|
|
20
|
+
*/
|
|
21
|
+
fields?: TransportRedactionFields;
|
|
22
|
+
}
|
|
23
|
+
type SectionKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'responseHeaders';
|
|
24
|
+
type NormalizedRule = {
|
|
25
|
+
kind: 'string';
|
|
26
|
+
needle: string;
|
|
27
|
+
exact: boolean;
|
|
28
|
+
action: RedactionStrategy;
|
|
29
|
+
maskWith?: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'regex';
|
|
32
|
+
regex: RegExp;
|
|
33
|
+
action: RedactionStrategy;
|
|
34
|
+
maskWith?: string;
|
|
35
|
+
};
|
|
36
|
+
export type TransportRedactionState = {
|
|
37
|
+
maskWith: string;
|
|
38
|
+
rules: Record<SectionKey, NormalizedRule[]>;
|
|
39
|
+
};
|
|
40
|
+
export declare function configureTransportRedaction(options?: TransportRedactionOptions | null): TransportRedactionState;
|
|
41
|
+
export declare function getTransportRedactionState(): TransportRedactionState;
|
|
42
|
+
export declare function redactPayload<T>(payload: T, stateOverride?: TransportRedactionState | null): T;
|
|
43
|
+
export declare function redactSection<T>(value: T, section: Exclude<SectionKey, 'any'>, stateOverride?: TransportRedactionState | null): T;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redactSection = exports.redactPayload = exports.getTransportRedactionState = exports.configureTransportRedaction = void 0;
|
|
4
|
+
const SECTION_KEYS = ['any', 'headers', 'body', 'query', 'params', 'respBody', 'responseHeaders'];
|
|
5
|
+
const DEFAULT_MASK = '*****';
|
|
6
|
+
const DEFAULT_RULES = [
|
|
7
|
+
{ match: /pass(word|phrase)?/i },
|
|
8
|
+
{ match: /pwd/i },
|
|
9
|
+
{ match: /secret/i },
|
|
10
|
+
{ match: /token/i },
|
|
11
|
+
{ match: /authorization/i },
|
|
12
|
+
{ match: /api[_-]?key/i },
|
|
13
|
+
];
|
|
14
|
+
let CURRENT_STATE = buildState();
|
|
15
|
+
function configureTransportRedaction(options) {
|
|
16
|
+
CURRENT_STATE = buildState(options);
|
|
17
|
+
return CURRENT_STATE;
|
|
18
|
+
}
|
|
19
|
+
exports.configureTransportRedaction = configureTransportRedaction;
|
|
20
|
+
function getTransportRedactionState() {
|
|
21
|
+
return CURRENT_STATE;
|
|
22
|
+
}
|
|
23
|
+
exports.getTransportRedactionState = getTransportRedactionState;
|
|
24
|
+
function redactPayload(payload, stateOverride) {
|
|
25
|
+
const state = stateOverride ?? CURRENT_STATE;
|
|
26
|
+
applyRules(payload, gatherRules('any', state), state.maskWith);
|
|
27
|
+
return payload;
|
|
28
|
+
}
|
|
29
|
+
exports.redactPayload = redactPayload;
|
|
30
|
+
function redactSection(value, section, stateOverride) {
|
|
31
|
+
const state = stateOverride ?? CURRENT_STATE;
|
|
32
|
+
applyRules(value, gatherRules(section, state), state.maskWith);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
exports.redactSection = redactSection;
|
|
36
|
+
function buildState(options) {
|
|
37
|
+
const state = {
|
|
38
|
+
maskWith: typeof options?.maskWith === 'string' && options.maskWith ? options.maskWith : DEFAULT_MASK,
|
|
39
|
+
rules: SECTION_KEYS.reduce((acc, key) => {
|
|
40
|
+
acc[key] = [];
|
|
41
|
+
return acc;
|
|
42
|
+
}, {}),
|
|
43
|
+
};
|
|
44
|
+
appendRules(state.rules.any, DEFAULT_RULES);
|
|
45
|
+
if (!options?.fields) {
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
appendRules(state.rules.any, options.fields.any);
|
|
49
|
+
appendRules(state.rules.headers, options.fields.headers);
|
|
50
|
+
appendRules(state.rules.body, options.fields.body);
|
|
51
|
+
appendRules(state.rules.query, options.fields.query);
|
|
52
|
+
appendRules(state.rules.params, options.fields.params);
|
|
53
|
+
appendRules(state.rules.respBody, options.fields.respBody);
|
|
54
|
+
appendRules(state.rules.respBody, options.fields.response);
|
|
55
|
+
appendRules(state.rules.respBody, options.fields.responseBody);
|
|
56
|
+
appendRules(state.rules.responseHeaders, options.fields.responseHeaders);
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
function appendRules(bucket, inputs) {
|
|
60
|
+
if (!inputs || !Array.isArray(inputs))
|
|
61
|
+
return;
|
|
62
|
+
for (const entry of inputs) {
|
|
63
|
+
const normalized = normalizeRule(entry);
|
|
64
|
+
if (normalized)
|
|
65
|
+
bucket.push(normalized);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function normalizeRule(input) {
|
|
69
|
+
if (!input)
|
|
70
|
+
return null;
|
|
71
|
+
if (typeof input === 'string') {
|
|
72
|
+
const needle = input.trim().toLowerCase();
|
|
73
|
+
if (!needle)
|
|
74
|
+
return null;
|
|
75
|
+
return { kind: 'string', needle, exact: false, action: 'mask' };
|
|
76
|
+
}
|
|
77
|
+
if (input instanceof RegExp) {
|
|
78
|
+
return { kind: 'regex', regex: cloneRegex(input), action: 'mask' };
|
|
79
|
+
}
|
|
80
|
+
if (typeof input === 'object') {
|
|
81
|
+
const pattern = input.match ?? input.field;
|
|
82
|
+
const exact = !!input.exact;
|
|
83
|
+
const action = input.strategy === 'remove' ? 'remove' : 'mask';
|
|
84
|
+
const maskWith = typeof input.maskWith === 'string' && input.maskWith ? input.maskWith : undefined;
|
|
85
|
+
if (pattern instanceof RegExp) {
|
|
86
|
+
return { kind: 'regex', regex: cloneRegex(pattern), action, maskWith };
|
|
87
|
+
}
|
|
88
|
+
if (typeof pattern === 'string') {
|
|
89
|
+
const needle = pattern.trim().toLowerCase();
|
|
90
|
+
if (!needle)
|
|
91
|
+
return null;
|
|
92
|
+
return { kind: 'string', needle, exact, action, maskWith };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function cloneRegex(rx) {
|
|
98
|
+
const flags = rx.flags.includes('g') ? rx.flags.replace(/g/g, '') : rx.flags;
|
|
99
|
+
return new RegExp(rx.source, flags);
|
|
100
|
+
}
|
|
101
|
+
function gatherRules(section, state) {
|
|
102
|
+
if (section === 'any')
|
|
103
|
+
return state.rules.any;
|
|
104
|
+
return state.rules.any.concat(state.rules[section] ?? []);
|
|
105
|
+
}
|
|
106
|
+
function applyRules(value, rules, defaultMask) {
|
|
107
|
+
if (!rules.length)
|
|
108
|
+
return;
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
for (let i = 0; i < value.length; i++) {
|
|
111
|
+
const item = value[i];
|
|
112
|
+
if (item && typeof item === 'object') {
|
|
113
|
+
applyRules(item, rules, defaultMask);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!isRedactableObject(value))
|
|
119
|
+
return;
|
|
120
|
+
for (const key of Object.keys(value)) {
|
|
121
|
+
const decision = pickDecision(key, rules, defaultMask);
|
|
122
|
+
if (!decision) {
|
|
123
|
+
const child = value[key];
|
|
124
|
+
if (child && typeof child === 'object') {
|
|
125
|
+
applyRules(child, rules, defaultMask);
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (decision === 'remove') {
|
|
130
|
+
delete value[key];
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
value[key] = decision;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function isRedactableObject(value) {
|
|
138
|
+
if (!value || typeof value !== 'object')
|
|
139
|
+
return false;
|
|
140
|
+
if (Buffer.isBuffer(value))
|
|
141
|
+
return false;
|
|
142
|
+
if (ArrayBuffer.isView(value))
|
|
143
|
+
return false;
|
|
144
|
+
if (value instanceof Date || value instanceof RegExp)
|
|
145
|
+
return false;
|
|
146
|
+
if (value instanceof Map || value instanceof Set)
|
|
147
|
+
return false;
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
function pickDecision(key, rules, defaultMask) {
|
|
151
|
+
const lower = key.toLowerCase();
|
|
152
|
+
for (const rule of rules) {
|
|
153
|
+
if (rule.kind === 'string') {
|
|
154
|
+
const match = rule.exact ? lower === rule.needle : lower.includes(rule.needle);
|
|
155
|
+
if (!match)
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
if (!rule.regex.test(key))
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (rule.action === 'remove')
|
|
163
|
+
return 'remove';
|
|
164
|
+
return rule.maskWith ?? defaultMask;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
const express = require('express');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
6
|
+
(0, index_1.initReproTracing)();
|
|
7
|
+
const app = express();
|
|
8
|
+
(async () => {
|
|
9
|
+
await mongoose.connect('mongodb://127.0.0.1:27018', { dbName: 'trace_demo' });
|
|
10
|
+
const userSchema = new mongoose.Schema({
|
|
11
|
+
name: String, email: String, age: Number
|
|
12
|
+
}, { timestamps: true });
|
|
13
|
+
const User = mongoose.model('User', userSchema);
|
|
14
|
+
// seed some data
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
app.get('/seed', async (_req, res) => {
|
|
17
|
+
await User.deleteMany({});
|
|
18
|
+
await User.insertMany([
|
|
19
|
+
{ name: 'Ada', email: 'ada@example.com', age: 27 },
|
|
20
|
+
{ name: 'Lin', email: 'lin@example.com', age: 35 },
|
|
21
|
+
{ name: 'Sam', email: 'sam@example.com', age: 17 },
|
|
22
|
+
]);
|
|
23
|
+
res.json({ ok: true, count: await User.countDocuments() });
|
|
24
|
+
});
|
|
25
|
+
app.listen(3000, () => process.stdout.write('listening on http://localhost:3000\n'));
|
|
26
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reproapp/node-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Repro Nest SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"require": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"test": "npm run build && node tmp/unawaited.test.js && node tmp/integration-unawaited.js && node -r ./tracer/register tmp/promise-map.test.js && node tmp/disable-subtree.test.js"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"express": "^5.1.0",
|
|
29
|
+
"mongoose": ">=6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/express": "^5.0.3"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@babel/core": "^7.28.4",
|
|
39
|
+
"@babel/generator": "^7.28.3",
|
|
40
|
+
"@babel/parser": "^7.28.4",
|
|
41
|
+
"@babel/plugin-transform-typescript": "^7.28.0",
|
|
42
|
+
"@babel/traverse": "^7.28.4",
|
|
43
|
+
"@babel/types": "^7.28.4",
|
|
44
|
+
"@jridgewell/trace-mapping": "^0.3.25",
|
|
45
|
+
"@nestjs/common": "^11.1.9",
|
|
46
|
+
"@nestjs/core": "^11.1.9",
|
|
47
|
+
"@nestjs/platform-express": "^11.1.9",
|
|
48
|
+
"express": "^5.1.0",
|
|
49
|
+
"import-in-the-middle": "^2.0.0",
|
|
50
|
+
"mongoose": ">=6",
|
|
51
|
+
"pirates": "^4.0.7",
|
|
52
|
+
"reflect-metadata": "^0.2.2",
|
|
53
|
+
"require-in-the-middle": "^8.0.1",
|
|
54
|
+
"rxjs": "^7.8.2",
|
|
55
|
+
"shimmer": "^1.2.1",
|
|
56
|
+
"testcontainers": "^11.8.1"
|
|
57
|
+
}
|
|
58
|
+
}
|