@mtorre4580/is-deprecated 0.0.2 → 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 CHANGED
@@ -1,89 +1,173 @@
1
1
  # is-deprecated
2
2
 
3
- A utility library designed to notify about deprecated functions or methods within objects.
3
+ Runtime deprecation warnings for functions, methods, properties and classes, for **Node and the browser**, with **TypeScript types**, **decorators**, **telemetry hooks** and **warn / throw / silent** modes. Zero dependencies.
4
4
 
5
- <img width="128" height="128" src="https://github.com/mtorre4580/is-deprecated/blob/main/deprecated.png?raw=true" />
6
-
7
- ## Installation
5
+ <img width="128" height="128" src="https://github.com/mtorre4580/is-deprecated/blob/main/deprecated.png?raw=true" />
8
6
 
9
7
  ```
10
8
  npm i @mtorre4580/is-deprecated
11
9
  ```
12
10
 
13
- ## Parameters
11
+ ```ts
12
+ import { deprecate } from '@mtorre4580/is-deprecated';
13
+
14
+ export const getUsers = deprecate(getUsersImpl, {
15
+ since: '1.4.0',
16
+ removeIn: '2.0.0',
17
+ replacement: 'getUsersV2()',
18
+ });
19
+
20
+ getUsers();
21
+ // (node:4242) DeprecationWarning: getUsers is deprecated since 1.4.0 and will be removed in 2.0.0. Use getUsersV2() instead.
22
+ // Called from file:///app/src/routes.ts:12:3
23
+ ```
24
+
25
+ ## Why this one
26
+
27
+ | | is-deprecated | [depd](https://www.npmjs.com/package/depd) | [util-deprecate](https://www.npmjs.com/package/util-deprecate) | Node `util.deprecate` |
28
+ |---|:-:|:-:|:-:|:-:|
29
+ | Works in browsers | ✅ | ❌ (no-op) | ✅ | ❌ |
30
+ | TypeScript types included | ✅ | ❌ | ❌ | ✅ |
31
+ | Functions and classes (`new`) | ✅ | ✅ | ✅ | ✅ |
32
+ | Object methods and properties | ✅ | ✅ | ❌ | ❌ |
33
+ | Decorators (TC39 + legacy) | ✅ | ❌ | ❌ | ❌ |
34
+ | Call site in the warning | ✅ | ✅ | ❌ | with `--trace-deprecation` |
35
+ | Once per call site | ✅ | ✅ | once total | once total |
36
+ | `since` / `removeIn` / `replacement` metadata | ✅ | ❌ | ❌ | `code` only |
37
+ | Overdue removal dates | ✅ | ❌ | ❌ | ❌ |
38
+ | Telemetry hook with structured event | ✅ | `process.on('deprecation')` | ❌ | `process.on('warning')` |
39
+ | Modes warn / throw / silent | ✅ | ✅ (Node flags) | partial | ✅ (Node flags) |
40
+ | ESM + CJS | ✅ | CJS | CJS | n/a |
41
+
42
+ If you only target Node and need to deprecate a single function, `util.deprecate` is perfectly fine. Use this library when you ship code to browsers, want typed metadata, decorators or telemetry.
14
43
 
15
- - obj (Object|Function): The object or function to check for deprecation.
44
+ ## API
16
45
 
17
- - config (Object): Configuration object with the following properties:
18
- - method (String): The method name to be deprecated within the object.
19
- - message (String): Custom deprecation message (default: 'This will be deprecated soon').
20
- - logger (Object): Logger object for warning messages (default: console).
21
- - sendMetrics (Function): Function to send metrics upon deprecation (default: empty function).
46
+ ### `deprecate(fn, options?)`
22
47
 
23
- ## Usage
48
+ Wraps a function or class. It warns **when called** (or instantiated with `new`), never when wrapped. It keeps `this`, `new`, `instanceof`, `name`, `length`, `prototype` and static members.
24
49
 
25
- Function to notify about deprecation and wrap the function/method with deprecation warnings.
50
+ ```ts
51
+ const LegacyClient = deprecate(Client, { replacement: 'ApiClient' });
52
+ new LegacyClient(); // Client is deprecated. Use ApiClient instead.
53
+ ```
54
+
55
+ ### `deprecateMethod(obj, method | methods[], options?)`
26
56
 
27
- <img alt="example usage" src="https://github.com/mtorre4580/is-deprecated/blob/main/example_usage.png?raw=true" />
57
+ Returns a proxy of `obj` whose methods warn when **called**. Reading the property, `typeof`, spreading or logging the object does not warn. Deprecated methods run against the original object, so private fields work and their internal `this.x()` calls are not reported again.
58
+
59
+ ```ts
60
+ export const api = deprecateMethod(apiImpl, ['getUsers', 'getUser'], {
61
+ removeIn: '2027-06-30',
62
+ url: 'https://example.com/migrate-users',
63
+ });
64
+ ```
28
65
 
29
- ### With Function
66
+ ### `deprecateProperty(obj, property, options?)`
67
+
68
+ Redefines the property **in place** so reading or writing it warns. It works with data properties and with getters/setters.
69
+
70
+ ```ts
71
+ deprecateProperty(config, 'retries', { replacement: 'config.maxRetries' });
72
+ ```
30
73
 
31
- ```js
32
- import deprecated from "@mtorre4580/is-deprecated";
74
+ ### `@Deprecated(options?)`
33
75
 
34
- // Function to add the deprecated helper
35
- const myFunction = (arg1, arg2) => {};
76
+ Decorator for classes, methods, getters, setters and `accessor` fields. It supports standard decorators (TypeScript 5+, Babel) and legacy `experimentalDecorators` (Angular, NestJS).
36
77
 
37
- // Basic config
38
- const config = {
39
- message:
40
- "myFunction will be removed in the next release, please replace with this https://www.npmjs.com",
41
- };
78
+ ```ts
79
+ import { Deprecated } from '@mtorre4580/is-deprecated';
42
80
 
43
- // Create the wrapper to use
44
- const deprecatedMyFunction = deprecated(myFunction, config);
81
+ class Api {
82
+ /** @deprecated Use {@link Api.getUsersV2} */
83
+ @Deprecated({ since: '1.2.0', replacement: 'Api.getUsersV2()' })
84
+ getUsers() {}
45
85
 
46
- // Call the wrapper function to see the warning
47
- deprecatedMyFunction('', {});
86
+ @Deprecated()
87
+ accessor timeout = 5000;
88
+ }
89
+ // Api.getUsers() is deprecated since 1.2.0. Use Api.getUsersV2() instead.
48
90
  ```
49
91
 
50
- ### With Function in Object
51
-
52
- ```js
53
- import deprecated from "@mtorre4580/is-deprecated";
54
-
55
- // Object to apply the deprecated method
56
- const API = {
57
- timeout: 5000,
58
- getUsers() {
59
- return new Promise((resolve) => {
60
- setTimeout(() => {
61
- resolve([
62
- { id: 2, name: "Matias" },
63
- { id: 3, name: "Belen" },
64
- ]);
65
- }, 1000);
66
- });
92
+ Plain class fields cannot be intercepted: use `accessor` or a getter/setter.
93
+
94
+ ### Options
95
+
96
+ | Option | Type | Description |
97
+ |---|---|---|
98
+ | `name` | `string` | Name shown in the message. Defaults to the function, method or property name. |
99
+ | `message` | `string` | Full custom message (replaces the generated one). |
100
+ | `since` | `string` | Version in which the API was deprecated. |
101
+ | `removeIn` | `string \| Date` | Version (`"2.0.0"`) or date (`"2027-01-01"`) of removal. Past dates are marked `[OVERDUE]` and logged with `logger.error` when available. |
102
+ | `replacement` | `string` | What to use instead. |
103
+ | `code` | `string` | Stable id such as `MYLIB_DEP001`. Shown by Node and used for dedupe. |
104
+ | `url` | `string` | Migration guide. |
105
+ | `once` | `boolean` | Warn once per call site (default `true`). |
106
+ | `logger` | `{ warn, error? }` | Logger for this API. |
107
+ | `onDeprecation` | `(event) => void` | Telemetry hook for this API. |
108
+
109
+ ### `configure(config)`
110
+
111
+ ```ts
112
+ import { configure } from '@mtorre4580/is-deprecated';
113
+
114
+ configure({
115
+ mode: process.env.NODE_ENV === 'production' ? 'silent' : 'warn', // or 'throw' in CI
116
+ once: true,
117
+ logger: pinoLogger, // default: process.emitWarning in Node, console.warn in browsers
118
+ onDeprecation(event) {
119
+ metrics.increment('deprecated_api', { name: event.name, code: event.code });
67
120
  },
68
- getUsersV2() {},
69
- };
121
+ });
122
+ ```
123
+
124
+ - **`warn`** (default) prints the warning.
125
+ - **`throw`** throws a `DeprecationError` on every call (`error.event` holds the details). It is handy in CI to make sure nothing still uses old APIs.
126
+ - **`silent`** prints nothing but **still calls `onDeprecation`**, so you can track usage in production without noise.
70
127
 
71
- // Basic config
72
- const config = {
73
- message: "getUsers is deprecated in the next release, please with getUsersV2",
74
- method: "getUsers",
75
- };
128
+ When `mode` is not configured it is read from the `IS_DEPRECATED_MODE` env var (`warn|throw|silent`), and then from Node's `--no-deprecation` / `--throw-deprecation` flags.
76
129
 
77
- // Create the wrapper to use
78
- const APIWithDeprecated = deprecated(API, config);
130
+ The `onDeprecation` event has this shape:
79
131
 
80
- // Call the wrapper function to see the warning
81
- const users = await APIWithDeprecated.getUsers();
132
+ ```ts
133
+ { name, message, code?, since?, removeIn?, replacement?, url?, overdue, callSite?, stack?, mode }
82
134
  ```
83
135
 
84
- ## Best Practices
136
+ Hooks that throw are ignored: telemetry never breaks your app.
137
+
138
+ ### `resetDeprecations()`
139
+
140
+ Clears the "already warned" cache. It is useful in tests.
141
+
142
+ ## Best practices
143
+
144
+ - Combine the static and runtime signals: the JSDoc `/** @deprecated */` tag strikes the API through in editors and is caught by [`@typescript-eslint/no-deprecated`](https://typescript-eslint.io/rules/no-deprecated/), while this library warns people who use it at runtime or from plain JavaScript.
145
+ - Always set `replacement` and `removeIn`, so a warning tells people what to do and by when.
146
+ - Use `code` so people can search for it and filter it with Node's `--no-deprecation`/`process.on('warning')`.
147
+ - Send `onDeprecation` to your metrics, so you know when it is safe to remove the API.
148
+
149
+ ## Migrating from 0.x
150
+
151
+ The 0.x default export still works but is deprecated:
152
+
153
+ ```diff
154
+ - import deprecated from '@mtorre4580/is-deprecated';
155
+ - const fn = deprecated(myFn, { message: '...' });
156
+ - const api = deprecated(API, { method: 'getUsers', sendMetrics });
157
+ + import { deprecate, deprecateMethod } from '@mtorre4580/is-deprecated';
158
+ + const fn = deprecate(myFn, { message: '...' });
159
+ + const api = deprecateMethod(API, 'getUsers', { onDeprecation });
160
+ ```
161
+
162
+ Behavior changes in 1.0:
163
+
164
+ - **Bug fix:** in 0.x a wrapped function warned once when it was *wrapped* and never when it was *called*. It now warns on calls.
165
+ - Object methods warn when called, not on every property read.
166
+ - Warnings are deduplicated per call site (`once: false` restores warning on every call).
167
+ - The default message is generated from the metadata instead of `"This will be deprecated soon"`.
168
+ - `sendMetrics` now receives the event object.
169
+ - In CommonJS, `require()` returns the module namespace: use `const { deprecate } = require('@mtorre4580/is-deprecated')`.
170
+
171
+ ## License
85
172
 
86
- - To building time use the jsdoc decorator [@deprecated](https://jsdoc.app/tags-deprecated) in the code
87
- - To runtime use the function from this lib `deprecated` to notify in the console
88
- - The sendMetrics function allows to get more insight what app is using the deprecated method to notify the team
89
- - You can use any environment variable to apply the wrapper or not, if you only need to run in development or staging
173
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,347 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ // src/env.ts
6
+ function getProcess() {
7
+ const proc = globalThis.process;
8
+ return proc && typeof proc === "object" ? proc : void 0;
9
+ }
10
+ var MODES = ["warn", "throw", "silent"];
11
+ function modeFromEnvironment() {
12
+ const proc = getProcess();
13
+ let fromEnv;
14
+ try {
15
+ fromEnv = proc?.env?.IS_DEPRECATED_MODE;
16
+ } catch {
17
+ }
18
+ const normalized = fromEnv?.trim().toLowerCase();
19
+ if (normalized && MODES.includes(normalized)) return normalized;
20
+ if (proc?.noDeprecation) return "silent";
21
+ if (proc?.throwDeprecation) return "throw";
22
+ return "warn";
23
+ }
24
+ function nodeEmitWarning() {
25
+ const emit = getProcess()?.emitWarning;
26
+ return typeof emit === "function" ? emit.bind(getProcess()) : void 0;
27
+ }
28
+
29
+ // src/core.ts
30
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("@mtorre4580/is-deprecated");
31
+ var store = globalThis;
32
+ var state = store[STATE_KEY] ?? (store[STATE_KEY] = { config: {}, seen: /* @__PURE__ */ new Set() });
33
+ function configure(config) {
34
+ Object.assign(state.config, config);
35
+ return { ...state.config };
36
+ }
37
+ function resetDeprecations() {
38
+ state.seen.clear();
39
+ }
40
+ var DeprecationError = class extends Error {
41
+ constructor(event) {
42
+ super(event.message);
43
+ this.name = "DeprecationError";
44
+ this.code = event.code;
45
+ this.event = event;
46
+ }
47
+ };
48
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}/;
49
+ function parseRemoval(removeIn) {
50
+ if (removeIn instanceof Date) {
51
+ return Number.isNaN(removeIn.getTime()) ? {} : { label: removeIn.toISOString().slice(0, 10), date: removeIn };
52
+ }
53
+ if (typeof removeIn !== "string" || removeIn === "") return {};
54
+ if (ISO_DATE.test(removeIn)) {
55
+ const date = new Date(removeIn);
56
+ if (!Number.isNaN(date.getTime())) return { label: removeIn, date };
57
+ }
58
+ return { label: removeIn };
59
+ }
60
+ function buildMessage(options, name, removal, overdue) {
61
+ let message = options.message;
62
+ if (message === void 0) {
63
+ message = `${name} is deprecated`;
64
+ if (options.since) message += ` since ${options.since}`;
65
+ if (removal.label) {
66
+ if (overdue) message += ` and was scheduled for removal on ${removal.label}`;
67
+ else message += ` and will be removed ${removal.date ? "on" : "in"} ${removal.label}`;
68
+ }
69
+ message += ".";
70
+ if (options.replacement) message += ` Use ${options.replacement} instead.`;
71
+ if (options.url) message += ` See ${options.url}`;
72
+ }
73
+ return overdue ? `[OVERDUE] ${message}` : message;
74
+ }
75
+ function captureCallSite(boundary) {
76
+ const E = Error;
77
+ const holder = {};
78
+ const previousLimit = E.stackTraceLimit;
79
+ let skip = 0;
80
+ try {
81
+ if (typeof previousLimit === "number") E.stackTraceLimit = 10;
82
+ if (typeof E.captureStackTrace === "function") {
83
+ E.captureStackTrace(holder, boundary);
84
+ } else {
85
+ holder.stack = new Error().stack;
86
+ skip = 3;
87
+ }
88
+ void holder.stack;
89
+ } catch {
90
+ return {};
91
+ } finally {
92
+ if (typeof previousLimit === "number") E.stackTraceLimit = previousLimit;
93
+ }
94
+ if (typeof holder.stack !== "string") return {};
95
+ const frames = holder.stack.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !/^Error\b/.test(line)).slice(skip);
96
+ if (frames.length === 0) return {};
97
+ return {
98
+ callSite: frames[0].replace(/^at\s+/, ""),
99
+ stack: frames.join("\n")
100
+ };
101
+ }
102
+ function safeCall(hook, event) {
103
+ if (typeof hook !== "function") return;
104
+ try {
105
+ hook(event);
106
+ } catch {
107
+ }
108
+ }
109
+ function notify(options, fallbackName, boundary) {
110
+ const { config } = state;
111
+ const mode = config.mode ?? modeFromEnvironment();
112
+ const name = options.name ?? fallbackName;
113
+ const { callSite, stack } = captureCallSite(boundary);
114
+ if (mode !== "throw" && (options.once ?? config.once ?? true)) {
115
+ const key = `${options.code ?? ""}|${name}|${callSite ?? ""}`;
116
+ if (state.seen.has(key)) return;
117
+ state.seen.add(key);
118
+ }
119
+ const removal = parseRemoval(options.removeIn);
120
+ const overdue = removal.date !== void 0 && removal.date.getTime() <= Date.now();
121
+ const message = buildMessage(options, name, removal, overdue);
122
+ const event = {
123
+ name,
124
+ message,
125
+ code: options.code,
126
+ since: options.since,
127
+ removeIn: removal.label,
128
+ replacement: options.replacement,
129
+ url: options.url,
130
+ overdue,
131
+ callSite,
132
+ stack,
133
+ mode
134
+ };
135
+ safeCall(config.onDeprecation, event);
136
+ safeCall(options.onDeprecation, event);
137
+ safeCall(options.sendMetrics, event);
138
+ if (mode === "silent") return;
139
+ if (mode === "throw") throw new DeprecationError(event);
140
+ const logger = options.logger ?? config.logger;
141
+ if (logger) {
142
+ if (overdue && typeof logger.error === "function") logger.error(message);
143
+ else logger.warn(message);
144
+ return;
145
+ }
146
+ const emitWarning = nodeEmitWarning();
147
+ if (emitWarning) {
148
+ emitWarning(message, {
149
+ type: "DeprecationWarning",
150
+ code: options.code,
151
+ detail: callSite ? `Called from ${callSite}` : void 0
152
+ });
153
+ return;
154
+ }
155
+ if (typeof console !== "undefined") {
156
+ console.warn(`DeprecationWarning: ${message}${callSite ? `
157
+ at ${callSite}` : ""}`);
158
+ }
159
+ }
160
+ function copyFunctionShape(target, source) {
161
+ for (const key of ["name", "length"]) {
162
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
163
+ if (descriptor) Object.defineProperty(target, key, { ...descriptor, configurable: true });
164
+ }
165
+ }
166
+ function deprecate(fn, options = {}) {
167
+ if (typeof fn !== "function") {
168
+ throw new TypeError("deprecate() expects a function or class as first argument");
169
+ }
170
+ const fallbackName = fn.name || "anonymous function";
171
+ const handler = {
172
+ apply(target, thisArg, args) {
173
+ notify(options, fallbackName, handler.apply);
174
+ return Reflect.apply(target, thisArg, args);
175
+ },
176
+ construct(target, args, newTarget) {
177
+ notify(options, fallbackName, handler.construct);
178
+ return Reflect.construct(target, args, newTarget);
179
+ }
180
+ };
181
+ return new Proxy(fn, handler);
182
+ }
183
+ function deprecateMethod(obj, methods, options = {}) {
184
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
185
+ throw new TypeError("deprecateMethod() expects an object as first argument");
186
+ }
187
+ const keys = Array.isArray(methods) ? methods : [methods];
188
+ if (keys.length === 0) throw new TypeError("deprecateMethod() expects at least one method name");
189
+ for (const key of keys) {
190
+ if (typeof obj[key] !== "function") {
191
+ throw new TypeError(`"${String(key)}" is not a method of the given object`);
192
+ }
193
+ }
194
+ const cache = /* @__PURE__ */ new Map();
195
+ const proxy = new Proxy(obj, {
196
+ get(target, property, receiver) {
197
+ const value = Reflect.get(target, property, receiver);
198
+ if (typeof value !== "function" || !keys.includes(property)) return value;
199
+ const original = value;
200
+ const cached = cache.get(property);
201
+ if (cached && cached.original === original) return cached.wrapped;
202
+ const methodOptions = keys.length === 1 ? options : { ...options, name: void 0 };
203
+ const wrapped = function deprecatedMethod(...args) {
204
+ notify(methodOptions, String(property), wrapped);
205
+ return Reflect.apply(original, this === proxy ? target : this, args);
206
+ };
207
+ copyFunctionShape(wrapped, original);
208
+ cache.set(property, { original, wrapped });
209
+ return wrapped;
210
+ }
211
+ });
212
+ return proxy;
213
+ }
214
+ function deprecateProperty(obj, property, options = {}) {
215
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
216
+ throw new TypeError("deprecateProperty() expects an object as first argument");
217
+ }
218
+ const descriptor = Object.getOwnPropertyDescriptor(obj, property);
219
+ if (!descriptor) throw new TypeError(`"${String(property)}" is not an own property of the given object`);
220
+ if (!descriptor.configurable) throw new TypeError(`"${String(property)}" is not configurable`);
221
+ const name = options.name ?? String(property);
222
+ const next = { configurable: true, enumerable: descriptor.enumerable };
223
+ if ("value" in descriptor || "writable" in descriptor) {
224
+ let current = descriptor.value;
225
+ next.get = function get() {
226
+ notify(options, name, get);
227
+ return current;
228
+ };
229
+ if (descriptor.writable) {
230
+ next.set = function set(value) {
231
+ notify(options, name, set);
232
+ current = value;
233
+ };
234
+ }
235
+ } else {
236
+ const { get: originalGet, set: originalSet } = descriptor;
237
+ if (originalGet) {
238
+ next.get = function get() {
239
+ notify(options, name, get);
240
+ return originalGet.call(this);
241
+ };
242
+ }
243
+ if (originalSet) {
244
+ next.set = function set(value) {
245
+ notify(options, name, set);
246
+ originalSet.call(this, value);
247
+ };
248
+ }
249
+ }
250
+ Object.defineProperty(obj, property, next);
251
+ return obj;
252
+ }
253
+
254
+ // src/decorator.ts
255
+ function isStandardContext(context) {
256
+ return typeof context === "object" && context !== null && "kind" in context;
257
+ }
258
+ function ownerName(instanceOrClass, isStatic) {
259
+ try {
260
+ const owner = isStatic ? instanceOrClass : instanceOrClass?.constructor;
261
+ return typeof owner?.name === "string" && owner.name !== "" ? owner.name : "Object";
262
+ } catch {
263
+ return "Object";
264
+ }
265
+ }
266
+ function wrapCallable(original, member, isStatic, suffix, options) {
267
+ const wrapped = function(...args) {
268
+ notify(options, `${ownerName(this, isStatic)}.${member}${suffix}`, wrapped);
269
+ return original.apply(this, args);
270
+ };
271
+ Object.defineProperty(wrapped, "name", { value: original.name, configurable: true });
272
+ return wrapped;
273
+ }
274
+ function fieldError() {
275
+ return new TypeError(
276
+ "@Deprecated() cannot intercept plain class fields: use an `accessor` field or a getter/setter"
277
+ );
278
+ }
279
+ function decorateStandard(value, context, options) {
280
+ const member = String(context.name);
281
+ const isStatic = Boolean(context.static);
282
+ switch (context.kind) {
283
+ case "class":
284
+ return deprecate(value, { ...options, name: options.name ?? (member || value.name) });
285
+ case "method":
286
+ return wrapCallable(value, member, isStatic, "()", options);
287
+ case "getter":
288
+ case "setter":
289
+ return wrapCallable(value, member, isStatic, "", options);
290
+ case "accessor": {
291
+ const get = function() {
292
+ notify(options, `${ownerName(this, isStatic)}.${member}`, get);
293
+ return value.get.call(this);
294
+ };
295
+ const set = function(next) {
296
+ notify(options, `${ownerName(this, isStatic)}.${member}`, set);
297
+ value.set.call(this, next);
298
+ };
299
+ return { get, set, init: (initial) => initial };
300
+ }
301
+ default:
302
+ throw fieldError();
303
+ }
304
+ }
305
+ function decorateLegacy(target, key, descriptor, options) {
306
+ if (key === void 0 && typeof target === "function") {
307
+ return deprecate(target, { ...options, name: options.name ?? target.name });
308
+ }
309
+ if (!descriptor) throw fieldError();
310
+ const member = String(key);
311
+ const isStatic = typeof target === "function";
312
+ const next = { ...descriptor };
313
+ if (typeof descriptor.value === "function") {
314
+ next.value = wrapCallable(descriptor.value, member, isStatic, "()", options);
315
+ }
316
+ if (descriptor.get) next.get = wrapCallable(descriptor.get, member, isStatic, "", options);
317
+ if (descriptor.set) next.set = wrapCallable(descriptor.set, member, isStatic, "", options);
318
+ return next;
319
+ }
320
+ function Deprecated(options = {}) {
321
+ return function deprecatedDecorator(value, context, descriptor) {
322
+ if (isStandardContext(context)) return decorateStandard(value, context, options);
323
+ return decorateLegacy(value, context, descriptor, options);
324
+ };
325
+ }
326
+
327
+ // src/index.ts
328
+ function deprecated(obj, config = {}) {
329
+ if (obj == null) throw new TypeError("The param obj is required");
330
+ const { method, ...options } = config;
331
+ if (typeof obj === "function" && method === void 0) {
332
+ return deprecate(obj, options);
333
+ }
334
+ if (method === void 0 || typeof obj[method] !== "function") {
335
+ throw new TypeError("The object not have the method defined");
336
+ }
337
+ return deprecateMethod(obj, method, options);
338
+ }
339
+
340
+ exports.Deprecated = Deprecated;
341
+ exports.DeprecationError = DeprecationError;
342
+ exports.configure = configure;
343
+ exports.default = deprecated;
344
+ exports.deprecate = deprecate;
345
+ exports.deprecateMethod = deprecateMethod;
346
+ exports.deprecateProperty = deprecateProperty;
347
+ exports.resetDeprecations = resetDeprecations;
@@ -0,0 +1,130 @@
1
+ /** How deprecations are surfaced. */
2
+ type DeprecationMode = 'warn' | 'throw' | 'silent';
3
+ /** Anything with a `warn` method (console, pino, winston, a custom logger...). */
4
+ interface Logger {
5
+ warn: (...args: unknown[]) => void;
6
+ error?: (...args: unknown[]) => void;
7
+ }
8
+ /** Metadata describing a single deprecated API. */
9
+ interface DeprecationOptions {
10
+ /** Human readable name of the deprecated API. Defaults to the function/property name. */
11
+ name?: string;
12
+ /** Full custom message. When omitted, a message is built from the other fields. */
13
+ message?: string;
14
+ /** Version (or date) in which the API was deprecated, e.g. `"1.4.0"`. */
15
+ since?: string;
16
+ /**
17
+ * Version or date in which the API will be removed, e.g. `"2.0.0"`, `"2027-01-01"` or a `Date`.
18
+ * When it is a date that already passed, the deprecation is flagged as overdue.
19
+ */
20
+ removeIn?: string | Date;
21
+ /** What to use instead, e.g. `"getUsersV2()"`. */
22
+ replacement?: string;
23
+ /** Stable identifier, e.g. `"MYLIB_DEP001"`. Used for dedupe and shown in Node warnings. */
24
+ code?: string;
25
+ /** Link to the migration guide. */
26
+ url?: string;
27
+ /** Warn only once per call site (default: global `once`, which defaults to `true`). */
28
+ once?: boolean;
29
+ /** Per-API logger. When set, it is used instead of `process.emitWarning`/`console.warn`. */
30
+ logger?: Logger;
31
+ /** Per-API telemetry hook, called in addition to the global `onDeprecation`. */
32
+ onDeprecation?: (event: DeprecationEvent) => void;
33
+ /**
34
+ * @deprecated Use `onDeprecation` instead. Kept for compatibility with v0.x.
35
+ */
36
+ sendMetrics?: (event: DeprecationEvent) => void;
37
+ }
38
+ /** Payload delivered to `onDeprecation` hooks. */
39
+ interface DeprecationEvent {
40
+ name: string;
41
+ message: string;
42
+ code?: string;
43
+ since?: string;
44
+ removeIn?: string;
45
+ replacement?: string;
46
+ url?: string;
47
+ /** `true` when `removeIn` is a date that already passed. */
48
+ overdue: boolean;
49
+ /** First stack frame outside this library (best effort, `undefined` when unavailable). */
50
+ callSite?: string;
51
+ /** Stack trace starting at the caller (best effort). */
52
+ stack?: string;
53
+ /** Mode that was applied to this event. */
54
+ mode: DeprecationMode;
55
+ }
56
+ /** Global configuration, set with `configure()`. */
57
+ interface GlobalConfig {
58
+ /**
59
+ * `warn` (default), `throw` or `silent`. When not set, it is read from the
60
+ * `IS_DEPRECATED_MODE` env var, then from Node's `--no-deprecation` / `--throw-deprecation`.
61
+ */
62
+ mode?: DeprecationMode;
63
+ /** Warn only once per API and call site. Default `true`. */
64
+ once?: boolean;
65
+ /** Global logger. Default: `process.emitWarning` in Node, `console.warn` elsewhere. */
66
+ logger?: Logger;
67
+ /** Global telemetry hook. Called even in `silent` mode. */
68
+ onDeprecation?: (event: DeprecationEvent) => void;
69
+ }
70
+
71
+ type AnyFunction = (...args: any[]) => any;
72
+ /**
73
+ * Set global behavior. Only the keys you pass are changed; pass `undefined` to reset a key.
74
+ * Returns the resulting configuration.
75
+ */
76
+ declare function configure(config: GlobalConfig): Readonly<GlobalConfig>;
77
+ /** Forget which deprecations were already reported, so `once` warnings show again. */
78
+ declare function resetDeprecations(): void;
79
+ /** Thrown in `throw` mode. */
80
+ declare class DeprecationError extends Error {
81
+ readonly code?: string;
82
+ readonly event: DeprecationEvent;
83
+ constructor(event: DeprecationEvent);
84
+ }
85
+ /**
86
+ * Wrap a function or class so every call (or `new`) reports a deprecation.
87
+ * The wrapper keeps `this`, `new`, `name`, `length`, `prototype` and static members.
88
+ */
89
+ declare function deprecate<T extends AnyFunction | (abstract new (...args: any[]) => any)>(fn: T, options?: DeprecationOptions): T;
90
+ /**
91
+ * Return a proxy of `obj` whose given methods report a deprecation when they are **called**
92
+ * (reading the property, `typeof`, spreading or logging the object does not warn).
93
+ * Deprecated methods run against the original object, so calls they make through `this`
94
+ * are not reported again and private class fields keep working.
95
+ */
96
+ declare function deprecateMethod<T extends object>(obj: T, methods: keyof T | readonly (keyof T)[], options?: DeprecationOptions): T;
97
+ /**
98
+ * Redefine `obj[property]` in place so reading or writing it reports a deprecation.
99
+ * Works with data properties and getters/setters. Returns the same object.
100
+ */
101
+ declare function deprecateProperty<T extends object>(obj: T, property: keyof T, options?: DeprecationOptions): T;
102
+
103
+ /**
104
+ * Decorator that reports a deprecation when a class is instantiated, or when a method,
105
+ * getter, setter or `accessor` field is used.
106
+ *
107
+ * Works with standard (TC39, TypeScript 5+) decorators and with legacy
108
+ * `experimentalDecorators` (Angular, NestJS...).
109
+ *
110
+ * @example
111
+ * class Api {
112
+ * \@Deprecated({ since: '1.2.0', replacement: 'Api.getUsersV2()' })
113
+ * getUsers() {}
114
+ * }
115
+ */
116
+ declare function Deprecated(options?: DeprecationOptions): any;
117
+
118
+ /** Options accepted by the v0.x default export. */
119
+ interface LegacyConfig extends DeprecationOptions {
120
+ /** Name of the method to deprecate when the first argument is an object. */
121
+ method?: string;
122
+ }
123
+ /**
124
+ * v0.x API, kept so existing code keeps working.
125
+ * @deprecated Use `deprecate(fn, options)` or `deprecateMethod(obj, 'method', options)`.
126
+ */
127
+ declare function deprecated<T extends object>(obj: T, config?: LegacyConfig): T;
128
+
129
+ export { Deprecated, DeprecationError, configure, deprecated as default, deprecate, deprecateMethod, deprecateProperty, resetDeprecations };
130
+ export type { DeprecationEvent, DeprecationMode, DeprecationOptions, GlobalConfig, LegacyConfig, Logger };
@@ -0,0 +1,130 @@
1
+ /** How deprecations are surfaced. */
2
+ type DeprecationMode = 'warn' | 'throw' | 'silent';
3
+ /** Anything with a `warn` method (console, pino, winston, a custom logger...). */
4
+ interface Logger {
5
+ warn: (...args: unknown[]) => void;
6
+ error?: (...args: unknown[]) => void;
7
+ }
8
+ /** Metadata describing a single deprecated API. */
9
+ interface DeprecationOptions {
10
+ /** Human readable name of the deprecated API. Defaults to the function/property name. */
11
+ name?: string;
12
+ /** Full custom message. When omitted, a message is built from the other fields. */
13
+ message?: string;
14
+ /** Version (or date) in which the API was deprecated, e.g. `"1.4.0"`. */
15
+ since?: string;
16
+ /**
17
+ * Version or date in which the API will be removed, e.g. `"2.0.0"`, `"2027-01-01"` or a `Date`.
18
+ * When it is a date that already passed, the deprecation is flagged as overdue.
19
+ */
20
+ removeIn?: string | Date;
21
+ /** What to use instead, e.g. `"getUsersV2()"`. */
22
+ replacement?: string;
23
+ /** Stable identifier, e.g. `"MYLIB_DEP001"`. Used for dedupe and shown in Node warnings. */
24
+ code?: string;
25
+ /** Link to the migration guide. */
26
+ url?: string;
27
+ /** Warn only once per call site (default: global `once`, which defaults to `true`). */
28
+ once?: boolean;
29
+ /** Per-API logger. When set, it is used instead of `process.emitWarning`/`console.warn`. */
30
+ logger?: Logger;
31
+ /** Per-API telemetry hook, called in addition to the global `onDeprecation`. */
32
+ onDeprecation?: (event: DeprecationEvent) => void;
33
+ /**
34
+ * @deprecated Use `onDeprecation` instead. Kept for compatibility with v0.x.
35
+ */
36
+ sendMetrics?: (event: DeprecationEvent) => void;
37
+ }
38
+ /** Payload delivered to `onDeprecation` hooks. */
39
+ interface DeprecationEvent {
40
+ name: string;
41
+ message: string;
42
+ code?: string;
43
+ since?: string;
44
+ removeIn?: string;
45
+ replacement?: string;
46
+ url?: string;
47
+ /** `true` when `removeIn` is a date that already passed. */
48
+ overdue: boolean;
49
+ /** First stack frame outside this library (best effort, `undefined` when unavailable). */
50
+ callSite?: string;
51
+ /** Stack trace starting at the caller (best effort). */
52
+ stack?: string;
53
+ /** Mode that was applied to this event. */
54
+ mode: DeprecationMode;
55
+ }
56
+ /** Global configuration, set with `configure()`. */
57
+ interface GlobalConfig {
58
+ /**
59
+ * `warn` (default), `throw` or `silent`. When not set, it is read from the
60
+ * `IS_DEPRECATED_MODE` env var, then from Node's `--no-deprecation` / `--throw-deprecation`.
61
+ */
62
+ mode?: DeprecationMode;
63
+ /** Warn only once per API and call site. Default `true`. */
64
+ once?: boolean;
65
+ /** Global logger. Default: `process.emitWarning` in Node, `console.warn` elsewhere. */
66
+ logger?: Logger;
67
+ /** Global telemetry hook. Called even in `silent` mode. */
68
+ onDeprecation?: (event: DeprecationEvent) => void;
69
+ }
70
+
71
+ type AnyFunction = (...args: any[]) => any;
72
+ /**
73
+ * Set global behavior. Only the keys you pass are changed; pass `undefined` to reset a key.
74
+ * Returns the resulting configuration.
75
+ */
76
+ declare function configure(config: GlobalConfig): Readonly<GlobalConfig>;
77
+ /** Forget which deprecations were already reported, so `once` warnings show again. */
78
+ declare function resetDeprecations(): void;
79
+ /** Thrown in `throw` mode. */
80
+ declare class DeprecationError extends Error {
81
+ readonly code?: string;
82
+ readonly event: DeprecationEvent;
83
+ constructor(event: DeprecationEvent);
84
+ }
85
+ /**
86
+ * Wrap a function or class so every call (or `new`) reports a deprecation.
87
+ * The wrapper keeps `this`, `new`, `name`, `length`, `prototype` and static members.
88
+ */
89
+ declare function deprecate<T extends AnyFunction | (abstract new (...args: any[]) => any)>(fn: T, options?: DeprecationOptions): T;
90
+ /**
91
+ * Return a proxy of `obj` whose given methods report a deprecation when they are **called**
92
+ * (reading the property, `typeof`, spreading or logging the object does not warn).
93
+ * Deprecated methods run against the original object, so calls they make through `this`
94
+ * are not reported again and private class fields keep working.
95
+ */
96
+ declare function deprecateMethod<T extends object>(obj: T, methods: keyof T | readonly (keyof T)[], options?: DeprecationOptions): T;
97
+ /**
98
+ * Redefine `obj[property]` in place so reading or writing it reports a deprecation.
99
+ * Works with data properties and getters/setters. Returns the same object.
100
+ */
101
+ declare function deprecateProperty<T extends object>(obj: T, property: keyof T, options?: DeprecationOptions): T;
102
+
103
+ /**
104
+ * Decorator that reports a deprecation when a class is instantiated, or when a method,
105
+ * getter, setter or `accessor` field is used.
106
+ *
107
+ * Works with standard (TC39, TypeScript 5+) decorators and with legacy
108
+ * `experimentalDecorators` (Angular, NestJS...).
109
+ *
110
+ * @example
111
+ * class Api {
112
+ * \@Deprecated({ since: '1.2.0', replacement: 'Api.getUsersV2()' })
113
+ * getUsers() {}
114
+ * }
115
+ */
116
+ declare function Deprecated(options?: DeprecationOptions): any;
117
+
118
+ /** Options accepted by the v0.x default export. */
119
+ interface LegacyConfig extends DeprecationOptions {
120
+ /** Name of the method to deprecate when the first argument is an object. */
121
+ method?: string;
122
+ }
123
+ /**
124
+ * v0.x API, kept so existing code keeps working.
125
+ * @deprecated Use `deprecate(fn, options)` or `deprecateMethod(obj, 'method', options)`.
126
+ */
127
+ declare function deprecated<T extends object>(obj: T, config?: LegacyConfig): T;
128
+
129
+ export { Deprecated, DeprecationError, configure, deprecated as default, deprecate, deprecateMethod, deprecateProperty, resetDeprecations };
130
+ export type { DeprecationEvent, DeprecationMode, DeprecationOptions, GlobalConfig, LegacyConfig, Logger };
package/dist/index.js ADDED
@@ -0,0 +1,336 @@
1
+ // src/env.ts
2
+ function getProcess() {
3
+ const proc = globalThis.process;
4
+ return proc && typeof proc === "object" ? proc : void 0;
5
+ }
6
+ var MODES = ["warn", "throw", "silent"];
7
+ function modeFromEnvironment() {
8
+ const proc = getProcess();
9
+ let fromEnv;
10
+ try {
11
+ fromEnv = proc?.env?.IS_DEPRECATED_MODE;
12
+ } catch {
13
+ }
14
+ const normalized = fromEnv?.trim().toLowerCase();
15
+ if (normalized && MODES.includes(normalized)) return normalized;
16
+ if (proc?.noDeprecation) return "silent";
17
+ if (proc?.throwDeprecation) return "throw";
18
+ return "warn";
19
+ }
20
+ function nodeEmitWarning() {
21
+ const emit = getProcess()?.emitWarning;
22
+ return typeof emit === "function" ? emit.bind(getProcess()) : void 0;
23
+ }
24
+
25
+ // src/core.ts
26
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("@mtorre4580/is-deprecated");
27
+ var store = globalThis;
28
+ var state = store[STATE_KEY] ?? (store[STATE_KEY] = { config: {}, seen: /* @__PURE__ */ new Set() });
29
+ function configure(config) {
30
+ Object.assign(state.config, config);
31
+ return { ...state.config };
32
+ }
33
+ function resetDeprecations() {
34
+ state.seen.clear();
35
+ }
36
+ var DeprecationError = class extends Error {
37
+ constructor(event) {
38
+ super(event.message);
39
+ this.name = "DeprecationError";
40
+ this.code = event.code;
41
+ this.event = event;
42
+ }
43
+ };
44
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}/;
45
+ function parseRemoval(removeIn) {
46
+ if (removeIn instanceof Date) {
47
+ return Number.isNaN(removeIn.getTime()) ? {} : { label: removeIn.toISOString().slice(0, 10), date: removeIn };
48
+ }
49
+ if (typeof removeIn !== "string" || removeIn === "") return {};
50
+ if (ISO_DATE.test(removeIn)) {
51
+ const date = new Date(removeIn);
52
+ if (!Number.isNaN(date.getTime())) return { label: removeIn, date };
53
+ }
54
+ return { label: removeIn };
55
+ }
56
+ function buildMessage(options, name, removal, overdue) {
57
+ let message = options.message;
58
+ if (message === void 0) {
59
+ message = `${name} is deprecated`;
60
+ if (options.since) message += ` since ${options.since}`;
61
+ if (removal.label) {
62
+ if (overdue) message += ` and was scheduled for removal on ${removal.label}`;
63
+ else message += ` and will be removed ${removal.date ? "on" : "in"} ${removal.label}`;
64
+ }
65
+ message += ".";
66
+ if (options.replacement) message += ` Use ${options.replacement} instead.`;
67
+ if (options.url) message += ` See ${options.url}`;
68
+ }
69
+ return overdue ? `[OVERDUE] ${message}` : message;
70
+ }
71
+ function captureCallSite(boundary) {
72
+ const E = Error;
73
+ const holder = {};
74
+ const previousLimit = E.stackTraceLimit;
75
+ let skip = 0;
76
+ try {
77
+ if (typeof previousLimit === "number") E.stackTraceLimit = 10;
78
+ if (typeof E.captureStackTrace === "function") {
79
+ E.captureStackTrace(holder, boundary);
80
+ } else {
81
+ holder.stack = new Error().stack;
82
+ skip = 3;
83
+ }
84
+ void holder.stack;
85
+ } catch {
86
+ return {};
87
+ } finally {
88
+ if (typeof previousLimit === "number") E.stackTraceLimit = previousLimit;
89
+ }
90
+ if (typeof holder.stack !== "string") return {};
91
+ const frames = holder.stack.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !/^Error\b/.test(line)).slice(skip);
92
+ if (frames.length === 0) return {};
93
+ return {
94
+ callSite: frames[0].replace(/^at\s+/, ""),
95
+ stack: frames.join("\n")
96
+ };
97
+ }
98
+ function safeCall(hook, event) {
99
+ if (typeof hook !== "function") return;
100
+ try {
101
+ hook(event);
102
+ } catch {
103
+ }
104
+ }
105
+ function notify(options, fallbackName, boundary) {
106
+ const { config } = state;
107
+ const mode = config.mode ?? modeFromEnvironment();
108
+ const name = options.name ?? fallbackName;
109
+ const { callSite, stack } = captureCallSite(boundary);
110
+ if (mode !== "throw" && (options.once ?? config.once ?? true)) {
111
+ const key = `${options.code ?? ""}|${name}|${callSite ?? ""}`;
112
+ if (state.seen.has(key)) return;
113
+ state.seen.add(key);
114
+ }
115
+ const removal = parseRemoval(options.removeIn);
116
+ const overdue = removal.date !== void 0 && removal.date.getTime() <= Date.now();
117
+ const message = buildMessage(options, name, removal, overdue);
118
+ const event = {
119
+ name,
120
+ message,
121
+ code: options.code,
122
+ since: options.since,
123
+ removeIn: removal.label,
124
+ replacement: options.replacement,
125
+ url: options.url,
126
+ overdue,
127
+ callSite,
128
+ stack,
129
+ mode
130
+ };
131
+ safeCall(config.onDeprecation, event);
132
+ safeCall(options.onDeprecation, event);
133
+ safeCall(options.sendMetrics, event);
134
+ if (mode === "silent") return;
135
+ if (mode === "throw") throw new DeprecationError(event);
136
+ const logger = options.logger ?? config.logger;
137
+ if (logger) {
138
+ if (overdue && typeof logger.error === "function") logger.error(message);
139
+ else logger.warn(message);
140
+ return;
141
+ }
142
+ const emitWarning = nodeEmitWarning();
143
+ if (emitWarning) {
144
+ emitWarning(message, {
145
+ type: "DeprecationWarning",
146
+ code: options.code,
147
+ detail: callSite ? `Called from ${callSite}` : void 0
148
+ });
149
+ return;
150
+ }
151
+ if (typeof console !== "undefined") {
152
+ console.warn(`DeprecationWarning: ${message}${callSite ? `
153
+ at ${callSite}` : ""}`);
154
+ }
155
+ }
156
+ function copyFunctionShape(target, source) {
157
+ for (const key of ["name", "length"]) {
158
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
159
+ if (descriptor) Object.defineProperty(target, key, { ...descriptor, configurable: true });
160
+ }
161
+ }
162
+ function deprecate(fn, options = {}) {
163
+ if (typeof fn !== "function") {
164
+ throw new TypeError("deprecate() expects a function or class as first argument");
165
+ }
166
+ const fallbackName = fn.name || "anonymous function";
167
+ const handler = {
168
+ apply(target, thisArg, args) {
169
+ notify(options, fallbackName, handler.apply);
170
+ return Reflect.apply(target, thisArg, args);
171
+ },
172
+ construct(target, args, newTarget) {
173
+ notify(options, fallbackName, handler.construct);
174
+ return Reflect.construct(target, args, newTarget);
175
+ }
176
+ };
177
+ return new Proxy(fn, handler);
178
+ }
179
+ function deprecateMethod(obj, methods, options = {}) {
180
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
181
+ throw new TypeError("deprecateMethod() expects an object as first argument");
182
+ }
183
+ const keys = Array.isArray(methods) ? methods : [methods];
184
+ if (keys.length === 0) throw new TypeError("deprecateMethod() expects at least one method name");
185
+ for (const key of keys) {
186
+ if (typeof obj[key] !== "function") {
187
+ throw new TypeError(`"${String(key)}" is not a method of the given object`);
188
+ }
189
+ }
190
+ const cache = /* @__PURE__ */ new Map();
191
+ const proxy = new Proxy(obj, {
192
+ get(target, property, receiver) {
193
+ const value = Reflect.get(target, property, receiver);
194
+ if (typeof value !== "function" || !keys.includes(property)) return value;
195
+ const original = value;
196
+ const cached = cache.get(property);
197
+ if (cached && cached.original === original) return cached.wrapped;
198
+ const methodOptions = keys.length === 1 ? options : { ...options, name: void 0 };
199
+ const wrapped = function deprecatedMethod(...args) {
200
+ notify(methodOptions, String(property), wrapped);
201
+ return Reflect.apply(original, this === proxy ? target : this, args);
202
+ };
203
+ copyFunctionShape(wrapped, original);
204
+ cache.set(property, { original, wrapped });
205
+ return wrapped;
206
+ }
207
+ });
208
+ return proxy;
209
+ }
210
+ function deprecateProperty(obj, property, options = {}) {
211
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
212
+ throw new TypeError("deprecateProperty() expects an object as first argument");
213
+ }
214
+ const descriptor = Object.getOwnPropertyDescriptor(obj, property);
215
+ if (!descriptor) throw new TypeError(`"${String(property)}" is not an own property of the given object`);
216
+ if (!descriptor.configurable) throw new TypeError(`"${String(property)}" is not configurable`);
217
+ const name = options.name ?? String(property);
218
+ const next = { configurable: true, enumerable: descriptor.enumerable };
219
+ if ("value" in descriptor || "writable" in descriptor) {
220
+ let current = descriptor.value;
221
+ next.get = function get() {
222
+ notify(options, name, get);
223
+ return current;
224
+ };
225
+ if (descriptor.writable) {
226
+ next.set = function set(value) {
227
+ notify(options, name, set);
228
+ current = value;
229
+ };
230
+ }
231
+ } else {
232
+ const { get: originalGet, set: originalSet } = descriptor;
233
+ if (originalGet) {
234
+ next.get = function get() {
235
+ notify(options, name, get);
236
+ return originalGet.call(this);
237
+ };
238
+ }
239
+ if (originalSet) {
240
+ next.set = function set(value) {
241
+ notify(options, name, set);
242
+ originalSet.call(this, value);
243
+ };
244
+ }
245
+ }
246
+ Object.defineProperty(obj, property, next);
247
+ return obj;
248
+ }
249
+
250
+ // src/decorator.ts
251
+ function isStandardContext(context) {
252
+ return typeof context === "object" && context !== null && "kind" in context;
253
+ }
254
+ function ownerName(instanceOrClass, isStatic) {
255
+ try {
256
+ const owner = isStatic ? instanceOrClass : instanceOrClass?.constructor;
257
+ return typeof owner?.name === "string" && owner.name !== "" ? owner.name : "Object";
258
+ } catch {
259
+ return "Object";
260
+ }
261
+ }
262
+ function wrapCallable(original, member, isStatic, suffix, options) {
263
+ const wrapped = function(...args) {
264
+ notify(options, `${ownerName(this, isStatic)}.${member}${suffix}`, wrapped);
265
+ return original.apply(this, args);
266
+ };
267
+ Object.defineProperty(wrapped, "name", { value: original.name, configurable: true });
268
+ return wrapped;
269
+ }
270
+ function fieldError() {
271
+ return new TypeError(
272
+ "@Deprecated() cannot intercept plain class fields: use an `accessor` field or a getter/setter"
273
+ );
274
+ }
275
+ function decorateStandard(value, context, options) {
276
+ const member = String(context.name);
277
+ const isStatic = Boolean(context.static);
278
+ switch (context.kind) {
279
+ case "class":
280
+ return deprecate(value, { ...options, name: options.name ?? (member || value.name) });
281
+ case "method":
282
+ return wrapCallable(value, member, isStatic, "()", options);
283
+ case "getter":
284
+ case "setter":
285
+ return wrapCallable(value, member, isStatic, "", options);
286
+ case "accessor": {
287
+ const get = function() {
288
+ notify(options, `${ownerName(this, isStatic)}.${member}`, get);
289
+ return value.get.call(this);
290
+ };
291
+ const set = function(next) {
292
+ notify(options, `${ownerName(this, isStatic)}.${member}`, set);
293
+ value.set.call(this, next);
294
+ };
295
+ return { get, set, init: (initial) => initial };
296
+ }
297
+ default:
298
+ throw fieldError();
299
+ }
300
+ }
301
+ function decorateLegacy(target, key, descriptor, options) {
302
+ if (key === void 0 && typeof target === "function") {
303
+ return deprecate(target, { ...options, name: options.name ?? target.name });
304
+ }
305
+ if (!descriptor) throw fieldError();
306
+ const member = String(key);
307
+ const isStatic = typeof target === "function";
308
+ const next = { ...descriptor };
309
+ if (typeof descriptor.value === "function") {
310
+ next.value = wrapCallable(descriptor.value, member, isStatic, "()", options);
311
+ }
312
+ if (descriptor.get) next.get = wrapCallable(descriptor.get, member, isStatic, "", options);
313
+ if (descriptor.set) next.set = wrapCallable(descriptor.set, member, isStatic, "", options);
314
+ return next;
315
+ }
316
+ function Deprecated(options = {}) {
317
+ return function deprecatedDecorator(value, context, descriptor) {
318
+ if (isStandardContext(context)) return decorateStandard(value, context, options);
319
+ return decorateLegacy(value, context, descriptor, options);
320
+ };
321
+ }
322
+
323
+ // src/index.ts
324
+ function deprecated(obj, config = {}) {
325
+ if (obj == null) throw new TypeError("The param obj is required");
326
+ const { method, ...options } = config;
327
+ if (typeof obj === "function" && method === void 0) {
328
+ return deprecate(obj, options);
329
+ }
330
+ if (method === void 0 || typeof obj[method] !== "function") {
331
+ throw new TypeError("The object not have the method defined");
332
+ }
333
+ return deprecateMethod(obj, method, options);
334
+ }
335
+
336
+ export { Deprecated, DeprecationError, configure, deprecated as default, deprecate, deprecateMethod, deprecateProperty, resetDeprecations };
package/package.json CHANGED
@@ -1,62 +1,95 @@
1
1
  {
2
2
  "name": "@mtorre4580/is-deprecated",
3
- "version": "0.0.2",
4
- "description": "Library to deprecated properties and methods in objects or functions",
3
+ "version": "1.0.0",
4
+ "description": "Runtime deprecation warnings for functions, methods, properties and classes: Node + browser, TypeScript, decorators, telemetry hooks and warn/throw/silent modes",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/mtorre4580/is-deprecated"
8
+ "url": "git+https://github.com/mtorre4580/is-deprecated.git"
9
9
  },
10
- "homepage": "https://github.com/mtorre4580/is-deprecated/blob/main/README.md",
10
+ "homepage": "https://github.com/mtorre4580/is-deprecated#readme",
11
11
  "bugs": {
12
12
  "url": "https://github.com/mtorre4580/is-deprecated/issues"
13
13
  },
14
14
  "keywords": [
15
15
  "deprecated",
16
- "javascript",
17
- "nodejs",
18
- "functions",
19
- "methods",
20
- "utility"
16
+ "deprecate",
17
+ "deprecation",
18
+ "depd",
19
+ "warning",
20
+ "decorator",
21
+ "typescript",
22
+ "browser",
23
+ "nodejs"
21
24
  ],
22
25
  "author": "mtorre4580",
26
+ "type": "module",
23
27
  "files": [
24
28
  "dist"
25
29
  ],
26
- "main": "./dist/is-deprecated.umd.js",
27
- "module": "./dist/is-deprecated.es.js",
30
+ "sideEffects": false,
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.js",
33
+ "types": "./dist/index.d.cts",
28
34
  "exports": {
29
35
  ".": {
30
- "import": "./dist/is-deprecated.es.js",
31
- "require": "./dist/is-deprecated.umd.js"
32
- }
36
+ "import": {
37
+ "types": "./dist/index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/index.d.cts",
42
+ "default": "./dist/index.cjs"
43
+ }
44
+ },
45
+ "./package.json": "./package.json"
33
46
  },
34
47
  "scripts": {
35
- "prepare": "husky install",
36
- "dev": "vite",
37
- "build": "vite build",
38
- "preview": "vite preview",
39
- "lint": "eslint '**/*.js'",
40
- "lint:fix": "eslint '**/*.js' --fix",
41
- "test": "jest --config jest.config.js",
42
- "test:coverage": "jest --config jest.config.js --coverage",
43
- "test:ci": "node --expose-gc ./node_modules/.bin/jest --ci --bail --runInBand --logHeapUsage --coverage",
44
- "update-changelog": "git fetch && auto-changelog",
45
- "audit": "yarn audit --groups 'dependencies devDependencies'"
48
+ "prepare": "husky",
49
+ "build": "tsup",
50
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
51
+ "lint": "eslint .",
52
+ "lint:fix": "eslint . --fix",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "test:coverage": "vitest run --coverage",
56
+ "check:package": "publint && attw --pack . --profile node16",
57
+ "size": "size-limit",
58
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run check:package",
59
+ "update-changelog": "git fetch && auto-changelog"
46
60
  },
47
61
  "devDependencies": {
48
- "@commitlint/cli": "^18.4.3",
49
- "@commitlint/config-conventional": "^18.4.3",
50
- "@types/jest": "^29.5.11",
51
- "@types/node": "^20.10.4",
52
- "auto-changelog": "^2.4.0",
53
- "eslint": "^8.56.0",
54
- "eslint-config-airbnb-base": "^15.0.0",
55
- "eslint-plugin-import": "^2.29.1",
56
- "husky": "^8.0.3",
57
- "jest": "^29.7.0",
58
- "vite": "^5.0.10"
62
+ "@arethetypeswrong/cli": "^0.18.5",
63
+ "@commitlint/cli": "^21.2.4",
64
+ "@commitlint/config-conventional": "^21.2.4",
65
+ "@eslint/js": "^10.0.1",
66
+ "@size-limit/preset-small-lib": "^14.3.0",
67
+ "@types/node": "^24.0.0",
68
+ "@vitest/coverage-v8": "^5.1.0",
69
+ "auto-changelog": "^2.5.0",
70
+ "eslint": "^10.15.0",
71
+ "happy-dom": "^21.0.4",
72
+ "husky": "^9.1.7",
73
+ "publint": "^0.3.25",
74
+ "size-limit": "^14.3.0",
75
+ "tsup": "^9.0.0",
76
+ "typescript": "~6.0.3",
77
+ "typescript-eslint": "^8.72.2",
78
+ "vitest": "^5.1.0"
59
79
  },
80
+ "size-limit": [
81
+ {
82
+ "name": "full bundle (ESM, gzip)",
83
+ "path": "dist/index.js",
84
+ "limit": "2.5 kB"
85
+ },
86
+ {
87
+ "name": "deprecate() only",
88
+ "path": "dist/index.js",
89
+ "import": "{ deprecate }",
90
+ "limit": "1.5 kB"
91
+ }
92
+ ],
60
93
  "auto-changelog": {
61
94
  "output": "CHANGELOG.md",
62
95
  "template": "update-changelog.hbs",
@@ -67,6 +100,10 @@
67
100
  "hideEmptyReleases": true
68
101
  },
69
102
  "engines": {
70
- "node": ">=16"
103
+ "node": ">=18"
104
+ },
105
+ "publishConfig": {
106
+ "access": "public",
107
+ "provenance": true
71
108
  }
72
109
  }
@@ -1,28 +0,0 @@
1
- var f = (e, r) => () => (r || e((r = { exports: {} }).exports, r), r.exports);
2
- var h = f((w, c) => {
3
- function u(e) {
4
- return typeof e == "function";
5
- }
6
- function a(e, r) {
7
- const {
8
- method: n = null,
9
- message: t = "This will be deprecated soon",
10
- logger: o = console,
11
- sendMetrics: i = () => {
12
- }
13
- } = r || {};
14
- if (e == null)
15
- throw new Error("The param obj is required");
16
- if (u(e))
17
- return o.warn(t), i(), (...s) => e(...s);
18
- if (!u(e[n]))
19
- throw new Error("The object not have the method defined");
20
- return new Proxy(e, {
21
- get(s, d) {
22
- return d === n && (o.warn(t), i()), Reflect.get(...arguments);
23
- }
24
- });
25
- }
26
- c.exports = a;
27
- });
28
- export default h();
@@ -1 +0,0 @@
1
- (function(n){typeof define=="function"&&define.amd?define(n):n()})(function(){"use strict";function n(e){return typeof e=="function"}function f(e,d){const{method:t=null,message:r="This will be deprecated soon",logger:i=console,sendMetrics:o=()=>{}}=d||{};if(e==null)throw new Error("The param obj is required");if(n(e))return i.warn(r),o(),(...u)=>e(...u);if(!n(e[t]))throw new Error("The object not have the method defined");return new Proxy(e,{get(u,c){return c===t&&(i.warn(r),o()),Reflect.get(...arguments)}})}module.exports=f});