1
0
Fork 0

feat: switch to instantiated API, add `createSchema` entrypoint

BREAKING CHANGE: this allows multiple schemas to coexist, but requires
each schema to be created with a call to `createSchema`, taking a set
of options that includes the former `includeOptions` singleton.
This commit is contained in:
ssube 2020-08-08 20:01:34 -05:00
parent 533c1907df
commit 6985a2343e
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
22 changed files with 273 additions and 112 deletions

View File

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [createInclude](./js-yaml-schema.createinclude.md)
## createInclude() function
Instantiate an includer with closure over the provided options.
<b>Signature:</b>
```typescript
export declare function createInclude(includeOptions: IncludeOptions): YamlType;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| includeOptions | [IncludeOptions](./js-yaml-schema.includeoptions.md) | |
<b>Returns:</b>
YamlType

View File

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [createSchema](./js-yaml-schema.createschema.md)
## createSchema() function
Safe schema with additional library types added.
<b>Signature:</b>
```typescript
export declare function createSchema(options: SchemaOptions): Schema;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| options | [SchemaOptions](./js-yaml-schema.schemaoptions.md) | |
<b>Returns:</b>
Schema

View File

@ -1,13 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md)
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [envType](./js-yaml-schema.envtype.md)
## CONFIG\_SCHEMA variable
## envType variable
Safe schema with additional library types added.
<b>Signature:</b>
```typescript
CONFIG_SCHEMA: Schema
envType: YamlType
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md) &gt; [exists](./js-yaml-schema.includeoptions.exists.md)
## IncludeOptions.exists property
<b>Signature:</b>
```typescript
exists: (path: string) => boolean;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md) &gt; [join](./js-yaml-schema.includeoptions.join.md)
## IncludeOptions.join property
<b>Signature:</b>
```typescript
join: (...path: Array<string>) => string;
```

View File

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md)
## IncludeOptions interface
<b>Signature:</b>
```typescript
export interface IncludeOptions
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [exists](./js-yaml-schema.includeoptions.exists.md) | (path: string) =&gt; boolean | |
| [join](./js-yaml-schema.includeoptions.join.md) | (...path: Array&lt;string&gt;) =&gt; string | |
| [read](./js-yaml-schema.includeoptions.read.md) | IncludeReader | |
| [resolve](./js-yaml-schema.includeoptions.resolve.md) | (path: string) =&gt; string | |
| [schema](./js-yaml-schema.includeoptions.schema.md) | Schema | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md) &gt; [read](./js-yaml-schema.includeoptions.read.md)
## IncludeOptions.read property
<b>Signature:</b>
```typescript
read: IncludeReader;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md) &gt; [resolve](./js-yaml-schema.includeoptions.resolve.md)
## IncludeOptions.resolve property
<b>Signature:</b>
```typescript
resolve: (path: string) => string;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [IncludeOptions](./js-yaml-schema.includeoptions.md) &gt; [schema](./js-yaml-schema.includeoptions.schema.md)
## IncludeOptions.schema property
<b>Signature:</b>
```typescript
schema: Schema;
```

View File

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [includeSchema](./js-yaml-schema.includeschema.md)
## includeSchema variable
The schema to be used for included files. This is necessary to work around circular dependency errors.
<b>Signature:</b>
```typescript
includeOptions: IncludeOptions
```

View File

@ -4,10 +4,25 @@
## js-yaml-schema package
## Functions
| Function | Description |
| --- | --- |
| [createInclude(includeOptions)](./js-yaml-schema.createinclude.md) | Instantiate an includer with closure over the provided options. |
| [createSchema(options)](./js-yaml-schema.createschema.md) | Safe schema with additional library types added. |
## Interfaces
| Interface | Description |
| --- | --- |
| [IncludeOptions](./js-yaml-schema.includeoptions.md) | |
| [SchemaOptions](./js-yaml-schema.schemaoptions.md) | |
## Variables
| Variable | Description |
| --- | --- |
| [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md) | Safe schema with additional library types added. |
| [includeSchema](./js-yaml-schema.includeschema.md) | The schema to be used for included files. This is necessary to work around circular dependency errors. |
| [envType](./js-yaml-schema.envtype.md) | |
| [regexpType](./js-yaml-schema.regexptype.md) | |
| [streamType](./js-yaml-schema.streamtype.md) | |

View File

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [regexpType](./js-yaml-schema.regexptype.md)
## regexpType variable
<b>Signature:</b>
```typescript
regexpType: YamlType
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [SchemaOptions](./js-yaml-schema.schemaoptions.md) &gt; [include](./js-yaml-schema.schemaoptions.include.md)
## SchemaOptions.include property
<b>Signature:</b>
```typescript
include: IncludeOptions;
```

View File

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [SchemaOptions](./js-yaml-schema.schemaoptions.md)
## SchemaOptions interface
<b>Signature:</b>
```typescript
export interface SchemaOptions
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [include](./js-yaml-schema.schemaoptions.include.md) | [IncludeOptions](./js-yaml-schema.includeoptions.md) | |

View File

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-yaml-schema](./js-yaml-schema.md) &gt; [streamType](./js-yaml-schema.streamtype.md)
## streamType variable
<b>Signature:</b>
```typescript
streamType: YamlType
```

View File

@ -1,5 +1,5 @@
export { CONFIG_SCHEMA } from './schema';
export { createSchema, SchemaOptions } from './schema';
export { envType } from './type/Env';
export { includeOptions, includeType, IncludeOptions } from './type/Include';
export { createInclude, IncludeOptions } from './type/Include';
export { regexpType } from './type/Regexp';
export { streamType } from './type/Stream';

View File

@ -1,20 +1,29 @@
import { DEFAULT_SAFE_SCHEMA, Schema } from 'js-yaml';
import { envType } from './type/Env';
import { includeOptions, includeType } from './type/Include';
import { createInclude, IncludeOptions } from './type/Include';
import { regexpType } from './type/Regexp';
import { streamType } from './type/Stream';
export interface SchemaOptions {
include: IncludeOptions;
}
/**
* Safe schema with additional library types added.
*
* @public
*/
export const CONFIG_SCHEMA = Schema.create([DEFAULT_SAFE_SCHEMA], [
envType,
includeType,
regexpType,
streamType,
]);
export function createSchema(options: SchemaOptions) {
const includeType = createInclude(options.include);
const schema = Schema.create([DEFAULT_SAFE_SCHEMA], [
envType,
includeType,
regexpType,
streamType,
]);
includeOptions.schema = CONFIG_SCHEMA;
options.include.schema = schema;
return schema;
}

View File

@ -2,7 +2,7 @@ import { NotFoundError } from '@apextoaster/js-utils';
import { Type as YamlType } from 'js-yaml';
/**
* @internal
* @public
*/
export const envType = new YamlType('!env', {
kind: 'scalar',

View File

@ -1,5 +1,5 @@
import { InvalidArgumentError, NotFoundError, NotImplementedError } from '@apextoaster/js-utils';
import { SAFE_SCHEMA, safeLoad, Schema, Type as YamlType } from 'js-yaml';
import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils';
import { safeLoad, Schema, Type as YamlType } from 'js-yaml';
export interface ReaderOptions {
encoding: string;
@ -16,62 +16,36 @@ export interface IncludeOptions {
}
/**
* The schema to be used for included files. This is necessary to work around circular dependency errors.
*
* Instantiate an includer with closure over the provided options.
* @public
*/
export const includeOptions: IncludeOptions = {
exists: (path: string) => false,
join: (...path: Array<string>) => {
throw new NotImplementedError('join stub');
},
read: (path: string, encoding: ReaderOptions) => {
throw new NotImplementedError('read stub');
},
resolve: (path: string) => {
throw new NotImplementedError('resolve stub');
},
schema: SAFE_SCHEMA,
};
/**
* @internal
*/
export const includeType = new YamlType('!include', {
kind: 'scalar',
resolve(path: string) {
try {
const canonical = resolvePath(path);
// throws in node 11+
if (includeOptions.exists(canonical)) {
return true;
} else {
throw new NotFoundError('included file does not exist');
export function createInclude(includeOptions: IncludeOptions) {
return new YamlType('!include', {
kind: 'scalar',
resolve(path: string) {
try {
const canonical = includeOptions.resolve(path);
// throws in node 11+
if (includeOptions.exists(canonical)) {
return true;
} else {
throw new NotFoundError('included file does not exist');
}
} catch (err) {
throw new NotFoundError('included file does not exist', err);
}
} catch (err) {
throw new NotFoundError('included file does not exist', err);
}
},
construct(path: string): unknown {
try {
return safeLoad(includeOptions.read(resolvePath(path), {
encoding: 'utf-8',
}), {
schema: includeOptions.schema,
});
} catch (err) {
throw new InvalidArgumentError('error including file', err);
}
},
});
/**
* @todo take root parameter instead of __dirname
*/
export function resolvePath(path: string): string {
if (path[0] === '.') {
return includeOptions.resolve(includeOptions.join(__dirname, path));
} else {
return includeOptions.resolve(path);
}
},
construct(path: string): unknown {
try {
const abs = includeOptions.resolve(path);
return safeLoad(includeOptions.read(abs, {
encoding: 'utf-8',
}), {
schema: includeOptions.schema,
});
} catch (err) {
throw new InvalidArgumentError('error including file', err);
}
},
});
}

View File

@ -4,7 +4,7 @@ import { Type as YamlType } from 'js-yaml';
export const REGEXP_REGEXP = /^\/(.+)\/([gimsuy]*)$/;
/**
* @internal
* @public
*/
export const regexpType = new YamlType('!regexp', {
kind: 'scalar',

View File

@ -7,7 +7,7 @@ const ALLOWED_STREAMS = new Set([
]);
/**
* @internal
* @public
*/
export const streamType = new YamlType('!stream', {
kind: 'scalar',

View File

@ -1,38 +1,33 @@
import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils';
import { expect } from 'chai';
import { DEFAULT_SAFE_SCHEMA } from 'js-yaml';
import { join } from 'path';
import { IncludeOptions, includeOptions, includeType } from '../../src/type/Include';
import { createInclude, IncludeOptions } from '../../src/type/Include';
const TEST_ROOT = '../test/type';
const ORIGINAL_SCHEMA: IncludeOptions = {
...includeOptions,
const TEST_OPTIONS: IncludeOptions = {
exists: () => true,
join: (...path) => path.join('/'),
read: () => 'test',
resolve: (path: string) => path,
schema: DEFAULT_SAFE_SCHEMA,
};
describe('include config type', async () => {
beforeEach(() => {
includeOptions.exists = () => true;
includeOptions.join = (...path) => path.join('/');
includeOptions.read = () => 'test';
includeOptions.resolve = (path: string) => path;
});
afterEach(() => {
includeOptions.exists = ORIGINAL_SCHEMA.exists;
includeOptions.join = ORIGINAL_SCHEMA.join;
includeOptions.read = ORIGINAL_SCHEMA.read;
includeOptions.resolve = ORIGINAL_SCHEMA.resolve;
});
it('should resolve existing files', async () => {
const includeType = createInclude(TEST_OPTIONS);
expect(includeType.resolve(join(TEST_ROOT, 'include.yml'))).to.equal(true);
});
it('should throw when resolving missing files', async () => {
includeOptions.resolve = () => {
throw new NotFoundError();
};
const includeType = createInclude({
...TEST_OPTIONS,
resolve: () => {
throw new NotFoundError();
},
});
expect(() => {
includeType.resolve(join(TEST_ROOT, 'missing.yml'));
@ -40,13 +35,17 @@ describe('include config type', async () => {
});
it('should construct data from file', async () => {
const includeType = createInclude(TEST_OPTIONS);
expect(includeType.construct(join(TEST_ROOT, 'include.yml'))).to.equal('test');
});
it('should throw when constructing missing files', async () => {
includeOptions.read = () => {
throw new InvalidArgumentError();
};
const includeType = createInclude({
...TEST_OPTIONS,
read: () => {
throw new InvalidArgumentError();
},
});
expect(() => {
includeType.construct(join(TEST_ROOT, 'missing.yml'));