@flighthq/easing 0.3.0-next.1315.1d4eb64 → 0.3.0-next.1392.f0bd0cc

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.
@@ -1,5 +1,6 @@
1
1
  export * from './createEasingSamples';
2
2
  export * from './easeBack';
3
+ export * from './enableEasingGuards';
3
4
  export * from './easeBounce';
4
5
  export * from './easeCircular';
5
6
  export * from './easeCombinators';
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
package/dist/contract.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './createEasingSamples';
2
2
  export * from './easeBack';
3
+ export * from './enableEasingGuards';
3
4
  export * from './easeBounce';
4
5
  export * from './easeCircular';
5
6
  export * from './easeCombinators';
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function disableEasingGuards(): void;
2
+ export declare function enableEasingGuards(): void;
3
+ //# sourceMappingURL=enableEasingGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableEasingGuards.d.ts","sourceRoot":"","sources":["../src/enableEasingGuards.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAWD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,25 @@
1
+ import { logOnce } from '@flighthq/log/contract';
2
+ import { LogLevel } from '@flighthq/types/contract';
3
+ import { setEasingStepsGuard } from './easeSteps';
4
+ // Uninstalls the guard installed by enableEasingGuards.
5
+ export function disableEasingGuards() {
6
+ setEasingStepsGuard(null);
7
+ }
8
+ // Installs the caller-facing easing guard (opt-in, dev-only). This package has one silent footgun:
9
+ // `easeSteps(count, 'jumpNone')` with `count < 2` leaves zero jumps, so the returned function divides by
10
+ // zero and hands back NaN for every input. Nothing throws and nothing logs — the NaN just propagates into
11
+ // whatever the easing drives, which is why a comment telling callers "pass count >= 2" was not enough.
12
+ //
13
+ // @flighthq/easing is a CORE package, and core may otherwise depend only on types/core. This module is the
14
+ // sanctioned exception: it is separately importable and shakeable, so a build that never imports it never
15
+ // pulls @flighthq/log, and the layer rule's actual concern — feature weight in core's always-loaded graph —
16
+ // does not arise. `packages:check` enforces that the import appears in guard modules only. Idempotent.
17
+ export function enableEasingGuards() {
18
+ setEasingStepsGuard(warnOnDegenerateSteps);
19
+ }
20
+ function warnOnDegenerateSteps(count, position) {
21
+ logOnce('easing:degenerate-steps', LogLevel.Warn, {
22
+ message: `easeSteps(${count}, '${position}'): '${position}' has count - 1 jumps, so it needs count >= 2. With ${count} there are no jumps, the easing divides by zero, and every input returns NaN. Pass count >= 2, or use a position that jumps at an edge ('jumpEnd', 'jumpStart', 'jumpBoth') — those are defined for count >= 1.`,
23
+ }, 'easing');
24
+ }
25
+ //# sourceMappingURL=enableEasingGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableEasingGuards.js","sourceRoot":"","sources":["../src/enableEasingGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,wDAAwD;AACxD,MAAM,UAAU,mBAAmB;IACjC,mBAAmB,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,mGAAmG;AACnG,yGAAyG;AACzG,0GAA0G;AAC1G,uGAAuG;AACvG,EAAE;AACF,2GAA2G;AAC3G,0GAA0G;AAC1G,4GAA4G;AAC5G,uGAAuG;AACvG,MAAM,UAAU,kBAAkB;IAChC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,QAAsB;IAClE,OAAO,CACL,yBAAyB,EACzB,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EAAE,aAAa,KAAK,MAAM,QAAQ,QAAQ,QAAQ,uDAAuD,KAAK,iNAAiN;KACvU,EACD,QAAQ,CACT,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/easing",
3
- "version": "0.3.0-next.1315.1d4eb64",
3
+ "version": "0.3.0-next.1392.f0bd0cc",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -36,7 +36,8 @@
36
36
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@flighthq/types": "0.3.0-next.1315.1d4eb64"
39
+ "@flighthq/log": "0.3.0-next.1392.f0bd0cc",
40
+ "@flighthq/types": "0.3.0-next.1392.f0bd0cc"
40
41
  },
41
42
  "devDependencies": {
42
43
  "typescript": "^5.3.0"
@@ -1,3 +1,5 @@
1
+ import type { StepPosition } from '@flighthq/types/contract';
2
+
1
3
  import { easeSteps, setEasingStepsGuard } from './easeSteps';
2
4
 
3
5
  describe('easeSteps', () => {
@@ -65,34 +67,31 @@ describe('easeSteps', () => {
65
67
  });
66
68
 
67
69
  describe('setEasingStepsGuard', () => {
68
- it('invokes the installed guard for the degenerate jumpNone, count<2 call', () => {
69
- const calls: unknown[] = [];
70
- setEasingStepsGuard((count, position) => calls.push([count, position]));
70
+ it('reports the degenerate jumpNone call that silently returns NaN', () => {
71
+ const seen: [number, StepPosition][] = [];
72
+ setEasingStepsGuard((count, position) => seen.push([count, position]));
71
73
  try {
72
- easeSteps(1, 'jumpNone');
73
- expect(calls).toEqual([[1, 'jumpNone']]);
74
+ // The failure being reported: one step with no edge jump leaves zero jumps to divide by.
75
+ expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
74
76
  } finally {
75
77
  setEasingStepsGuard(null);
76
78
  }
79
+ expect(seen).toEqual([[1, 'jumpNone']]);
77
80
  });
78
81
 
79
- it('does not invoke the guard for well-defined calls', () => {
80
- const calls: unknown[] = [];
81
- setEasingStepsGuard((count, position) => calls.push([count, position]));
82
+ it('stays silent for well-defined calls, and once uninstalled', () => {
83
+ const seen: number[] = [];
84
+ setEasingStepsGuard((count) => seen.push(count));
82
85
  try {
83
- easeSteps(4, 'jumpNone');
84
- easeSteps(1, 'jumpEnd');
85
- expect(calls).toEqual([]);
86
+ easeSteps(2, 'jumpNone')(0.5); // count >= 2 is the documented minimum for jumpNone
87
+ easeSteps(1, 'jumpEnd')(0.5); // edge-jumping positions are defined for count >= 1
88
+ easeSteps(1, 'jumpBoth')(0.5);
86
89
  } finally {
87
90
  setEasingStepsGuard(null);
88
91
  }
89
- });
90
-
91
- it('stops invoking a previously installed guard once set back to null', () => {
92
- const calls: unknown[] = [];
93
- setEasingStepsGuard((count, position) => calls.push([count, position]));
94
- setEasingStepsGuard(null);
95
- easeSteps(1, 'jumpNone');
96
- expect(calls).toEqual([]);
92
+ expect(seen).toEqual([]);
93
+ // Uninstalled: the degenerate call still returns NaN, but nothing is reported.
94
+ easeSteps(1, 'jumpNone')(0.5);
95
+ expect(seen).toEqual([]);
97
96
  });
98
97
  });
@@ -0,0 +1,70 @@
1
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
2
+ import type { LogEntry } from '@flighthq/types/contract';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { easeSteps } from './easeSteps';
6
+ import { disableEasingGuards, enableEasingGuards } from './enableEasingGuards';
7
+
8
+ function captureLog(run: () => void): readonly LogEntry[] {
9
+ const sink = createMemoryLogSink(8);
10
+ addLogSink(sink.sink);
11
+ try {
12
+ run();
13
+ return getMemoryLogSinkEntries(sink);
14
+ } finally {
15
+ removeLogSink(sink.sink);
16
+ }
17
+ }
18
+
19
+ function messageOf(entry: Readonly<LogEntry>): string {
20
+ const data = entry.data;
21
+ return typeof data === 'string' ? data : String(data.message);
22
+ }
23
+
24
+ describe('disableEasingGuards', () => {
25
+ it('uninstalls the guard, returning the degenerate call to silence', () => {
26
+ const entries = captureLog(() => {
27
+ enableEasingGuards();
28
+ disableEasingGuards();
29
+ // Still NaN — disabling removes the reporting, not the sharp edge.
30
+ expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
31
+ });
32
+ expect(entries.length).toBe(0);
33
+ });
34
+ });
35
+
36
+ describe('enableEasingGuards', () => {
37
+ it('WARNS on the degenerate jumpNone step count that silently returns NaN', () => {
38
+ const entries = captureLog(() => {
39
+ enableEasingGuards();
40
+ try {
41
+ expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
42
+ } finally {
43
+ disableEasingGuards();
44
+ }
45
+ });
46
+ expect(entries.length).toBe(1);
47
+ expect(messageOf(entries[0])).toContain('count >= 2');
48
+ });
49
+
50
+ it('stays SILENT for well-defined calls, and without the guard at all', () => {
51
+ const guarded = captureLog(() => {
52
+ enableEasingGuards();
53
+ try {
54
+ easeSteps(2, 'jumpNone')(0.5);
55
+ easeSteps(1, 'jumpEnd')(0.5);
56
+ easeSteps(1, 'jumpStart')(0.5);
57
+ easeSteps(1, 'jumpBoth')(0.5);
58
+ } finally {
59
+ disableEasingGuards();
60
+ }
61
+ });
62
+ expect(guarded.length).toBe(0);
63
+
64
+ // Production default: still NaN, but nothing is logged — the guard is opt-in.
65
+ const unguarded = captureLog(() => {
66
+ expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
67
+ });
68
+ expect(unguarded.length).toBe(0);
69
+ });
70
+ });
@@ -1,34 +0,0 @@
1
- import type { StepPosition } from '@flighthq/types/contract';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import { easeSteps, setEasingStepsGuard } from './easeSteps';
5
-
6
- describe('setEasingStepsGuard', () => {
7
- it('reports the degenerate jumpNone call that silently returns NaN', () => {
8
- const seen: [number, StepPosition][] = [];
9
- setEasingStepsGuard((count, position) => seen.push([count, position]));
10
- try {
11
- // The failure being reported: one step with no edge jump leaves zero jumps to divide by.
12
- expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
13
- } finally {
14
- setEasingStepsGuard(null);
15
- }
16
- expect(seen).toEqual([[1, 'jumpNone']]);
17
- });
18
-
19
- it('stays silent for well-defined calls, and when no guard is installed', () => {
20
- const seen: number[] = [];
21
- setEasingStepsGuard((count) => seen.push(count));
22
- try {
23
- easeSteps(2, 'jumpNone')(0.5); // count >= 2 is the documented minimum for jumpNone
24
- easeSteps(1, 'jumpEnd')(0.5); // every edge-jumping position is defined for count >= 1
25
- easeSteps(1, 'jumpStart')(0.5);
26
- easeSteps(1, 'jumpBoth')(0.5);
27
- } finally {
28
- setEasingStepsGuard(null);
29
- }
30
- expect(seen).toEqual([]);
31
- // Production default: still NaN, but nothing is reported.
32
- expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
33
- });
34
- });