lambder 4.2.1 → 4.2.3

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
@@ -4,10 +4,10 @@ Lambder is a highly opinionated dynamic serverless framework designed to facilit
4
4
 
5
5
  **New in 4.2:**
6
6
 
7
- - **Rate-limit budgets are explicit**: every policy declares `budget: "perApi"` (each referencing API gets its own counter, so the numbers are a per-API ceiling) or `budget: "perPolicy"` (one counter shared by every API referencing the policy). There is no default, so a declaration always says what its numbers span; the policy is the group, and two separate shared budgets are two policies.
7
+ - **Rate-limit budgets**: a policy's `budget` is `"perApi"` (default: each referencing API gets its own counter, so the numbers are a per-API ceiling and three APIs on a 60/min policy allow one IP 180/min in total) or `"perPolicy"` (one counter shared by every API referencing the policy). The policy is the group, and two separate shared budgets are two policies.
8
8
  - **Per-API tuning**: the `rateLimit` option gained a map form like guards, `rateLimit: { lookupPerIp: { perMin: 20 } }`, which merges window overrides over a perApi policy's own (a tighter burst keeps the policy's daily cap). Overriding the windows of a perPolicy policy is a startup error; `errorMessage` is overridable on either.
9
9
  - **Retry-After**: a 429 carries the exceeded window's reset as a `Retry-After` header (CORS exposes it by default via the new `exposeHeaders` option), `LambderCaller` failure outcomes surface it as `retryAfterSeconds`, `LambderDdbRateLimiter.isRateLimited()` answers `false | { window, limit, resetAt }`, and `LambderApiError`/`refuse()` accept `headers`.
10
- - **One refusal shape**: every refusal the framework itself authors (rate limit 429, idempotency 409 and 400, unknown API) is a `LambderRefusalMessage` (`{ type, content }`), and a policy's `errorMessage` is typed as one, so an `errorMessageHandler` reading `.content` works everywhere.
10
+ - **One refusal shape, with codes**: `LambderRefusalMessage` gained an optional machine-readable `code` (`refuse(content, { code })`), so clients branch and translate on an identifier instead of string-matching prose. Every refusal the framework itself authors (rate limit 429, idempotency 409 and 400, unknown API) is a `LambderRefusalMessage` stamped with a `LAMBDER_REFUSAL_CODES` constant under the reserved `lambder/` prefix; a rate-limit policy's own `errorMessage` (typed as a refusal message) inherits `lambder/rate-limited` unless it sets a code.
11
11
  - **One validation path**: preflight slices (guard `apiInput`/`guardInput`, rate-limit `apiInput` keys) answer through `setApiInputValidationErrorHandler` exactly like the API's own schema.
12
12
 
13
13
  **New in v4:**
@@ -479,7 +479,7 @@ Responses are finalized once at the end of the request: automatic gzip (when the
479
479
 
480
480
  ### Typed API Refusals (refuse / LambderApiError)
481
481
 
482
- A refusal ("you are not allowed", "quota exceeded") is not a crash. `res.die.*` covers refusals where you hold the resolver, but shared helpers (permission checks, validators) usually don't. The one-liner for the common case is `refuse()`: callable from anywhere in an API call's stack, it throws a typed refusal carrying the standard `LambderRefusalMessage` shape (`{ type, title?, content }`) that the pipeline maps onto the envelope's `errorMessage`, so refusals never pollute crash logging and clients get a parseable response:
482
+ A refusal ("you are not allowed", "quota exceeded") is not a crash. `res.die.*` covers refusals where you hold the resolver, but shared helpers (permission checks, validators) usually don't. The one-liner for the common case is `refuse()`: callable from anywhere in an API call's stack, it throws a typed refusal carrying the standard `LambderRefusalMessage` shape (`{ type, code?, title?, content }`) that the pipeline maps onto the envelope's `errorMessage`, so refusals never pollute crash logging and clients get a parseable response:
483
483
 
484
484
  ```typescript
485
485
  import { refuse } from "lambder";
@@ -487,9 +487,12 @@ import { refuse } from "lambder";
487
487
  if (!row) refuse("Record not found."); // { type: "warning", content }
488
488
  if (!isAdmin) refuse("Admins only.", { notAuthorized: true }); // + envelope flag
489
489
  refuse("Too many attempts.", { type: "error", statusCode: 429 }); // custom rendering intent + status
490
+ if (exists) refuse("Already reported.", { code: "ALREADY_REPORTED" }); // + machine-readable identity
490
491
  // TypeScript applies never-return narrowing: after `if (!row) refuse(...)`, row is defined.
491
492
  ```
492
493
 
494
+ `code` is the refusal's identity for machines: clients branch and translate on it (a translated client never displays `content`, it looks the code up), and `content` stays the human-readable fallback for codes a client does not know yet. Keep your app's codes as one typed vocabulary in shared code. The framework stamps the refusals it authors itself with `LAMBDER_REFUSAL_CODES` (exported from `lambder` and `lambder/client`) under the reserved `lambder/` prefix, so app codes never collide: `rateLimited`, `duplicateInFlight`, `invalidIdempotencyKey`, `apiNotFound`. A rate-limit policy's own `errorMessage` inherits `lambder/rate-limited` unless it sets a code, so an `errorMessageHandler` can treat every rate limit alike and still special-case the ones you name.
495
+
493
496
  For full control of the errorMessage payload (apps with their own message vocabulary), throw `LambderApiError` directly; `refuse()` is sugar over it:
494
497
 
495
498
  ```typescript
@@ -521,16 +524,16 @@ const lambder = initLambder<SessionData>().create({
521
524
  apiPath: "/api",
522
525
  // 1. Rate limiting: your limiter instance + named policies. Each policy
523
526
  // declares its windows, what one counter tracks ("per"), and what one
524
- // budget spans ("budget", required so the numbers are never ambiguous):
525
- // "perApi" gives every referencing API its own counter (three APIs on a
526
- // 60/min policy allow one IP 180/min in total), "perPolicy" makes every
527
- // referencing API share ONE counter. The policy IS the group: separate
528
- // shared budgets for, say, user APIs and report APIs are two policies.
527
+ // budget spans ("budget"): "perApi" (default) gives every referencing
528
+ // API its own counter, so three APIs on a 60/min policy allow one IP
529
+ // 180/min in total; "perPolicy" makes every referencing API share ONE
530
+ // counter. The policy IS the group: separate shared budgets for, say,
531
+ // user APIs and report APIs are two policies.
529
532
  rateLimits: {
530
533
  limiter: new LambderDdbRateLimiter({ tableName: "app-rate-limiter", region: "us-east-1", failOpen: true }),
531
534
  policies: {
532
- authPerIp: { perMin: 5, perHour: 30, per: "ip", budget: "perApi" },
533
- writePerUser: { perMin: 30, per: "session", budget: "perApi" }, // only referable from addSessionApi (also enforced at compile time)
535
+ authPerIp: { perMin: 5, perHour: 30, per: "ip" },
536
+ writePerUser: { perMin: 30, per: "session" }, // only referable from addSessionApi (also enforced at compile time)
534
537
  codePerEmail: {
535
538
  perMin: 3,
536
539
  // ONE combined budget across every API that references this
package/dist/client.d.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  */
9
9
  export { default as LambderCaller } from "./client/LambderCaller.js";
10
10
  export type { LambderApiOutcome, LambderApiFailureReason, LambderCallOptions, LambderIdempotencyKeyScope, } from "./client/LambderCaller.js";
11
- export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
12
- export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefuseOptions } from "./shared/LambderApiError.js";
11
+ export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
12
+ export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/LambderApiError.js";
13
13
  export type { ApiContractShape, LambderApiResponse, LambderApiResponseConfig } from "./shared/LambderApiContract.js";
14
14
  export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
15
15
  export { createLambderI18n } from "./shared/LambderI18n.js";
package/dist/client.js CHANGED
@@ -10,7 +10,7 @@
10
10
  export { default as LambderCaller } from "./client/LambderCaller.js";
11
11
  // Typed API refusals (isomorphic: shared code may throw them from anywhere;
12
12
  // in the browser they are plain Errors).
13
- export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
13
+ export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
14
14
  // Type-safe templating (tagged templates with auto-escaping)
15
15
  export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
16
16
  // Typed translations (standalone, isomorphic)
@@ -6,7 +6,7 @@ import { applyCorsHeaders } from "./LambderCors.js";
6
6
  import LambderSessionManager from "../session/LambderSessionManager.js";
7
7
  import LambderSessionController from "../session/LambderSessionController.js";
8
8
  import { LambderPublicFilesHandler } from "./LambderPublicFiles.js";
9
- import { isLambderApiError } from "../shared/LambderApiError.js";
9
+ import { isLambderApiError, LAMBDER_REFUSAL_CODES } from "../shared/LambderApiError.js";
10
10
  import { LambderApiPolicyEngine } from "../policies/LambderApiPolicies.js";
11
11
  import { createContext, isV2HttpEvent } from "./LambderContext.js";
12
12
  /**
@@ -423,7 +423,7 @@ export default class Lambder {
423
423
  if (isAPI) {
424
424
  if (this.apiFallbackHandler)
425
425
  return await this.apiFallbackHandler(ctx, resolver);
426
- return resolver.api(null, { errorMessage: { type: "warning", content: "API not found." } });
426
+ return resolver.api(null, { errorMessage: { type: "warning", code: LAMBDER_REFUSAL_CODES.apiNotFound, content: "API not found." } });
427
427
  }
428
428
  if (this.publicFilesHandler) {
429
429
  const fileResponse = await this.publicFilesHandler.handle(ctx);
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export default Lambder;
3
3
  export { initLambder } from './core/Lambder.js';
4
4
  export { default as LambderCaller } from "./client/LambderCaller.js";
5
5
  export type { LambderApiOutcome, LambderApiFailureReason, LambderCallOptions, LambderIdempotencyKeyScope } from "./client/LambderCaller.js";
6
- export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
7
- export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefuseOptions } from "./shared/LambderApiError.js";
6
+ export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
7
+ export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/LambderApiError.js";
8
8
  export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
9
9
  export { default as LambderResolver } from "./core/LambderResolver.js";
10
10
  export { default as LambderSessionManager } from "./session/LambderSessionManager.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ export default Lambder;
3
3
  export { initLambder } from './core/Lambder.js';
4
4
  export { default as LambderCaller } from "./client/LambderCaller.js";
5
5
  // Typed API refusals (isomorphic: shared code may throw them from anywhere)
6
- export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
6
+ export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
7
7
  export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
8
8
  export { default as LambderResolver } from "./core/LambderResolver.js";
9
9
  export { default as LambderSessionManager } from "./session/LambderSessionManager.js";
@@ -1,4 +1,4 @@
1
- import { LambderApiError } from "../shared/LambderApiError.js";
1
+ import { LambderApiError, LAMBDER_REFUSAL_CODES } from "../shared/LambderApiError.js";
2
2
  import { LambderResponse, normalizeHeaders } from "../core/LambderResponse.js";
3
3
  /** A crashed original must not block retries forever: pending claims expire on their own. */
4
4
  const IDEMPOTENCY_PENDING_TTL_SECONDS = 300;
@@ -40,7 +40,7 @@ export class LambderApiIdempotencyEngine {
40
40
  const content = `Invalid idempotency key: must be a string of ${IDEMPOTENCY_MIN_KEY_LENGTH}-${IDEMPOTENCY_MAX_KEY_LENGTH} characters.`;
41
41
  throw new LambderApiError(content, {
42
42
  statusCode: 400,
43
- errorMessage: { type: "error", content },
43
+ errorMessage: { type: "error", code: LAMBDER_REFUSAL_CODES.invalidIdempotencyKey, content },
44
44
  });
45
45
  }
46
46
  return rawKey;
@@ -116,7 +116,7 @@ export class LambderApiIdempotencyEngine {
116
116
  if (begun.state === "pending") {
117
117
  throw new LambderApiError(`Duplicate request for "${apiName}": the original is still processing.`, {
118
118
  statusCode: 409,
119
- errorMessage: { type: "warning", content: "This request is already being processed." },
119
+ errorMessage: { type: "warning", code: LAMBDER_REFUSAL_CODES.duplicateInFlight, content: "This request is already being processed." },
120
120
  });
121
121
  }
122
122
  if (begun.state === "done") {
@@ -42,13 +42,12 @@ export declare function lambderRateLimitKey(key: {
42
42
  /** What one rate-limit counter tracks: the client IP, the session identity, or a custom payload-derived key. */
43
43
  export type LambderRateLimitPer = "ip" | "session" | LambderRateLimitKeyFn<any>;
44
44
  /**
45
- * What one budget spans. Required on every policy, so a declaration always
46
- * says what its numbers mean:
45
+ * What one budget spans:
47
46
  *
48
- * - "perApi": every API referencing the policy gets its own counter, so the
49
- * windows are a per-API ceiling (three APIs referencing a 60/min policy
50
- * allow one subject 180/min in total). An API may tune the windows in its
51
- * declaration: `rateLimit: { name: { perMin: 20 } }`.
47
+ * - "perApi" (default): every API referencing the policy gets its own
48
+ * counter, so the windows are a per-API ceiling (three APIs referencing a
49
+ * 60/min policy allow one subject 180/min in total). An API may tune the
50
+ * windows in its declaration: `rateLimit: { name: { perMin: 20 } }`.
52
51
  * - "perPolicy": every API referencing the policy shares ONE counter, so the
53
52
  * windows are one combined budget (e.g. one per-email allowance across
54
53
  * send, register, and reset). The policy IS the group: to give user APIs
@@ -58,9 +57,9 @@ export type LambderRateLimitBudget = "perApi" | "perPolicy";
58
57
  /** A named rate-limit policy: fixed windows, the key one counter tracks, and what one budget spans. */
59
58
  export type LambderApiRateLimitPolicyConfig = LambderRateLimitPolicy & {
60
59
  per: LambderRateLimitPer;
61
- /** Whether the windows are a per-API ceiling or one budget shared by every referencing API. See LambderRateLimitBudget. */
62
- budget: LambderRateLimitBudget;
63
- /** Envelope errorMessage for refused requests. Default: a warning saying too many requests. */
60
+ /** Whether the windows are a per-API ceiling (default) or one budget shared by every referencing API. See LambderRateLimitBudget. */
61
+ budget?: LambderRateLimitBudget;
62
+ /** Envelope errorMessage for refused requests; inherits code "lambder/rate-limited" unless it sets its own. Default: a warning saying too many requests. */
64
63
  errorMessage?: LambderRefusalMessage;
65
64
  };
66
65
  export type LambderApiRateLimitsConfig<TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>> = {
@@ -94,8 +93,8 @@ export type LambderRateLimitOverride = LambderRateLimitPolicy & {
94
93
  errorMessage?: LambderRefusalMessage;
95
94
  };
96
95
  type LambderRateLimitOverrideFor<TPolicy> = TPolicy extends {
97
- budget: "perApi";
98
- } ? LambderRateLimitOverride : Pick<LambderRateLimitOverride, "errorMessage">;
96
+ budget: "perPolicy";
97
+ } ? Pick<LambderRateLimitOverride, "errorMessage"> : LambderRateLimitOverride;
99
98
  /**
100
99
  * The per-API `rateLimit` option: one policy name, an ordered list of names,
101
100
  * or an object map that can carry each policy's override (`true` applies the
@@ -1,9 +1,9 @@
1
1
  import { RATE_LIMIT_WINDOWS } from "../stores/LambderDdbRateLimiter.js";
2
- import { LambderApiError } from "../shared/LambderApiError.js";
2
+ import { LambderApiError, LAMBDER_REFUSAL_CODES } from "../shared/LambderApiError.js";
3
3
  import { parsePreflightSlice } from "./LambderApiGuards.js";
4
4
  const RATE_LIMIT_WINDOW_KEYS = RATE_LIMIT_WINDOWS.map((window) => window.key);
5
5
  /** Refusal a rate-limited request answers unless the policy or the API's override names its own. */
6
- const DEFAULT_RATE_LIMIT_REFUSAL = { type: "warning", content: "Too many requests. Please try again later." };
6
+ const DEFAULT_RATE_LIMIT_REFUSAL = { type: "warning", code: LAMBDER_REFUSAL_CODES.rateLimited, content: "Too many requests. Please try again later." };
7
7
  export function lambderRateLimitKey(key) { return key; }
8
8
  /** Normalize the three rateLimit-option forms into ordered entries; an explicit `undefined` map value declares nothing. */
9
9
  const toRateLimitEntries = (value) => {
@@ -41,8 +41,8 @@ export class LambderApiRateLimitsEngine {
41
41
  throw new Error(`Lambder: rate-limit policy "${name}" declares no window (${RATE_LIMIT_WINDOW_KEYS.join("/")}).`);
42
42
  }
43
43
  const budget = policy.budget;
44
- if (budget !== "perApi" && budget !== "perPolicy") {
45
- throw new Error(`Lambder: rate-limit policy "${name}" needs budget: "perApi" (each referencing API counts separately) or "perPolicy" (one counter shared by every referencing API).`);
44
+ if (budget !== undefined && budget !== "perApi" && budget !== "perPolicy") {
45
+ throw new Error(`Lambder: rate-limit policy "${name}" has budget "${String(budget)}"; use "perApi" (default: each referencing API counts separately) or "perPolicy" (one counter shared by every referencing API).`);
46
46
  }
47
47
  }
48
48
  this.limiter = config.limiter;
@@ -91,8 +91,11 @@ export class LambderApiRateLimitsEngine {
91
91
  const exceeded = await this.limiter.isRateLimited(trackerKey, limits);
92
92
  if (exceeded) {
93
93
  const retryAfterSeconds = Math.max(1, exceeded.resetAt - Math.floor(Date.now() / 1000));
94
+ // A policy's (or override's) own message inherits the framework
95
+ // code unless it sets a more specific one of its own.
96
+ const message = override?.errorMessage ?? policy.errorMessage;
94
97
  throw new LambderApiError(`Rate limited: "${apiName}" exceeded policy "${name}" (${exceeded.window}: ${exceeded.limit}).`, {
95
- errorMessage: override?.errorMessage ?? policy.errorMessage ?? DEFAULT_RATE_LIMIT_REFUSAL,
98
+ errorMessage: message ? { code: LAMBDER_REFUSAL_CODES.rateLimited, ...message } : DEFAULT_RATE_LIMIT_REFUSAL,
96
99
  statusCode: 429,
97
100
  headers: { "Retry-After": String(retryAfterSeconds) },
98
101
  });
@@ -57,17 +57,43 @@ export declare class LambderApiError extends Error {
57
57
  export declare const isLambderApiError: (err: unknown) => err is LambderApiError;
58
58
  /**
59
59
  * The standard shape refusals carry on the envelope's errorMessage field.
60
- * The caller's errorMessageHandler receives it as-is; apps with their own
61
- * errorMessage vocabulary can keep using LambderApiError directly instead.
60
+ * `code` is the refusal's machine-readable identity: clients branch and
61
+ * translate on it and never string-match `content`, which stays the
62
+ * human-readable fallback for codes a client does not know yet. Apps keep
63
+ * their own typed code vocabulary; the framework's own refusals carry a
64
+ * LambderRefusalCode. The caller's errorMessageHandler receives the object
65
+ * as-is; apps with their own errorMessage vocabulary can keep using
66
+ * LambderApiError directly instead.
62
67
  */
63
68
  export type LambderRefusalMessage = {
64
69
  type: "warning" | "error" | "info";
70
+ /** Machine-readable identity of the refusal (the app's own vocabulary, or a LambderRefusalCode). */
71
+ code?: string;
65
72
  title?: string;
66
73
  content: string;
67
74
  };
75
+ /**
76
+ * Codes the framework stamps on the refusals it authors itself, under the
77
+ * reserved `lambder/` prefix so app codes never collide. Compare against
78
+ * these constants on the client (exported from `lambder/client` too) rather
79
+ * than retyping the strings.
80
+ */
81
+ export declare const LAMBDER_REFUSAL_CODES: {
82
+ /** A rate-limit policy refused (429). A policy's own errorMessage inherits this unless it sets a code. */
83
+ readonly rateLimited: "lambder/rate-limited";
84
+ /** The original of an idempotent request is still processing (409). */
85
+ readonly duplicateInFlight: "lambder/duplicate-in-flight";
86
+ /** The idempotencyKey is malformed (400). */
87
+ readonly invalidIdempotencyKey: "lambder/invalid-idempotency-key";
88
+ /** No API is registered under the requested name. */
89
+ readonly apiNotFound: "lambder/api-not-found";
90
+ };
91
+ export type LambderRefusalCode = (typeof LAMBDER_REFUSAL_CODES)[keyof typeof LAMBDER_REFUSAL_CODES];
68
92
  export type LambderRefuseOptions = {
69
93
  /** Rendering intent for the client's errorMessageHandler. Default: "warning". */
70
94
  type?: LambderRefusalMessage["type"];
95
+ /** Machine-readable identity of the refusal, for clients to branch and translate on. */
96
+ code?: string;
71
97
  /** Optional heading shown above the content. */
72
98
  title?: string;
73
99
  /** Sets the envelope's notAuthorized flag (routed to the caller's notAuthorizedHandler). */
@@ -38,6 +38,22 @@ export class LambderApiError extends Error {
38
38
  }
39
39
  /** Brand-based type guard (see LambderApiError.isLambderApiError). */
40
40
  export const isLambderApiError = (err) => err instanceof Error && err.isLambderApiError === true;
41
+ /**
42
+ * Codes the framework stamps on the refusals it authors itself, under the
43
+ * reserved `lambder/` prefix so app codes never collide. Compare against
44
+ * these constants on the client (exported from `lambder/client` too) rather
45
+ * than retyping the strings.
46
+ */
47
+ export const LAMBDER_REFUSAL_CODES = {
48
+ /** A rate-limit policy refused (429). A policy's own errorMessage inherits this unless it sets a code. */
49
+ rateLimited: "lambder/rate-limited",
50
+ /** The original of an idempotent request is still processing (409). */
51
+ duplicateInFlight: "lambder/duplicate-in-flight",
52
+ /** The idempotencyKey is malformed (400). */
53
+ invalidIdempotencyKey: "lambder/invalid-idempotency-key",
54
+ /** No API is registered under the requested name. */
55
+ apiNotFound: "lambder/api-not-found",
56
+ };
41
57
  /**
42
58
  * Refuse the current API call: a routine business "no" (not found, invalid
43
59
  * input, not allowed) with a user-facing message. Throws a LambderApiError
@@ -54,6 +70,7 @@ export const refuse = (content, options = {}) => {
54
70
  throw new LambderApiError(content, {
55
71
  errorMessage: {
56
72
  type: options.type ?? "warning",
73
+ ...(options.code !== undefined ? { code: options.code } : {}),
57
74
  ...(options.title !== undefined ? { title: options.title } : {}),
58
75
  content,
59
76
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",