Skip to content

Commit 645cda9

Browse files
committed
nest now better at inference
1 parent 19e83c1 commit 645cda9

5 files changed

Lines changed: 280 additions & 40 deletions

File tree

docs/Types.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,10 @@ Session change tracking entry for audit and rollback capabilities.
717717
```typescript
718718
const revision: Revision = {
719719
action: 'set',
720+
options: {
721+
httpOnly: true,
722+
sameSite: 'lax'
723+
},
720724
value: 'newValue'
721725
};
722726

@@ -730,6 +734,7 @@ const removeRevision: Revision = {
730734
| Property | Type | Description |
731735
|----------|------|-------------|
732736
| `action` | `'set'|'remove'` | Type of change performed |
737+
| `options` | `CookieOptions` | Cookie settings stored for this specific key |
733738
| `value` | `string|string[]` | New value (for set operations) |
734739

735740
**Usage**

src/data/Nest.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1-
//common
2-
import type {
3-
Key,
1+
//client
2+
import type {
3+
CallableNest,
4+
Key,
5+
NestedObject,
6+
SetInputResult,
47
TypeOf,
5-
NestedObject,
6-
UnknownNest,
7-
CallableNest
8+
UnknownNest
89
} from '../types.js';
910
import Exception from '../Exception.js';
10-
//processors
1111
import ArgString from './processors/ArgString.js';
12+
import FormData from './processors/FormData.js';
1213
import PathString from './processors/PathString.js';
1314
import QueryString from './processors/QueryString.js';
14-
import FormData from './processors/FormData.js';
15-
//local
1615
import ReadonlyNest from './ReadonlyNest.js';
1716

1817
/**
1918
* Nest easily manipulates object data
2019
*/
21-
export default class Nest<M extends UnknownNest = UnknownNest>
20+
export default class Nest<M extends object = {}>
2221
extends ReadonlyNest<M>
2322
{
23+
/**
24+
* Starts a new typed builder chain
25+
*/
26+
public static load<M extends object = {}>(data?: M) {
27+
return new Nest<M>(data);
28+
}
29+
2430
/**
2531
* Parser for terminal args
2632
*/
@@ -75,7 +81,7 @@ export default class Nest<M extends UnknownNest = UnknownNest>
7581
*/
7682
public clear() {
7783
this._data = {} as M;
78-
return this;
84+
return this as unknown as Nest<{}>;
7985
}
8086

8187
/**
@@ -105,6 +111,9 @@ export default class Nest<M extends UnknownNest = UnknownNest>
105111
/**
106112
* Sets the data of a specified path
107113
*/
114+
public set<const I extends Array<any>>(
115+
...input: I
116+
): Nest<SetInputResult<M, I>>;
108117
public set(...path: any[]) {
109118
if (path.length < 1) {
110119
return this;
@@ -140,7 +149,7 @@ export default class Nest<M extends UnknownNest = UnknownNest>
140149
pointer[last] = value;
141150

142151
//loop through the steps one more time fixing the objects
143-
pointer = this._data;
152+
pointer = this._data as UnknownNest;
144153
path.forEach((step) => {
145154
const next = pointer[step] as UnknownNest;
146155
//if next is not an array and next should be an array
@@ -281,20 +290,24 @@ export function shouldBeAnArray(object: NestedObject<unknown> | null | undefined
281290
return true;
282291
}
283292

284-
export function nest<M extends UnknownNest = UnknownNest>(data?: M): CallableNest<M> {
293+
export function nest<M extends object = {}>(data?: M): CallableNest<M> {
285294
const store = new Nest<M>(data);
286-
const callable = Object.assign(
287-
<T = any>(...path: Key[]) => store.get<T>(...path),
295+
let callable: CallableNest<M>;
296+
297+
callable = Object.assign(
298+
(...path: Key[]) => store.get(...path),
288299
{
289300
withArgs: store.withArgs,
290301
withFormData: store.withFormData,
291302
withPath: store.withPath,
292303
withQuery: store.withQuery,
293304
clear() {
294-
return store.clear();
305+
store.clear();
306+
return callable as CallableNest<{}>;
295307
},
296308
delete(...path: Key[]) {
297-
return store.delete(...path);
309+
store.delete(...path);
310+
return callable as CallableNest<M>;
298311
},
299312
entries() {
300313
return store.entries();
@@ -315,18 +328,19 @@ export function nest<M extends UnknownNest = UnknownNest>(data?: M): CallableNes
315328
return store.path<T>(path, defaults);
316329
},
317330
set(...path: any[]) {
318-
return store.set(...path);
331+
store.set(...path);
332+
return callable;
319333
},
320334
toString() {
321335
return store.toString();
322336
},
323337
values() {
324338
return store.values();
325339
}
326-
} as Nest<M>
340+
} as CallableNest<M>
327341
);
328342
//magic size/data property
329343
Object.defineProperty(callable, 'size', { get: () => store.size });
330344
Object.defineProperty(callable, 'data', { get: () => store.data });
331345
return callable;
332-
};
346+
};

src/data/ReadonlyNest.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
//common
2-
import type { Key, UnknownNest, TypeOf } from '../types.js';
3-
//local
1+
//client
2+
import type {
3+
Infer,
4+
Key,
5+
KeyPath,
6+
PathValue,
7+
TypeOf,
8+
UnknownNest
9+
} from '../types.js';
410
import Path from './ReadonlyPath.js';
511

612
/**
713
* Nest easily manipulates object data
814
*/
9-
export default class ReadonlyNest<M extends UnknownNest = UnknownNest> {
15+
export default class ReadonlyNest<M extends object = {}> {
1016
/**
1117
* Parser for path notations
1218
*/
@@ -51,7 +57,7 @@ export default class ReadonlyNest<M extends UnknownNest = UnknownNest> {
5157
*/
5258
async forEach(...path: any[]): Promise<boolean> {
5359
const callback = path.pop() as Function;
54-
let list = this.get(...path);
60+
let list: any = this.get(...path);
5561

5662
if (!list
5763
|| Array.isArray(list) && !list.length
@@ -73,15 +79,17 @@ export default class ReadonlyNest<M extends UnknownNest = UnknownNest> {
7379
/**
7480
* Retrieves the data hashed specified by the path
7581
*/
76-
public get<T extends UnknownNest = M>(): T;
77-
public get<T = any>(...path: Key[]): T;
78-
public get<T = any>(...path: Key[]): UnknownNest|T {
82+
public get<T extends object = M>(): T;
83+
public get<V = Infer, const P extends KeyPath = KeyPath>(
84+
...path: P
85+
): V extends Infer ? PathValue<M, P> : V;
86+
public get(...path: Key[]): unknown {
7987
if (!path.length) {
8088
return this._data;
8189
}
8290

8391
if (!this.has(...path)) {
84-
return undefined as T;
92+
return undefined;
8593
}
8694

8795
const last = path.pop() as Key;
@@ -91,7 +99,7 @@ export default class ReadonlyNest<M extends UnknownNest = UnknownNest> {
9199
pointer = pointer[step] as UnknownNest;
92100
});
93101

94-
return pointer[last] as T;
102+
return pointer[last];
95103
}
96104

97105
/**
@@ -151,4 +159,4 @@ export default class ReadonlyNest<M extends UnknownNest = UnknownNest> {
151159
public values() {
152160
return Object.values(this._data);
153161
}
154-
}
162+
}

src/types.ts

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,88 @@ export interface NestedObject<V = unknown> {
4242
[ key: Key ]: V|NestedObject<V>
4343
};
4444
export type UnknownNest = NestedObject<unknown>;
45+
export type Infer = { readonly __infer: unique symbol };
46+
export type KeyPath = readonly Key[];
4547
export type Scalar = string|number|boolean|null;
4648
export type Hash = NestedObject<Scalar>;
4749
export type ScalarInput = Scalar|Scalar[]|Hash;
4850

51+
export type Widen<T> = T extends string
52+
? string
53+
: T extends number
54+
? number
55+
: T extends boolean
56+
? boolean
57+
: T extends undefined
58+
? undefined
59+
: T extends null
60+
? null
61+
: T extends ReadonlyArray<infer V>
62+
? Widen<V>[]
63+
: T extends object
64+
? { [K in keyof T]: Widen<T[K]> }
65+
: T;
66+
67+
type ArrayValue<T> = T extends ReadonlyArray<infer V> ? V : never;
68+
type IsArray<T> = T extends ReadonlyArray<any> ? true : false;
69+
type IsPlainObject<T> = T extends object
70+
? T extends Function
71+
? false
72+
: IsArray<T> extends true
73+
? false
74+
: true
75+
: false;
76+
77+
export type ValueAt<T, K extends Key> = T extends null | undefined
78+
? unknown
79+
: K extends keyof T
80+
? T[K]
81+
: K extends number
82+
? ArrayValue<T>
83+
: K extends `${number}`
84+
? ArrayValue<T>
85+
: unknown;
86+
87+
export type PathValue<T, P extends KeyPath> = P extends readonly [
88+
infer Head extends Key,
89+
...infer Rest extends KeyPath
90+
]
91+
? PathValue<ValueAt<T, Head>, Rest>
92+
: T;
93+
94+
export type PathObject<P extends KeyPath, V> = P extends readonly [
95+
infer Head extends Key,
96+
...infer Rest extends KeyPath
97+
]
98+
? { [K in Head]: PathObject<Rest, V> }
99+
: Widen<V>;
100+
101+
export type Merge<A, B> = IsPlainObject<A> extends true
102+
? IsPlainObject<B> extends true
103+
? {
104+
[K in keyof A | keyof B]: K extends keyof B
105+
? K extends keyof A
106+
? Merge<A[K], B[K]>
107+
: B[K]
108+
: K extends keyof A
109+
? A[K]
110+
: never
111+
}
112+
: B
113+
: B;
114+
115+
export type SetResult<M, P extends KeyPath, V> = Merge<M, PathObject<P, V>>;
116+
117+
export type SetInputResult<M, I extends Array<any>> = I extends []
118+
? M
119+
: I extends [infer N extends object]
120+
? Merge<M, Widen<N>>
121+
: I extends [...infer P, infer V]
122+
? P extends [Key, ...Key[]]
123+
? SetResult<M, P, V>
124+
: M
125+
: M;
126+
49127
export type FileMeta = {
50128
data: Buffer|string,
51129
name: string,
@@ -56,9 +134,20 @@ export type CallableSet<V = any> = ((index: number) => V|undefined) & DataSet<V>
56134
index: (index: number) => V|undefined
57135
};
58136
export type CallableMap<K = any, V = any> = ((name: K) => V|undefined) & DataMap<K, V>;
59-
export type CallableNest<M extends UnknownNest = UnknownNest> = (
60-
<T = any>(...path: Key[]) => T
61-
) & Nest<M>;
137+
export type CallableNest<M extends object = {}> = Omit<
138+
Nest<M>,
139+
'clear'|'delete'|'set'
140+
> & {
141+
(): M;
142+
<V = Infer, const P extends KeyPath = KeyPath>(
143+
...path: P
144+
): V extends Infer ? PathValue<M, P> : V;
145+
clear(): CallableNest<{}>;
146+
delete(...path: Key[]): CallableNest<M>;
147+
set<const I extends Array<any>>(
148+
...input: I
149+
): CallableNest<SetInputResult<M, I>>;
150+
};
62151

63152
export type DataSetIterator<
64153
V = any,
@@ -234,6 +323,7 @@ export type RequestOptions<R = unknown> = {
234323
//this is a revision entry
235324
export type Revision = {
236325
action: 'set'|'remove',
326+
options?: CookieOptions,
237327
value?: string|string[]
238328
};
239329

@@ -353,4 +443,4 @@ export type TemplateOptions = {
353443
resolve?: TemplateResolver,
354444
helpers?: Record<string, TemplateHelper>,
355445
delimiters?: [ string, string ]
356-
};
446+
};

0 commit comments

Comments
 (0)