From 6985a2343e56bbaf900d38d90bb343398c16a357 Mon Sep 17 00:00:00 2001 From: ssube Date: Sat, 8 Aug 2020 20:01:34 -0500 Subject: [PATCH] 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. --- docs/api/js-yaml-schema.createinclude.md | 24 +++++ docs/api/js-yaml-schema.createschema.md | 24 +++++ ...ig_schema.md => js-yaml-schema.envtype.md} | 7 +- .../js-yaml-schema.includeoptions.exists.md | 11 +++ .../api/js-yaml-schema.includeoptions.join.md | 11 +++ docs/api/js-yaml-schema.includeoptions.md | 22 +++++ .../api/js-yaml-schema.includeoptions.read.md | 11 +++ .../js-yaml-schema.includeoptions.resolve.md | 11 +++ .../js-yaml-schema.includeoptions.schema.md | 11 +++ docs/api/js-yaml-schema.includeschema.md | 13 --- docs/api/js-yaml-schema.md | 19 +++- docs/api/js-yaml-schema.regexptype.md | 12 +++ .../js-yaml-schema.schemaoptions.include.md | 11 +++ docs/api/js-yaml-schema.schemaoptions.md | 18 ++++ docs/api/js-yaml-schema.streamtype.md | 12 +++ src/index.ts | 4 +- src/schema.ts | 25 ++++-- src/type/Env.ts | 2 +- src/type/Include.ts | 88 +++++++------------ src/type/Regexp.ts | 2 +- src/type/Stream.ts | 2 +- test/type/TestInclude.ts | 45 +++++----- 22 files changed, 273 insertions(+), 112 deletions(-) create mode 100644 docs/api/js-yaml-schema.createinclude.md create mode 100644 docs/api/js-yaml-schema.createschema.md rename docs/api/{js-yaml-schema.config_schema.md => js-yaml-schema.envtype.md} (50%) create mode 100644 docs/api/js-yaml-schema.includeoptions.exists.md create mode 100644 docs/api/js-yaml-schema.includeoptions.join.md create mode 100644 docs/api/js-yaml-schema.includeoptions.md create mode 100644 docs/api/js-yaml-schema.includeoptions.read.md create mode 100644 docs/api/js-yaml-schema.includeoptions.resolve.md create mode 100644 docs/api/js-yaml-schema.includeoptions.schema.md delete mode 100644 docs/api/js-yaml-schema.includeschema.md create mode 100644 docs/api/js-yaml-schema.regexptype.md create mode 100644 docs/api/js-yaml-schema.schemaoptions.include.md create mode 100644 docs/api/js-yaml-schema.schemaoptions.md create mode 100644 docs/api/js-yaml-schema.streamtype.md diff --git a/docs/api/js-yaml-schema.createinclude.md b/docs/api/js-yaml-schema.createinclude.md new file mode 100644 index 0000000..6aaeb0e --- /dev/null +++ b/docs/api/js-yaml-schema.createinclude.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [createInclude](./js-yaml-schema.createinclude.md) + +## createInclude() function + +Instantiate an includer with closure over the provided options. + +Signature: + +```typescript +export declare function createInclude(includeOptions: IncludeOptions): YamlType; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| includeOptions | [IncludeOptions](./js-yaml-schema.includeoptions.md) | | + +Returns: + +YamlType + diff --git a/docs/api/js-yaml-schema.createschema.md b/docs/api/js-yaml-schema.createschema.md new file mode 100644 index 0000000..df6b5af --- /dev/null +++ b/docs/api/js-yaml-schema.createschema.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [createSchema](./js-yaml-schema.createschema.md) + +## createSchema() function + +Safe schema with additional library types added. + +Signature: + +```typescript +export declare function createSchema(options: SchemaOptions): Schema; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [SchemaOptions](./js-yaml-schema.schemaoptions.md) | | + +Returns: + +Schema + diff --git a/docs/api/js-yaml-schema.config_schema.md b/docs/api/js-yaml-schema.envtype.md similarity index 50% rename from docs/api/js-yaml-schema.config_schema.md rename to docs/api/js-yaml-schema.envtype.md index 9b60633..2b3d0ff 100644 --- a/docs/api/js-yaml-schema.config_schema.md +++ b/docs/api/js-yaml-schema.envtype.md @@ -1,13 +1,12 @@ -[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [CONFIG\_SCHEMA](./js-yaml-schema.config_schema.md) +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [envType](./js-yaml-schema.envtype.md) -## CONFIG\_SCHEMA variable +## envType variable -Safe schema with additional library types added. Signature: ```typescript -CONFIG_SCHEMA: Schema +envType: YamlType ``` diff --git a/docs/api/js-yaml-schema.includeoptions.exists.md b/docs/api/js-yaml-schema.includeoptions.exists.md new file mode 100644 index 0000000..54c84a1 --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.exists.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) > [exists](./js-yaml-schema.includeoptions.exists.md) + +## IncludeOptions.exists property + +Signature: + +```typescript +exists: (path: string) => boolean; +``` diff --git a/docs/api/js-yaml-schema.includeoptions.join.md b/docs/api/js-yaml-schema.includeoptions.join.md new file mode 100644 index 0000000..ad9cd73 --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.join.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) > [join](./js-yaml-schema.includeoptions.join.md) + +## IncludeOptions.join property + +Signature: + +```typescript +join: (...path: Array) => string; +``` diff --git a/docs/api/js-yaml-schema.includeoptions.md b/docs/api/js-yaml-schema.includeoptions.md new file mode 100644 index 0000000..7ae75fb --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) + +## IncludeOptions interface + +Signature: + +```typescript +export interface IncludeOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [exists](./js-yaml-schema.includeoptions.exists.md) | (path: string) => boolean | | +| [join](./js-yaml-schema.includeoptions.join.md) | (...path: Array<string>) => string | | +| [read](./js-yaml-schema.includeoptions.read.md) | IncludeReader | | +| [resolve](./js-yaml-schema.includeoptions.resolve.md) | (path: string) => string | | +| [schema](./js-yaml-schema.includeoptions.schema.md) | Schema | | + diff --git a/docs/api/js-yaml-schema.includeoptions.read.md b/docs/api/js-yaml-schema.includeoptions.read.md new file mode 100644 index 0000000..ff0d2c6 --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.read.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) > [read](./js-yaml-schema.includeoptions.read.md) + +## IncludeOptions.read property + +Signature: + +```typescript +read: IncludeReader; +``` diff --git a/docs/api/js-yaml-schema.includeoptions.resolve.md b/docs/api/js-yaml-schema.includeoptions.resolve.md new file mode 100644 index 0000000..6115496 --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.resolve.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) > [resolve](./js-yaml-schema.includeoptions.resolve.md) + +## IncludeOptions.resolve property + +Signature: + +```typescript +resolve: (path: string) => string; +``` diff --git a/docs/api/js-yaml-schema.includeoptions.schema.md b/docs/api/js-yaml-schema.includeoptions.schema.md new file mode 100644 index 0000000..f05f3d1 --- /dev/null +++ b/docs/api/js-yaml-schema.includeoptions.schema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [IncludeOptions](./js-yaml-schema.includeoptions.md) > [schema](./js-yaml-schema.includeoptions.schema.md) + +## IncludeOptions.schema property + +Signature: + +```typescript +schema: Schema; +``` diff --git a/docs/api/js-yaml-schema.includeschema.md b/docs/api/js-yaml-schema.includeschema.md deleted file mode 100644 index eaf3dd7..0000000 --- a/docs/api/js-yaml-schema.includeschema.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [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. - -Signature: - -```typescript -includeOptions: IncludeOptions -``` diff --git a/docs/api/js-yaml-schema.md b/docs/api/js-yaml-schema.md index d045dd8..aeeb770 100644 --- a/docs/api/js-yaml-schema.md +++ b/docs/api/js-yaml-schema.md @@ -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) | | diff --git a/docs/api/js-yaml-schema.regexptype.md b/docs/api/js-yaml-schema.regexptype.md new file mode 100644 index 0000000..c690fc0 --- /dev/null +++ b/docs/api/js-yaml-schema.regexptype.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [regexpType](./js-yaml-schema.regexptype.md) + +## regexpType variable + + +Signature: + +```typescript +regexpType: YamlType +``` diff --git a/docs/api/js-yaml-schema.schemaoptions.include.md b/docs/api/js-yaml-schema.schemaoptions.include.md new file mode 100644 index 0000000..ff5507d --- /dev/null +++ b/docs/api/js-yaml-schema.schemaoptions.include.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [SchemaOptions](./js-yaml-schema.schemaoptions.md) > [include](./js-yaml-schema.schemaoptions.include.md) + +## SchemaOptions.include property + +Signature: + +```typescript +include: IncludeOptions; +``` diff --git a/docs/api/js-yaml-schema.schemaoptions.md b/docs/api/js-yaml-schema.schemaoptions.md new file mode 100644 index 0000000..8901772 --- /dev/null +++ b/docs/api/js-yaml-schema.schemaoptions.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [SchemaOptions](./js-yaml-schema.schemaoptions.md) + +## SchemaOptions interface + +Signature: + +```typescript +export interface SchemaOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [include](./js-yaml-schema.schemaoptions.include.md) | [IncludeOptions](./js-yaml-schema.includeoptions.md) | | + diff --git a/docs/api/js-yaml-schema.streamtype.md b/docs/api/js-yaml-schema.streamtype.md new file mode 100644 index 0000000..e24f4a9 --- /dev/null +++ b/docs/api/js-yaml-schema.streamtype.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [@apextoaster/js-yaml-schema](./js-yaml-schema.md) > [streamType](./js-yaml-schema.streamtype.md) + +## streamType variable + + +Signature: + +```typescript +streamType: YamlType +``` diff --git a/src/index.ts b/src/index.ts index 217f26b..ed9f453 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/schema.ts b/src/schema.ts index 91d0402..5f04e02 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -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; +} diff --git a/src/type/Env.ts b/src/type/Env.ts index 06c66c8..0b64f50 100644 --- a/src/type/Env.ts +++ b/src/type/Env.ts @@ -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', diff --git a/src/type/Include.ts b/src/type/Include.ts index 88354e3..811b06d 100644 --- a/src/type/Include.ts +++ b/src/type/Include.ts @@ -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) => { - 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); + } + }, + }); } diff --git a/src/type/Regexp.ts b/src/type/Regexp.ts index 7ce3843..5d578ed 100644 --- a/src/type/Regexp.ts +++ b/src/type/Regexp.ts @@ -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', diff --git a/src/type/Stream.ts b/src/type/Stream.ts index 96b15eb..599aba4 100644 --- a/src/type/Stream.ts +++ b/src/type/Stream.ts @@ -7,7 +7,7 @@ const ALLOWED_STREAMS = new Set([ ]); /** - * @internal + * @public */ export const streamType = new YamlType('!stream', { kind: 'scalar', diff --git a/test/type/TestInclude.ts b/test/type/TestInclude.ts index e2566e4..61c4d29 100644 --- a/test/type/TestInclude.ts +++ b/test/type/TestInclude.ts @@ -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'));