-
Notifications
You must be signed in to change notification settings - Fork 13
V1 of Pulpo #1
Changes from all commits
5c944e4
45896c7
eb11219
a284379
b49b424
28ace5a
8fa8fb6
8d72961
57eadf3
7e7df4d
daa1c5c
ac5abb3
53d700e
9f2b8b8
4cd88e2
6e62bb2
999a7d3
d6cf313
3d7a5e1
7d64a42
df29175
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
node_modules/ | ||
*.log | ||
.DS_Store | ||
lib/ | ||
*.json.gzip | ||
coverage/ | ||
src/**/*.js | ||
test/**/*.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
test/ | ||
lib/ | ||
coverage/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
save=true | ||
save-exact=true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
Pulpo | ||
--- | ||
|
||
Validate and build configurations in Node. | ||
|
||
- **Static Schema**: Use JSON to define schemas for configuration | ||
|
||
- **Extensible and Configurable**: Pulpo provides an easy interface for defining types, resolving configurations, coercing values, and allowing CLI overrides. | ||
|
||
## Getting Started | ||
|
||
Install Pulpo with `npm` by running: | ||
|
||
``` | ||
npm install --save @bonito/pulpo | ||
``` | ||
|
||
Getting started is as simple as importing Pulpo and passing it a JSON object for the schema and hydrating with a config object: | ||
|
||
```js | ||
import Pulpo from 'pulpo'; | ||
|
||
const schema = new Pulpo({ | ||
host: { | ||
description: 'Host for the server', | ||
type: 'string', | ||
default: 'localhost' | ||
}, | ||
port: { | ||
description: 'Port for dev server to run on' | ||
type: 'int', | ||
default: '3000', | ||
env: 'PORT' | ||
} | ||
}); | ||
|
||
const config = schema.hydrate({port: 8888}); | ||
|
||
console.log(config); | ||
// { host: 'localhost', port: 8888 } | ||
``` | ||
|
||
Schema | ||
--- | ||
A schema is comprised of keyed properties that are used to parse and validate configurations pulled from JSON objects, environmental variables, and command line arguments. | ||
|
||
### Constructor | ||
|
||
```js | ||
import Pulpo from 'pulpo'; | ||
|
||
const schema = new Pulpo({...schema...}); | ||
``` | ||
|
||
The JSON object passed in is parsed and turned into a series of [Properties](#properties). You can also pass in nested properties: | ||
|
||
```js | ||
import Pulpo from 'pulpo'; | ||
|
||
const schema = new Pulpo({ | ||
server: { | ||
host: { | ||
description: 'Host for the server', | ||
type: 'string', | ||
default: 'localhost' | ||
}, | ||
port: { | ||
description: 'Port for dev server to run on' | ||
type: 'number', | ||
default: '3000', | ||
env: 'PORT' | ||
} | ||
} | ||
}); | ||
|
||
const config = schema.hydrate({ server: { port: 8888 } }); | ||
console.log(config); | ||
// { server: { host: 'localhost', port: 8888 } } | ||
``` | ||
|
||
Any object keys that are in the [Property reserved key list](#properties-reserved-keys) will be parsed as part of the Property definition, and all other keys will be treated as nested Properties. | ||
|
||
### Hydrate | ||
Once an instance of a schema has been created, it can be used to build a config object using `schema.hydrate`: | ||
|
||
```js | ||
const schema = new Pulpo({...schema...}); | ||
... | ||
|
||
const config = schema.hydrate({ server: { port: 8888 } }); | ||
``` | ||
|
||
The hydrate function will also validate the values and throw any errors. | ||
|
||
#### Options | ||
Hydrate also allows for a second argument to be passed in that contains options: | ||
|
||
* transform (**default true**) - whether or not to use the property transform method on a value | ||
* cast (**default true**) - whether or not to cast the value before validating | ||
* validate (**default true**) - whether or not to validate a given value | ||
|
||
Properties | ||
--- | ||
Properties are definitions for a given configuration key. | ||
|
||
```js | ||
import Pulpo from 'pulpo'; | ||
|
||
const schema = new Pulpo({ | ||
environment: { | ||
description: 'Runtime environment', | ||
type: 'string', | ||
default: 'development', | ||
env: 'ENV', | ||
argv: 'env', | ||
resolve: (configObj) => configObj.env, | ||
transform: (value) => value.toUpperCase() | ||
} | ||
}); | ||
|
||
const config = schema.hydrate({env: 'test'}); | ||
console.log(config.environment); // TEST | ||
``` | ||
|
||
In the above example, Pulpo is doing a few things at the time of hydrate for **each** property in the schema: | ||
|
||
1. Resolves the value in the following order, stopping once a value is found: | ||
1. Command line arg `--env=<value>` | ||
1. Process environment value `process.env.ENV` | ||
1. Passed in configuration object value using the given `resolve` function in the property, | ||
*or* the name of the property if a resolve method is not provided | ||
1. Default value defined in the property | ||
1. Transforms the value if a transform method has been provided for the property | ||
1. Casts the value using the property type provided | ||
1. Validates that the resolved, transformed, cast value is of type defined in the property | ||
|
||
### Definition | ||
A property is comprised of the following keyed values: | ||
|
||
* **description** (*required string*) - a string to define the purpose of the property | ||
* **type** (*required string*) - a name for the type of value to be provided | ||
* **default** (*optional value matching the type*) - the value to be used if none is provided | ||
* **required** (*optional boolean, defaults to false*)- whether a value must be found (this is ignored if a default value is given, since a value is always provided) | ||
* **env** (*optional string*) - the `process.env` key for looking up a property value | ||
* **argv** (*optional string*) - the command line arg key for looking up a property value | ||
* **resolve** (*optional function*) - a function to look up a value from a config object in a different way than using the property name | ||
|
||
```js | ||
(configObject) => configObject.differentKey | ||
``` | ||
* **transform** (*optional function*) - a function that accepts a found value and maps it to a new value | ||
|
||
```js | ||
(value) => parseInt(value, 10) | ||
``` | ||
|
||
### Reserved Keys | ||
When a schema is parsed, any keys that match the [Property definition](#definition) will be automatically used in the definition of the Property. These keys cannot be used as Property names. | ||
|
||
Types | ||
--- | ||
Types are used to validate config values. By default Pulpo comes with basic primitive types: | ||
* 'string' | ||
* 'number' | ||
* 'object' | ||
* 'boolean' | ||
* 'array' | ||
|
||
### Adding Types | ||
New Types can be added to Pulpo for further validation: | ||
|
||
```js | ||
import Pulpo from 'pulpo'; | ||
|
||
Pulpo.addType('int', { | ||
validate: (value) => { | ||
if (!Number.isInteger(parseFloat(value, 10)) { | ||
return 'Value must be of type integer'; | ||
} | ||
}, | ||
cast: (value) => parseFloat(value, 10), | ||
); | ||
|
||
... | ||
|
||
const schema = new Pulpo({ | ||
port: { | ||
... | ||
type: 'int', | ||
... | ||
} | ||
}); | ||
``` | ||
|
||
Types are comprised of a validation function that receives a value and returns either void or an error message to be displayed to the user and a cast method that transforms a value before validating. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"name": "@bonito/pulpo", | ||
"version": "1.0.0", | ||
"description": "Configuration mechanism", | ||
"author": "Patrick Camacho <patrick.camacho@docker.com>", | ||
"license": "MIT", | ||
"main": "lib/index.js", | ||
"typings": "lib/index", | ||
"files": [ | ||
"lib/" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/docker/pulpo.git" | ||
}, | ||
"scripts": { | ||
"build": "tsc", | ||
"deps": "next-update --tldr --keep", | ||
"start": "npm run -s test -- --watch --coverage", | ||
"test": "jest" | ||
}, | ||
"devDependencies": { | ||
"jest-cli": "15.1.1", | ||
"next-update": "1.5.0", | ||
"typescript": "1.8.10" | ||
}, | ||
"jest": { | ||
"scriptPreprocessor": "<rootDir>/preprocessor.js", | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js" | ||
], | ||
"testRegex": "/test/.*\\.spec\\.(ts|tsx)$" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moduleFileExtensions seem to be ts, tsx and js. Should the testRegex also check for js? Or does it handle js extensions automatically? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we explicitly do not want to run any JS files found since most of the editor add-ons for typescript will compile the files in the same directory while working, so we only want to pay attention to our original source files |
||
}, | ||
"dependencies": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
"dotty": "0.0.2", | ||
"minimist": "1.2.0", | ||
"typecast": "0.0.1" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright 2004-present Facebook. All Rights Reserved. | ||
|
||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
const tsc = require('typescript'); | ||
|
||
module.exports = { | ||
process(src, path) { | ||
if (path.endsWith('.ts') || path.endsWith('.tsx')) { | ||
return tsc.transpile( | ||
src, | ||
{ | ||
module: tsc.ModuleKind.CommonJS, | ||
}, | ||
path, | ||
[] | ||
); | ||
} | ||
return src; | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/// <reference path='../typings/index.d.ts' /> | ||
|
||
import Schema from './schema'; | ||
module.exports = Schema |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍