Skip to content

Commit c991efd

Browse files
committed
Add Reset feature, update get to return undefined instead of null
1 parent eeed895 commit c991efd

File tree

6 files changed

+174
-49
lines changed

6 files changed

+174
-49
lines changed

mod.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
export { RESET } from "./src/constants.ts";
2+
export {
3+
InvalidStackSubClassErreur,
4+
MissingContextErreur,
5+
} from "./src/erreur.ts";
6+
export type {
7+
InvalidStackSubClassErreurData,
8+
MissingContextErreurData,
9+
} from "./src/erreur.ts";
110
export * from "./src/Key.ts";
211
export { Stack } from "./src/Stack.ts";
312
export { StackCore } from "./src/StackCore.ts";
413
export type { TStackCoreTuple, TStackCoreValue } from "./src/StackCore.ts";
5-
export { MissingContextErreur } from "./src/erreur.ts";

src/Key.ts

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { INTERNAL } from "./constants.ts";
1+
import { INTERNAL, RESET } from "./constants.ts";
22

33
/**
44
* A function that stringify a value.
55
* This should return a single line that represent the value.
66
*/
77
export type TStringify<T> = (value: T) => string;
88

9+
/**
10+
* The object used to read a value from the stack.
11+
*
12+
* ```ts
13+
* stack.get(MyKey.Consumer)
14+
* ```
15+
*/
916
export interface TKeyConsumer<T, HasDefault extends boolean = boolean> {
1017
readonly [INTERNAL]: true;
1118
readonly stringify: TStringify<T>;
@@ -14,6 +21,14 @@ export interface TKeyConsumer<T, HasDefault extends boolean = boolean> {
1421
readonly defaultValue: T | undefined;
1522
}
1623

24+
/**
25+
* An object that can be used to set a value in the stack.
26+
* Use `MyKey.Provider(value)` to create it.
27+
*
28+
* ```ts
29+
* stack.with(MyKey.Provider(value))
30+
* ```
31+
*/
1732
export interface TKeyProvider<T, HasDefault extends boolean = boolean> {
1833
readonly [INTERNAL]: true;
1934
readonly name: string;
@@ -23,78 +38,120 @@ export interface TKeyProvider<T, HasDefault extends boolean = boolean> {
2338

2439
export type TArgsBase = readonly unknown[];
2540

41+
/**
42+
* A function that create a TKeyProvider.
43+
*/
2644
export type TKeyProviderFn<
2745
T,
2846
HasDefault extends boolean,
29-
Args extends TArgsBase,
30-
> = (
31-
...args: Args
32-
) => TKeyProvider<T, HasDefault>;
47+
Args extends TArgsBase
48+
> = (...args: Args) => TKeyProvider<T, HasDefault>;
3349

34-
// Expose both Provider & Consumer because this way you can expose only one of them
50+
/**
51+
* Result of `createKey`, `createKeyWithDefault` and `createEmptyKey`.
52+
*
53+
* ```ts
54+
* const MyKey = createKey<number>('num');
55+
* ```
56+
*/
3557
export interface TKeyBase<
3658
T,
3759
HasDefault extends boolean,
38-
Args extends TArgsBase = [T],
60+
Args extends TArgsBase = [T]
3961
> {
4062
Consumer: TKeyConsumer<T, HasDefault>;
4163
Provider: TKeyProviderFn<T, HasDefault, Args>;
64+
Reset: TKeyProvider<T, HasDefault>;
4265
}
4366

67+
/**
68+
* Type of a key created with `createKey`.
69+
*/
4470
export type TKey<T, HasDefault extends boolean = false> = TKeyBase<
4571
T,
4672
HasDefault,
4773
[value: T]
4874
>;
75+
76+
/**
77+
* Type of a key created with `createEmptyKey`.
78+
*/
4979
export type TVoidKey<HasDefault extends boolean = false> = TKeyBase<
5080
undefined,
5181
HasDefault,
5282
[]
5383
>;
5484

85+
/**
86+
* Create a key for a Stack
87+
*
88+
* ```ts
89+
* const MyKey = createKey<number>('num');
90+
* const MyKeyStringified = createKey<number>('str', (value) => value.toFixed(2));
91+
* ```
92+
*
93+
* @param name The name of the key, used when inspecting the stack.
94+
* @param stringify An optional function that convert the value to a string when inspecting the stack.
95+
* @returns {TKey} A key object.
96+
*/
5597
export function createKey<T>(
5698
name: string,
57-
stringify: TStringify<T> = strigifyUnknow,
99+
stringify: TStringify<T> = strigifyUnknow
58100
): TKey<T, false> {
59101
return createInternal<T, false, [value: T]>(
60102
name,
61103
stringify,
62104
false,
63-
undefined,
105+
undefined
64106
);
65107
}
66108

109+
/**
110+
* Create a key for a Stack with a default value.
111+
*
112+
* ```ts
113+
* const MyKey = createKeyWithDefault<number>('num', 42);
114+
* ```
115+
*
116+
* @param name The name of the key, used when inspecting the stack.
117+
* @param defaultValue The default value of the key, this value will be used if the key is not set in the stack when calling `get`.
118+
* @param stringify An optional function that convert the value to a string when inspecting the stack.
119+
* @returns A key object with a default value.
120+
*/
67121
export function createKeyWithDefault<T>(
68122
name: string,
69123
defaultValue: T,
70-
stringify: TStringify<T> = strigifyUnknow,
124+
stringify: TStringify<T> = strigifyUnknow
71125
): TKey<T, true> {
72126
return createInternal<T, true, [value: T]>(
73127
name,
74128
stringify,
75129
true,
76-
defaultValue,
130+
defaultValue
77131
);
78132
}
79133

134+
/**
135+
* Create a key for a Stack with no value.
136+
* With suck key, `get` will always return `undefined`. You should use `has` to check if the key is set.
137+
*
138+
* @param name The name of the key, used when inspecting the stack.
139+
* @returns A key object with no value.
140+
*/
80141
export function createEmptyKey(name: string): TVoidKey<false> {
81142
return createInternal<undefined, false, []>(
82143
name,
83144
strigifyEmpty,
84145
false,
85-
undefined,
146+
undefined
86147
);
87148
}
88149

89-
function createInternal<
90-
T,
91-
HasDefault extends boolean,
92-
Args extends TArgsBase,
93-
>(
150+
function createInternal<T, HasDefault extends boolean, Args extends TArgsBase>(
94151
name: string,
95152
stringify: TStringify<T>,
96153
hasDefault: HasDefault,
97-
defaultValue: T | undefined,
154+
defaultValue: T | undefined
98155
): TKeyBase<T, HasDefault, Args> {
99156
const Consumer: TKeyConsumer<T, HasDefault> = {
100157
[INTERNAL]: true,
@@ -109,6 +166,7 @@ function createInternal<
109166
return {
110167
Consumer,
111168
Provider: Provider as unknown as TKeyProviderFn<T, HasDefault, Args>,
169+
Reset: Provider(RESET as T) as TKeyProvider<T, HasDefault>,
112170
};
113171
}
114172

src/Stack.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { TKeyConsumer, TKeyProvider } from "./Key.ts";
44
import type { TStackCoreValue } from "./StackCore.ts";
55
import { StackCore } from "./StackCore.ts";
66
import { INTERNAL, NODE_INSPECT } from "./constants.ts";
7+
import { createInvalidStackSubClassErreur } from "./erreur.ts";
78
import { indent } from "./indent.ts";
89

910
/**
@@ -28,8 +29,8 @@ export class Stack {
2829
}
2930

3031
public get<T, HasDefault extends boolean>(
31-
consumer: TKeyConsumer<T, HasDefault>,
32-
): HasDefault extends true ? T : T | null {
32+
consumer: TKeyConsumer<T, HasDefault>
33+
): HasDefault extends true ? T : T | undefined {
3334
return StackCore.get(this[INTERNAL], consumer);
3435
}
3536

@@ -61,9 +62,7 @@ export class Stack {
6162
protected instantiate(stackCore: TStackCoreValue): this {
6263
// make sure we are instantiating the same class
6364
if (this.constructor !== Stack) {
64-
throw new Error(
65-
"Cannot instantiate a Stack subclass, you need to override instantiate()",
66-
);
65+
throw createInvalidStackSubClassErreur(this.constructor);
6766
}
6867
return new Stack(stackCore) as this;
6968
}

src/StackCore.ts

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// deno-lint-ignore-file no-explicit-any
22

33
import type { TKeyConsumer, TKeyProvider } from "./Key.ts";
4-
import { DEBUG, NODE_INSPECT, PARENT, PROVIDER } from "./constants.ts";
5-
import { throwMissingContextErreur } from "./erreur.ts";
4+
import { DEBUG, NODE_INSPECT, PARENT, PROVIDER, RESET } from "./constants.ts";
5+
import { createMissingContextErreur } from "./erreur.ts";
66
import { indent } from "./indent.ts";
77

88
export type TStackCoreTuple = [parent: StackCore, provider: TKeyProvider<any>];
@@ -15,7 +15,7 @@ export class StackCore {
1515

1616
protected constructor(
1717
provider: TKeyProvider<any>,
18-
parent: TStackCoreValue = null,
18+
parent: TStackCoreValue = null
1919
) {
2020
Object.defineProperty(this, PARENT, {
2121
enumerable: false,
@@ -53,38 +53,51 @@ export class StackCore {
5353

5454
static findFirstMatch(
5555
stack: TStackCoreValue,
56-
consumer: TKeyConsumer<any, any>,
56+
consumer: TKeyConsumer<any, any>
5757
): { found: boolean; value: any } {
5858
if (stack === null) {
5959
return { found: false, value: null };
6060
}
6161
const provider = stack[PROVIDER];
6262
if (provider.consumer === consumer) {
63-
return {
64-
found: true,
65-
value: provider.value,
66-
};
63+
if (provider.value === RESET) {
64+
return { found: false, value: null };
65+
}
66+
return { found: true, value: provider.value };
6767
}
6868
return StackCore.findFirstMatch(stack[PARENT], consumer);
6969
}
7070

71+
/**
72+
* Return true if the stack has a value for the given consumer.
73+
* If the last value is RESET, it will return false.
74+
* @param stack
75+
* @param consumer
76+
* @returns
77+
*/
7178
static has(
7279
stack: TStackCoreValue,
73-
consumer: TKeyConsumer<any, any>,
80+
consumer: TKeyConsumer<any, any>
7481
): boolean {
7582
return StackCore.findFirstMatch(stack, consumer).found;
7683
}
7784

85+
/**
86+
* Get the value for a given consumer.
87+
* @param stack
88+
* @param consumer
89+
* @returns
90+
*/
7891
static get<T, HasDefault extends boolean>(
7992
stack: TStackCoreValue,
80-
consumer: TKeyConsumer<T, HasDefault>,
81-
): HasDefault extends true ? T : T | null {
93+
consumer: TKeyConsumer<T, HasDefault>
94+
): HasDefault extends true ? T : T | undefined {
8295
const res = StackCore.findFirstMatch(stack, consumer);
8396
if (res.found === false) {
8497
if (consumer.hasDefault) {
8598
return consumer.defaultValue as any;
8699
}
87-
return null as any;
100+
return undefined as any;
88101
}
89102
return res.value;
90103
}
@@ -100,8 +113,10 @@ export class StackCore {
100113
for (const [, provider] of StackCore.extract(stack)) {
101114
details.unshift(
102115
`${provider.consumer.name}: ${
103-
provider.consumer.stringify(provider.value)
104-
}`,
116+
provider.value === RESET
117+
? "RESET"
118+
: provider.consumer.stringify(provider.value)
119+
}`
105120
);
106121
}
107122
if (details.length === 0) {
@@ -114,9 +129,14 @@ export class StackCore {
114129
return details.join("\n");
115130
}
116131

132+
/**
133+
* Get all the values for a given consumer as an iterator.
134+
* The values are returned in the reverse order they were set (First In Last Out).
135+
* The iterator will stop when it reaches the end of the stack or when it finds a RESET value.
136+
*/
117137
static getAll<T>(
118138
stack: TStackCoreValue,
119-
consumer: TKeyConsumer<T>,
139+
consumer: TKeyConsumer<T>
120140
): IterableIterator<T> {
121141
let current: TStackCoreValue = stack;
122142
return {
@@ -125,6 +145,10 @@ export class StackCore {
125145
const provider = current[PROVIDER];
126146
current = current[PARENT];
127147
if (provider.consumer === consumer) {
148+
if (provider.value === RESET) {
149+
current = null;
150+
return { value: undefined, done: true };
151+
}
128152
return { value: provider.value, done: false };
129153
}
130154
}
@@ -142,7 +166,7 @@ export class StackCore {
142166
if (consumer.hasDefault) {
143167
return consumer.defaultValue as any;
144168
}
145-
return throwMissingContextErreur(consumer);
169+
throw createMissingContextErreur(stack, consumer);
146170
}
147171
return res.value;
148172
}
@@ -193,7 +217,7 @@ export class StackCore {
193217
}
194218
const rightExtracted = Array.from(
195219
StackCore.extract(right),
196-
([, provider]) => provider,
220+
([, provider]) => provider
197221
).reverse();
198222
return StackCore.with(left, ...rightExtracted);
199223
}
@@ -218,7 +242,10 @@ export class StackCore {
218242
continue;
219243
}
220244
seenKeys.add(provider.consumer);
221-
baseQueue.push(provider);
245+
// If the value is RESET, no need to add it to the queue
246+
if (provider.value !== RESET) {
247+
baseQueue.push(provider);
248+
}
222249
}
223250
if (base === stack) {
224251
// no duplicates
@@ -231,7 +258,8 @@ export class StackCore {
231258

232259
static debug(stack: TStackCoreValue): Array<{ value: any; ctxId: string }> {
233260
const world: any = globalThis;
234-
const idMap = (world[DEBUG] as WeakMap<any, string>) ||
261+
const idMap =
262+
(world[DEBUG] as WeakMap<any, string>) ||
235263
(world[DEBUG] = new WeakMap<any, string>());
236264
const result: Array<{ value: any; ctxName: string; ctxId: string }> = [];
237265
traverse(stack);

src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ export const PROVIDER = Symbol.for("dldc.stack.provider");
33
export const PARENT = Symbol.for("dldc.stack.parent");
44
export const DEBUG = Symbol.for("dldc.stack.debug");
55

6+
/**
7+
* Special symbol to reset a Key.
8+
* If the latest value is RESET, has() will return false.
9+
*/
10+
export const RESET = Symbol.for("dldc.stack.reset");
11+
612
export const NODE_INSPECT = Symbol.for("nodejs.util.inspect.custom");

0 commit comments

Comments
 (0)