Skip to content

Commit

Permalink
feat: add path getter to get method
Browse files Browse the repository at this point in the history
  • Loading branch information
ASafaeirad committed Dec 27, 2023
1 parent 78f79dc commit a3861be
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 19 deletions.
4 changes: 2 additions & 2 deletions docs/pages/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ yarn add --dev @fullstacksjs/config
### Deno

```typescript copy
import * as config from 'https://raw.githubusercontent.com/fullstacksjs/config/main/mod.ts';
import { Config } from 'https://raw.githubusercontent.com/fullstacksjs/config/main/mod.ts';
```

### Browser ESM
Expand All @@ -34,7 +34,7 @@ ES Module

```html copy
<script type="module">
import * as config from 'https://www.unpkg.com/@fullstacksjs/config@<VERSION>/lib/esm/index.js';
import { Config } from 'https://www.unpkg.com/@fullstacksjs/config@<VERSION>/lib/esm/index.js';
</script>
```

Expand Down
12 changes: 11 additions & 1 deletion docs/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,27 @@ const schema = new Config({
host: Config.string({ default: 'localhost' }),
token: Config.string(),
featureX: Config.boolean({ default: true }),
scope: Config.object({
nested: Config.string()
}),
urls: Config.array(Config.string()),
});

const config = schema.parse({
port: '4200',
token: 'TOKEN',
host: undefined,
featureX: false,
scope: {
nested: 'nested'
},
urls: ['http', 'https'],
})

config.get('port'); // 4200
config.get('host'); // 'localhost'
config.get('scope.nested'); // 'nested'
config.get('urls'); // ['http', 'https']

const { port, token, host, featureX } = config.getAll();
const { port, token, host, featureX, scope, urls } = config.getAll();
```
32 changes: 19 additions & 13 deletions src/Config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@ describe('Config', () => {
const config = new Config({
s: Config.string(),
n: Config.number().required(),
foo: Config.object({
nested: Config.object({
foo1: Config.string().required(),
foo2: Config.object({ foo3: Config.boolean() }),
}),
arr: Config.array(Config.string()),
})
.parse({
s: 's',
n: 0,
foo: { foo1: 'foo1', foo2: { foo3: false } },
arr: ['a', 'b'],
})
.getAll();
}).parse({
s: 's',
n: 0,
nested: { foo1: 'foo1', foo2: { foo3: false } },
arr: ['a', 'b'],
});

const configs = config.getAll();
const nested = config.get('nested');
const foo2 = config.get('nested.foo2');
const foo3 = config.get('nested.foo2.foo3');

expect(config).toEqual({
expect(configs).toEqual({
s: 's',
n: 0,
foo: { foo1: 'foo1', foo2: { foo3: false } },
nested: { foo1: 'foo1', foo2: { foo3: false } },
arr: ['a', 'b'],
});
expect(nested).toEqual({ foo1: 'foo1', foo2: { foo3: false } });
expect(foo2).toEqual({ foo3: false });
expect(foo3).toBe(false);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Test = Expect<
Equals<
typeof config,
typeof configs,
{
s: string | undefined;
n: number;
foo: { foo1: string; foo2: { foo3: boolean | undefined } };
nested: { foo1: string; foo2: { foo3: boolean | undefined } };
arr: string[];
}
>
Expand Down
16 changes: 13 additions & 3 deletions src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {
ObjectSchema,
StringSchema,
} from './Schema';
import type { InferSchema, Prettify, RequiredSchema } from './types';
import type {
GetPath,
InferSchema,
ObjectPath,
Prettify,
RequiredSchema,
} from './types';

export class Config<TSchema extends Record<string, Schema<any, any, boolean>>> {
private value!: InferSchema<TSchema>;
Expand Down Expand Up @@ -62,8 +68,12 @@ export class Config<TSchema extends Record<string, Schema<any, any, boolean>>> {
return new ArraySchema(schema) as any;
}

public get<TKey extends keyof TSchema>(key: TKey) {
return this.value[key] as Prettify<InferSchema<TSchema>[TKey]>;
public get<TKey extends ObjectPath<InferSchema<TSchema>>>(key: TKey) {
const keys = key.split('.');
// @ts-expect-error error page
return keys.reduce((acc, k) => acc[k], this.value) as any as Prettify<
GetPath<InferSchema<TSchema>, TKey>
>;
}

public getAll() {
Expand Down
28 changes: 28 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,31 @@ export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <

export type RequiredSchema<T extends Schema<any, any, boolean>> =
T extends Schema<infer I, infer O> ? Schema<I, O, true> : T;

export type ObjectPath<ObjectType extends object> = {
[Key in keyof ObjectType & (number | string)]: ObjectType[Key] extends any[]
? `${Key}`
: ObjectType[Key] extends object
? `${Key}.${ObjectPath<ObjectType[Key]>}` | `${Key}`
: `${Key}`;
}[keyof ObjectType & (number | string)];

export type SchemaKeys<
T extends Record<string, Schema<unknown, unknown, boolean>>,
> = {
[K in keyof T]: T[K] extends ObjectSchema
? InferObjectSchema<T[K]>
: T[K] extends ArraySchema<infer TArrSchema>
? TArrSchema extends Schema
? NonNullable<TArrSchema['value']>[]
: never
: K;
};

export type GetPath<T, P extends string> = P extends keyof T
? T[P]
: P extends `${infer K}.${infer Rest}`
? K extends keyof T
? GetPath<T[K], Rest>
: never
: T;

0 comments on commit a3861be

Please sign in to comment.