├── .eslintrc
├── .gitignore
├── .prettierrc
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── demo
├── README.md
├── backwards.json
├── bundle.js
├── example-simple-issue.json
├── github-arthropod-complete.lens.yml
├── github-arthropod.lens.yml
├── github-issue.json
├── github-to-arthropod-original.json
├── index.html
├── new-github-issue.json
├── simple-issue.json
└── web-components
│ ├── cambria-demo.js
│ ├── cambria-document.js
│ ├── cambria-lens.js
│ └── cambria-schema.js
├── dist
├── cli.d.ts
├── cli.js
├── cli.js.map
├── defaults.d.ts
├── defaults.js
├── defaults.js.map
├── doc.d.ts
├── doc.js
├── doc.js.map
├── helpers.d.ts
├── helpers.js
├── helpers.js.map
├── index.d.ts
├── index.js
├── index.js.map
├── json-schema.d.ts
├── json-schema.js
├── json-schema.js.map
├── lens-graph.d.ts
├── lens-graph.js
├── lens-graph.js.map
├── lens-loader.d.ts
├── lens-loader.js
├── lens-loader.js.map
├── lens-ops.d.ts
├── lens-ops.js
├── lens-ops.js.map
├── patch.d.ts
├── patch.js
├── patch.js.map
├── reverse.d.ts
├── reverse.js
└── reverse.js.map
├── package.json
├── src
├── cambria-lens-schema.json
├── cli.ts
├── defaults.ts
├── doc.ts
├── helpers.ts
├── index.ts
├── json-schema.ts
├── lens-graph.ts
├── lens-loader.ts
├── lens-ops.ts
├── patch.ts
└── reverse.ts
├── test
├── github-arthropod.ts
├── github-issue.json
├── json-schema.ts
├── lens-graph.ts
└── patch.ts
├── tsconfig.json
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "mocha": true
6 | },
7 | "globals": {
8 | "__static": true
9 | },
10 | "parser": "@typescript-eslint/parser",
11 | "plugins": ["@typescript-eslint"],
12 | "extends": [
13 | "eslint:recommended",
14 | "plugin:@typescript-eslint/recommended",
15 | "plugin:@typescript-eslint/eslint-recommended",
16 | "prettier/@typescript-eslint",
17 | "plugin:prettier/recommended"
18 | ],
19 | "settings": {
20 | "import/extensions": [".js", ".jsx", ".ts", ".tsx"]
21 | },
22 | "rules": {
23 | // we agreed we don't like semicolons
24 | "semi": ["error", "never"],
25 |
26 | // we agreed class methods don't have to use "this".
27 | "class-methods-use-this": "off",
28 |
29 | // we like dangling commas
30 | "comma-dangle": "off",
31 |
32 | // we agreed we don't require default cases
33 | "default-case": "off",
34 |
35 | // doesn't really make sense when targeting electron
36 | "no-restricted-syntax": "off",
37 |
38 | // we agreed that we're okay with idiomatic short-circuits
39 | "no-unused-expressions": [
40 | "error",
41 | {
42 | "allowShortCircuit": true
43 | }
44 | ],
45 |
46 | // we agreed this feels unnecessarily opinionated
47 | "lines-between-class-members": "off",
48 |
49 | // arguably we should not do this, but we do, 18 times
50 | "no-shadow": "off",
51 | // arguably we should not do this, but there are 70 cases where we do
52 | "no-param-reassign": "off",
53 |
54 | // third-party libs often use this
55 | "no-underscore-dangle": "off",
56 |
57 | // pushpin was inherently visual, so we've disabled quite a few accessibility rules
58 | // it would be reasonable to re-enable these but would take some work, and might be
59 | // a good idea for an app like arthropod
60 | "jsx-a11y/no-static-element-interactions": "off",
61 | "jsx-a11y/anchor-is-valid": "off",
62 | "jsx-a11y/interactive-supports-focus": "off",
63 | "jsx-a11y/no-noninteractive-tabindex": "off",
64 | "jsx-a11y/click-events-have-key-events": "off",
65 | "jsx-a11y/no-autofocus": "off",
66 | "jsx-a11y/media-has-caption": "off", // randomly sourced audio doesn't come captioned
67 |
68 | // This isn't really useful
69 | "@typescript-eslint/no-empty-interface": "off",
70 |
71 | // we might want to do this, but there are 97 cases where we don't
72 | "@typescript-eslint/explicit-member-accessibility": "off",
73 |
74 | // we might want to this, but there are 424 places we don't
75 | "@typescript-eslint/explicit-function-return-type": "off",
76 |
77 | // we agreed this rule is gratuitious
78 | "@typescript-eslint/no-use-before-define": "off",
79 |
80 | // someday, we should turn this back on, but we use it 44 times
81 | "@typescript-eslint/no-explicit-any": "off",
82 |
83 | // sometimes third-party libs are typed incorrectly
84 | "@typescript-eslint/no-non-null-assertion": "off",
85 |
86 | // we agreed unused arguments should be left in-place and not removed
87 | "@typescript-eslint/no-unused-vars": [
88 | "error",
89 | {
90 | "args": "none",
91 | "ignoreRestSiblings": true
92 | }
93 | ],
94 |
95 | // import-specific rulings
96 | // we probably want to enable this, and it's violated 23 times
97 | "import/no-extraneous-dependencies": "off",
98 |
99 | // we probably don't like this rule, but only Content violates it, so we could have it
100 | "import/no-named-as-default-member": "off",
101 |
102 | // we agreed it's better to be consistent in how you export than follow this rule
103 | "import/prefer-default-export": "off",
104 |
105 | // tsc handles this better, and allows for multiple typed exports of the same name
106 | "import/export": "off",
107 |
108 | // we agreed we don't really care about this rule
109 | "import/no-cycle": "off"
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "arrowParens": "always",
4 | "trailingComma": "es5",
5 | "tabWidth": 2,
6 | "semi": false,
7 | "singleQuote": true,
8 | "endOfLine": "auto"
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}\\src\\electron.js"
12 | },
13 | {
14 | "type": "node",
15 | "request": "launch",
16 | "name": "Mocha All",
17 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
18 | "args": [
19 | "--timeout",
20 | "999999",
21 | "--colors",
22 | "-r",
23 | "ts-node/register",
24 | "${workspaceFolder}/test/**.ts"
25 | ],
26 | "console": "integratedTerminal"
27 | },
28 | {
29 | "type": "node",
30 | "request": "launch",
31 | "name": "Mocha Current File",
32 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
33 | "args": ["--timeout", "999999", "-r", "ts-node/register", "--colors", "${file}"],
34 | "console": "integratedTerminal",
35 | "internalConsoleOptions": "neverOpen"
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "editor.formatOnSave": true,
4 | "editor.rulers": [100],
5 | "eslint.enable": true,
6 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
7 | "editor.codeActionsOnSave": {
8 | "source.fixAll.eslint": true
9 | },
10 | "typescript.tsdk": "node_modules/typescript/lib",
11 | "debug.node.autoAttach": "on",
12 | "json.schemas": [
13 | {
14 | "url": "./src/cambria-lens-schema.json",
15 | "fileMatch": ["*/lenses/*.json"]
16 | }
17 | ],
18 | "yaml.schemas": {
19 | "./src/cambria-lens-schema.json": ["*/lenses/*.yml", "**/*.lens.yml"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "test",
7 | "group": {
8 | "kind": "test",
9 | "isDefault": true
10 | },
11 | "problemMatcher": [],
12 | "label": "npm: test",
13 | "detail": "mocha -r ts-node/register test/**.ts"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ink & Switch LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cambria
2 |
3 | Cambria is a Javascript/Typescript library for converting JSON data between related schemas.
4 |
5 | You specify (in YAML or JSON) a _lens_, which specifies a data transformation. Cambria lets you use this lens to convert:
6 |
7 | - a whole document, in JSON
8 | - an edit to a document, in [JSON Patch](http://jsonpatch.com/)
9 | - a schema description, in [JSON Schema](https://json-schema.org/)
10 |
11 | Lenses are bidirectional. Once you've converted a document from schema A to schema B, you can edit the document in schema B and propagate those edits _backwards through the same lens_ to schema A.
12 |
13 | **For more background on why Cambria exists and what it can do, see the [research essay](https://www.inkandswitch.com/cambria.html).**
14 |
15 | ⚠ Cambria is still immature software, and isn't yet ready for production use
16 |
17 | ## Use cases
18 |
19 | - Manage backwards compatibility in a JSON API
20 | - Manage database migrations for JSON data
21 | - Transform a JSON document into a different shape on the command line
22 | - Combine with [cambria-automerge](https://github.com/inkandswitch/cambria-automerge) to collaborate on documents across multiple versions of [local-first software](https://www.inkandswitch.com/local-first.html)
23 |
24 | ## CLI Usage
25 |
26 | Cambria includes a simple CLI tool for converting JSON from the command line.
27 |
28 | (You'll want to run `yarn build` to compile the latest code.)
29 |
30 | Covert the github issue into a an arthropod-style issue:
31 |
32 | `cat ./demo/github-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml`
33 |
34 | To get a live updating pipeline using `entr`:
35 |
36 | `echo ./demo/github-arthropod.lens.yml | entr bash -c "cat ./demo/github-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml > ./demo/simple-issue.json"`
37 |
38 | Compile back from an updated "simple issue" to a new github issue file:
39 |
40 | `cat ./demo/simple-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml -r -b ./demo/github-issue.json`
41 |
42 | Live updating pipeline backwards:
43 |
44 | `echo ./demo/simple-issue.json | entr bash -c "cat ./demo/simple-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml -r -b ./demo/github-issue.json > ./demo/new-github-issue.json"`
45 |
46 | ## API Usage
47 |
48 | Cambria is mostly intended to be used as a Typescript / Javascript library. Here's a simple example of converting an entire document.
49 |
50 | ```js
51 | // read doc from stdin if no input specified
52 | const input = readFileSync(program.input || 0, 'utf-8')
53 | const doc = JSON.parse(input)
54 |
55 | // we can (optionally) apply the contents of the changed document to a target document
56 | const targetDoc = program.base ? JSON.parse(readFileSync(program.base, 'utf-8')) : {}
57 |
58 | // now load a (yaml) lens definition
59 | const lensData = readFileSync(program.lens, 'utf-8')
60 | let lens = loadYamlLens(lensData)
61 |
62 | // should we reverse this lens?
63 | if (program.reverse) {
64 | lens = reverseLens(lens)
65 | }
66 |
67 | // finally, apply the lens to the document, with the schema, onto the target document!
68 | const newDoc = applyLensToDoc(lens, doc, program.schema, targetDoc)
69 | console.log(JSON.stringify(newDoc, null, 4))
70 | ```
71 |
72 | ## Install
73 |
74 | If you're using npm, run `npm install cambria`. If you're using yarn, run `yarn add cambria`. Then you can import it with `require('cambria')` as in the examples (or `import * as Cambria from 'cambria'` if using ES2015 or TypeScript).
75 |
76 | ## Tests
77 |
78 | `npm run test`
79 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # 7/16 Demo
2 |
3 | this is just a demo we ran in workshop on 7/16.
4 |
5 | ## Running the demo
6 |
7 | Run `yarn build` to compile the latest code
8 |
9 | Compile the github issue into a "simple issue":
10 |
11 | `cat ./demo/github-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml`
12 |
13 | To get a live updating pipeline using `entr`:
14 |
15 | `echo ./demo/github-arthropod.lens.yml | entr bash -c "cat ./demo/github-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml > ./demo/simple-issue.json"`
16 |
17 | Compile back from an updated "simple issue" to a new github issue file:
18 |
19 | `cat ./demo/simple-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml -r -b ./demo/github-issue.json`
20 |
21 | Live updating pipeline backwards:
22 |
23 | `echo ./demo/simple-issue.json | entr bash -c "cat ./demo/simple-issue.json | node ./dist/cli.js -l ./demo/github-arthropod.lens.yml -r -b ./demo/github-issue.json > ./demo/new-github-issue.json"`
24 |
--------------------------------------------------------------------------------
/demo/backwards.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "node_id": "MDU6SXNzdWUx",
4 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
5 | "repository_url": "https://api.github.com/repos/octocat/Hello-World",
6 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
8 | "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events",
9 | "html_url": "https://github.com/octocat/Hello-World/issues/1347",
10 | "number": 1347,
11 | "state": "closed",
12 | "title": "Found a difficult bug",
13 | "body": "I know what's going on!",
14 | "user": {
15 | "login": "octocat",
16 | "id": 1,
17 | "node_id": "MDQ6VXNlcjE=",
18 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
19 | "gravatar_id": "",
20 | "url": "https://api.github.com/users/octocat",
21 | "html_url": "https://github.com/octocat",
22 | "followers_url": "https://api.github.com/users/octocat/followers",
23 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
24 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
25 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
26 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
27 | "organizations_url": "https://api.github.com/users/octocat/orgs",
28 | "repos_url": "https://api.github.com/users/octocat/repos",
29 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
30 | "received_events_url": "https://api.github.com/users/octocat/received_events",
31 | "type": "User",
32 | "site_admin": false
33 | },
34 | "labels": [
35 | {
36 | "id": 208045946,
37 | "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
38 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
39 | "name": "bug",
40 | "description": "Something isn't working",
41 | "color": "f29513",
42 | "default": true
43 | }
44 | ],
45 | "assignee": {
46 | "login": "octocat",
47 | "id": 1,
48 | "node_id": "MDQ6VXNlcjE=",
49 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
50 | "gravatar_id": "",
51 | "url": "https://api.github.com/users/octocat",
52 | "html_url": "https://github.com/octocat",
53 | "followers_url": "https://api.github.com/users/octocat/followers",
54 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
55 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
56 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
57 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
58 | "organizations_url": "https://api.github.com/users/octocat/orgs",
59 | "repos_url": "https://api.github.com/users/octocat/repos",
60 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
61 | "received_events_url": "https://api.github.com/users/octocat/received_events",
62 | "type": "User",
63 | "site_admin": false
64 | },
65 | "assignees": [
66 | {
67 | "login": "octocat",
68 | "id": 1,
69 | "node_id": "MDQ6VXNlcjE=",
70 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
71 | "gravatar_id": "",
72 | "url": "https://api.github.com/users/octocat",
73 | "html_url": "https://github.com/octocat",
74 | "followers_url": "https://api.github.com/users/octocat/followers",
75 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
76 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
77 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
78 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
79 | "organizations_url": "https://api.github.com/users/octocat/orgs",
80 | "repos_url": "https://api.github.com/users/octocat/repos",
81 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
82 | "received_events_url": "https://api.github.com/users/octocat/received_events",
83 | "type": "User",
84 | "site_admin": false
85 | }
86 | ],
87 | "milestone": {
88 | "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
89 | "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
90 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
91 | "id": 1002604,
92 | "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
93 | "number": 1,
94 | "state": "open",
95 | "title": "v1.0",
96 | "description": "Tracking milestone for version 1.0",
97 | "creator": {
98 | "login": "octocat",
99 | "id": 1,
100 | "node_id": "MDQ6VXNlcjE=",
101 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
102 | "gravatar_id": "",
103 | "url": "https://api.github.com/users/octocat",
104 | "html_url": "https://github.com/octocat",
105 | "followers_url": "https://api.github.com/users/octocat/followers",
106 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
107 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
108 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
109 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
110 | "organizations_url": "https://api.github.com/users/octocat/orgs",
111 | "repos_url": "https://api.github.com/users/octocat/repos",
112 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
113 | "received_events_url": "https://api.github.com/users/octocat/received_events",
114 | "type": "User",
115 | "site_admin": false
116 | },
117 | "open_issues": 4,
118 | "closed_issues": 8,
119 | "created_at": "2011-04-10T20:09:31Z",
120 | "updated_at": "2014-03-03T18:58:10Z",
121 | "closed_at": "2013-02-12T13:22:01Z",
122 | "due_on": "2012-10-09T23:39:01Z"
123 | },
124 | "locked": true,
125 | "active_lock_reason": "too heated",
126 | "comments": 0,
127 | "pull_request": {
128 | "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
129 | "html_url": "https://github.com/octocat/Hello-World/pull/1347",
130 | "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
131 | "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
132 | },
133 | "closed_at": null,
134 | "created_at": "2011-04-22T13:33:48Z",
135 | "updated_at": "2011-04-22T13:33:48Z",
136 | "closed_by": {
137 | "login": "octocat",
138 | "id": 1,
139 | "node_id": "MDQ6VXNlcjE=",
140 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
141 | "gravatar_id": "",
142 | "url": "https://api.github.com/users/octocat",
143 | "html_url": "https://github.com/octocat",
144 | "followers_url": "https://api.github.com/users/octocat/followers",
145 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
146 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
147 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
148 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
149 | "organizations_url": "https://api.github.com/users/octocat/orgs",
150 | "repos_url": "https://api.github.com/users/octocat/repos",
151 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
152 | "received_events_url": "https://api.github.com/users/octocat/received_events",
153 | "type": "User",
154 | "site_admin": false
155 | },
156 | "hello": "world"
157 | }
158 |
--------------------------------------------------------------------------------
/demo/example-simple-issue.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "status": "todo",
4 | "name": "Found a bug",
5 | "description": "I'm having a problem with this.",
6 | "category": "bug",
7 | "metadata": {
8 | "created_at": "2011-04-22T13:33:48Z",
9 | "updated_at": "2011-04-22T13:33:48Z"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demo/github-arthropod-complete.lens.yml:
--------------------------------------------------------------------------------
1 | # Convert a Github Issue into the Arthropod format
2 | schemaName: Issue
3 |
4 | lens:
5 | # remove unnecessary fields
6 | - remove: { name: milestone }
7 | - remove: { name: pull_request }
8 | - remove: { name: closed_by }
9 | - remove: { name: repository_url }
10 | - remove: { name: number }
11 | - remove: { name: assignees }
12 | - remove: { name: user }
13 | - remove: { name: url }
14 | - remove: { name: comments_url }
15 | - remove: { name: events_url }
16 | - remove: { name: html_url }
17 | - remove: { name: locked }
18 | - remove: { name: active_lock_reason }
19 | - remove: { name: comments }
20 | - remove: { name: node_id }
21 | - remove: { name: closed_at }
22 |
23 | # Some simple renames
24 | - rename:
25 | source: title
26 | destination: name
27 | - rename:
28 | source: body
29 | destination: description
30 |
31 | # Convert github's state field to our status field
32 | - rename:
33 | source: state
34 | destination: status
35 | - convert:
36 | name: status
37 | mapping:
38 | - open: todo
39 | closed: done
40 | - todo: open
41 | doing: open
42 | done: closed
43 |
44 | # pull the creator up to the top level
45 | - in:
46 | name: user
47 | lens:
48 | - rename:
49 | source: login
50 | destination: created_by
51 | - hoist:
52 | name: created_by
53 | host: user
54 | - remove: { name: user }
55 |
56 | # pull first label up to category
57 | - head:
58 | name: labels
59 | - in:
60 | name: labels
61 | lens:
62 | - rename:
63 | source: name
64 | destination: category
65 | - hoist:
66 | name: category
67 | host: labels
68 | - remove:
69 | name: labels
70 |
71 | # push created_at and updated_at inside a metadata object
72 | - add:
73 | name: metadata
74 | type: object
75 | - plunge:
76 | name: created_at
77 | host: metadata
78 | - plunge:
79 | name: updated_at
80 | host: metadata
81 |
--------------------------------------------------------------------------------
/demo/github-arthropod.lens.yml:
--------------------------------------------------------------------------------
1 | # Convert a Github Issue into the Arthropod format
2 | schemaName: Issue
3 |
4 | lens:
5 | # remove unnecessary fields
6 | - remove: { name: milestone }
7 | - remove: { name: pull_request }
8 | - remove: { name: closed_by }
9 | - remove: { name: repository_url }
10 | - remove: { name: number }
11 | - remove: { name: assignees }
12 | - remove: { name: user }
13 | - remove: { name: url }
14 | - remove: { name: comments_url }
15 | - remove: { name: events_url }
16 | - remove: { name: html_url }
17 | - remove: { name: locked }
18 | - remove: { name: active_lock_reason }
19 | - remove: { name: comments }
20 | - remove: { name: node_id }
21 | - remove: { name: closed_at }
22 |
23 | - rename:
24 | source: title
25 | destination: name
26 |
27 | - head:
28 | name: labels
29 |
30 | - in:
31 | name: labels
32 | lens:
33 | - rename:
34 | source: name
35 | destination: category
36 |
37 | - hoist:
38 | host: labels
39 | name: category
40 |
41 | - remove:
42 | name: labels
43 |
--------------------------------------------------------------------------------
/demo/github-issue.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "node_id": "MDU6SXNzdWUx",
4 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
5 | "repository_url": "https://api.github.com/repos/octocat/Hello-World",
6 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
8 | "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events",
9 | "html_url": "https://github.com/octocat/Hello-World/issues/1347",
10 | "number": 1347,
11 | "state": "open",
12 | "title": "Found a bug",
13 | "body": "I'm having a problem with this.",
14 | "user": {
15 | "login": "octocat",
16 | "id": 1,
17 | "node_id": "MDQ6VXNlcjE=",
18 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
19 | "gravatar_id": "",
20 | "url": "https://api.github.com/users/octocat",
21 | "html_url": "https://github.com/octocat",
22 | "followers_url": "https://api.github.com/users/octocat/followers",
23 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
24 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
25 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
26 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
27 | "organizations_url": "https://api.github.com/users/octocat/orgs",
28 | "repos_url": "https://api.github.com/users/octocat/repos",
29 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
30 | "received_events_url": "https://api.github.com/users/octocat/received_events",
31 | "type": "User",
32 | "site_admin": false
33 | },
34 | "labels": [
35 | {
36 | "id": 208045946,
37 | "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
38 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
39 | "name": "bug",
40 | "description": "Something isn't working",
41 | "color": "f29513",
42 | "default": true
43 | }
44 | ],
45 | "assignees": [
46 | {
47 | "login": "octocat",
48 | "id": 1,
49 | "node_id": "MDQ6VXNlcjE=",
50 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
51 | "gravatar_id": "",
52 | "url": "https://api.github.com/users/octocat",
53 | "html_url": "https://github.com/octocat",
54 | "followers_url": "https://api.github.com/users/octocat/followers",
55 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
56 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
57 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
58 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
59 | "organizations_url": "https://api.github.com/users/octocat/orgs",
60 | "repos_url": "https://api.github.com/users/octocat/repos",
61 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
62 | "received_events_url": "https://api.github.com/users/octocat/received_events",
63 | "type": "User",
64 | "site_admin": false
65 | }
66 | ],
67 | "milestone": {
68 | "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
69 | "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
70 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
71 | "id": 1002604,
72 | "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
73 | "number": 1,
74 | "state": "open",
75 | "title": "v1.0",
76 | "description": "Tracking milestone for version 1.0",
77 | "creator": {
78 | "login": "octocat",
79 | "id": 1,
80 | "node_id": "MDQ6VXNlcjE=",
81 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
82 | "gravatar_id": "",
83 | "url": "https://api.github.com/users/octocat",
84 | "html_url": "https://github.com/octocat",
85 | "followers_url": "https://api.github.com/users/octocat/followers",
86 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
87 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
88 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
89 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
90 | "organizations_url": "https://api.github.com/users/octocat/orgs",
91 | "repos_url": "https://api.github.com/users/octocat/repos",
92 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
93 | "received_events_url": "https://api.github.com/users/octocat/received_events",
94 | "type": "User",
95 | "site_admin": false
96 | },
97 | "open_issues": 4,
98 | "closed_issues": 8,
99 | "created_at": "2011-04-10T20:09:31Z",
100 | "updated_at": "2014-03-03T18:58:10Z",
101 | "closed_at": "2013-02-12T13:22:01Z",
102 | "due_on": "2012-10-09T23:39:01Z"
103 | },
104 | "locked": true,
105 | "active_lock_reason": "too heated",
106 | "comments": 0,
107 | "pull_request": {
108 | "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
109 | "html_url": "https://github.com/octocat/Hello-World/pull/1347",
110 | "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
111 | "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
112 | },
113 | "closed_at": null,
114 | "created_at": "2011-04-22T13:33:48Z",
115 | "updated_at": "2011-04-22T13:33:48Z",
116 | "closed_by": {
117 | "login": "octocat",
118 | "id": 1,
119 | "node_id": "MDQ6VXNlcjE=",
120 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
121 | "gravatar_id": "",
122 | "url": "https://api.github.com/users/octocat",
123 | "html_url": "https://github.com/octocat",
124 | "followers_url": "https://api.github.com/users/octocat/followers",
125 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
126 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
127 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
128 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
129 | "organizations_url": "https://api.github.com/users/octocat/orgs",
130 | "repos_url": "https://api.github.com/users/octocat/repos",
131 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
132 | "received_events_url": "https://api.github.com/users/octocat/received_events",
133 | "type": "User",
134 | "site_admin": false
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/demo/github-to-arthropod-original.json:
--------------------------------------------------------------------------------
1 | {
2 | "lens": [
3 | { "tag": "lensOp/renameProperty", "from": "title", "to": "name" },
4 | { "tag": "lensOp/renameProperty", "from": "body", "to": "description" },
5 | { "tag": "lensOp/renameProperty", "from": "state", "to": "status" },
6 | {
7 | "tag": "lensOp/convertValue",
8 | "target": "status",
9 | "mapping": {
10 | "right": { "open": "todo", "closed": "done" },
11 | "left": { "todo": "open", "doing": "open", "done": "closed" }
12 | }
13 | },
14 | {
15 | "tag": "lensOp/hoistProperty",
16 | "target": "login",
17 | "host": "assignee"
18 | },
19 | { "tag": "lensOp/removeProperty", "property": { "name": "assignee", "type": "object" } },
20 | { "tag": "lensOp/renameProperty", "from": "login", "to": "assignee" },
21 | { "tag": "lensOp/removeProperty", "property": { "name": "locked", "type": "object" } },
22 | {
23 | "tag": "lensOp/removeProperty",
24 | "property": { "name": "active_lock_reason", "type": "object" }
25 | },
26 |
27 | { "tag": "lensOp/removeProperty", "property": { "name": "milestone", "type": "object" } },
28 | { "tag": "lensOp/removeProperty", "property": { "name": "closed_by", "type": "object" } },
29 | { "tag": "lensOp/removeProperty", "property": { "name": "pull_request", "type": "object" } },
30 | { "tag": "lensOp/removeProperty", "property": { "name": "node_id", "type": "string" } },
31 | { "tag": "lensOp/removeProperty", "property": { "name": "assignees", "type": "array" } },
32 | { "tag": "lensOp/removeProperty", "property": { "name": "user", "type": "object" } },
33 | { "tag": "lensOp/removeProperty", "property": { "name": "repository_url", "type": "string" } },
34 | { "tag": "lensOp/removeProperty", "property": { "name": "labels_url", "type": "string" } },
35 | { "tag": "lensOp/removeProperty", "property": { "name": "comments_url", "type": "string" } },
36 | { "tag": "lensOp/removeProperty", "property": { "name": "events_url", "type": "string" } },
37 | { "tag": "lensOp/removeProperty", "property": { "name": "html_url", "type": "string" } },
38 | { "tag": "lensOp/removeProperty", "property": { "name": "number", "type": "string" } }
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Cambria Demos
6 |
7 |
8 |
9 | Cambria component demo
10 |
11 |
12 |
13 | { "name": "The Fifth Element" }
14 |
15 |
16 | - rename:
17 | source: name
18 | destination: title
19 | - add:
20 | name: nullable
21 | type: [string, "null"]
22 |
23 | {}
24 |
25 |
26 |
27 |
28 | { "assignee": "Peter" }
29 |
30 |
31 | - wrap:
32 | name: assignee
33 |
34 | {}
35 |
36 |
37 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/demo/new-github-issue.json:
--------------------------------------------------------------------------------
1 | {
2 | "labels": [
3 | {
4 | "id": 208045946,
5 | "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
6 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
7 | "name": "feature",
8 | "description": "Something isn't working",
9 | "color": "f29513",
10 | "default": true
11 | }
12 | ],
13 | "closed_at": null,
14 | "node_id": "MDU6SXNzdWUx",
15 | "comments": 0,
16 | "active_lock_reason": "too heated",
17 | "locked": true,
18 | "html_url": "https://github.com/octocat/Hello-World/issues/1347",
19 | "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events",
20 | "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
21 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
22 | "user": {
23 | "login": "octocat",
24 | "id": 1,
25 | "node_id": "MDQ6VXNlcjE=",
26 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
27 | "gravatar_id": "",
28 | "url": "https://api.github.com/users/octocat",
29 | "html_url": "https://github.com/octocat",
30 | "followers_url": "https://api.github.com/users/octocat/followers",
31 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
32 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
33 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
34 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
35 | "organizations_url": "https://api.github.com/users/octocat/orgs",
36 | "repos_url": "https://api.github.com/users/octocat/repos",
37 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
38 | "received_events_url": "https://api.github.com/users/octocat/received_events",
39 | "type": "User",
40 | "site_admin": false
41 | },
42 | "assignees": [
43 | {
44 | "login": "octocat",
45 | "id": 1,
46 | "node_id": "MDQ6VXNlcjE=",
47 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
48 | "gravatar_id": "",
49 | "url": "https://api.github.com/users/octocat",
50 | "html_url": "https://github.com/octocat",
51 | "followers_url": "https://api.github.com/users/octocat/followers",
52 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
53 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
54 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
55 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
56 | "organizations_url": "https://api.github.com/users/octocat/orgs",
57 | "repos_url": "https://api.github.com/users/octocat/repos",
58 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
59 | "received_events_url": "https://api.github.com/users/octocat/received_events",
60 | "type": "User",
61 | "site_admin": false
62 | }
63 | ],
64 | "number": 1347,
65 | "repository_url": "https://api.github.com/repos/octocat/Hello-World",
66 | "closed_by": {
67 | "login": "octocat",
68 | "id": 1,
69 | "node_id": "MDQ6VXNlcjE=",
70 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
71 | "gravatar_id": "",
72 | "url": "https://api.github.com/users/octocat",
73 | "html_url": "https://github.com/octocat",
74 | "followers_url": "https://api.github.com/users/octocat/followers",
75 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
76 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
77 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
78 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
79 | "organizations_url": "https://api.github.com/users/octocat/orgs",
80 | "repos_url": "https://api.github.com/users/octocat/repos",
81 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
82 | "received_events_url": "https://api.github.com/users/octocat/received_events",
83 | "type": "User",
84 | "site_admin": false
85 | },
86 | "pull_request": {
87 | "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
88 | "html_url": "https://github.com/octocat/Hello-World/pull/1347",
89 | "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
90 | "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
91 | },
92 | "milestone": {
93 | "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
94 | "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
95 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
96 | "id": 1002604,
97 | "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
98 | "number": 1,
99 | "state": "open",
100 | "title": "v1.0",
101 | "description": "Tracking milestone for version 1.0",
102 | "creator": {
103 | "login": "octocat",
104 | "id": 1,
105 | "node_id": "MDQ6VXNlcjE=",
106 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
107 | "gravatar_id": "",
108 | "url": "https://api.github.com/users/octocat",
109 | "html_url": "https://github.com/octocat",
110 | "followers_url": "https://api.github.com/users/octocat/followers",
111 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
112 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
113 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
114 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
115 | "organizations_url": "https://api.github.com/users/octocat/orgs",
116 | "repos_url": "https://api.github.com/users/octocat/repos",
117 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
118 | "received_events_url": "https://api.github.com/users/octocat/received_events",
119 | "type": "User",
120 | "site_admin": false
121 | },
122 | "open_issues": 4,
123 | "closed_issues": 8,
124 | "created_at": "2011-04-10T20:09:31Z",
125 | "updated_at": "2014-03-03T18:58:10Z",
126 | "closed_at": "2013-02-12T13:22:01Z",
127 | "due_on": "2012-10-09T23:39:01Z"
128 | },
129 | "id": 1,
130 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}",
131 | "state": "open",
132 | "title": "Found a bug!!!",
133 | "body": "I'm having a problem with this.",
134 | "created_at": "2011-04-22T13:33:48Z",
135 | "updated_at": "2011-04-22T13:33:48Z"
136 | }
137 |
--------------------------------------------------------------------------------
/demo/simple-issue.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "state": "open",
4 | "name": "Found a bug!!!",
5 | "body": "I'm having a problem with this.",
6 | "category": "feature",
7 | "created_at": "2011-04-22T13:33:48Z",
8 | "updated_at": "2011-04-22T13:33:48Z"
9 | }
10 |
--------------------------------------------------------------------------------
/demo/web-components/cambria-demo.js:
--------------------------------------------------------------------------------
1 | require('./cambria-document')
2 | require('./cambria-lens')
3 | require('./cambria-schema')
4 |
5 | class CambriaDemo extends HTMLElement {
6 | template = document.createElement('template')
7 |
8 | constructor() {
9 | super()
10 |
11 | this.template.innerHTML = `
12 |
65 |
66 |
Left Document
67 |
68 |
Left Schema
69 |
70 |
Patch
71 |
72 |
75 |
76 |
77 |
Right Document
78 |
79 |
Right Schema
80 |
81 |
82 |
83 |
84 |
Last Patch
85 |
... no activity ...
86 |
87 |
88 |
89 |
Last Error
90 |
... no errors yet ...
91 |
`
92 |
93 | // Create a shadow root
94 | const shadow = this.attachShadow({ mode: 'open' })
95 |
96 | const result = this.template.content.cloneNode(true)
97 | shadow.appendChild(result)
98 |
99 | this.error = shadow.querySelector('.error .content')
100 | this.patch = shadow.querySelector('.patch .content')
101 |
102 | const slots = {}
103 | shadow.querySelectorAll('slot').forEach((slot) => {
104 | slots[slot.name] = slot.assignedElements()[0] || slot.firstElementChild
105 | })
106 |
107 | this.left = slots.left
108 |
109 | this.leftSchema = shadow.querySelector('.left .schema')
110 | this.rightSchema = shadow.querySelector('.right .schema')
111 |
112 | this.right = slots.right
113 | this.lens = slots.lens
114 |
115 | slots.lens.addEventListener('lens-changed', (e) => {
116 | // trigger a re-processing of the document
117 | this.right.clear()
118 | this.left.importDoc()
119 | })
120 |
121 | this.left.addEventListener('doc-change', (e) => {
122 | this.leftSchema.setSchema(e.detail.schema)
123 | const { patch, schema } = this.lens.translateChange(e.detail)
124 | this.rightSchema.setSchema(schema)
125 | this.right.applyChange({ patch, schema })
126 | console.log('doc-change', e)
127 | })
128 |
129 | this.left.addEventListener('doc-patch', (e) => {
130 | const { patch } = this.lens.translatePatch(e.detail)
131 | this.right.applyPatch({ patch })
132 | console.log('doc-patch', e)
133 | })
134 |
135 | this.right.addEventListener('doc-change', (e) => {
136 | const { patch, schema } = this.lens.translateChange({ ...e.detail, reverse: true })
137 | this.left.applyChange({ patch, schema })
138 | console.log('doc-change from right', e)
139 | })
140 |
141 | this.right.addEventListener('doc-patch', (e) => {
142 | const { patch } = this.lens.translatePatch({ ...e.detail, reverse: true })
143 | this.left.applyPatch({ patch })
144 | console.log('doc-patchfrom right', e)
145 | })
146 |
147 | /*
148 | // ehhhhhh
149 | slots.left.addEventListener('doc-change', (e) => {
150 | this.renderSchema(this.leftSchema, e.detail.schema)
151 | slots.lens.dispatchEvent(
152 | new CustomEvent('doc-change', { detail: { ...e.detail, destination: slots.right } })
153 | )
154 | })
155 |
156 | slots.right.addEventListener('doc-change', (e) => {
157 | this.renderSchema(this.rightSchema, e.detail.schema)
158 |
159 | slots.lens.dispatchEvent(
160 | new CustomEvent('doc-change', {
161 | detail: { ...e.detail, reverse: true, destination: slots.left },
162 | })
163 | )
164 | })
165 |
166 | slots.left.addEventListener('doc-patch', (e) => {
167 | slots.lens.dispatchEvent(
168 | new CustomEvent('doc-patch', { detail: { ...e.detail, destination: slots.right } })
169 | )
170 | })
171 |
172 | slots.right.addEventListener('doc-patch', (e) => {
173 | slots.lens.dispatchEvent(
174 | new CustomEvent('doc-patch', {
175 | detail: { ...e.detail, reverse: true, destination: slots.left },
176 | })
177 | )
178 | })
179 |
180 | // hack
181 | Object.values(slots).forEach((slot) =>
182 | slot.addEventListener('cloudina-error', (e) => {
183 | this.error.innerText = `${e.detail.topic}: ${e.detail.message}`
184 | })
185 | )
186 |
187 | slots.lens.addEventListener('doc-change', (e) => {
188 | debugger
189 | const { detail } = e
190 | const { destination } = e.detail
191 |
192 | destination.dispatchEvent(
193 | new CustomEvent('doc-change', { bubbles: false, detail: { ...detail, origin: 'LENS' } })
194 | )
195 | })
196 |
197 | slots.lens.addEventListener('doc-patch', (e) => {
198 | debugger
199 | const { detail } = e
200 | const { patch, destination } = e.detail
201 | this.patch.innerText = JSON.stringify(patch, null, 2)
202 |
203 | if (destination === slots.left) {
204 | this.renderSchema(this.leftSchema, e.detail.schema)
205 | } else if (destination === slots.right) {
206 | this.renderSchema(this.rightSchema, e.detail.schema)
207 | }
208 | destination.dispatchEvent(new CustomEvent('doc-patch', { detail }))
209 | }) */
210 |
211 | this.left.importDoc()
212 | }
213 |
214 | renderSchema(target, schema) {
215 | target.innerText = JSON.stringify(schema, null, 2)
216 | }
217 | }
218 |
219 | // Define the new element
220 | customElements.define('cambria-demo', CambriaDemo)
221 |
--------------------------------------------------------------------------------
/demo/web-components/cambria-document.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const jsonpatch = require('fast-json-patch')
3 | const { JSONEditor } = require('@json-editor/json-editor')
4 |
5 | const Cambria = require('../../dist')
6 |
7 | class CambriaDocument extends HTMLElement {
8 | lastJSON = {}
9 |
10 | constructor() {
11 | super()
12 |
13 | const shadow = this.attachShadow({ mode: 'open' })
14 | this.editorHost = document.createElement('div')
15 | shadow.appendChild(this.editorHost)
16 |
17 | this.addEventListener('doc-patch', (e) => this.handlePatch(e))
18 | this.addEventListener('doc-change', (e) => this.handleChange(e))
19 | }
20 |
21 | /** import a new JSON doc into the system
22 | * this also triggers "downstream" editors to regenerate schemas
23 | * we also run this when the lens changes to reset state
24 | */
25 | importDoc() {
26 | const rawText = this.firstChild.wholeText
27 | const rawJSON = JSON.parse(rawText)
28 | const [schema, patch] = Cambria.importDoc(rawJSON)
29 | this.lastJSON = rawJSON
30 | this.schema = schema
31 |
32 | // This bit here is rather dubious.
33 | const initializationPatch = [{ op: 'add', path: '', value: {} }]
34 | this.dispatchEvent(
35 | new CustomEvent('doc-change', {
36 | bubbles: true,
37 | composed: true,
38 | detail: { schema, patch: initializationPatch },
39 | })
40 | )
41 |
42 | this.dispatchEvent(
43 | new CustomEvent('doc-patch', {
44 | bubbles: true,
45 | composed: true,
46 | detail: { patch },
47 | })
48 | )
49 | }
50 |
51 | clear() {
52 | if (!this.editor) {
53 | throw new Error("can't clear without an editor initialized")
54 | }
55 | this.editor.setValue({})
56 | }
57 |
58 | handleEdit() {
59 | try {
60 | const validation = this.editor.validate()
61 | if (validation.valid === false && validation.errors.length > 0) {
62 | throw new Error(validation.errors[0].message)
63 | }
64 | const newJSON = this.editor.getValue()
65 | const patch = jsonpatch.compare(this.lastJSON, newJSON)
66 | this.lastJSON = newJSON
67 |
68 | if (patch.length > 0) {
69 | this.dispatchEvent(
70 | new CustomEvent('doc-patch', {
71 | bubbles: true,
72 | composed: true,
73 | detail: { patch },
74 | })
75 | )
76 | }
77 |
78 | // clear the error status
79 | this.dispatchEvent(
80 | new CustomEvent('cloudina-error', {
81 | detail: { topic: 'document edit', message: '' },
82 | })
83 | )
84 | } catch (e) {
85 | this.dispatchEvent(
86 | new CustomEvent('cloudina-error', {
87 | detail: { topic: 'document edit', message: e.message },
88 | })
89 | )
90 | }
91 | }
92 |
93 | applyChange({ patch, schema }) {
94 | if (this.editor) {
95 | this.editor.destroy()
96 | }
97 | this.editor = new JSONEditor(this.editorHost, { schema })
98 | this.applyPatch({ patch })
99 | this.editor.on('change', (e) => this.handleEdit(e))
100 | }
101 |
102 | applyPatch({ patch }) {
103 | if (!this.editor) {
104 | throw new Error('received a patch before editor initialized')
105 | }
106 |
107 | const { lastJSON } = this
108 | const newJSON = jsonpatch.applyPatch(lastJSON, patch).newDocument
109 | this.editor.setValue(newJSON)
110 | this.lastJSON = newJSON
111 | }
112 |
113 | /** receive a new schema,
114 | * make a new editor, clear the old state */
115 | handleChange(event) {
116 | const { schema } = event.detail
117 | if (this.editor) {
118 | this.editor.destroy()
119 | }
120 | this.editor = new JSONEditor(this.editorHost, { schema })
121 | this.editor.on('change', (e) => this.handleEdit(e))
122 |
123 | // let handlePatch take care of filling in the data
124 | this.handlePatch(event)
125 | }
126 |
127 | handlePatch(event) {
128 | const { patch } = event.detail
129 |
130 | if (!this.editor) {
131 | throw new Error('received a patch before editor initialized')
132 | }
133 |
134 | const { lastJSON } = this
135 | const newJSON = jsonpatch.applyPatch(lastJSON, patch).newDocument
136 | this.editor.setValue(newJSON)
137 | this.lastJSON = newJSON
138 | }
139 |
140 | connectedCallback() {
141 | this.dispatchEvent(new Event('input'))
142 | }
143 | }
144 |
145 | customElements.define('cambria-document', CambriaDocument)
146 |
--------------------------------------------------------------------------------
/demo/web-components/cambria-lens.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const { safeLoad, safeDump } = require('js-yaml')
3 | const Cambria = require('../../dist')
4 |
5 | // Sends `lens-compiled` events when it gets a new, good lens.
6 | // Receives `doc-change` events and emits `doc-patch` ones in response.
7 | class CambriaLens extends HTMLPreElement {
8 | constructor() {
9 | super()
10 |
11 | this.addEventListener('input', (e) => this.handleInput(e.target.innerText))
12 | this.handleInput(this.innerText)
13 |
14 | this.addEventListener('doc-patch', (e) => this.handleDocPatch(e, this.compiledLens))
15 | this.addEventListener('doc-change', (e) => this.handleDocChange(e, this.compiledLens))
16 | }
17 |
18 | translateChange(detail) {
19 | const { schema, patch, reverse } = detail
20 | const lens = this.compiledLens
21 |
22 | const outSchema = Cambria.updateSchema(schema, reverse ? Cambria.reverseLens(lens) : lens)
23 |
24 | this.schema = schema
25 | this.outSchema = outSchema
26 |
27 | const outPatch = Cambria.applyLensToPatch(
28 | reverse ? Cambria.reverseLens(lens) : lens,
29 | patch,
30 | this.schema
31 | )
32 |
33 | return { schema: outSchema, patch: outPatch, reverse }
34 | }
35 |
36 | translatePatch(detail) {
37 | const { patch, reverse } = detail
38 | const lens = this.compiledLens
39 |
40 | const outPatch = Cambria.applyLensToPatch(
41 | reverse ? Cambria.reverseLens(lens) : lens,
42 | patch,
43 | reverse ? this.outSchema : this.schema
44 | )
45 |
46 | return { patch: outPatch, reverse }
47 | }
48 |
49 | handleDocChange(event, lens) {
50 | try {
51 | const { schema, patch, reverse, destination } = event.detail
52 |
53 | const outSchema = Cambria.updateSchema(schema, reverse ? Cambria.reverseLens(lens) : lens)
54 |
55 | this.schema = schema
56 | this.outSchema = outSchema
57 |
58 | const convertedPatch = Cambria.applyLensToPatch(
59 | reverse ? Cambria.reverseLens(lens) : lens,
60 | patch,
61 | this.schema
62 | )
63 |
64 | destination.dispatchEvent(
65 | new CustomEvent('doc-change', {
66 | bubbles: false,
67 | detail: { schema: outSchema, patch: convertedPatch, destination },
68 | })
69 | )
70 | } catch (e) {
71 | this.dispatchEvent(
72 | new CustomEvent('cloudina-error', {
73 | detail: { topic: 'doc conversion', message: e.message },
74 | })
75 | )
76 | }
77 | }
78 |
79 | handleDocPatch(event, lens) {
80 | try {
81 | const { patch, reverse, destination } = event.detail
82 |
83 | if (!this.schema) {
84 | throw new Error('Tried to convert a patch before receiving the schema.')
85 | }
86 |
87 | const convertedPatch = Cambria.applyLensToPatch(
88 | reverse ? Cambria.reverseLens(lens) : lens,
89 | patch,
90 | this.schema
91 | )
92 |
93 | destination.dispatchEvent(
94 | new CustomEvent('doc-patch', {
95 | bubbles: false,
96 | detail: { patch: convertedPatch, destination },
97 | })
98 | )
99 | } catch (e) {
100 | this.dispatchEvent(
101 | new CustomEvent('cloudina-error', {
102 | detail: { topic: 'doc conversion', message: e.message },
103 | })
104 | )
105 | }
106 | }
107 | handleInput(value) {
108 | try {
109 | let hackYaml = safeLoad(value)
110 | if (!hackYaml.lens) {
111 | hackYaml = { lens: hackYaml }
112 | }
113 | value = safeDump(hackYaml)
114 |
115 | this.compiledLens = Cambria.loadYamlLens(value)
116 | this.dispatchEvent(new CustomEvent('lens-changed', { bubbles: true }))
117 | } catch (e) {
118 | this.dispatchEvent(
119 | new CustomEvent('cloudina-error', {
120 | detail: { topic: 'lens compilation', message: e.message },
121 | })
122 | )
123 | }
124 | }
125 | }
126 |
127 | customElements.define('cambria-lens', CambriaLens, { extends: 'pre' })
128 |
--------------------------------------------------------------------------------
/demo/web-components/cambria-schema.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const JSONSchemaView = require('json-schema-view-js')
3 |
4 | class CambriaSchema extends HTMLElement {
5 | css = `.json-schema-view,
6 | json-schema-view {
7 | font-family: monospace;
8 | font-size: 0;
9 | }
10 | .json-schema-view > *,
11 | json-schema-view > * {
12 | font-size: 14px;
13 | }
14 | .json-schema-view .toggle-handle,
15 | json-schema-view .toggle-handle {
16 | cursor: pointer;
17 | margin: auto .3em;
18 | font-size: 10px;
19 | display: inline-block;
20 | transform-origin: 50% 40%;
21 | transition: transform 150ms ease-in;
22 | }
23 | .json-schema-view .toggle-handle:after,
24 | json-schema-view .toggle-handle:after {
25 | content: "▼";
26 | }
27 | .json-schema-view .toggle-handle,
28 | json-schema-view .toggle-handle,
29 | .json-schema-view .toggle-handle:hover,
30 | json-schema-view .toggle-handle:hover {
31 | text-decoration: none;
32 | color: #333;
33 | }
34 | .json-schema-view .description,
35 | json-schema-view .description {
36 | color: gray;
37 | font-style: italic;
38 | }
39 | .json-schema-view .title,
40 | json-schema-view .title {
41 | font-weight: bold;
42 | cursor: pointer;
43 | }
44 | .json-schema-view .title,
45 | json-schema-view .title,
46 | .json-schema-view .title:hover,
47 | json-schema-view .title:hover {
48 | text-decoration: none;
49 | color: #333;
50 | }
51 | .json-schema-view .title,
52 | json-schema-view .title,
53 | .json-schema-view .brace,
54 | json-schema-view .brace,
55 | .json-schema-view .bracket,
56 | json-schema-view .bracket {
57 | color: #333;
58 | }
59 | .json-schema-view .property,
60 | json-schema-view .property {
61 | font-size: 0;
62 | display: table-row;
63 | }
64 | .json-schema-view .property > *,
65 | json-schema-view .property > * {
66 | font-size: 14px;
67 | padding: .2em;
68 | }
69 | .json-schema-view .name,
70 | json-schema-view .name {
71 | color: blue;
72 | display: table-cell;
73 | vertical-align: top;
74 | }
75 | .json-schema-view .type,
76 | json-schema-view .type {
77 | color: green;
78 | }
79 | .json-schema-view .type-any,
80 | json-schema-view .type-any {
81 | color: #3333ff;
82 | }
83 | .json-schema-view .required,
84 | json-schema-view .required {
85 | color: #F00;
86 | }
87 | .json-schema-view .format,
88 | json-schema-view .format,
89 | .json-schema-view .enums,
90 | json-schema-view .enums,
91 | .json-schema-view .pattern,
92 | json-schema-view .pattern {
93 | color: #000;
94 | }
95 | .json-schema-view .inner,
96 | json-schema-view .inner {
97 | padding-left: 18px;
98 | }
99 | .json-schema-view.collapsed .description,
100 | json-schema-view.collapsed .description {
101 | display: none;
102 | }
103 | .json-schema-view.collapsed .property,
104 | json-schema-view.collapsed .property {
105 | display: none;
106 | }
107 | .json-schema-view.collapsed .closeing.brace,
108 | json-schema-view.collapsed .closeing.brace {
109 | display: inline-block;
110 | }
111 | .json-schema-view.collapsed .toggle-handle,
112 | json-schema-view.collapsed .toggle-handle {
113 | transform: rotate(-90deg);
114 | }
115 | `
116 | constructor() {
117 | super()
118 | this.shadow = this.attachShadow({ mode: 'open' })
119 | this.shadow.innerHTML = ``
120 | }
121 |
122 | setSchema(schema) {
123 | const view = new JSONSchemaView.default(schema, 1)
124 |
125 | const attach = this.shadow.querySelector('.attach')
126 | attach.innerHTML = ''
127 | attach.appendChild(view.render())
128 | }
129 | }
130 |
131 | // Define the new element
132 | customElements.define('cambria-schema', CambriaSchema)
133 |
--------------------------------------------------------------------------------
/dist/cli.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/dist/cli.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const commander_1 = require("commander");
4 | const fs_1 = require("fs");
5 | const reverse_1 = require("./reverse");
6 | const doc_1 = require("./doc");
7 | const lens_loader_1 = require("./lens-loader");
8 | commander_1.program
9 | .description('// A CLI document conversion tool for cambria')
10 | .requiredOption('-l, --lens ', 'lens source as yaml')
11 | .option('-i, --input ', 'input document filename')
12 | .option('-s, --schema ', 'json schema for input document')
13 | .option('-b, --base ', 'base document filename')
14 | .option('-r, --reverse', 'run the lens in reverse');
15 | commander_1.program.parse(process.argv);
16 | // read doc from stdin if no input specified
17 | const input = fs_1.readFileSync(commander_1.program.input || 0, 'utf-8');
18 | const baseDoc = commander_1.program.base ? JSON.parse(fs_1.readFileSync(commander_1.program.base, 'utf-8')) : {};
19 | const doc = JSON.parse(input);
20 | const lensData = fs_1.readFileSync(commander_1.program.lens, 'utf-8');
21 | let lens = lens_loader_1.loadYamlLens(lensData);
22 | if (commander_1.program.reverse) {
23 | lens = reverse_1.reverseLens(lens);
24 | }
25 | const newDoc = doc_1.applyLensToDoc(lens, doc, commander_1.program.schema, baseDoc);
26 | console.log(JSON.stringify(newDoc, null, 4));
27 | //# sourceMappingURL=cli.js.map
--------------------------------------------------------------------------------
/dist/cli.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;AAAA,yCAAmC;AACnC,2BAAiC;AAEjC,uCAAuC;AACvC,+BAAsC;AACtC,+CAA4C;AAE5C,mBAAO;KACJ,WAAW,CAAC,+CAA+C,CAAC;KAC5D,cAAc,CAAC,uBAAuB,EAAE,qBAAqB,CAAC;KAC9D,MAAM,CAAC,wBAAwB,EAAE,yBAAyB,CAAC;KAC3D,MAAM,CAAC,uBAAuB,EAAE,gCAAgC,CAAC;KACjE,MAAM,CAAC,uBAAuB,EAAE,wBAAwB,CAAC;KACzD,MAAM,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAA;AAErD,mBAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3B,4CAA4C;AAC5C,MAAM,KAAK,GAAG,iBAAY,CAAC,mBAAO,CAAC,KAAK,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;AACvD,MAAM,OAAO,GAAG,mBAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAY,CAAC,mBAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACnF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AAC7B,MAAM,QAAQ,GAAG,iBAAY,CAAC,mBAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAEpD,IAAI,IAAI,GAAG,0BAAY,CAAC,QAAQ,CAAC,CAAA;AAEjC,IAAI,mBAAO,CAAC,OAAO,EAAE;IACnB,IAAI,GAAG,qBAAW,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,MAAM,GAAG,oBAAc,CAAC,IAAI,EAAE,GAAG,EAAE,mBAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEjE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA"}
--------------------------------------------------------------------------------
/dist/defaults.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7, JSONSchema7TypeName } from 'json-schema';
2 | import { Patch } from './patch';
3 | export declare function defaultValuesByType(type: JSONSchema7TypeName | JSONSchema7TypeName[]): JSONSchema7['default'];
4 | export declare function defaultObjectForSchema(schema: JSONSchema7): JSONSchema7;
5 | export declare function addDefaultValues(patch: Patch, schema: JSONSchema7): Patch;
6 |
--------------------------------------------------------------------------------
/dist/defaults.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.addDefaultValues = exports.defaultObjectForSchema = exports.defaultValuesByType = void 0;
4 | /* eslint-disable no-use-before-define */
5 | const fast_json_patch_1 = require("fast-json-patch");
6 | /**
7 | * behaviour:
8 | * - if we have an array of types where null is an option, that's our default
9 | * - otherwise use the first type in the array to pick a default from the table
10 | * - otherwise just use the value to lookup in the table
11 | */
12 | const defaultValuesForType = {
13 | string: '',
14 | number: 0,
15 | boolean: false,
16 | array: [],
17 | object: {},
18 | };
19 | function defaultValuesByType(type) {
20 | if (Array.isArray(type)) {
21 | if (type.includes('null')) {
22 | return null;
23 | }
24 | return defaultValuesForType[type[0]];
25 | }
26 | return defaultValuesForType[type];
27 | }
28 | exports.defaultValuesByType = defaultValuesByType;
29 | // Return a recursively filled-in default object for a given schema
30 | function defaultObjectForSchema(schema) {
31 | // By setting the root to empty object,
32 | // we kick off a recursive process that fills in the entire thing
33 | const initializeRootPatch = [
34 | {
35 | op: 'add',
36 | path: '',
37 | value: {},
38 | },
39 | ];
40 | const defaultsPatch = addDefaultValues(initializeRootPatch, schema);
41 | return fast_json_patch_1.applyPatch({}, defaultsPatch).newDocument;
42 | }
43 | exports.defaultObjectForSchema = defaultObjectForSchema;
44 | function addDefaultValues(patch, schema) {
45 | return patch
46 | .map((op) => {
47 | const isMakeMap = (op.op === 'add' || op.op === 'replace') &&
48 | op.value !== null &&
49 | typeof op.value === 'object' &&
50 | Object.entries(op.value).length === 0;
51 | if (!isMakeMap)
52 | return op;
53 | const objectProperties = getPropertiesForPath(schema, op.path);
54 | return [
55 | op,
56 | // fill in default values for each property on the object
57 | ...Object.entries(objectProperties).map(([propName, propSchema]) => {
58 | if (typeof propSchema !== 'object')
59 | throw new Error(`Missing property ${propName}`);
60 | const path = `${op.path}/${propName}`;
61 | // Fill in a default iff:
62 | // 1) it's an object or array: init to empty
63 | // 2) it's another type and there's a default value set.
64 | // TODO: is this right?
65 | // Should we allow defaulting containers to non-empty? seems like no.
66 | // Should we fill in "default defaults" like empty string?
67 | // I think better to let the json schema explicitly define defaults
68 | let defaultValue;
69 | if (propSchema.type === 'object') {
70 | defaultValue = {};
71 | }
72 | else if (propSchema.type === 'array') {
73 | defaultValue = [];
74 | }
75 | else if ('default' in propSchema) {
76 | defaultValue = propSchema.default;
77 | }
78 | else if (Array.isArray(propSchema.type) && propSchema.type.includes('null')) {
79 | defaultValue = null;
80 | }
81 | if (defaultValue !== undefined) {
82 | // todo: this is a TS hint, see if we can remove
83 | if (op.op !== 'add' && op.op !== 'replace')
84 | throw new Error('');
85 | return addDefaultValues([Object.assign(Object.assign({}, op), { path, value: defaultValue })], schema);
86 | }
87 | return [];
88 | }),
89 | ].flat(Infinity);
90 | })
91 | .flat(Infinity);
92 | }
93 | exports.addDefaultValues = addDefaultValues;
94 | // given a json schema and a json path to an object field somewhere in that schema,
95 | // return the json schema for the object being pointed to
96 | function getPropertiesForPath(schema, path) {
97 | const pathComponents = path.split('/').slice(1);
98 | const { properties } = pathComponents.reduce((schema, pathSegment) => {
99 | const types = Array.isArray(schema.type) ? schema.type : [schema.type];
100 | if (types.includes('object')) {
101 | const schemaForProperty = schema.properties && schema.properties[pathSegment];
102 | if (typeof schemaForProperty !== 'object')
103 | throw new Error('Expected object');
104 | return schemaForProperty;
105 | }
106 | if (types.includes('array')) {
107 | // throw away the array index, just return the schema for array items
108 | if (!schema.items || typeof schema.items !== 'object')
109 | throw new Error('Expected array items to have types');
110 | // todo: revisit this "as", was a huge pain to get this past TS
111 | return schema.items;
112 | }
113 | throw new Error('Expected object or array in schema based on JSON Pointer');
114 | }, schema);
115 | if (properties === undefined)
116 | return {};
117 | return properties;
118 | }
119 | //# sourceMappingURL=defaults.js.map
--------------------------------------------------------------------------------
/dist/defaults.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/doc.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { Patch } from './patch';
3 | import { LensSource } from './lens-ops';
4 | /**
5 | * importDoc - convert any Plain Old Javascript Object into an implied JSON Schema and
6 | * a JSON Patch that sets every value in that document.
7 | * @param inputDoc a document to convert into a big JSON patch describing its full contents
8 | */
9 | export declare function importDoc(inputDoc: any): [JSONSchema7, Patch];
10 | /**
11 | * applyLensToDoc - converts a full document through a lens.
12 | * Under the hood, we convert your input doc into a big patch and the apply it to the targetDoc.
13 | * This allows merging data back and forth with other omitted values.
14 | * @property lensSource: the lens specification to apply to the document
15 | * @property inputDoc: the Plain Old Javascript Object to convert
16 | * @property inputSchema: (default: inferred from inputDoc) a JSON schema defining the input
17 | * @property targetDoc: (default: {}) a document to apply the contents of this document to as a patch
18 | */
19 | export declare function applyLensToDoc(lensSource: LensSource, inputDoc: any, inputSchema?: JSONSchema7, targetDoc?: any): any;
20 |
--------------------------------------------------------------------------------
/dist/doc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.applyLensToDoc = exports.importDoc = void 0;
7 | const fast_json_patch_1 = require("fast-json-patch");
8 | const to_json_schema_1 = __importDefault(require("to-json-schema"));
9 | const defaults_1 = require("./defaults");
10 | const patch_1 = require("./patch");
11 | const json_schema_1 = require("./json-schema");
12 | /**
13 | * importDoc - convert any Plain Old Javascript Object into an implied JSON Schema and
14 | * a JSON Patch that sets every value in that document.
15 | * @param inputDoc a document to convert into a big JSON patch describing its full contents
16 | */
17 | function importDoc(inputDoc) {
18 | const options = {
19 | postProcessFnc: (type, schema, obj, defaultFnc) => (Object.assign(Object.assign({}, defaultFnc(type, schema, obj)), { type: [type, 'null'] })),
20 | objects: {
21 | postProcessFnc: (schema, obj, defaultFnc) => (Object.assign(Object.assign({}, defaultFnc(schema, obj)), { required: Object.getOwnPropertyNames(obj) })),
22 | },
23 | };
24 | const schema = to_json_schema_1.default(inputDoc, options);
25 | const patch = fast_json_patch_1.compare({}, inputDoc);
26 | return [schema, patch];
27 | }
28 | exports.importDoc = importDoc;
29 | /**
30 | * applyLensToDoc - converts a full document through a lens.
31 | * Under the hood, we convert your input doc into a big patch and the apply it to the targetDoc.
32 | * This allows merging data back and forth with other omitted values.
33 | * @property lensSource: the lens specification to apply to the document
34 | * @property inputDoc: the Plain Old Javascript Object to convert
35 | * @property inputSchema: (default: inferred from inputDoc) a JSON schema defining the input
36 | * @property targetDoc: (default: {}) a document to apply the contents of this document to as a patch
37 | */
38 | function applyLensToDoc(lensSource,
39 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
40 | inputDoc, inputSchema,
41 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
42 | targetDoc) {
43 | const [impliedSchema, patchForOriginalDoc] = importDoc(inputDoc);
44 | if (inputSchema === undefined || inputSchema === null) {
45 | inputSchema = impliedSchema;
46 | }
47 | // construct the "base" upon which we will apply the patches from doc.
48 | // We start with the default object for the output schema,
49 | // then we add in any existing fields on the target doc.
50 | // TODO: I think we need to deep merge here, can't just shallow merge?
51 | const outputSchema = json_schema_1.updateSchema(inputSchema, lensSource);
52 | const base = Object.assign(defaults_1.defaultObjectForSchema(outputSchema), targetDoc || {});
53 | // return a doc based on the converted patch.
54 | // (start with either a specified baseDoc, or just empty doc)
55 | // convert the patch through the lens
56 | const outputPatch = patch_1.applyLensToPatch(lensSource, patchForOriginalDoc, inputSchema);
57 | return fast_json_patch_1.applyPatch(base, outputPatch).newDocument;
58 | }
59 | exports.applyLensToDoc = applyLensToDoc;
60 | //# sourceMappingURL=doc.js.map
--------------------------------------------------------------------------------
/dist/doc.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"doc.js","sourceRoot":"","sources":["../src/doc.ts"],"names":[],"mappings":";;;;;;AAEA,qDAAqD;AACrD,oEAAyC;AAEzC,yCAAmD;AACnD,mCAAiD;AAEjD,+CAA4C;AAE5C;;;;GAIG;AACH,SAAgB,SAAS,CAAC,QAAa;IACrC,MAAM,OAAO,GAAG;QACd,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,iCAC9C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,KAChC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,IACpB;QACF,OAAO,EAAE;YACP,cAAc,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,iCACxC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,KAC1B,QAAQ,EAAE,MAAM,CAAC,mBAAmB,CAAC,GAAG,CAAC,IACzC;SACH;KACF,CAAA;IAED,MAAM,MAAM,GAAG,wBAAY,CAAC,QAAQ,EAAE,OAAO,CAAgB,CAAA;IAC7D,MAAM,KAAK,GAAG,yBAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;IAEnC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AACxB,CAAC;AAlBD,8BAkBC;AAED;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAC5B,UAAsB;AACtB,6EAA6E;AAC7E,QAAa,EACb,WAAyB;AACzB,6EAA6E;AAC7E,SAAe;IAEf,MAAM,CAAC,aAAa,EAAE,mBAAmB,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IAEhE,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,IAAI,EAAE;QACrD,WAAW,GAAG,aAAa,CAAA;KAC5B;IAED,sEAAsE;IACtE,0DAA0D;IAC1D,wDAAwD;IACxD,sEAAsE;IACtE,MAAM,YAAY,GAAG,0BAAY,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,iCAAsB,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC,CAAA;IAEjF,6CAA6C;IAC7C,6DAA6D;IAC7D,qCAAqC;IACrC,MAAM,WAAW,GAAG,wBAAgB,CAAC,UAAU,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAA;IAClF,OAAO,4BAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,WAAW,CAAA;AAClD,CAAC;AA1BD,wCA0BC"}
--------------------------------------------------------------------------------
/dist/helpers.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7TypeName } from 'json-schema';
2 | import { LensSource, LensMap, LensIn, Property, AddProperty, RemoveProperty, RenameProperty, HoistProperty, PlungeProperty, WrapProperty, HeadProperty, ValueMapping, ConvertValue } from './lens-ops';
3 | export declare function addProperty(property: Property): AddProperty;
4 | export declare function removeProperty(property: Property): RemoveProperty;
5 | export declare function renameProperty(source: string, destination: string): RenameProperty;
6 | export declare function hoistProperty(host: string, name: string): HoistProperty;
7 | export declare function plungeProperty(host: string, name: string): PlungeProperty;
8 | export declare function wrapProperty(name: string): WrapProperty;
9 | export declare function headProperty(name: string): HeadProperty;
10 | export declare function inside(name: string, lens: LensSource): LensIn;
11 | export declare function map(lens: LensSource): LensMap;
12 | export declare function convertValue(name: string, mapping: ValueMapping, sourceType?: JSONSchema7TypeName, destinationType?: JSONSchema7TypeName): ConvertValue;
13 |
--------------------------------------------------------------------------------
/dist/helpers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // helper functions for nicer syntax
3 | // (we might write our own parser later, but at least for now
4 | // this avoids seeing the raw json...)
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.convertValue = exports.map = exports.inside = exports.headProperty = exports.wrapProperty = exports.plungeProperty = exports.hoistProperty = exports.renameProperty = exports.removeProperty = exports.addProperty = void 0;
7 | function addProperty(property) {
8 | return Object.assign({ op: 'add' }, property);
9 | }
10 | exports.addProperty = addProperty;
11 | function removeProperty(property) {
12 | return Object.assign({ op: 'remove' }, property);
13 | }
14 | exports.removeProperty = removeProperty;
15 | function renameProperty(source, destination) {
16 | return {
17 | op: 'rename',
18 | source,
19 | destination,
20 | };
21 | }
22 | exports.renameProperty = renameProperty;
23 | function hoistProperty(host, name) {
24 | return {
25 | op: 'hoist',
26 | host,
27 | name,
28 | };
29 | }
30 | exports.hoistProperty = hoistProperty;
31 | function plungeProperty(host, name) {
32 | return {
33 | op: 'plunge',
34 | host,
35 | name,
36 | };
37 | }
38 | exports.plungeProperty = plungeProperty;
39 | function wrapProperty(name) {
40 | return {
41 | op: 'wrap',
42 | name,
43 | };
44 | }
45 | exports.wrapProperty = wrapProperty;
46 | function headProperty(name) {
47 | return {
48 | op: 'head',
49 | name,
50 | };
51 | }
52 | exports.headProperty = headProperty;
53 | function inside(name, lens) {
54 | return {
55 | op: 'in',
56 | name,
57 | lens,
58 | };
59 | }
60 | exports.inside = inside;
61 | function map(lens) {
62 | return {
63 | op: 'map',
64 | lens,
65 | };
66 | }
67 | exports.map = map;
68 | function convertValue(name, mapping, sourceType, destinationType) {
69 | return {
70 | op: 'convert',
71 | name,
72 | mapping,
73 | sourceType,
74 | destinationType,
75 | };
76 | }
77 | exports.convertValue = convertValue;
78 | //# sourceMappingURL=helpers.js.map
--------------------------------------------------------------------------------
/dist/helpers.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":";AAAA,oCAAoC;AACpC,6DAA6D;AAC7D,sCAAsC;;;AAmBtC,SAAgB,WAAW,CAAC,QAAkB;IAC5C,uBACE,EAAE,EAAE,KAAK,IACN,QAAQ,EACZ;AACH,CAAC;AALD,kCAKC;AAED,SAAgB,cAAc,CAAC,QAAkB;IAC/C,uBACE,EAAE,EAAE,QAAQ,IACT,QAAQ,EACZ;AACH,CAAC;AALD,wCAKC;AAED,SAAgB,cAAc,CAAC,MAAc,EAAE,WAAmB;IAChE,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,MAAM;QACN,WAAW;KACZ,CAAA;AACH,CAAC;AAND,wCAMC;AAED,SAAgB,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI;QACJ,IAAI;KACL,CAAA;AACH,CAAC;AAND,sCAMC;AAED,SAAgB,cAAc,CAAC,IAAY,EAAE,IAAY;IACvD,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI;QACJ,IAAI;KACL,CAAA;AACH,CAAC;AAND,wCAMC;AAED,SAAgB,YAAY,CAAC,IAAY;IACvC,OAAO;QACL,EAAE,EAAE,MAAM;QACV,IAAI;KACL,CAAA;AACH,CAAC;AALD,oCAKC;AAED,SAAgB,YAAY,CAAC,IAAY;IACvC,OAAO;QACL,EAAE,EAAE,MAAM;QACV,IAAI;KACL,CAAA;AACH,CAAC;AALD,oCAKC;AAED,SAAgB,MAAM,CAAC,IAAY,EAAE,IAAgB;IACnD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,IAAI;QACJ,IAAI;KACL,CAAA;AACH,CAAC;AAND,wBAMC;AAED,SAAgB,GAAG,CAAC,IAAgB;IAClC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,IAAI;KACL,CAAA;AACH,CAAC;AALD,kBAKC;AAED,SAAgB,YAAY,CAC1B,IAAY,EACZ,OAAqB,EACrB,UAAgC,EAChC,eAAqC;IAErC,OAAO;QACL,EAAE,EAAE,SAAS;QACb,IAAI;QACJ,OAAO;QACP,UAAU;QACV,eAAe;KAChB,CAAA;AACH,CAAC;AAbD,oCAaC"}
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | export { updateSchema, schemaForLens } from './json-schema';
2 | export { compile, applyLensToPatch, Patch, CompiledLens } from './patch';
3 | export { applyLensToDoc, importDoc } from './doc';
4 | export { LensSource, LensOp, Property } from './lens-ops';
5 | export { defaultObjectForSchema } from './defaults';
6 | export { reverseLens } from './reverse';
7 | export { LensGraph, initLensGraph, registerLens, lensGraphSchema, lensFromTo } from './lens-graph';
8 | export { addProperty, removeProperty, renameProperty, hoistProperty, plungeProperty, wrapProperty, headProperty, inside, map, convertValue, } from './helpers';
9 | export { loadYamlLens } from './lens-loader';
10 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // TODO: The exported surface is fairly large right now,
3 | // See how much we can narrow this.
4 | Object.defineProperty(exports, "__esModule", { value: true });
5 | var json_schema_1 = require("./json-schema");
6 | Object.defineProperty(exports, "updateSchema", { enumerable: true, get: function () { return json_schema_1.updateSchema; } });
7 | Object.defineProperty(exports, "schemaForLens", { enumerable: true, get: function () { return json_schema_1.schemaForLens; } });
8 | var patch_1 = require("./patch");
9 | Object.defineProperty(exports, "compile", { enumerable: true, get: function () { return patch_1.compile; } });
10 | Object.defineProperty(exports, "applyLensToPatch", { enumerable: true, get: function () { return patch_1.applyLensToPatch; } });
11 | var doc_1 = require("./doc");
12 | Object.defineProperty(exports, "applyLensToDoc", { enumerable: true, get: function () { return doc_1.applyLensToDoc; } });
13 | Object.defineProperty(exports, "importDoc", { enumerable: true, get: function () { return doc_1.importDoc; } });
14 | var defaults_1 = require("./defaults");
15 | Object.defineProperty(exports, "defaultObjectForSchema", { enumerable: true, get: function () { return defaults_1.defaultObjectForSchema; } });
16 | var reverse_1 = require("./reverse");
17 | Object.defineProperty(exports, "reverseLens", { enumerable: true, get: function () { return reverse_1.reverseLens; } });
18 | var lens_graph_1 = require("./lens-graph");
19 | Object.defineProperty(exports, "initLensGraph", { enumerable: true, get: function () { return lens_graph_1.initLensGraph; } });
20 | Object.defineProperty(exports, "registerLens", { enumerable: true, get: function () { return lens_graph_1.registerLens; } });
21 | Object.defineProperty(exports, "lensGraphSchema", { enumerable: true, get: function () { return lens_graph_1.lensGraphSchema; } });
22 | Object.defineProperty(exports, "lensFromTo", { enumerable: true, get: function () { return lens_graph_1.lensFromTo; } });
23 | var helpers_1 = require("./helpers");
24 | Object.defineProperty(exports, "addProperty", { enumerable: true, get: function () { return helpers_1.addProperty; } });
25 | Object.defineProperty(exports, "removeProperty", { enumerable: true, get: function () { return helpers_1.removeProperty; } });
26 | Object.defineProperty(exports, "renameProperty", { enumerable: true, get: function () { return helpers_1.renameProperty; } });
27 | Object.defineProperty(exports, "hoistProperty", { enumerable: true, get: function () { return helpers_1.hoistProperty; } });
28 | Object.defineProperty(exports, "plungeProperty", { enumerable: true, get: function () { return helpers_1.plungeProperty; } });
29 | Object.defineProperty(exports, "wrapProperty", { enumerable: true, get: function () { return helpers_1.wrapProperty; } });
30 | Object.defineProperty(exports, "headProperty", { enumerable: true, get: function () { return helpers_1.headProperty; } });
31 | Object.defineProperty(exports, "inside", { enumerable: true, get: function () { return helpers_1.inside; } });
32 | Object.defineProperty(exports, "map", { enumerable: true, get: function () { return helpers_1.map; } });
33 | Object.defineProperty(exports, "convertValue", { enumerable: true, get: function () { return helpers_1.convertValue; } });
34 | var lens_loader_1 = require("./lens-loader");
35 | Object.defineProperty(exports, "loadYamlLens", { enumerable: true, get: function () { return lens_loader_1.loadYamlLens; } });
36 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/dist/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,wDAAwD;AACxD,mCAAmC;;AAEnC,6CAA2D;AAAlD,2GAAA,YAAY,OAAA;AAAE,4GAAA,aAAa,OAAA;AACpC,iCAAwE;AAA/D,gGAAA,OAAO,OAAA;AAAE,yGAAA,gBAAgB,OAAA;AAClC,6BAAiD;AAAxC,qGAAA,cAAc,OAAA;AAAE,gGAAA,SAAS,OAAA;AAElC,uCAAmD;AAA1C,kHAAA,sBAAsB,OAAA;AAC/B,qCAAuC;AAA9B,sGAAA,WAAW,OAAA;AACpB,2CAAkG;AAA9E,2GAAA,aAAa,OAAA;AAAE,0GAAA,YAAY,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,wGAAA,UAAU,OAAA;AAE5E,qCAWkB;AAVhB,sGAAA,WAAW,OAAA;AACX,yGAAA,cAAc,OAAA;AACd,yGAAA,cAAc,OAAA;AACd,wGAAA,aAAa,OAAA;AACb,yGAAA,cAAc,OAAA;AACd,uGAAA,YAAY,OAAA;AACZ,uGAAA,YAAY,OAAA;AACZ,iGAAA,MAAM,OAAA;AACN,8FAAA,GAAG,OAAA;AACH,uGAAA,YAAY,OAAA;AAGd,6CAA4C;AAAnC,2GAAA,YAAY,OAAA"}
--------------------------------------------------------------------------------
/dist/json-schema.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { LensSource } from './lens-ops';
3 | export declare const emptySchema: {
4 | $schema: string;
5 | type: "object";
6 | additionalProperties: boolean;
7 | };
8 | export declare function updateSchema(schema: JSONSchema7, lens: LensSource): JSONSchema7;
9 | export declare function schemaForLens(lens: LensSource): JSONSchema7;
10 |
--------------------------------------------------------------------------------
/dist/json-schema.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"json-schema.js","sourceRoot":"","sources":["../src/json-schema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AACA,+BAA8B;AAC9B,yCAAgD;AAWnC,QAAA,WAAW,GAAG;IACzB,OAAO,EAAE,wCAAwC;IACjD,IAAI,EAAE,QAAiB;IACvB,oBAAoB,EAAE,KAAK;CAC5B,CAAA;AAED,SAAS,WAAW,CAAC,MAAW;IAC9B,OAAO,cAAO,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAC3C,CAAC;AAED,6BAA6B;AAC7B,0DAA0D;AAC1D,uDAAuD;AAEvD,uCAAuC;AACvC,6CAA6C;AAC7C,SAAS,WAAW,CAAC,MAAmB,EAAE,QAAkB;IAC1D,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,EAAE,EAAE,QAAQ,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IAC/E,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAAA;IAC9D,IAAI,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAA;IAEvB,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,MAAM,IAAI,KAAK,CAAC,kDAAkD,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;KAC9F;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;KAClD;IAED,MAAM,2BAA2B,GAAG;QAClC,IAAI;QACJ,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,8BAAmB,CAAC,IAAI,CAAC;KACvD,CAAA;IACD,0EAA0E;IAC1E,MAAM,kBAAkB,GACtB,IAAI,KAAK,OAAO,IAAI,KAAK;QACvB,CAAC,iCACM,2BAA2B,KAC9B,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,8BAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAE1F,CAAC,CAAC,2BAA2B,CAAA;IAEjC,MAAM,UAAU,mCAAQ,cAAc,KAAE,CAAC,IAAI,CAAC,EAAE,kBAAkB,GAAE,CAAA;IACpE,MAAM,SAAS,GAAG,kBAAkB,KAAK,KAAK,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC9E,MAAM,QAAQ,GAAG,CAAC,GAAG,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAChE,uCACK,MAAM,KACT,UAAU;QACV,QAAQ,IACT;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB,EAAE,EAAmC;IAC5E,IAAI,MAAM,CAAC,KAAK,EAAE;QAChB,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;YAC7B,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAA;SAC9F;QACD,uCAAY,MAAM,KAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAE;KAC9F;SAAM;QACL,OAAO,EAAE,CAAC,MAAM,CAAC,CAAA;KAClB;AACH,CAAC;AAED,SAAS,cAAc,CAAC,OAAoB,EAAE,IAAY,EAAE,EAAU;IACpE,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;QACtC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE;YACvE,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;SACzE;QACD,IAAI,CAAC,IAAI,EAAE;YACT,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;SAClE;QACD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;YAC5B,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,qCAAqC,MAAM,CAAC,IAAI,CAC7E,MAAM,CAAC,UAAU,CAClB,GAAG,CACL,CAAA;SACF;QACD,IAAI,CAAC,EAAE,EAAE;YACP,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,MAAM,CAAC,CAAA;SAC9D;QAED,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA,CAAC,2CAA2C;QAC7F,MAAyC,KAAA,UAAU,EAA3C,KAAC,IAAK,EAAE,WAAW,SAAA,EAAK,IAAI,cAA9B,uCAAgC,CAAa,CAAA,CAAC,yBAAyB;QAE7E,IAAI,WAAW,KAAK,SAAS,EAAE;YAC7B,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,EAAE,CAAC,CAAA;SACnE;QAED,uCACK,MAAM,KACT,UAAU,kBAAI,CAAC,EAAE,CAAC,EAAE,WAAW,IAAK,IAAI,GACxC,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC,IACtD,CAAC,2BAA2B;IAC/B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,kCAAkC;AAClC,iEAAiE;AACjE,oEAAoE;AACpE,SAAS,cAAc,CAAC,MAAmB,EAAE,cAAsB;IACjE,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IACjD,MAAM,OAAO,GAAG,cAAc,CAAA;IAC9B,kDAAkD;IAClD,6DAA6D;IAE7D,IAAI,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAA;KACzE;IAED,wBAAwB;IACxB,6DAA6D;IAC7D,MAA0C,KAAA,UAAU,EAA5C,KAAC,OAAQ,EAAE,SAAS,SAAA,EAAK,IAAI,cAA/B,uCAAiC,CAAa,CAAA;IAEpD,uCACK,MAAM,KACT,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAChD;AACH,CAAC;AAED,SAAS,kBAAkB,CACzB,SAAkE,EAClE,IAAyB;IAEzB,IAAI,CAAC,SAAS,EAAE;QACd,OAAO,KAAK,CAAA;KACb;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;QAC7B,SAAS,GAAG,CAAC,SAAS,CAAC,CAAA;KACxB;IAED,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC;AAED;;;GAGG;AACH,SAAS,EAAE,CAAC,CAAwB;IAClC,IAAI,CAAC,KAAK,IAAI,EAAE;QACd,OAAO,EAAE,CAAA;KACV;IACD,IAAI,CAAC,KAAK,KAAK,EAAE;QACf,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAA;KACnB;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB;;IACvC,OAAO,CACL,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;QACvC,CAAC,QAAC,MAAM,CAAC,KAAK,0CAAE,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAC,CACpF,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,MAAmB,EAAE,IAAY;;IACjD,IAAI,MAAM,CAAC,KAAK,EAAE;QAChB,MAAM,WAAW,SAAG,MAAM,CAAC,KAAK,0CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,CAAA;QACpF,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,OAAO,WAAW,CAAC,UAAU,KAAK,QAAQ,EAAE;YACjF,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAC9C,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,IAAI,EAAE;gBAC7C,OAAO,SAAS,CAAA;aACjB;SACF;KACF;SAAM,IAAI,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;QACvD,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACzC,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,IAAI,EAAE;YAC7C,OAAO,SAAS,CAAA;SACjB;KACF;IACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;AAC1D,CAAC;AAED,SAAS,QAAQ,CAAC,MAAmB,EAAE,EAAU;;IAC/C,MAAM,UAAU,GAAgB,MAAM,CAAC,UAAU;QAC/C,CAAC,CAAC,MAAM,CAAC,UAAU;QACnB,CAAC,CAAC,CAAC,MAAA,MAAM,CAAC,KAAK,0CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAS,CAAA,CAAC,UAAU,CAAA;IAExF,IAAI,CAAC,UAAU,EAAE;QACf,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;KAC5E;IAED,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;IAEzB,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;KACjF;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAEnC,IAAI,IAAI,KAAK,SAAS,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;KACzF;IAED,MAAM,aAAa,mCACd,UAAU,KACb,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,GACjC,CAAA;IAED,OAAO,gCACF,MAAM,KACT,UAAU,EAAE,aAAa,GACX,CAAA;AAClB,CAAC;AAGD,SAAS,mBAAmB,CAAC,KAAuB;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;KACtE;IACD,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,uDAAuD,KAAK,GAAG,CAAC,CAAA;KACjF;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,SAAS,CAAC,MAAmB,EAAE,IAAgB;IACtD,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;KAChE;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;QACjB,MAAM,IAAI,KAAK,CAAC,iDAAiD,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;KACxF;IACD,uCAAY,MAAM,KAAE,KAAK,EAAE,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,IAAE;AACpF,CAAC;AAED,SAAS,mBAAmB,CAAI,CAAU,EAAE,EAAqB;IAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACrB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;KACR;IACD,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAChB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;QAClB,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;KACZ;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,wCAAwC;AACxC,SAAS,iBAAiB,CAAC,IAAiB;IAC1C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;QACvB,OAAO,IAAI,CAAA;KACZ;IACD,IAAI,IAAI,CAAC,IAAI,EAAE;QACb,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE;YACxB,OAAO,IAAI,CAAA;SACZ;QAED,IAAI,mCAAQ,IAAI,KAAE,IAAI,EAAE,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,GAAE,CAAA;QAE7E,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE;YACzB,IAAI,CAAC,OAAO,GAAG,8BAAmB,CAAC,IAAI,CAAC,IAAK,CAAC,CAAA,CAAC,wCAAwC;SACxF;KACF;IAED,IAAI,IAAI,CAAC,KAAK,EAAE;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAkB,EAAE,CAAC,EAAE,EAAE;YAC3D,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YACtC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QACtC,CAAC,EAAE,EAAE,CAAC,CAAA;QACN,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;YACzB,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAA;SACnB;QACD,IAAI,mCAAQ,IAAI,KAAE,KAAK,EAAE,QAAQ,GAAE,CAAA;KACpC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB,EAAE,EAAgB;IACzD,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QACZ,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;KAC7E;IAED,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;KACzE;IAED,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAC3C,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,yBAAyB,EAAE,CAAC,IAAI,8BAA8B,CAAC,CAAA;KAChF;IAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;QACvB,MAAM,IAAI,KAAK,CACb,yBAAyB,EAAE,CAAC,IAAI,4CAA4C,WAAW,CACrF,MAAM,CACP,EAAE,CACJ,CAAA;KACF;IAED,uCACK,MAAM,KACT,UAAU,kCACL,MAAM,CAAC,UAAU,KACpB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;gBACT,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,EAAE;gBACX,KAAK,EAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;aAC9C,OAEJ;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAM,EAAE,EAAgB;IAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QACZ,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAA;KAC9E;IACD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;QAC/B,MAAM,IAAI,KAAK,CAAC,yBAAyB,EAAE,CAAC,IAAI,8BAA8B,CAAC,CAAA;KAChF;IAED,uCACK,MAAM,KACT,UAAU,kCACL,MAAM,CAAC,UAAU,KACpB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,OAE7E;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAoB,EAAE,IAAY,EAAE,IAAY;IACrE,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;QACtC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE;YACnC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;SAChE;QACD,IAAI,CAAC,IAAI,EAAE;YACT,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;SAC3D;QACD,IAAI,CAAC,IAAI,EAAE;YACT,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;SAC1D;QAED,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAA;QAC7B,IAAI,CAAC,CAAC,IAAI,IAAI,UAAU,CAAC,EAAE;YACzB,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,+CAA+C,MAAM,CAAC,IAAI,CACzF,UAAU,CACX,GAAG,CACL,CAAA;SACF;QAED,MAAM,qBAAqB,GAAG,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE;YAC9E,MAAM,cAAc,GAAG,UAAU,CAAC,UAAU,CAAA;YAC5C,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAA;YAC9C,IAAI,CAAC,cAAc,EAAE;gBACnB,MAAM,IAAI,KAAK,CACb,2CAA2C,IAAI,WAAW,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CACpF,CAAA;aACF;YACD,IAAI,CAAC,CAAC,IAAI,IAAI,cAAc,CAAC,EAAE;gBAC7B,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,+CAA+C,MAAM,CAAC,IAAI,CACzF,UAAU,CACX,GAAG,CACL,CAAA;aACF;YACD,MAAmD,KAAA,cAAc,EAAzD,KAAC,IAAK,EAAE,MAAM,SAAA,EAAK,mBAAmB,cAAxC,uCAA0C,CAAiB,CAAA;YACjE,uCACK,UAAU,KACb,UAAU,EAAE,mBAAmB,EAC/B,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,IACjD;QACH,CAAC,CAAC,CAAA;QACF,MAAM,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE;YACpE,MAAM,cAAc,GAAG,UAAU,CAAC,UAAW,CAAA;YAC7C,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAA;YACzC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC,CAAC,CAAA;QAEF,uCACK,MAAM,KACT,UAAU,kCACL,MAAM,CAAC,UAAU,KACpB,CAAC,IAAI,CAAC,EAAE,qBAAqB,EAC7B,CAAC,IAAI,CAAC,EAAE,WAAW,KAErB,QAAQ,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,IAC7C;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,MAAmB,EAAE,IAAY,EAAE,IAAY;IACrE,8DAA8D;IAC9D,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IAElC,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;KAC3D;IAED,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAA;KACxD;IAED,MAAM,yBAAyB,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAElD,IAAI,CAAC,yBAAyB,EAAE;QAC9B,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,UAAU,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;KAC7F;IAED,oDAAoD;IACpD,IAAI,yBAAyB,KAAK,IAAI,EAAE;QACtC,qBAAqB;QACrB,OAAO,MAAM,CAAA;KACd;IAED,sCAAsC;IACtC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE;QACxB,EAAE,EAAE,IAAI;QACR,IAAI,EAAE,IAAI;QACV,IAAI,EAAE;0CAEF,EAAE,EAAE,KAAK,IACL,yBAAsC,KAC1C,IAAI;SAEP;KACF,CAAC,CAAA;IAEF,oCAAoC;IACpC,UAAU;IACV,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAErC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB,EAAE,MAAoB;IAC7D,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,MAAM,CAAA;IACjD,IAAI,CAAC,eAAe,EAAE;QACpB,OAAO,MAAM,CAAA;KACd;IACD,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,gDAAgD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;KAC1F;IACD,IAAI,CAAC,OAAO,EAAE;QACZ,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;KACrF;IAED,uCACK,MAAM,KACT,UAAU,kCACL,MAAM,CAAC,UAAU,KACpB,CAAC,IAAI,CAAC,EAAE;gBACN,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,8BAAmB,CAAC,eAAe,CAAC;aAC9C,OAEJ;AACH,CAAC;AAED,SAAS,WAAW,CAAC,CAAQ;IAC3B,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAmB,EAAE,EAAU;IACzD,QAAQ,EAAE,CAAC,EAAE,EAAE;QACb,KAAK,KAAK;YACR,OAAO,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAChC,KAAK,QAAQ;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;QAC9C,KAAK,QAAQ;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,CAAA;QAC1D,KAAK,IAAI;YACP,OAAO,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAC7B,KAAK,KAAK;YACR,OAAO,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;QACnC,KAAK,MAAM;YACT,OAAO,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QACjC,KAAK,MAAM;YACT,OAAO,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QACjC,KAAK,OAAO;YACV,OAAO,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;QAChD,KAAK,QAAQ;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;QACjD,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAEjC;YACE,WAAW,CAAC,EAAE,CAAC,CAAA,CAAC,uBAAuB;YACvC,OAAO,IAAI,CAAA;KACd;AACH,CAAC;AACD,SAAgB,YAAY,CAAC,MAAmB,EAAE,IAAgB;IAChE,OAAO,IAAI,CAAC,MAAM,CAAc,CAAC,MAAmB,EAAE,EAAU,EAAE,EAAE;QAClE,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;QAC1E,OAAO,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACvC,CAAC,EAAE,MAAqB,CAAC,CAAA;AAC3B,CAAC;AALD,oCAKC;AAED,SAAgB,aAAa,CAAC,IAAgB;IAC5C,MAAM,WAAW,GAAG;QAClB,OAAO,EAAE,wCAAwC;QACjD,IAAI,EAAE,QAAiB;QACvB,oBAAoB,EAAE,KAAK;KAC5B,CAAA;IAED,OAAO,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;AACxC,CAAC;AARD,sCAQC"}
--------------------------------------------------------------------------------
/dist/lens-graph.d.ts:
--------------------------------------------------------------------------------
1 | import { Graph } from 'graphlib';
2 | import { LensSource } from '.';
3 | import { JSONSchema7 } from 'json-schema';
4 | export interface LensGraph {
5 | graph: Graph;
6 | }
7 | export declare function initLensGraph(): LensGraph;
8 | export declare function registerLens({ graph }: LensGraph, from: string, to: string, lenses: LensSource): LensGraph;
9 | export declare function lensGraphSchemas({ graph }: LensGraph): string[];
10 | export declare function lensGraphSchema({ graph }: LensGraph, schema: string): JSONSchema7;
11 | export declare function lensFromTo({ graph }: LensGraph, from: string, to: string): LensSource;
12 |
--------------------------------------------------------------------------------
/dist/lens-graph.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.lensFromTo = exports.lensGraphSchema = exports.lensGraphSchemas = exports.registerLens = exports.initLensGraph = void 0;
4 | const graphlib_1 = require("graphlib");
5 | const _1 = require(".");
6 | const json_schema_1 = require("./json-schema");
7 | function initLensGraph() {
8 | const lensGraph = { graph: new graphlib_1.Graph() };
9 | lensGraph.graph.setNode('mu', json_schema_1.emptySchema);
10 | return lensGraph;
11 | }
12 | exports.initLensGraph = initLensGraph;
13 | // Add a new lens to the schema graph.
14 | // If the "to" schema doesn't exist yet, registers the schema too.
15 | // Returns a copy of the graph with the new contents.
16 | function registerLens({ graph }, from, to, lenses) {
17 | // clone the graph to ensure this is a pure function
18 | graph = graphlib_1.json.read(graphlib_1.json.write(graph)); // (these are graphlib's jsons)
19 | if (!graph.node(from)) {
20 | throw new RangeError(`unknown schema ${from}`);
21 | }
22 | const existingLens = graph.edge({ v: from, w: to });
23 | if (existingLens) {
24 | // we could assert this? assert.deepEqual(existingLens, lenses)
25 | // we've already registered a lens on this edge, hope it's the same one!
26 | // todo: maybe warn here? seems dangerous to silently return...
27 | return { graph };
28 | }
29 | if (!graph.node(to)) {
30 | graph.setNode(to, _1.updateSchema(graph.node(from), lenses));
31 | }
32 | graph.setEdge(from, to, lenses);
33 | graph.setEdge(to, from, _1.reverseLens(lenses));
34 | return { graph };
35 | }
36 | exports.registerLens = registerLens;
37 | function lensGraphSchemas({ graph }) {
38 | return graph.nodes();
39 | }
40 | exports.lensGraphSchemas = lensGraphSchemas;
41 | function lensGraphSchema({ graph }, schema) {
42 | return graph.node(schema);
43 | }
44 | exports.lensGraphSchema = lensGraphSchema;
45 | function lensFromTo({ graph }, from, to) {
46 | if (!graph.hasNode(from)) {
47 | throw new Error(`couldn't find schema in graph: ${from}`);
48 | }
49 | if (!graph.hasNode(to)) {
50 | throw new Error(`couldn't find schema in graph: ${to}`);
51 | }
52 | const migrationPaths = graphlib_1.alg.dijkstra(graph, to);
53 | const lenses = [];
54 | if (migrationPaths[from].distance == Infinity) {
55 | throw new Error(`no path found from ${from} to ${to}`);
56 | }
57 | if (migrationPaths[from].distance == 0) {
58 | return [];
59 | }
60 | for (let v = from; v != to; v = migrationPaths[v].predecessor) {
61 | const w = migrationPaths[v].predecessor;
62 | const edge = graph.edge({ v, w });
63 | lenses.push(...edge);
64 | }
65 | return lenses;
66 | }
67 | exports.lensFromTo = lensFromTo;
68 | //# sourceMappingURL=lens-graph.js.map
--------------------------------------------------------------------------------
/dist/lens-graph.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"lens-graph.js","sourceRoot":"","sources":["../src/lens-graph.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/lens-loader.d.ts:
--------------------------------------------------------------------------------
1 | import { LensSource } from './lens-ops';
2 | interface YAMLLens {
3 | lens: LensSource;
4 | }
5 | export declare function loadLens(rawLens: YAMLLens): LensSource;
6 | export declare function loadYamlLens(lensData: string): LensSource;
7 | export {};
8 |
--------------------------------------------------------------------------------
/dist/lens-loader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.loadYamlLens = exports.loadLens = void 0;
7 | const js_yaml_1 = __importDefault(require("js-yaml"));
8 | const foldInOp = (lensOpJson) => {
9 | const opName = Object.keys(lensOpJson)[0];
10 | // the json format is
11 | // {"": {opArgs}}
12 | // and the internal format is
13 | // {op: , ...opArgs}
14 | const data = lensOpJson[opName];
15 | if (['in', 'map'].includes(opName)) {
16 | data.lens = data.lens.map((lensOp) => foldInOp(lensOp));
17 | }
18 | const op = Object.assign({ op: opName }, data);
19 | return op;
20 | };
21 | function loadLens(rawLens) {
22 | return rawLens.lens
23 | .filter((o) => o !== null)
24 | .map((lensOpJson) => foldInOp(lensOpJson));
25 | }
26 | exports.loadLens = loadLens;
27 | function loadYamlLens(lensData) {
28 | const rawLens = js_yaml_1.default.safeLoad(lensData);
29 | if (!rawLens || typeof rawLens !== 'object')
30 | throw new Error('Error loading lens');
31 | if (!('lens' in rawLens))
32 | throw new Error(`Expected top-level key 'lens' in YAML lens file`);
33 | // we could have a root op to make this consistent...
34 | return loadLens(rawLens);
35 | }
36 | exports.loadYamlLens = loadYamlLens;
37 | //# sourceMappingURL=lens-loader.js.map
--------------------------------------------------------------------------------
/dist/lens-loader.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"lens-loader.js","sourceRoot":"","sources":["../src/lens-loader.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA0B;AAO1B,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAU,EAAE;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAEzC,qBAAqB;IACrB,yBAAyB;IACzB,6BAA6B;IAC7B,4BAA4B;IAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;KACxD;IAED,MAAM,EAAE,mBAAK,EAAE,EAAE,MAAM,IAAK,IAAI,CAAE,CAAA;IAClC,OAAO,EAAE,CAAA;AACX,CAAC,CAAA;AAED,SAAgB,QAAQ,CAAC,OAAiB;IACxC,OAAQ,OAAO,CAAC,IAAmB;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SACzB,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAA;AAC9C,CAAC;AAJD,4BAIC;AAED,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,OAAO,GAAG,iBAAI,CAAC,QAAQ,CAAC,QAAQ,CAAa,CAAA;IACnD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IAClF,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;IAE5F,qDAAqD;IACrD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAA;AAC1B,CAAC;AAPD,oCAOC"}
--------------------------------------------------------------------------------
/dist/lens-ops.d.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7TypeName } from 'json-schema';
2 | export interface Property {
3 | name?: string;
4 | type: JSONSchema7TypeName | JSONSchema7TypeName[];
5 | default?: any;
6 | required?: boolean;
7 | items?: Property;
8 | }
9 | export interface AddProperty extends Property {
10 | op: 'add';
11 | }
12 | export interface RemoveProperty extends Property {
13 | op: 'remove';
14 | }
15 | export interface RenameProperty {
16 | op: 'rename';
17 | source: string;
18 | destination: string;
19 | }
20 | export interface HoistProperty {
21 | op: 'hoist';
22 | name: string;
23 | host: string;
24 | }
25 | export interface PlungeProperty {
26 | op: 'plunge';
27 | name: string;
28 | host: string;
29 | }
30 | export interface WrapProperty {
31 | op: 'wrap';
32 | name: string;
33 | }
34 | export interface HeadProperty {
35 | op: 'head';
36 | name: string;
37 | }
38 | export interface LensIn {
39 | op: 'in';
40 | name: string;
41 | lens: LensSource;
42 | }
43 | export interface LensMap {
44 | op: 'map';
45 | lens: LensSource;
46 | }
47 | export declare type ValueMapping = {
48 | [key: string]: any;
49 | }[];
50 | export interface ConvertValue {
51 | op: 'convert';
52 | name: string;
53 | mapping: ValueMapping;
54 | sourceType?: JSONSchema7TypeName;
55 | destinationType?: JSONSchema7TypeName;
56 | }
57 | export declare type LensOp = AddProperty | RemoveProperty | RenameProperty | HoistProperty | WrapProperty | HeadProperty | PlungeProperty | LensIn | LensMap | ConvertValue;
58 | export declare type LensSource = LensOp[];
59 |
--------------------------------------------------------------------------------
/dist/lens-ops.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | //# sourceMappingURL=lens-ops.js.map
--------------------------------------------------------------------------------
/dist/lens-ops.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"lens-ops.js","sourceRoot":"","sources":["../src/lens-ops.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/dist/patch.d.ts:
--------------------------------------------------------------------------------
1 | import { Operation } from 'fast-json-patch';
2 | import { JSONSchema7 } from 'json-schema';
3 | import { LensSource } from './lens-ops';
4 | export declare type PatchOp = Operation;
5 | declare type MaybePatchOp = PatchOp | null;
6 | export declare type Patch = Operation[];
7 | export declare type CompiledLens = (patch: Patch, targetDoc: any) => Patch;
8 | export declare function compile(lensSource: LensSource): {
9 | right: CompiledLens;
10 | left: CompiledLens;
11 | };
12 | export declare function applyLensToPatch(lensSource: LensSource, patch: Patch, patchSchema: JSONSchema7): Patch;
13 | export declare function applyLensToPatchOp(lensSource: LensSource, patchOp: MaybePatchOp): MaybePatchOp;
14 | export declare function expandPatch(patchOp: PatchOp): PatchOp[];
15 | export {};
16 |
--------------------------------------------------------------------------------
/dist/patch.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.expandPatch = exports.applyLensToPatchOp = exports.applyLensToPatch = exports.compile = void 0;
4 | const reverse_1 = require("./reverse");
5 | const defaults_1 = require("./defaults");
6 | const json_schema_1 = require("./json-schema");
7 | function assertNever(x) {
8 | throw new Error(`Unexpected object: ${x}`);
9 | }
10 | function noNulls(items) {
11 | return items.filter((x) => x !== null);
12 | }
13 | // Provide curried functions that incorporate the lenses internally;
14 | // this is useful for exposing a pre-baked converter function to developers
15 | // without them needing to access the lens themselves
16 | // TODO: the public interface could just be runLens and reverseLens
17 | // ... maybe also composeLens?
18 | function compile(lensSource) {
19 | return {
20 | right: (patch, targetDoc) => applyLensToPatch(lensSource, patch, targetDoc),
21 | left: (patch, targetDoc) => applyLensToPatch(reverse_1.reverseLens(lensSource), patch, targetDoc),
22 | };
23 | }
24 | exports.compile = compile;
25 | // given a patch, returns a new patch that has had the lens applied to it.
26 | function applyLensToPatch(lensSource, patch, patchSchema // the json schema for the doc the patch was operating on
27 | ) {
28 | // expand patches that set nested objects into scalar patches
29 | const expandedPatch = patch.map((op) => expandPatch(op)).flat();
30 | // send everything through the lens
31 | const lensedPatch = noNulls(expandedPatch.map((patchOp) => applyLensToPatchOp(lensSource, patchOp)));
32 | // add in default values needed (based on the new schema after lensing)
33 | const readerSchema = json_schema_1.updateSchema(patchSchema, lensSource);
34 | const lensedPatchWithDefaults = defaults_1.addDefaultValues(lensedPatch, readerSchema);
35 | return lensedPatchWithDefaults;
36 | }
37 | exports.applyLensToPatch = applyLensToPatch;
38 | // todo: remove destinationDoc entirely
39 | function applyLensToPatchOp(lensSource, patchOp) {
40 | return lensSource.reduce((prevPatch, lensOp) => {
41 | return runLensOp(lensOp, prevPatch);
42 | }, patchOp);
43 | }
44 | exports.applyLensToPatchOp = applyLensToPatchOp;
45 | function runLensOp(lensOp, patchOp) {
46 | if (patchOp === null) {
47 | return null;
48 | }
49 | switch (lensOp.op) {
50 | case 'rename':
51 | if (
52 | // TODO: what about other JSON patch op types?
53 | // (consider other parts of JSON patch: move / copy / test / remove ?)
54 | (patchOp.op === 'replace' || patchOp.op === 'add') &&
55 | patchOp.path.split('/')[1] === lensOp.source) {
56 | const path = patchOp.path.replace(lensOp.source, lensOp.destination);
57 | return Object.assign(Object.assign({}, patchOp), { path });
58 | }
59 | break;
60 | case 'hoist': {
61 | // leading slash needs trimming
62 | const pathElements = patchOp.path.substr(1).split('/');
63 | const [possibleSource, possibleDestination, ...rest] = pathElements;
64 | if (possibleSource === lensOp.host && possibleDestination === lensOp.name) {
65 | const path = ['', lensOp.name, ...rest].join('/');
66 | return Object.assign(Object.assign({}, patchOp), { path });
67 | }
68 | break;
69 | }
70 | case 'plunge': {
71 | const pathElements = patchOp.path.substr(1).split('/');
72 | const [head] = pathElements;
73 | if (head === lensOp.name) {
74 | const path = ['', lensOp.host, pathElements].join('/');
75 | return Object.assign(Object.assign({}, patchOp), { path });
76 | }
77 | break;
78 | }
79 | case 'wrap': {
80 | const pathComponent = new RegExp(`^/(${lensOp.name})(.*)`);
81 | const match = patchOp.path.match(pathComponent);
82 | if (match) {
83 | const path = `/${match[1]}/0${match[2]}`;
84 | if ((patchOp.op === 'add' || patchOp.op === 'replace') &&
85 | patchOp.value === null &&
86 | match[2] === '') {
87 | return { op: 'remove', path };
88 | }
89 | return Object.assign(Object.assign({}, patchOp), { path });
90 | }
91 | break;
92 | }
93 | case 'head': {
94 | // break early if we're not handling a write to the array handled by this lens
95 | const arrayMatch = patchOp.path.split('/')[1] === lensOp.name;
96 | if (!arrayMatch)
97 | break;
98 | // We only care about writes to the head element, nothing else matters
99 | const headMatch = patchOp.path.match(new RegExp(`^/${lensOp.name}/0(.*)`));
100 | if (!headMatch)
101 | return null;
102 | if (patchOp.op === 'add' || patchOp.op === 'replace') {
103 | // If the write is to the first array element, write to the scalar
104 | return {
105 | op: patchOp.op,
106 | path: `/${lensOp.name}${headMatch[1] || ''}`,
107 | value: patchOp.value,
108 | };
109 | }
110 | if (patchOp.op === 'remove') {
111 | if (headMatch[1] === '') {
112 | return {
113 | op: 'replace',
114 | path: `/${lensOp.name}${headMatch[1] || ''}`,
115 | value: null,
116 | };
117 | }
118 | else {
119 | return Object.assign(Object.assign({}, patchOp), { path: `/${lensOp.name}${headMatch[1] || ''}` });
120 | }
121 | }
122 | break;
123 | }
124 | case 'add':
125 | // hmm, what do we do here? perhaps write the default value if there's nothing
126 | // already written into the doc there?
127 | // (could be a good use case for destinationDoc)
128 | break;
129 | case 'remove':
130 | if (patchOp.path.split('/')[1] === lensOp.name)
131 | return null;
132 | break;
133 | case 'in': {
134 | // Run the inner body in a context where the path has been narrowed down...
135 | const pathComponent = new RegExp(`^/${lensOp.name}`);
136 | if (patchOp.path.match(pathComponent)) {
137 | const childPatch = applyLensToPatchOp(lensOp.lens, Object.assign(Object.assign({}, patchOp), { path: patchOp.path.replace(pathComponent, '') }));
138 | if (childPatch) {
139 | return Object.assign(Object.assign({}, childPatch), { path: `/${lensOp.name}${childPatch.path}` });
140 | }
141 | else {
142 | return null;
143 | }
144 | }
145 | break;
146 | }
147 | case 'map': {
148 | const arrayIndexMatch = patchOp.path.match(/\/([0-9]+)\//);
149 | if (!arrayIndexMatch)
150 | break;
151 | const arrayIndex = arrayIndexMatch[1];
152 | const itemPatch = applyLensToPatchOp(lensOp.lens, Object.assign(Object.assign({}, patchOp), { path: patchOp.path.replace(/\/[0-9]+\//, '/') }));
153 | if (itemPatch) {
154 | return Object.assign(Object.assign({}, itemPatch), { path: `/${arrayIndex}${itemPatch.path}` });
155 | }
156 | return null;
157 | }
158 | case 'convert': {
159 | if (patchOp.op !== 'add' && patchOp.op !== 'replace')
160 | break;
161 | if (`/${lensOp.name}` !== patchOp.path)
162 | break;
163 | const stringifiedValue = String(patchOp.value);
164 | // todo: should we add in support for fallback/default conversions
165 | if (!Object.keys(lensOp.mapping[0]).includes(stringifiedValue)) {
166 | throw new Error(`No mapping for value: ${stringifiedValue}`);
167 | }
168 | return Object.assign(Object.assign({}, patchOp), { value: lensOp.mapping[0][stringifiedValue] });
169 | }
170 | default:
171 | assertNever(lensOp); // exhaustiveness check
172 | }
173 | return patchOp;
174 | }
175 | function expandPatch(patchOp) {
176 | // this only applies for add and replace ops; no expansion to do otherwise
177 | // todo: check the whole list of json patch verbs
178 | if (patchOp.op !== 'add' && patchOp.op !== 'replace')
179 | return [patchOp];
180 | if (patchOp.value && typeof patchOp.value === 'object') {
181 | let result = [
182 | {
183 | op: patchOp.op,
184 | path: patchOp.path,
185 | value: Array.isArray(patchOp.value) ? [] : {},
186 | },
187 | ];
188 | result = result.concat(Object.entries(patchOp.value).map(([key, value]) => {
189 | return expandPatch({
190 | op: patchOp.op,
191 | path: `${patchOp.path}/${key}`,
192 | value,
193 | });
194 | }));
195 | return result.flat(Infinity);
196 | }
197 | return [patchOp];
198 | }
199 | exports.expandPatch = expandPatch;
200 | //# sourceMappingURL=patch.js.map
--------------------------------------------------------------------------------
/dist/patch.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"patch.js","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":";;;AAGA,uCAAuC;AACvC,yCAA6C;AAC7C,+CAA4C;AAS5C,SAAS,WAAW,CAAC,CAAQ;IAC3B,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED,SAAS,OAAO,CAAI,KAAmB;IACrC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAU,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;AAChD,CAAC;AAED,oEAAoE;AACpE,2EAA2E;AAC3E,qDAAqD;AACrD,mEAAmE;AACnE,8BAA8B;AAC9B,SAAgB,OAAO,CAAC,UAAsB;IAC5C,OAAO;QACL,KAAK,EAAE,CAAC,KAAY,EAAE,SAAc,EAAE,EAAE,CAAC,gBAAgB,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC;QACvF,IAAI,EAAE,CAAC,KAAY,EAAE,SAAc,EAAE,EAAE,CACrC,gBAAgB,CAAC,qBAAW,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC;KAC9D,CAAA;AACH,CAAC;AAND,0BAMC;AAED,0EAA0E;AAC1E,SAAgB,gBAAgB,CAC9B,UAAsB,EACtB,KAAY,EACZ,WAAwB,CAAC,yDAAyD;;IAElF,6DAA6D;IAC7D,MAAM,aAAa,GAAU,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAEtE,mCAAmC;IACnC,MAAM,WAAW,GAAG,OAAO,CACzB,aAAa,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CACxE,CAAA;IAED,uEAAuE;IACvE,MAAM,YAAY,GAAG,0BAAY,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IAC1D,MAAM,uBAAuB,GAAG,2BAAgB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;IAE3E,OAAO,uBAAuB,CAAA;AAChC,CAAC;AAlBD,4CAkBC;AAED,uCAAuC;AACvC,SAAgB,kBAAkB,CAAC,UAAsB,EAAE,OAAqB;IAC9E,OAAO,UAAU,CAAC,MAAM,CAAe,CAAC,SAAuB,EAAE,MAAc,EAAE,EAAE;QACjF,OAAO,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACrC,CAAC,EAAE,OAAO,CAAC,CAAA;AACb,CAAC;AAJD,gDAIC;AAED,SAAS,SAAS,CAAC,MAAc,EAAE,OAAqB;IACtD,IAAI,OAAO,KAAK,IAAI,EAAE;QACpB,OAAO,IAAI,CAAA;KACZ;IAED,QAAQ,MAAM,CAAC,EAAE,EAAE;QACjB,KAAK,QAAQ;YACX;YACE,8CAA8C;YAC9C,sEAAsE;YACtE,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,KAAK,KAAK,CAAC;gBAClD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,EAC5C;gBACA,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;gBACpE,uCAAY,OAAO,KAAE,IAAI,IAAE;aAC5B;YAED,MAAK;QAEP,KAAK,OAAO,CAAC,CAAC;YACZ,+BAA+B;YAC/B,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACtD,MAAM,CAAC,cAAc,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC,GAAG,YAAY,CAAA;YACnE,IAAI,cAAc,KAAK,MAAM,CAAC,IAAI,IAAI,mBAAmB,KAAK,MAAM,CAAC,IAAI,EAAE;gBACzE,MAAM,IAAI,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACjD,uCAAY,OAAO,KAAE,IAAI,IAAE;aAC5B;YACD,MAAK;SACN;QAED,KAAK,QAAQ,CAAC,CAAC;YACb,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACtD,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,CAAA;YAC3B,IAAI,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE;gBACxB,MAAM,IAAI,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACtD,uCAAY,OAAO,KAAE,IAAI,IAAE;aAC5B;YACD,MAAK;SACN;QAED,KAAK,MAAM,CAAC,CAAC;YACX,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,OAAO,CAAC,CAAA;YAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;YAC/C,IAAI,KAAK,EAAE;gBACT,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;gBACxC,IACE,CAAC,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC;oBAClD,OAAO,CAAC,KAAK,KAAK,IAAI;oBACtB,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EACf;oBACA,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;iBAC9B;gBACD,uCAAY,OAAO,KAAE,IAAI,IAAE;aAC5B;YACD,MAAK;SACN;QAED,KAAK,MAAM,CAAC,CAAC;YACX,8EAA8E;YAC9E,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,CAAA;YAC7D,IAAI,CAAC,UAAU;gBAAE,MAAK;YAEtB,sEAAsE;YACtE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAA;YAC1E,IAAI,CAAC,SAAS;gBAAE,OAAO,IAAI,CAAA;YAE3B,IAAI,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE;gBACpD,kEAAkE;gBAClE,OAAO;oBACL,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE;oBAC5C,KAAK,EAAE,OAAO,CAAC,KAAK;iBACrB,CAAA;aACF;YAED,IAAI,OAAO,CAAC,EAAE,KAAK,QAAQ,EAAE;gBAC3B,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;oBACvB,OAAO;wBACL,EAAE,EAAE,SAAkB;wBACtB,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE;wBAC5C,KAAK,EAAE,IAAI;qBACZ,CAAA;iBACF;qBAAM;oBACL,uCAAY,OAAO,KAAE,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAE;iBACpE;aACF;YAED,MAAK;SACN;QAED,KAAK,KAAK;YACR,8EAA8E;YAC9E,sCAAsC;YACtC,gDAAgD;YAChD,MAAK;QAEP,KAAK,QAAQ;YACX,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAA;YAC3D,MAAK;QAEP,KAAK,IAAI,CAAC,CAAC;YACT,2EAA2E;YAC3E,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;YACpD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE;gBACrC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,kCAC5C,OAAO,KACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,IAC7C,CAAA;gBAEF,IAAI,UAAU,EAAE;oBACd,uCAAY,UAAU,KAAE,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,IAAE;iBACpE;qBAAM;oBACL,OAAO,IAAI,CAAA;iBACZ;aACF;YACD,MAAK;SACN;QAED,KAAK,KAAK,CAAC,CAAC;YACV,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;YAC1D,IAAI,CAAC,eAAe;gBAAE,MAAK;YAC3B,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;YACrC,MAAM,SAAS,GAAG,kBAAkB,CAClC,MAAM,CAAC,IAAI,kCACN,OAAO,KAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,IAE5D,CAAA;YAED,IAAI,SAAS,EAAE;gBACb,uCAAY,SAAS,KAAE,IAAI,EAAE,IAAI,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,IAAE;aACjE;YACD,OAAO,IAAI,CAAA;SACZ;QAED,KAAK,SAAS,CAAC,CAAC;YACd,IAAI,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;gBAAE,MAAK;YAC3D,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI;gBAAE,MAAK;YAC7C,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAE9C,kEAAkE;YAClE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE;gBAC9D,MAAM,IAAI,KAAK,CAAC,yBAAyB,gBAAgB,EAAE,CAAC,CAAA;aAC7D;YAED,uCAAY,OAAO,KAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAE;SAClE;QAED;YACE,WAAW,CAAC,MAAM,CAAC,CAAA,CAAC,uBAAuB;KAC9C;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAgB,WAAW,CAAC,OAAgB;IAC1C,0EAA0E;IAC1E,iDAAiD;IACjD,IAAI,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAEtE,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;QACtD,IAAI,MAAM,GAAU;YAClB;gBACE,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;aAC9C;SACF,CAAA;QAED,MAAM,GAAG,MAAM,CAAC,MAAM,CACpB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YACjD,OAAO,WAAW,CAAC;gBACjB,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE;gBAC9B,KAAK;aACN,CAAC,CAAA;QACJ,CAAC,CAAC,CACH,CAAA;QAED,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;KAC7B;IACD,OAAO,CAAC,OAAO,CAAC,CAAA;AAClB,CAAC;AA3BD,kCA2BC"}
--------------------------------------------------------------------------------
/dist/reverse.d.ts:
--------------------------------------------------------------------------------
1 | import { LensSource } from './lens-ops';
2 | export declare function reverseLens(lens: LensSource): LensSource;
3 |
--------------------------------------------------------------------------------
/dist/reverse.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.reverseLens = void 0;
4 | function assertNever(x) {
5 | throw new Error(`Unexpected object: ${x}`);
6 | }
7 | function reverseLens(lens) {
8 | return lens
9 | .slice()
10 | .reverse()
11 | .map((l) => reverseLensOp(l));
12 | }
13 | exports.reverseLens = reverseLens;
14 | function reverseLensOp(lensOp) {
15 | switch (lensOp.op) {
16 | case 'rename':
17 | return Object.assign(Object.assign({}, lensOp), { source: lensOp.destination, destination: lensOp.source });
18 | case 'add': {
19 | return Object.assign(Object.assign({}, lensOp), { op: 'remove' });
20 | }
21 | case 'remove':
22 | return Object.assign(Object.assign({}, lensOp), { op: 'add' });
23 | case 'wrap':
24 | return Object.assign(Object.assign({}, lensOp), { op: 'head' });
25 | case 'head':
26 | return Object.assign(Object.assign({}, lensOp), { op: 'wrap' });
27 | case 'in':
28 | case 'map':
29 | return Object.assign(Object.assign({}, lensOp), { lens: reverseLens(lensOp.lens) });
30 | case 'hoist':
31 | return Object.assign(Object.assign({}, lensOp), { op: 'plunge' });
32 | case 'plunge':
33 | return Object.assign(Object.assign({}, lensOp), { op: 'hoist' });
34 | case 'convert': {
35 | const mapping = [lensOp.mapping[1], lensOp.mapping[0]];
36 | const reversed = Object.assign(Object.assign({}, lensOp), { mapping, sourceType: lensOp.destinationType, destinationType: lensOp.sourceType });
37 | return reversed;
38 | }
39 | default:
40 | return assertNever(lensOp); // exhaustiveness check
41 | }
42 | }
43 | //# sourceMappingURL=reverse.js.map
--------------------------------------------------------------------------------
/dist/reverse.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"reverse.js","sourceRoot":"","sources":["../src/reverse.ts"],"names":[],"mappings":";;;AAEA,SAAS,WAAW,CAAC,CAAQ;IAC3B,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED,SAAgB,WAAW,CAAC,IAAgB;IAC1C,OAAO,IAAI;SACR,KAAK,EAAE;SACP,OAAO,EAAE;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;AACjC,CAAC;AALD,kCAKC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,QAAQ,MAAM,CAAC,EAAE,EAAE;QACjB,KAAK,QAAQ;YACX,uCACK,MAAM,KACT,MAAM,EAAE,MAAM,CAAC,WAAW,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,IAC3B;QAEH,KAAK,KAAK,CAAC,CAAC;YACV,uCACK,MAAM,KACT,EAAE,EAAE,QAAQ,IACb;SACF;QAED,KAAK,QAAQ;YACX,uCACK,MAAM,KACT,EAAE,EAAE,KAAK,IACV;QAEH,KAAK,MAAM;YACT,uCACK,MAAM,KACT,EAAE,EAAE,MAAM,IACX;QACH,KAAK,MAAM;YACT,uCACK,MAAM,KACT,EAAE,EAAE,MAAM,IACX;QAEH,KAAK,IAAI,CAAC;QACV,KAAK,KAAK;YACR,uCAAY,MAAM,KAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,IAAE;QAEtD,KAAK,OAAO;YACV,uCACK,MAAM,KACT,EAAE,EAAE,QAAQ,IACb;QACH,KAAK,QAAQ;YACX,uCACK,MAAM,KACT,EAAE,EAAE,OAAO,IACZ;QACH,KAAK,SAAS,CAAC,CAAC;YACd,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;YACtD,MAAM,QAAQ,mCACT,MAAM,KACT,OAAO,EACP,UAAU,EAAE,MAAM,CAAC,eAAe,EAClC,eAAe,EAAE,MAAM,CAAC,UAAU,GACnC,CAAA;YAED,OAAO,QAAQ,CAAA;SAChB;QAED;YACE,OAAO,WAAW,CAAC,MAAM,CAAC,CAAA,CAAC,uBAAuB;KACrD;AACH,CAAC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cambria",
3 | "version": "0.1.2",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "license": "MIT",
7 | "scripts": {
8 | "build": "tsc --outDir ./dist --project ./tsconfig.json",
9 | "test": "mocha -r ts-node/register test/**.ts",
10 | "prepare": "yarn run build",
11 | "build-demo": "browserify ./demo/web-components/cambria-demo.js -o ./demo/bundle.js"
12 | },
13 | "dependencies": {
14 | "commander": "^5.1.0",
15 | "fast-json-patch": "^3.0.0-1",
16 | "graphlib": "^2.1.8",
17 | "js-yaml": "^3.14.0",
18 | "json-schema": "^0.2.5",
19 | "to-json-schema": "^0.2.5"
20 | },
21 | "devDependencies": {
22 | "@json-editor/json-editor": "^2.3.0",
23 | "@types/graphlib": "^2.1.6",
24 | "@types/mocha": "^8.0.0",
25 | "@types/node": "^14.0.23",
26 | "@typescript-eslint/eslint-plugin": "^3.6.1",
27 | "@typescript-eslint/parser": "^3.6.1",
28 | "ajv": "^6.12.4",
29 | "assert": "^2.0.0",
30 | "browserify": "^16.5.2",
31 | "eslint": "^7.4.0",
32 | "eslint-config-airbnb-base": "^14.2.0",
33 | "eslint-config-prettier": "^6.11.0",
34 | "eslint-plugin-import": "^2.22.0",
35 | "eslint-plugin-jsx-a11y": "^6.3.1",
36 | "eslint-plugin-prettier": "^3.1.4",
37 | "json-schema-view-js": "^2.0.1",
38 | "mocha": "^8.0.1",
39 | "prettier": "^2.0.5",
40 | "ts-node": "^8.10.2",
41 | "typescript": "^3.9.6"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/cambria-lens-schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema",
3 | "$id": "lens",
4 | "title": "Cambria Lens",
5 | "type": "object",
6 | "properties": {
7 | "schemaName": {
8 | "type": "string",
9 | "title": "Schema name",
10 | "description": "The name of the schema being extended"
11 | },
12 | "lens": { "$ref": "#/definitions/lens" }
13 | },
14 | "required": ["schemaName", "lens"],
15 | "definitions": {
16 | "jsonSchemaType": {
17 | "type": "string",
18 | "enum": ["string", "boolean", "null", "object", "array", "number"]
19 | },
20 | "fields": {
21 | "dataType": {
22 | "allOf": [
23 | { "$ref": "#/definitions/jsonSchemaType" },
24 | {
25 | "title": "Type",
26 | "description": "The type of the new property"
27 | }
28 | ]
29 | },
30 | "name": {
31 | "type": "string",
32 | "title": "Name",
33 | "description": "The name of the property to operate on"
34 | },
35 | "default": {
36 | "title": "Default",
37 | "description": "The default value used when none can be found."
38 | },
39 | "req": {
40 | "title": "Required",
41 | "description": "Is the value required?",
42 | "optional": true
43 | }
44 | },
45 | "valueMapping": {
46 | "type": "array",
47 | "items": [
48 | {
49 | "type": "object",
50 | "title": "Old-to-new Map",
51 | "description": "A lookup table where keys will be translated to values when running the lens forward."
52 | },
53 | {
54 | "type": "object",
55 | "title": "New-to-old Map",
56 | "description": "A lookup table where keys will be translated to values when running the lens backwards."
57 | }
58 | ]
59 | },
60 | "lens": {
61 | "type": "array",
62 | "title": "Lens",
63 | "description": "A lens to apply inside of the given context",
64 | "items": { "$ref": "#/definitions/lensOp" }
65 | },
66 | "lensOp": {
67 | "title": "Lens Operation",
68 | "description": "One step in a lens conversion",
69 | "oneOf": [
70 | {
71 | "type": "object",
72 | "additionalProperties": false,
73 | "properties": {
74 | "add": {
75 | "type": "object",
76 | "additionalProperties": false,
77 | "properties": {
78 | "name": { "$ref": "#/definitions/fields/name" },
79 | "type": { "$ref": "#/definitions/fields/dataType" },
80 | "default": { "$ref": "#/definitions/fields/default" },
81 | "required": { "$ref": "#/definitions/fields/req" }
82 | }
83 | }
84 | }
85 | },
86 | {
87 | "type": "object",
88 | "additionalProperties": false,
89 | "properties": {
90 | "remove": {
91 | "type": "object",
92 | "additionalProperties": false,
93 | "properties": {
94 | "name": { "$ref": "#/definitions/fields/name" },
95 | "type": { "$ref": "#/definitions/fields/dataType" },
96 | "default": { "$ref": "#/definitions/fields/default" }
97 | }
98 | }
99 | }
100 | },
101 | {
102 | "type": "object",
103 | "additionalProperties": false,
104 | "properties": {
105 | "rename": {
106 | "type": "object",
107 | "additionalProperties": false,
108 | "properties": {
109 | "source": {
110 | "type": "string",
111 | "title": "Source",
112 | "description": "The old name for the property"
113 | },
114 | "destination": {
115 | "type": "string",
116 | "title": "Destination",
117 | "description": "The new name for the property"
118 | }
119 | }
120 | }
121 | }
122 | },
123 | {
124 | "type": "object",
125 | "additionalProperties": false,
126 | "properties": {
127 | "hoist": {
128 | "type": "object",
129 | "additionalProperties": false,
130 | "properties": {
131 | "name": { "$ref": "#/definitions/fields/name" },
132 | "host": {
133 | "type": "string",
134 | "title": "Host",
135 | "description": "The property name of the containing object to hoist out of"
136 | }
137 | }
138 | }
139 | }
140 | },
141 | {
142 | "type": "object",
143 | "additionalProperties": false,
144 | "properties": {
145 | "plunge": {
146 | "type": "object",
147 | "additionalProperties": false,
148 | "properties": {
149 | "name": { "$ref": "#/definitions/fields/name" },
150 | "host": {
151 | "type": "string",
152 | "title": "Host",
153 | "description": "The property name of the containing object to plunge into"
154 | }
155 | }
156 | }
157 | }
158 | },
159 | {
160 | "type": "object",
161 | "additionalProperties": false,
162 | "properties": {
163 | "wrap": {
164 | "type": "object",
165 | "additionalProperties": false,
166 | "properties": {
167 | "name": { "$ref": "#/definitions/fields/name" }
168 | }
169 | }
170 | }
171 | },
172 | {
173 | "type": "object",
174 | "additionalProperties": false,
175 | "properties": {
176 | "head": {
177 | "type": "object",
178 | "additionalProperties": false,
179 | "properties": {
180 | "name": { "$ref": "#/definitions/fields/name" }
181 | }
182 | }
183 | }
184 | },
185 | {
186 | "type": "object",
187 | "additionalProperties": false,
188 | "properties": {
189 | "in": {
190 | "type": "object",
191 | "additionalProperties": false,
192 | "properties": {
193 | "name": {
194 | "type": "string",
195 | "title": "Name",
196 | "description": "The property name in which the sub-lens will be run"
197 | },
198 | "lens": { "$ref": "#/definitions/lens" }
199 | }
200 | }
201 | }
202 | },
203 | {
204 | "type": "object",
205 | "additionalProperties": false,
206 | "properties": {
207 | "map": {
208 | "type": "object",
209 | "additionalProperties": false,
210 | "properties": {
211 | "lens": { "$ref": "#/definitions/lens" }
212 | }
213 | }
214 | }
215 | },
216 | {
217 | "type": "object",
218 | "additionalProperties": false,
219 | "properties": {
220 | "convert": {
221 | "type": "object",
222 | "additionalProperties": false,
223 | "properties": {
224 | "name": { "$ref": "#/definitions/fields/name" },
225 | "sourceType": { "$ref": "#/definitions/fields/dataType" },
226 | "destinationType": { "$ref": "#/definitions/fields/dataType" },
227 | "mapping": { "$ref": "#/definitions/valueMapping" }
228 | }
229 | }
230 | }
231 | }
232 | ]
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { program } from 'commander'
2 | import { readFileSync } from 'fs'
3 |
4 | import { reverseLens } from './reverse'
5 | import { applyLensToDoc } from './doc'
6 | import { loadYamlLens } from './lens-loader'
7 |
8 | program
9 | .description('// A CLI document conversion tool for cambria')
10 | .requiredOption('-l, --lens ', 'lens source as yaml')
11 | .option('-i, --input ', 'input document filename')
12 | .option('-s, --schema ', 'json schema for input document')
13 | .option('-b, --base ', 'base document filename')
14 | .option('-r, --reverse', 'run the lens in reverse')
15 |
16 | program.parse(process.argv)
17 |
18 | // read doc from stdin if no input specified
19 | const input = readFileSync(program.input || 0, 'utf-8')
20 | const baseDoc = program.base ? JSON.parse(readFileSync(program.base, 'utf-8')) : {}
21 | const doc = JSON.parse(input)
22 | const lensData = readFileSync(program.lens, 'utf-8')
23 |
24 | let lens = loadYamlLens(lensData)
25 |
26 | if (program.reverse) {
27 | lens = reverseLens(lens)
28 | }
29 |
30 | const newDoc = applyLensToDoc(lens, doc, program.schema, baseDoc)
31 |
32 | console.log(JSON.stringify(newDoc, null, 4))
33 |
--------------------------------------------------------------------------------
/src/defaults.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-use-before-define */
2 | import { applyPatch } from 'fast-json-patch'
3 | import { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from 'json-schema'
4 | import { Patch } from './patch'
5 |
6 | /**
7 | * behaviour:
8 | * - if we have an array of types where null is an option, that's our default
9 | * - otherwise use the first type in the array to pick a default from the table
10 | * - otherwise just use the value to lookup in the table
11 | */
12 | const defaultValuesForType = {
13 | string: '',
14 | number: 0,
15 | boolean: false,
16 | array: [],
17 | object: {},
18 | }
19 | export function defaultValuesByType(
20 | type: JSONSchema7TypeName | JSONSchema7TypeName[]
21 | ): JSONSchema7['default'] {
22 | if (Array.isArray(type)) {
23 | if (type.includes('null')) {
24 | return null
25 | }
26 | return defaultValuesForType[type[0]]
27 | }
28 | return defaultValuesForType[type]
29 | }
30 |
31 | // Return a recursively filled-in default object for a given schema
32 | export function defaultObjectForSchema(schema: JSONSchema7): JSONSchema7 {
33 | // By setting the root to empty object,
34 | // we kick off a recursive process that fills in the entire thing
35 | const initializeRootPatch = [
36 | {
37 | op: 'add' as const,
38 | path: '',
39 | value: {},
40 | },
41 | ]
42 | const defaultsPatch = addDefaultValues(initializeRootPatch, schema)
43 |
44 | return applyPatch({}, defaultsPatch).newDocument
45 | }
46 |
47 | export function addDefaultValues(patch: Patch, schema: JSONSchema7): Patch {
48 | return patch
49 | .map((op) => {
50 | const isMakeMap =
51 | (op.op === 'add' || op.op === 'replace') &&
52 | op.value !== null &&
53 | typeof op.value === 'object' &&
54 | Object.entries(op.value).length === 0
55 |
56 | if (!isMakeMap) return op
57 |
58 | const objectProperties = getPropertiesForPath(schema, op.path)
59 |
60 | return [
61 | op,
62 | // fill in default values for each property on the object
63 | ...Object.entries(objectProperties).map(([propName, propSchema]) => {
64 | if (typeof propSchema !== 'object') throw new Error(`Missing property ${propName}`)
65 | const path = `${op.path}/${propName}`
66 |
67 | // Fill in a default iff:
68 | // 1) it's an object or array: init to empty
69 | // 2) it's another type and there's a default value set.
70 | // TODO: is this right?
71 | // Should we allow defaulting containers to non-empty? seems like no.
72 | // Should we fill in "default defaults" like empty string?
73 | // I think better to let the json schema explicitly define defaults
74 | let defaultValue
75 | if (propSchema.type === 'object') {
76 | defaultValue = {}
77 | } else if (propSchema.type === 'array') {
78 | defaultValue = []
79 | } else if ('default' in propSchema) {
80 | defaultValue = propSchema.default
81 | } else if (Array.isArray(propSchema.type) && propSchema.type.includes('null')) {
82 | defaultValue = null
83 | }
84 |
85 | if (defaultValue !== undefined) {
86 | // todo: this is a TS hint, see if we can remove
87 | if (op.op !== 'add' && op.op !== 'replace') throw new Error('')
88 | return addDefaultValues([{ ...op, path, value: defaultValue }], schema)
89 | }
90 | return []
91 | }),
92 | ].flat(Infinity)
93 | })
94 | .flat(Infinity) as Patch
95 | }
96 |
97 | // given a json schema and a json path to an object field somewhere in that schema,
98 | // return the json schema for the object being pointed to
99 | function getPropertiesForPath(
100 | schema: JSONSchema7,
101 | path: string
102 | ): { [key: string]: JSONSchema7Definition } {
103 | const pathComponents = path.split('/').slice(1)
104 | const { properties } = pathComponents.reduce((schema: JSONSchema7, pathSegment: string) => {
105 | const types = Array.isArray(schema.type) ? schema.type : [schema.type]
106 | if (types.includes('object')) {
107 | const schemaForProperty = schema.properties && schema.properties[pathSegment]
108 | if (typeof schemaForProperty !== 'object') throw new Error('Expected object')
109 | return schemaForProperty
110 | }
111 | if (types.includes('array')) {
112 | // throw away the array index, just return the schema for array items
113 | if (!schema.items || typeof schema.items !== 'object')
114 | throw new Error('Expected array items to have types')
115 |
116 | // todo: revisit this "as", was a huge pain to get this past TS
117 | return schema.items as JSONSchema7
118 | }
119 | throw new Error('Expected object or array in schema based on JSON Pointer')
120 | }, schema)
121 |
122 | if (properties === undefined) return {}
123 | return properties
124 | }
125 |
--------------------------------------------------------------------------------
/src/doc.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2 | import { JSONSchema7 } from 'json-schema'
3 | import { compare, applyPatch } from 'fast-json-patch'
4 | import toJSONSchema from 'to-json-schema'
5 |
6 | import { defaultObjectForSchema } from './defaults'
7 | import { Patch, applyLensToPatch } from './patch'
8 | import { LensSource } from './lens-ops'
9 | import { updateSchema } from './json-schema'
10 |
11 | /**
12 | * importDoc - convert any Plain Old Javascript Object into an implied JSON Schema and
13 | * a JSON Patch that sets every value in that document.
14 | * @param inputDoc a document to convert into a big JSON patch describing its full contents
15 | */
16 | export function importDoc(inputDoc: any): [JSONSchema7, Patch] {
17 | const options = {
18 | postProcessFnc: (type, schema, obj, defaultFnc) => ({
19 | ...defaultFnc(type, schema, obj),
20 | type: [type, 'null'],
21 | }),
22 | objects: {
23 | postProcessFnc: (schema, obj, defaultFnc) => ({
24 | ...defaultFnc(schema, obj),
25 | required: Object.getOwnPropertyNames(obj),
26 | }),
27 | },
28 | }
29 |
30 | const schema = toJSONSchema(inputDoc, options) as JSONSchema7
31 | const patch = compare({}, inputDoc)
32 |
33 | return [schema, patch]
34 | }
35 |
36 | /**
37 | * applyLensToDoc - converts a full document through a lens.
38 | * Under the hood, we convert your input doc into a big patch and the apply it to the targetDoc.
39 | * This allows merging data back and forth with other omitted values.
40 | * @property lensSource: the lens specification to apply to the document
41 | * @property inputDoc: the Plain Old Javascript Object to convert
42 | * @property inputSchema: (default: inferred from inputDoc) a JSON schema defining the input
43 | * @property targetDoc: (default: {}) a document to apply the contents of this document to as a patch
44 | */
45 | export function applyLensToDoc(
46 | lensSource: LensSource,
47 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
48 | inputDoc: any,
49 | inputSchema?: JSONSchema7,
50 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
51 | targetDoc?: any
52 | ): any {
53 | const [impliedSchema, patchForOriginalDoc] = importDoc(inputDoc)
54 |
55 | if (inputSchema === undefined || inputSchema === null) {
56 | inputSchema = impliedSchema
57 | }
58 |
59 | // construct the "base" upon which we will apply the patches from doc.
60 | // We start with the default object for the output schema,
61 | // then we add in any existing fields on the target doc.
62 | // TODO: I think we need to deep merge here, can't just shallow merge?
63 | const outputSchema = updateSchema(inputSchema, lensSource)
64 | const base = Object.assign(defaultObjectForSchema(outputSchema), targetDoc || {})
65 |
66 | // return a doc based on the converted patch.
67 | // (start with either a specified baseDoc, or just empty doc)
68 | // convert the patch through the lens
69 | const outputPatch = applyLensToPatch(lensSource, patchForOriginalDoc, inputSchema)
70 | return applyPatch(base, outputPatch).newDocument
71 | }
72 |
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
1 | // helper functions for nicer syntax
2 | // (we might write our own parser later, but at least for now
3 | // this avoids seeing the raw json...)
4 |
5 | import { JSONSchema7TypeName } from 'json-schema'
6 | import {
7 | LensSource,
8 | LensMap,
9 | LensIn,
10 | Property,
11 | AddProperty,
12 | RemoveProperty,
13 | RenameProperty,
14 | HoistProperty,
15 | PlungeProperty,
16 | WrapProperty,
17 | HeadProperty,
18 | ValueMapping,
19 | ConvertValue,
20 | } from './lens-ops'
21 |
22 | export function addProperty(property: Property): AddProperty {
23 | return {
24 | op: 'add',
25 | ...property,
26 | }
27 | }
28 |
29 | export function removeProperty(property: Property): RemoveProperty {
30 | return {
31 | op: 'remove',
32 | ...property,
33 | }
34 | }
35 |
36 | export function renameProperty(source: string, destination: string): RenameProperty {
37 | return {
38 | op: 'rename',
39 | source,
40 | destination,
41 | }
42 | }
43 |
44 | export function hoistProperty(host: string, name: string): HoistProperty {
45 | return {
46 | op: 'hoist',
47 | host,
48 | name,
49 | }
50 | }
51 |
52 | export function plungeProperty(host: string, name: string): PlungeProperty {
53 | return {
54 | op: 'plunge',
55 | host,
56 | name,
57 | }
58 | }
59 |
60 | export function wrapProperty(name: string): WrapProperty {
61 | return {
62 | op: 'wrap',
63 | name,
64 | }
65 | }
66 |
67 | export function headProperty(name: string): HeadProperty {
68 | return {
69 | op: 'head',
70 | name,
71 | }
72 | }
73 |
74 | export function inside(name: string, lens: LensSource): LensIn {
75 | return {
76 | op: 'in',
77 | name,
78 | lens,
79 | }
80 | }
81 |
82 | export function map(lens: LensSource): LensMap {
83 | return {
84 | op: 'map',
85 | lens,
86 | }
87 | }
88 |
89 | export function convertValue(
90 | name: string,
91 | mapping: ValueMapping,
92 | sourceType?: JSONSchema7TypeName,
93 | destinationType?: JSONSchema7TypeName
94 | ): ConvertValue {
95 | return {
96 | op: 'convert',
97 | name,
98 | mapping,
99 | sourceType,
100 | destinationType,
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // TODO: The exported surface is fairly large right now,
2 | // See how much we can narrow this.
3 |
4 | export { updateSchema, schemaForLens } from './json-schema'
5 | export { compile, applyLensToPatch, Patch, CompiledLens } from './patch'
6 | export { applyLensToDoc, importDoc } from './doc'
7 | export { LensSource, LensOp, Property } from './lens-ops'
8 | export { defaultObjectForSchema } from './defaults'
9 | export { reverseLens } from './reverse'
10 | export { LensGraph, initLensGraph, registerLens, lensGraphSchema, lensFromTo } from './lens-graph'
11 |
12 | export {
13 | addProperty,
14 | removeProperty,
15 | renameProperty,
16 | hoistProperty,
17 | plungeProperty,
18 | wrapProperty,
19 | headProperty,
20 | inside,
21 | map,
22 | convertValue,
23 | } from './helpers'
24 |
25 | export { loadYamlLens } from './lens-loader'
26 |
--------------------------------------------------------------------------------
/src/json-schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from 'json-schema'
2 | import { inspect } from 'util'
3 | import { defaultValuesByType } from './defaults'
4 | import {
5 | Property,
6 | LensSource,
7 | ConvertValue,
8 | LensOp,
9 | HeadProperty,
10 | WrapProperty,
11 | LensIn,
12 | } from './lens-ops'
13 |
14 | export const emptySchema = {
15 | $schema: 'http://json-schema.org/draft-07/schema',
16 | type: 'object' as const,
17 | additionalProperties: false,
18 | }
19 |
20 | function deepInspect(object: any) {
21 | return inspect(object, false, null, true)
22 | }
23 |
24 | // add a property to a schema
25 | // note: property names are in json pointer with leading /
26 | // (because that's how our Property types work for now)
27 |
28 | // mutates the schema that is passed in
29 | // (should switch to a more functional style)
30 | function addProperty(schema: JSONSchema7, property: Property): JSONSchema7 {
31 | const { properties: origProperties = {}, required: origRequired = [] } = schema
32 | const { name, items, required: isPropertyRequired } = property
33 | let { type } = property
34 |
35 | if (!name || !type) {
36 | throw new Error(`Missing property name in addProperty.\nFound:\n${JSON.stringify(property)}`)
37 | }
38 |
39 | if (Array.isArray(type)) {
40 | type = type.map((t) => (t === null ? 'null' : t))
41 | }
42 |
43 | const arraylessPropertyDefinition = {
44 | type,
45 | default: property.default || defaultValuesByType(type), // default is a reserved keyword
46 | }
47 | // this is kludgey but you should see the crazy syntax for the alternative
48 | const propertyDefinition =
49 | type === 'array' && items
50 | ? {
51 | ...arraylessPropertyDefinition,
52 | items: { type: items.type, default: items.default || defaultValuesByType(items.type) },
53 | }
54 | : arraylessPropertyDefinition
55 |
56 | const properties = { ...origProperties, [name]: propertyDefinition }
57 | const shouldAdd = isPropertyRequired !== false && !origRequired.includes(name)
58 | const required = [...origRequired, ...(shouldAdd ? [name] : [])]
59 | return {
60 | ...schema,
61 | properties,
62 | required,
63 | }
64 | }
65 |
66 | function withNullable(schema: JSONSchema7, fn: (s: JSONSchema7) => JSONSchema7): JSONSchema7 {
67 | if (schema.anyOf) {
68 | if (schema.anyOf.length !== 2) {
69 | throw new Error('We only support this operation on schemas with one type or a nullable type')
70 | }
71 | return { ...schema, anyOf: schema.anyOf.map(db).map((s) => (s.type === 'null' ? s : fn(s))) }
72 | } else {
73 | return fn(schema)
74 | }
75 | }
76 |
77 | function renameProperty(_schema: JSONSchema7, from: string, to: string): JSONSchema7 {
78 | return withNullable(_schema, (schema) => {
79 | if (typeof schema !== 'object' || typeof schema.properties !== 'object') {
80 | throw new Error(`expected schema object, got ${JSON.stringify(schema)}`)
81 | }
82 | if (!from) {
83 | throw new Error("Rename property requires a 'source' to rename.")
84 | }
85 | if (!schema.properties[from]) {
86 | throw new Error(
87 | `Cannot rename property '${from}' because it does not exist among ${Object.keys(
88 | schema.properties
89 | )}.`
90 | )
91 | }
92 | if (!to) {
93 | throw new Error(`Need a 'destination' to rename ${from} to.`)
94 | }
95 |
96 | const { properties = {}, required = [] } = schema // extract properties with default of empty
97 | const { [from]: propDetails, ...rest } = properties // pull out the old value
98 |
99 | if (propDetails === undefined) {
100 | throw new Error(`Rename error: missing expected property ${from}`)
101 | }
102 |
103 | return {
104 | ...schema,
105 | properties: { [to]: propDetails, ...rest },
106 | required: [...required.filter((r) => r !== from), to],
107 | } // assign it to the new one
108 | })
109 | }
110 |
111 | // remove a property from a schema
112 | // property name is _not_ in JSON Pointer, no leading slash here.
113 | // (yes, that's inconsistent with addPropertyToSchema, which is bad)
114 | function removeProperty(schema: JSONSchema7, removedPointer: string): JSONSchema7 {
115 | const { properties = {}, required = [] } = schema
116 | const removed = removedPointer
117 | // we don't care about the `discarded` variable...
118 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
119 |
120 | if (!(removed in properties)) {
121 | throw new Error(`Attempting to remove nonexistent property: ${removed}`)
122 | }
123 |
124 | // no way to discard the
125 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
126 | const { [removed]: discarded, ...rest } = properties
127 |
128 | return {
129 | ...schema,
130 | properties: rest,
131 | required: required.filter((e) => e !== removed),
132 | }
133 | }
134 |
135 | function schemaSupportsType(
136 | typeValue: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined,
137 | type: JSONSchema7TypeName
138 | ): boolean {
139 | if (!typeValue) {
140 | return false
141 | }
142 | if (!Array.isArray(typeValue)) {
143 | typeValue = [typeValue]
144 | }
145 |
146 | return typeValue.includes(type)
147 | }
148 |
149 | /** db
150 | * removes the horrible, obnoxious, and annoying case where JSON schemas can just be
151 | * "true" or "false" meaning the below definitions and screwing up my type checker
152 | */
153 | function db(s: JSONSchema7Definition): JSONSchema7 {
154 | if (s === true) {
155 | return {}
156 | }
157 | if (s === false) {
158 | return { not: {} }
159 | }
160 | return s
161 | }
162 |
163 | function supportsNull(schema: JSONSchema7): boolean {
164 | return (
165 | schemaSupportsType(schema.type, 'null') ||
166 | !!schema.anyOf?.some((subSchema) => schemaSupportsType(db(subSchema).type, 'null'))
167 | )
168 | }
169 |
170 | function findHost(schema: JSONSchema7, name: string): JSONSchema7 {
171 | if (schema.anyOf) {
172 | const maybeSchema = schema.anyOf?.find((t) => typeof t === 'object' && t.properties)
173 | if (typeof maybeSchema === 'object' && typeof maybeSchema.properties === 'object') {
174 | const maybeHost = maybeSchema.properties[name]
175 | if (maybeHost !== false && maybeHost !== true) {
176 | return maybeHost
177 | }
178 | }
179 | } else if (schema.properties && schema.properties[name]) {
180 | const maybeHost = schema.properties[name]
181 | if (maybeHost !== false && maybeHost !== true) {
182 | return maybeHost
183 | }
184 | }
185 | throw new Error("Coudln't find the host for this data.")
186 | }
187 |
188 | function inSchema(schema: JSONSchema7, op: LensIn): JSONSchema7 {
189 | const properties: JSONSchema7 = schema.properties
190 | ? schema.properties
191 | : (schema.anyOf?.find((t) => typeof t === 'object' && t.properties) as any).properties
192 |
193 | if (!properties) {
194 | throw new Error("Cannot look 'in' an object that doesn't have properties.")
195 | }
196 |
197 | const { name, lens } = op
198 |
199 | if (!name) {
200 | throw new Error(`Expected to find property ${name} in ${Object.keys(op || {})}`)
201 | }
202 |
203 | const host = findHost(schema, name)
204 |
205 | if (host === undefined) {
206 | throw new Error(`Expected to find property ${name} in ${Object.keys(properties || {})}`)
207 | }
208 |
209 | const newProperties: JSONSchema7 = {
210 | ...properties,
211 | [name]: updateSchema(host, lens),
212 | }
213 |
214 | return {
215 | ...schema,
216 | properties: newProperties,
217 | } as JSONSchema7
218 | }
219 |
220 | type JSONSchema7Items = boolean | JSONSchema7 | JSONSchema7Definition[] | undefined
221 | function validateSchemaItems(items: JSONSchema7Items) {
222 | if (Array.isArray(items)) {
223 | throw new Error('Cambria only supports consistent types for arrays.')
224 | }
225 | if (!items || items === true) {
226 | throw new Error(`Cambria requires a specific items definition, found ${items}.`)
227 | }
228 | return items
229 | }
230 |
231 | function mapSchema(schema: JSONSchema7, lens: LensSource) {
232 | if (!lens) {
233 | throw new Error('Map requires a `lens` to map over the array.')
234 | }
235 | if (!schema.items) {
236 | throw new Error(`Map requires a schema with items to map over, ${deepInspect(schema)}`)
237 | }
238 | return { ...schema, items: updateSchema(validateSchemaItems(schema.items), lens) }
239 | }
240 |
241 | function filterScalarOrArray(v: T | T[], cb: (t: T) => boolean) {
242 | if (!Array.isArray(v)) {
243 | v = [v]
244 | }
245 | v = v.filter(cb)
246 | if (v.length === 1) {
247 | return v[0]
248 | }
249 | return v
250 | }
251 |
252 | // XXX: THIS SHOULD REMOVE DEFAULT: NULL
253 | function removeNullSupport(prop: JSONSchema7): JSONSchema7 | null {
254 | if (!supportsNull(prop)) {
255 | return prop
256 | }
257 | if (prop.type) {
258 | if (prop.type === 'null') {
259 | return null
260 | }
261 |
262 | prop = { ...prop, type: filterScalarOrArray(prop.type, (t) => t !== 'null') }
263 |
264 | if (prop.default === null) {
265 | prop.default = defaultValuesByType(prop.type!) // the above always assigns a legal type
266 | }
267 | }
268 |
269 | if (prop.anyOf) {
270 | const newAnyOf = prop.anyOf.reduce((acc: JSONSchema7[], s) => {
271 | const clean = removeNullSupport(db(s))
272 | return clean ? [...acc, clean] : acc
273 | }, [])
274 | if (newAnyOf.length === 1) {
275 | return newAnyOf[0]
276 | }
277 | prop = { ...prop, anyOf: newAnyOf }
278 | }
279 | return prop
280 | }
281 |
282 | function wrapProperty(schema: JSONSchema7, op: WrapProperty): JSONSchema7 {
283 | if (!op.name) {
284 | throw new Error('Wrap property requires a `name` to identify what to wrap.')
285 | }
286 |
287 | if (!schema.properties) {
288 | throw new Error('Cannot wrap a property here. There are no properties.')
289 | }
290 |
291 | const prop = db(schema.properties[op.name])
292 | if (!prop) {
293 | throw new Error(`Cannot wrap property '${op.name}' because it does not exist.`)
294 | }
295 |
296 | if (!supportsNull(prop)) {
297 | throw new Error(
298 | `Cannot wrap property '${op.name}' because it does not allow nulls, found ${deepInspect(
299 | schema
300 | )}`
301 | )
302 | }
303 |
304 | return {
305 | ...schema,
306 | properties: {
307 | ...schema.properties,
308 | [op.name]: {
309 | type: 'array',
310 | default: [],
311 | items: removeNullSupport(prop) || { not: {} },
312 | },
313 | },
314 | }
315 | }
316 |
317 | function headProperty(schema, op: HeadProperty) {
318 | if (!op.name) {
319 | throw new Error('Head requires a `name` to identify what to take head from.')
320 | }
321 | if (!schema.properties[op.name]) {
322 | throw new Error(`Cannot head property '${op.name}' because it does not exist.`)
323 | }
324 |
325 | return {
326 | ...schema,
327 | properties: {
328 | ...schema.properties,
329 | [op.name]: { anyOf: [{ type: 'null' }, schema.properties[op.name].items] },
330 | },
331 | }
332 | }
333 |
334 | function hoistProperty(_schema: JSONSchema7, host: string, name: string): JSONSchema7 {
335 | return withNullable(_schema, (schema) => {
336 | if (schema.properties === undefined) {
337 | throw new Error(`Can't hoist when root schema isn't an object`)
338 | }
339 | if (!host) {
340 | throw new Error(`Need a \`host\` property to hoist from.`)
341 | }
342 | if (!name) {
343 | throw new Error(`Need to provide a \`name\` to hoist up`)
344 | }
345 |
346 | const { properties } = schema
347 | if (!(host in properties)) {
348 | throw new Error(
349 | `Can't hoist anything from ${host}, it does not exist here. (Found properties ${Object.keys(
350 | properties
351 | )})`
352 | )
353 | }
354 |
355 | const hoistedPropertySchema = withNullable(db(properties[host]), (hostSchema) => {
356 | const hostProperties = hostSchema.properties
357 | const hostRequired = hostSchema.required || []
358 | if (!hostProperties) {
359 | throw new Error(
360 | `There are no properties to hoist out of ${host}, found ${Object.keys(hostSchema)}`
361 | )
362 | }
363 | if (!(name in hostProperties)) {
364 | throw new Error(
365 | `Can't hoist anything from ${host}, it does not exist here. (Found properties ${Object.keys(
366 | properties
367 | )})`
368 | )
369 | }
370 | const { [name]: target, ...remainingProperties } = hostProperties
371 | return {
372 | ...hostSchema,
373 | properties: remainingProperties,
374 | required: hostRequired.filter((e) => e !== name),
375 | }
376 | })
377 | const childObject = withNullable(db(properties[host]), (hostSchema) => {
378 | const hostProperties = hostSchema.properties!
379 | const { [name]: target } = hostProperties
380 | return db(target)
381 | })
382 |
383 | return {
384 | ...schema,
385 | properties: {
386 | ...schema.properties,
387 | [host]: hoistedPropertySchema,
388 | [name]: childObject,
389 | },
390 | required: [...(schema.required || []), name],
391 | }
392 | })
393 | }
394 |
395 | function plungeProperty(schema: JSONSchema7, host: string, name: string) {
396 | // XXXX what should we do for missing child properties? error?
397 | const { properties = {} } = schema
398 |
399 | if (!host) {
400 | throw new Error(`Need a \`host\` property to plunge into`)
401 | }
402 |
403 | if (!name) {
404 | throw new Error(`Need to provide a \`name\` to plunge`)
405 | }
406 |
407 | const destinationTypeProperties = properties[name]
408 |
409 | if (!destinationTypeProperties) {
410 | throw new Error(`Could not find a property called ${name} among ${Object.keys(properties)}`)
411 | }
412 |
413 | // we can throw an error here if things are missing?
414 | if (destinationTypeProperties === true) {
415 | // errrr... complain?
416 | return schema
417 | }
418 |
419 | // add the property to the root schema
420 | schema = inSchema(schema, {
421 | op: 'in',
422 | name: host,
423 | lens: [
424 | {
425 | op: 'add',
426 | ...(destinationTypeProperties as Property),
427 | name,
428 | },
429 | ],
430 | })
431 |
432 | // remove it from its current parent
433 | // PS: ugh
434 | schema = removeProperty(schema, name)
435 |
436 | return schema
437 | }
438 |
439 | function convertValue(schema: JSONSchema7, lensOp: ConvertValue) {
440 | const { name, destinationType, mapping } = lensOp
441 | if (!destinationType) {
442 | return schema
443 | }
444 | if (!name) {
445 | throw new Error(`Missing property name in 'convert'.\nFound:\n${JSON.stringify(lensOp)}`)
446 | }
447 | if (!mapping) {
448 | throw new Error(`Missing mapping for 'convert'.\nFound:\n${JSON.stringify(lensOp)}`)
449 | }
450 |
451 | return {
452 | ...schema,
453 | properties: {
454 | ...schema.properties,
455 | [name]: {
456 | type: destinationType,
457 | default: defaultValuesByType(destinationType),
458 | },
459 | },
460 | }
461 | }
462 |
463 | function assertNever(x: never): never {
464 | throw new Error(`Unexpected object: ${x}`)
465 | }
466 |
467 | function applyLensOperation(schema: JSONSchema7, op: LensOp) {
468 | switch (op.op) {
469 | case 'add':
470 | return addProperty(schema, op)
471 | case 'remove':
472 | return removeProperty(schema, op.name || '')
473 | case 'rename':
474 | return renameProperty(schema, op.source, op.destination)
475 | case 'in':
476 | return inSchema(schema, op)
477 | case 'map':
478 | return mapSchema(schema, op.lens)
479 | case 'wrap':
480 | return wrapProperty(schema, op)
481 | case 'head':
482 | return headProperty(schema, op)
483 | case 'hoist':
484 | return hoistProperty(schema, op.host, op.name)
485 | case 'plunge':
486 | return plungeProperty(schema, op.host, op.name)
487 | case 'convert':
488 | return convertValue(schema, op)
489 |
490 | default:
491 | assertNever(op) // exhaustiveness check
492 | return null
493 | }
494 | }
495 | export function updateSchema(schema: JSONSchema7, lens: LensSource): JSONSchema7 {
496 | return lens.reduce((schema: JSONSchema7, op: LensOp) => {
497 | if (schema === undefined) throw new Error("Can't update undefined schema")
498 | return applyLensOperation(schema, op)
499 | }, schema as JSONSchema7)
500 | }
501 |
502 | export function schemaForLens(lens: LensSource): JSONSchema7 {
503 | const emptySchema = {
504 | $schema: 'http://json-schema.org/draft-07/schema',
505 | type: 'object' as const,
506 | additionalProperties: false,
507 | }
508 |
509 | return updateSchema(emptySchema, lens)
510 | }
511 |
--------------------------------------------------------------------------------
/src/lens-graph.ts:
--------------------------------------------------------------------------------
1 | import { Graph, alg, json } from 'graphlib'
2 | import { LensSource, LensOp, updateSchema, reverseLens } from '.'
3 | import { emptySchema } from './json-schema'
4 | import { JSONSchema7 } from 'json-schema'
5 |
6 | export interface LensGraph {
7 | graph: Graph
8 | }
9 |
10 | export function initLensGraph(): LensGraph {
11 | const lensGraph: LensGraph = { graph: new Graph() }
12 |
13 | lensGraph.graph.setNode('mu', emptySchema)
14 | return lensGraph
15 | }
16 |
17 | // Add a new lens to the schema graph.
18 | // If the "to" schema doesn't exist yet, registers the schema too.
19 | // Returns a copy of the graph with the new contents.
20 | export function registerLens(
21 | { graph }: LensGraph,
22 | from: string,
23 | to: string,
24 | lenses: LensSource
25 | ): LensGraph {
26 | // clone the graph to ensure this is a pure function
27 | graph = json.read(json.write(graph)) // (these are graphlib's jsons)
28 |
29 | if (!graph.node(from)) {
30 | throw new RangeError(`unknown schema ${from}`)
31 | }
32 |
33 | const existingLens = graph.edge({ v: from, w: to })
34 | if (existingLens) {
35 | // we could assert this? assert.deepEqual(existingLens, lenses)
36 | // we've already registered a lens on this edge, hope it's the same one!
37 | // todo: maybe warn here? seems dangerous to silently return...
38 | return { graph }
39 | }
40 |
41 | if (!graph.node(to)) {
42 | graph.setNode(to, updateSchema(graph.node(from), lenses))
43 | }
44 |
45 | graph.setEdge(from, to, lenses)
46 | graph.setEdge(to, from, reverseLens(lenses))
47 |
48 | return { graph }
49 | }
50 |
51 | export function lensGraphSchemas({ graph }: LensGraph): string[] {
52 | return graph.nodes()
53 | }
54 |
55 | export function lensGraphSchema({ graph }: LensGraph, schema: string): JSONSchema7 {
56 | return graph.node(schema)
57 | }
58 |
59 | export function lensFromTo({ graph }: LensGraph, from: string, to: string): LensSource {
60 | if (!graph.hasNode(from)) {
61 | throw new Error(`couldn't find schema in graph: ${from}`)
62 | }
63 |
64 | if (!graph.hasNode(to)) {
65 | throw new Error(`couldn't find schema in graph: ${to}`)
66 | }
67 |
68 | const migrationPaths = alg.dijkstra(graph, to)
69 | const lenses: LensOp[] = []
70 | if (migrationPaths[from].distance == Infinity) {
71 | throw new Error(`no path found from ${from} to ${to}`)
72 | }
73 | if (migrationPaths[from].distance == 0) {
74 | return []
75 | }
76 | for (let v = from; v != to; v = migrationPaths[v].predecessor) {
77 | const w = migrationPaths[v].predecessor
78 | const edge = graph.edge({ v, w })
79 | lenses.push(...edge)
80 | }
81 | return lenses
82 | }
83 |
--------------------------------------------------------------------------------
/src/lens-loader.ts:
--------------------------------------------------------------------------------
1 | import YAML from 'js-yaml'
2 | import { LensSource, LensOp } from './lens-ops'
3 |
4 | interface YAMLLens {
5 | lens: LensSource
6 | }
7 |
8 | const foldInOp = (lensOpJson): LensOp => {
9 | const opName = Object.keys(lensOpJson)[0]
10 |
11 | // the json format is
12 | // {"": {opArgs}}
13 | // and the internal format is
14 | // {op: , ...opArgs}
15 | const data = lensOpJson[opName]
16 | if (['in', 'map'].includes(opName)) {
17 | data.lens = data.lens.map((lensOp) => foldInOp(lensOp))
18 | }
19 |
20 | const op = { op: opName, ...data }
21 | return op
22 | }
23 |
24 | export function loadLens(rawLens: YAMLLens): LensSource {
25 | return (rawLens.lens as LensSource)
26 | .filter((o) => o !== null)
27 | .map((lensOpJson) => foldInOp(lensOpJson))
28 | }
29 |
30 | export function loadYamlLens(lensData: string): LensSource {
31 | const rawLens = YAML.safeLoad(lensData) as YAMLLens
32 | if (!rawLens || typeof rawLens !== 'object') throw new Error('Error loading lens')
33 | if (!('lens' in rawLens)) throw new Error(`Expected top-level key 'lens' in YAML lens file`)
34 |
35 | // we could have a root op to make this consistent...
36 | return loadLens(rawLens)
37 | }
38 |
--------------------------------------------------------------------------------
/src/lens-ops.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7TypeName } from 'json-schema'
2 |
3 | export interface Property {
4 | name?: string
5 | type: JSONSchema7TypeName | JSONSchema7TypeName[]
6 | default?: any
7 | required?: boolean
8 | items?: Property
9 | }
10 |
11 | export interface AddProperty extends Property {
12 | op: 'add'
13 | }
14 |
15 | export interface RemoveProperty extends Property {
16 | op: 'remove'
17 | }
18 |
19 | export interface RenameProperty {
20 | op: 'rename'
21 | source: string
22 | destination: string
23 | }
24 |
25 | export interface HoistProperty {
26 | op: 'hoist'
27 | name: string
28 | host: string
29 | }
30 |
31 | export interface PlungeProperty {
32 | op: 'plunge'
33 | name: string
34 | host: string
35 | }
36 | export interface WrapProperty {
37 | op: 'wrap'
38 | name: string
39 | }
40 |
41 | export interface HeadProperty {
42 | op: 'head'
43 | name: string
44 | }
45 |
46 | export interface LensIn {
47 | op: 'in'
48 | name: string
49 | lens: LensSource
50 | }
51 |
52 | export interface LensMap {
53 | op: 'map'
54 | lens: LensSource
55 | }
56 |
57 | // ideally this would be a tuple, but the typechecker
58 | // wouldn't let me assign a flipped array in the reverse lens op
59 | export type ValueMapping = { [key: string]: any }[]
60 |
61 | // Notes on value conversion:
62 | // - Types are optional, only needed if the type is actually changing
63 | // - We only support hardcoded mappings for the time being;
64 | // can consider further conversions later
65 | export interface ConvertValue {
66 | op: 'convert'
67 | name: string
68 | mapping: ValueMapping
69 | sourceType?: JSONSchema7TypeName
70 | destinationType?: JSONSchema7TypeName
71 | }
72 |
73 | export type LensOp =
74 | | AddProperty
75 | | RemoveProperty
76 | | RenameProperty
77 | | HoistProperty
78 | | WrapProperty
79 | | HeadProperty
80 | | PlungeProperty
81 | | LensIn
82 | | LensMap
83 | | ConvertValue
84 |
85 | export type LensSource = LensOp[]
86 |
--------------------------------------------------------------------------------
/src/patch.ts:
--------------------------------------------------------------------------------
1 | import { Operation } from 'fast-json-patch'
2 | import { JSONSchema7 } from 'json-schema'
3 | import { LensSource, LensOp } from './lens-ops'
4 | import { reverseLens } from './reverse'
5 | import { addDefaultValues } from './defaults'
6 | import { updateSchema } from './json-schema'
7 |
8 | // todo: we're throwing away the type param right now so it doesn't actually do anything.
9 | // can we actually find a way to keep it around and typecheck patches against a type?
10 | export type PatchOp = Operation
11 | type MaybePatchOp = PatchOp | null
12 | export type Patch = Operation[]
13 | export type CompiledLens = (patch: Patch, targetDoc: any) => Patch
14 |
15 | function assertNever(x: never): never {
16 | throw new Error(`Unexpected object: ${x}`)
17 | }
18 |
19 | function noNulls(items: (T | null)[]) {
20 | return items.filter((x): x is T => x !== null)
21 | }
22 |
23 | // Provide curried functions that incorporate the lenses internally;
24 | // this is useful for exposing a pre-baked converter function to developers
25 | // without them needing to access the lens themselves
26 | // TODO: the public interface could just be runLens and reverseLens
27 | // ... maybe also composeLens?
28 | export function compile(lensSource: LensSource): { right: CompiledLens; left: CompiledLens } {
29 | return {
30 | right: (patch: Patch, targetDoc: any) => applyLensToPatch(lensSource, patch, targetDoc),
31 | left: (patch: Patch, targetDoc: any) =>
32 | applyLensToPatch(reverseLens(lensSource), patch, targetDoc),
33 | }
34 | }
35 |
36 | // given a patch, returns a new patch that has had the lens applied to it.
37 | export function applyLensToPatch(
38 | lensSource: LensSource,
39 | patch: Patch,
40 | patchSchema: JSONSchema7 // the json schema for the doc the patch was operating on
41 | ): Patch {
42 | // expand patches that set nested objects into scalar patches
43 | const expandedPatch: Patch = patch.map((op) => expandPatch(op)).flat()
44 |
45 | // send everything through the lens
46 | const lensedPatch = noNulls(
47 | expandedPatch.map((patchOp) => applyLensToPatchOp(lensSource, patchOp))
48 | )
49 |
50 | // add in default values needed (based on the new schema after lensing)
51 | const readerSchema = updateSchema(patchSchema, lensSource)
52 | const lensedPatchWithDefaults = addDefaultValues(lensedPatch, readerSchema)
53 |
54 | return lensedPatchWithDefaults
55 | }
56 |
57 | // todo: remove destinationDoc entirely
58 | export function applyLensToPatchOp(lensSource: LensSource, patchOp: MaybePatchOp): MaybePatchOp {
59 | return lensSource.reduce((prevPatch: MaybePatchOp, lensOp: LensOp) => {
60 | return runLensOp(lensOp, prevPatch)
61 | }, patchOp)
62 | }
63 |
64 | function runLensOp(lensOp: LensOp, patchOp: MaybePatchOp): MaybePatchOp {
65 | if (patchOp === null) {
66 | return null
67 | }
68 |
69 | switch (lensOp.op) {
70 | case 'rename':
71 | if (
72 | // TODO: what about other JSON patch op types?
73 | // (consider other parts of JSON patch: move / copy / test / remove ?)
74 | (patchOp.op === 'replace' || patchOp.op === 'add') &&
75 | patchOp.path.split('/')[1] === lensOp.source
76 | ) {
77 | const path = patchOp.path.replace(lensOp.source, lensOp.destination)
78 | return { ...patchOp, path }
79 | }
80 |
81 | break
82 |
83 | case 'hoist': {
84 | // leading slash needs trimming
85 | const pathElements = patchOp.path.substr(1).split('/')
86 | const [possibleSource, possibleDestination, ...rest] = pathElements
87 | if (possibleSource === lensOp.host && possibleDestination === lensOp.name) {
88 | const path = ['', lensOp.name, ...rest].join('/')
89 | return { ...patchOp, path }
90 | }
91 | break
92 | }
93 |
94 | case 'plunge': {
95 | const pathElements = patchOp.path.substr(1).split('/')
96 | const [head] = pathElements
97 | if (head === lensOp.name) {
98 | const path = ['', lensOp.host, pathElements].join('/')
99 | return { ...patchOp, path }
100 | }
101 | break
102 | }
103 |
104 | case 'wrap': {
105 | const pathComponent = new RegExp(`^/(${lensOp.name})(.*)`)
106 | const match = patchOp.path.match(pathComponent)
107 | if (match) {
108 | const path = `/${match[1]}/0${match[2]}`
109 | if (
110 | (patchOp.op === 'add' || patchOp.op === 'replace') &&
111 | patchOp.value === null &&
112 | match[2] === ''
113 | ) {
114 | return { op: 'remove', path }
115 | }
116 | return { ...patchOp, path }
117 | }
118 | break
119 | }
120 |
121 | case 'head': {
122 | // break early if we're not handling a write to the array handled by this lens
123 | const arrayMatch = patchOp.path.split('/')[1] === lensOp.name
124 | if (!arrayMatch) break
125 |
126 | // We only care about writes to the head element, nothing else matters
127 | const headMatch = patchOp.path.match(new RegExp(`^/${lensOp.name}/0(.*)`))
128 | if (!headMatch) return null
129 |
130 | if (patchOp.op === 'add' || patchOp.op === 'replace') {
131 | // If the write is to the first array element, write to the scalar
132 | return {
133 | op: patchOp.op,
134 | path: `/${lensOp.name}${headMatch[1] || ''}`,
135 | value: patchOp.value,
136 | }
137 | }
138 |
139 | if (patchOp.op === 'remove') {
140 | if (headMatch[1] === '') {
141 | return {
142 | op: 'replace' as const,
143 | path: `/${lensOp.name}${headMatch[1] || ''}`,
144 | value: null,
145 | }
146 | } else {
147 | return { ...patchOp, path: `/${lensOp.name}${headMatch[1] || ''}` }
148 | }
149 | }
150 |
151 | break
152 | }
153 |
154 | case 'add':
155 | // hmm, what do we do here? perhaps write the default value if there's nothing
156 | // already written into the doc there?
157 | // (could be a good use case for destinationDoc)
158 | break
159 |
160 | case 'remove':
161 | if (patchOp.path.split('/')[1] === lensOp.name) return null
162 | break
163 |
164 | case 'in': {
165 | // Run the inner body in a context where the path has been narrowed down...
166 | const pathComponent = new RegExp(`^/${lensOp.name}`)
167 | if (patchOp.path.match(pathComponent)) {
168 | const childPatch = applyLensToPatchOp(lensOp.lens, {
169 | ...patchOp,
170 | path: patchOp.path.replace(pathComponent, ''),
171 | })
172 |
173 | if (childPatch) {
174 | return { ...childPatch, path: `/${lensOp.name}${childPatch.path}` }
175 | } else {
176 | return null
177 | }
178 | }
179 | break
180 | }
181 |
182 | case 'map': {
183 | const arrayIndexMatch = patchOp.path.match(/\/([0-9]+)\//)
184 | if (!arrayIndexMatch) break
185 | const arrayIndex = arrayIndexMatch[1]
186 | const itemPatch = applyLensToPatchOp(
187 | lensOp.lens,
188 | { ...patchOp, path: patchOp.path.replace(/\/[0-9]+\//, '/') }
189 | // Then add the parent path back to the beginning of the results
190 | )
191 |
192 | if (itemPatch) {
193 | return { ...itemPatch, path: `/${arrayIndex}${itemPatch.path}` }
194 | }
195 | return null
196 | }
197 |
198 | case 'convert': {
199 | if (patchOp.op !== 'add' && patchOp.op !== 'replace') break
200 | if (`/${lensOp.name}` !== patchOp.path) break
201 | const stringifiedValue = String(patchOp.value)
202 |
203 | // todo: should we add in support for fallback/default conversions
204 | if (!Object.keys(lensOp.mapping[0]).includes(stringifiedValue)) {
205 | throw new Error(`No mapping for value: ${stringifiedValue}`)
206 | }
207 |
208 | return { ...patchOp, value: lensOp.mapping[0][stringifiedValue] }
209 | }
210 |
211 | default:
212 | assertNever(lensOp) // exhaustiveness check
213 | }
214 |
215 | return patchOp
216 | }
217 |
218 | export function expandPatch(patchOp: PatchOp): PatchOp[] {
219 | // this only applies for add and replace ops; no expansion to do otherwise
220 | // todo: check the whole list of json patch verbs
221 | if (patchOp.op !== 'add' && patchOp.op !== 'replace') return [patchOp]
222 |
223 | if (patchOp.value && typeof patchOp.value === 'object') {
224 | let result: any[] = [
225 | {
226 | op: patchOp.op,
227 | path: patchOp.path,
228 | value: Array.isArray(patchOp.value) ? [] : {},
229 | },
230 | ]
231 |
232 | result = result.concat(
233 | Object.entries(patchOp.value).map(([key, value]) => {
234 | return expandPatch({
235 | op: patchOp.op,
236 | path: `${patchOp.path}/${key}`,
237 | value,
238 | })
239 | })
240 | )
241 |
242 | return result.flat(Infinity)
243 | }
244 | return [patchOp]
245 | }
246 |
--------------------------------------------------------------------------------
/src/reverse.ts:
--------------------------------------------------------------------------------
1 | import { LensSource, LensOp } from './lens-ops'
2 |
3 | function assertNever(x: never): never {
4 | throw new Error(`Unexpected object: ${x}`)
5 | }
6 |
7 | export function reverseLens(lens: LensSource): LensSource {
8 | return lens
9 | .slice()
10 | .reverse()
11 | .map((l) => reverseLensOp(l))
12 | }
13 |
14 | function reverseLensOp(lensOp: LensOp): LensOp {
15 | switch (lensOp.op) {
16 | case 'rename':
17 | return {
18 | ...lensOp,
19 | source: lensOp.destination,
20 | destination: lensOp.source,
21 | }
22 |
23 | case 'add': {
24 | return {
25 | ...lensOp,
26 | op: 'remove',
27 | }
28 | }
29 |
30 | case 'remove':
31 | return {
32 | ...lensOp,
33 | op: 'add',
34 | }
35 |
36 | case 'wrap':
37 | return {
38 | ...lensOp,
39 | op: 'head',
40 | }
41 | case 'head':
42 | return {
43 | ...lensOp,
44 | op: 'wrap',
45 | }
46 |
47 | case 'in':
48 | case 'map':
49 | return { ...lensOp, lens: reverseLens(lensOp.lens) }
50 |
51 | case 'hoist':
52 | return {
53 | ...lensOp,
54 | op: 'plunge',
55 | }
56 | case 'plunge':
57 | return {
58 | ...lensOp,
59 | op: 'hoist',
60 | }
61 | case 'convert': {
62 | const mapping = [lensOp.mapping[1], lensOp.mapping[0]]
63 | const reversed = {
64 | ...lensOp,
65 | mapping,
66 | sourceType: lensOp.destinationType,
67 | destinationType: lensOp.sourceType,
68 | }
69 |
70 | return reversed
71 | }
72 |
73 | default:
74 | return assertNever(lensOp) // exhaustiveness check
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/test/github-arthropod.ts:
--------------------------------------------------------------------------------
1 | // a quasi-integration test, converting a github doc to an arthropod doc--
2 | // testing a complex doc + lens
3 |
4 | import assert from 'assert'
5 | import githubIssue from './github-issue.json'
6 | import { applyLensToDoc } from '../src/doc'
7 | import { reverseLens } from '../src/reverse'
8 |
9 | describe('renaming title, and hoisting label name to category', () => {
10 | const lens = [
11 | { op: 'rename' as const, source: 'title', destination: 'name' },
12 | { op: 'head' as const, name: 'labels' },
13 | {
14 | op: 'in' as const,
15 | name: 'labels',
16 | lens: [{ op: 'rename' as const, source: 'name', destination: 'category' }],
17 | },
18 | { op: 'hoist' as const, host: 'labels', name: 'category' },
19 | {
20 | op: 'remove' as const,
21 | name: 'labels',
22 | type: ['object' as const, 'null' as const],
23 | },
24 | ]
25 |
26 | it('converts the doc forwards', () => {
27 | const { title: _title, labels: _labels, ...rest } = githubIssue
28 | assert.deepEqual(applyLensToDoc(lens, githubIssue), {
29 | ...rest,
30 | name: githubIssue.title,
31 | category: githubIssue.labels[0].name,
32 | })
33 | })
34 |
35 | it('converts the doc backwards, merging with the original doc', () => {
36 | const newArthropod = {
37 | name: 'Changed the name',
38 | category: 'Bug',
39 | }
40 |
41 | const newGithub = applyLensToDoc(reverseLens(lens), newArthropod, undefined, githubIssue)
42 |
43 | assert.deepEqual(newGithub, {
44 | ...githubIssue,
45 | title: 'Changed the name',
46 | labels: [
47 | {
48 | ...githubIssue.labels[0],
49 | name: 'Bug',
50 | },
51 | ],
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/test/github-issue.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1,
3 | "node_id": "MDU6SXNzdWUx",
4 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
5 | "repository_url": "https://api.github.com/repos/octocat/Hello-World",
6 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
8 | "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events",
9 | "html_url": "https://github.com/octocat/Hello-World/issues/1347",
10 | "number": 1347,
11 | "state": "open",
12 | "title": "Found a bug",
13 | "body": "I'm having a problem with this.",
14 | "user": {
15 | "login": "octocat",
16 | "id": 1,
17 | "node_id": "MDQ6VXNlcjE=",
18 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
19 | "gravatar_id": "",
20 | "url": "https://api.github.com/users/octocat",
21 | "html_url": "https://github.com/octocat",
22 | "followers_url": "https://api.github.com/users/octocat/followers",
23 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
24 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
25 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
26 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
27 | "organizations_url": "https://api.github.com/users/octocat/orgs",
28 | "repos_url": "https://api.github.com/users/octocat/repos",
29 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
30 | "received_events_url": "https://api.github.com/users/octocat/received_events",
31 | "type": "User",
32 | "site_admin": false
33 | },
34 | "labels": [
35 | {
36 | "id": 208045946,
37 | "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
38 | "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
39 | "name": "bug",
40 | "description": "Something isn't working",
41 | "color": "f29513",
42 | "default": true
43 | }
44 | ],
45 | "assignees": [
46 | {
47 | "login": "octocat",
48 | "id": 1,
49 | "node_id": "MDQ6VXNlcjE=",
50 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
51 | "gravatar_id": "",
52 | "url": "https://api.github.com/users/octocat",
53 | "html_url": "https://github.com/octocat",
54 | "followers_url": "https://api.github.com/users/octocat/followers",
55 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
56 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
57 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
58 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
59 | "organizations_url": "https://api.github.com/users/octocat/orgs",
60 | "repos_url": "https://api.github.com/users/octocat/repos",
61 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
62 | "received_events_url": "https://api.github.com/users/octocat/received_events",
63 | "type": "User",
64 | "site_admin": false
65 | }
66 | ],
67 | "milestone": {
68 | "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
69 | "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
70 | "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
71 | "id": 1002604,
72 | "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
73 | "number": 1,
74 | "state": "open",
75 | "title": "v1.0",
76 | "description": "Tracking milestone for version 1.0",
77 | "creator": {
78 | "login": "octocat",
79 | "id": 1,
80 | "node_id": "MDQ6VXNlcjE=",
81 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
82 | "gravatar_id": "",
83 | "url": "https://api.github.com/users/octocat",
84 | "html_url": "https://github.com/octocat",
85 | "followers_url": "https://api.github.com/users/octocat/followers",
86 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
87 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
88 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
89 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
90 | "organizations_url": "https://api.github.com/users/octocat/orgs",
91 | "repos_url": "https://api.github.com/users/octocat/repos",
92 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
93 | "received_events_url": "https://api.github.com/users/octocat/received_events",
94 | "type": "User",
95 | "site_admin": false
96 | },
97 | "open_issues": 4,
98 | "closed_issues": 8,
99 | "created_at": "2011-04-10T20:09:31Z",
100 | "updated_at": "2014-03-03T18:58:10Z",
101 | "closed_at": "2013-02-12T13:22:01Z",
102 | "due_on": "2012-10-09T23:39:01Z"
103 | },
104 | "locked": true,
105 | "active_lock_reason": "too heated",
106 | "comments": 0,
107 | "pull_request": {
108 | "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
109 | "html_url": "https://github.com/octocat/Hello-World/pull/1347",
110 | "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
111 | "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
112 | },
113 | "closed_at": null,
114 | "created_at": "2011-04-22T13:33:48Z",
115 | "updated_at": "2011-04-22T13:33:48Z",
116 | "closed_by": {
117 | "login": "octocat",
118 | "id": 1,
119 | "node_id": "MDQ6VXNlcjE=",
120 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
121 | "gravatar_id": "",
122 | "url": "https://api.github.com/users/octocat",
123 | "html_url": "https://github.com/octocat",
124 | "followers_url": "https://api.github.com/users/octocat/followers",
125 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
126 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
127 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
128 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
129 | "organizations_url": "https://api.github.com/users/octocat/orgs",
130 | "repos_url": "https://api.github.com/users/octocat/repos",
131 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
132 | "received_events_url": "https://api.github.com/users/octocat/received_events",
133 | "type": "User",
134 | "site_admin": false
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/test/json-schema.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import { JSONSchema7 } from 'json-schema'
3 | import { updateSchema } from '../src/json-schema'
4 | import {
5 | addProperty,
6 | inside,
7 | map,
8 | headProperty,
9 | wrapProperty,
10 | hoistProperty,
11 | plungeProperty,
12 | renameProperty,
13 | convertValue,
14 | } from '../src/helpers'
15 |
16 | describe('transforming a json schema', () => {
17 | const v1Schema = {
18 | $schema: 'http://json-schema.org/draft-07/schema',
19 | type: 'object',
20 | title: 'ProjectDoc',
21 | description: 'An Arthropod project with some tasks',
22 | additionalProperties: false,
23 | $id: 'ProjectV1',
24 | properties: {
25 | name: {
26 | type: 'string',
27 | default: '',
28 | },
29 | summary: {
30 | type: 'string',
31 | default: '',
32 | },
33 | },
34 | required: ['name', 'summary'],
35 | } as JSONSchema7 // need to convince typescript this is valid json schema
36 |
37 | describe('addProperty', () => {
38 | it('adds the property', () => {
39 | const newSchema = updateSchema(v1Schema, [
40 | addProperty({ name: 'description', type: 'string' }),
41 | ])
42 |
43 | assert.deepEqual(newSchema.properties, {
44 | ...v1Schema.properties,
45 | description: { type: 'string', default: '' },
46 | })
47 | })
48 |
49 | it('supports nullable fields', () => {
50 | const newSchema = updateSchema(v1Schema, [
51 | addProperty({ name: 'description', type: ['string', 'null'] }),
52 | ])
53 |
54 | assert.deepEqual(newSchema.properties, {
55 | ...v1Schema.properties,
56 | description: { type: ['string', 'null'], default: null },
57 | })
58 | })
59 |
60 | it('uses default value if provided', () => {
61 | const newSchema = updateSchema(v1Schema, [
62 | addProperty({ name: 'description', type: 'string', default: 'hi' }),
63 | ])
64 |
65 | assert.deepEqual(newSchema.properties, {
66 | ...v1Schema.properties,
67 | description: { type: 'string', default: 'hi' },
68 | })
69 | })
70 |
71 | it('sets field as required', () => {
72 | const newSchema = updateSchema(v1Schema, [
73 | addProperty({ name: 'description', type: 'string', required: true }),
74 | ])
75 |
76 | assert.deepEqual(newSchema.properties, {
77 | ...v1Schema.properties,
78 | description: { type: 'string', default: '' },
79 | })
80 |
81 | assert.deepEqual(newSchema.required, [...(v1Schema.required || []), 'description'])
82 | })
83 |
84 | it('fails when presented with invalid data', () => {
85 | const badData: any = { garbage: 'input' }
86 | assert.throws(() => {
87 | updateSchema(v1Schema, [addProperty(badData)])
88 | }, `Missing property name in addProperty.\nFound:\n${JSON.stringify(badData)}`)
89 | })
90 | })
91 |
92 | describe('renameProperty', () => {
93 | const newSchema = updateSchema(v1Schema, [renameProperty('name', 'title')])
94 |
95 | it('adds a new property and removes the old property', () => {
96 | assert.deepEqual(newSchema.properties, {
97 | title: {
98 | type: 'string',
99 | default: '',
100 | },
101 | summary: {
102 | type: 'string',
103 | default: '',
104 | },
105 | })
106 | })
107 |
108 | it('removes the old property from required array', () => {
109 | assert.equal(newSchema.required?.indexOf('name'), -1)
110 | })
111 | })
112 |
113 | describe('convertValue', () => {
114 | it('changes the type on the existing property', () => {
115 | const newSchema = updateSchema(v1Schema, [
116 | convertValue(
117 | 'summary',
118 | [
119 | { todo: false, inProgress: false, done: true },
120 | { false: 'todo', true: 'done' },
121 | ],
122 | 'string',
123 | 'boolean'
124 | ),
125 | ])
126 |
127 | assert.deepEqual(newSchema.properties, {
128 | name: {
129 | type: 'string',
130 | default: '',
131 | },
132 | summary: {
133 | type: 'boolean',
134 | default: false,
135 | },
136 | })
137 | })
138 |
139 | it("doesn't update the schema when there's no type change", () => {
140 | const newSchema = updateSchema(v1Schema, [
141 | convertValue('summary', [{ something: 'another' }, { another: 'something' }]),
142 | ])
143 |
144 | assert.deepEqual(newSchema, v1Schema)
145 | })
146 |
147 | it('fails when presented with invalid data', () => {
148 | const badData: any = { garbage: 'input' }
149 | assert.throws(() => {
150 | updateSchema(v1Schema, [addProperty(badData)])
151 | }, `Missing property destinationType in 'convert'.\nFound:\n${JSON.stringify(badData)}`)
152 | })
153 | })
154 |
155 | describe('inside', () => {
156 | it('adds new properties inside a key', () => {
157 | const newSchema = updateSchema(v1Schema, [
158 | addProperty({ name: 'metadata', type: 'object' }),
159 | inside('metadata', [
160 | addProperty({ name: 'createdAt', type: 'number' }),
161 | addProperty({ name: 'updatedAt', type: 'number' }),
162 | ]),
163 | ])
164 |
165 | assert.deepEqual(newSchema.properties, {
166 | ...v1Schema.properties,
167 | metadata: {
168 | type: 'object',
169 | default: {},
170 | properties: {
171 | createdAt: {
172 | type: 'number',
173 | default: 0,
174 | },
175 | updatedAt: {
176 | type: 'number',
177 | default: 0,
178 | },
179 | },
180 | required: ['createdAt', 'updatedAt'],
181 | },
182 | })
183 | })
184 |
185 | it('renames properties inside a key', () => {
186 | const newSchema = updateSchema(v1Schema, [
187 | addProperty({ name: 'metadata', type: 'object' }),
188 | inside('metadata', [
189 | addProperty({ name: 'createdAt', type: 'number' }),
190 | renameProperty('createdAt', 'created'),
191 | ]),
192 | ])
193 |
194 | assert.deepEqual(newSchema.properties, {
195 | name: {
196 | type: 'string',
197 | default: '',
198 | },
199 | summary: {
200 | type: 'string',
201 | default: '',
202 | },
203 | metadata: {
204 | type: 'object',
205 | default: {},
206 | properties: {
207 | created: {
208 | type: 'number',
209 | default: 0,
210 | },
211 | },
212 | required: ['created'],
213 | },
214 | })
215 | })
216 | })
217 |
218 | describe('map', () => {
219 | it('adds new properties inside an array', () => {
220 | const newSchema = updateSchema(v1Schema, [
221 | addProperty({ name: 'tasks', type: 'array', items: { type: 'object' as const } }),
222 | inside('tasks', [
223 | map([
224 | addProperty({ name: 'name', type: 'string' }),
225 | addProperty({ name: 'description', type: 'string' }),
226 | ]),
227 | ]),
228 | ])
229 |
230 | assert.deepEqual(newSchema.properties, {
231 | ...v1Schema.properties,
232 | tasks: {
233 | type: 'array',
234 | default: [],
235 | items: {
236 | type: 'object',
237 | default: {},
238 | properties: {
239 | name: {
240 | type: 'string',
241 | default: '',
242 | },
243 | description: {
244 | type: 'string',
245 | default: '',
246 | },
247 | },
248 | required: ['name', 'description'],
249 | },
250 | },
251 | })
252 | })
253 |
254 | it('renames properties inside an array', () => {
255 | const newSchema = updateSchema(v1Schema, [
256 | addProperty({ name: 'tasks', type: 'array', items: { type: 'object' as const } }),
257 | inside('tasks', [
258 | map([addProperty({ name: 'name', type: 'string' }), renameProperty('name', 'title')]),
259 | ]),
260 | ])
261 |
262 | assert.deepEqual(newSchema.properties, {
263 | ...v1Schema.properties,
264 | tasks: {
265 | type: 'array',
266 | default: [],
267 | items: {
268 | type: 'object',
269 | default: {},
270 | properties: {
271 | title: {
272 | type: 'string',
273 | default: '',
274 | },
275 | },
276 | required: ['title'],
277 | },
278 | },
279 | })
280 | })
281 | })
282 |
283 | describe('headProperty', () => {
284 | it('can turn an array into a scalar', () => {
285 | const newSchema = updateSchema(v1Schema, [
286 | addProperty({ name: 'assignees', type: 'array', items: { type: 'string' as const } }),
287 | headProperty('assignees'),
288 | ])
289 |
290 | // Really, the correct result would be:
291 | // { { type: 'null', type: 'string' }, default: 'Joe' } }
292 | // the behaviour you see below here doesn't really work with at least AJV
293 | // https://github.com/ajv-validator/ajv/issues/276
294 | assert.deepEqual(newSchema.properties, {
295 | ...v1Schema.properties,
296 | assignees: { anyOf: [{ type: 'null' }, { type: 'string', default: '' }] },
297 | })
298 | })
299 |
300 | it('can preserve schema information for an array of objects becoming a single object', () => {
301 | const newSchema = updateSchema(v1Schema, [
302 | addProperty({ name: 'assignees', type: 'array', items: { type: 'object' as const } }),
303 | inside('assignees', [map([addProperty({ name: 'name', type: 'string' })])]),
304 | headProperty('assignees'),
305 | ])
306 |
307 | const expectedSchema = {
308 | ...v1Schema.properties,
309 | assignees: {
310 | anyOf: [
311 | { type: 'null' },
312 | {
313 | type: 'object',
314 | default: {},
315 | properties: {
316 | name: { type: 'string', default: '' },
317 | },
318 | required: ['name'],
319 | },
320 | ],
321 | },
322 | }
323 |
324 | assert.deepEqual(newSchema.properties, expectedSchema)
325 | })
326 | })
327 |
328 | describe('wrapProperty', () => {
329 | it('can wrap a scalar into an array', () => {
330 | const newSchema = updateSchema(v1Schema, [
331 | addProperty({ name: 'assignee', type: ['string', 'null'] }),
332 | wrapProperty('assignee'),
333 | ])
334 |
335 | assert.deepEqual(newSchema.properties, {
336 | ...v1Schema.properties,
337 | assignee: {
338 | type: 'array',
339 | default: [],
340 | items: {
341 | type: 'string' as const,
342 | default: '',
343 | },
344 | },
345 | })
346 | })
347 |
348 | it.skip('can wrap an object into an array', () => {
349 | const newSchema = updateSchema(v1Schema, [
350 | addProperty({ name: 'assignee', type: ['object', 'null'] }),
351 | inside('assignee', [
352 | addProperty({ name: 'id', type: 'string' }),
353 | addProperty({ name: 'name', type: 'string' }),
354 | ]),
355 | wrapProperty('assignee'),
356 | ])
357 |
358 | assert.deepEqual(newSchema.properties, {
359 | ...v1Schema.properties,
360 | assignee: {
361 | type: 'array',
362 | default: [],
363 | items: {
364 | type: 'object' as const,
365 | properties: {
366 | name: { type: 'string', default: '' },
367 | id: { type: 'string', default: '' },
368 | },
369 | },
370 | },
371 | })
372 | })
373 | })
374 |
375 | describe('hoistProperty', () => {
376 | it('hoists the property up in the schema', () => {
377 | const newSchema = updateSchema(v1Schema, [
378 | addProperty({ name: 'metadata', type: 'object' }),
379 | inside('metadata', [
380 | addProperty({ name: 'createdAt', type: 'number' }),
381 | addProperty({ name: 'editedAt', type: 'number' }),
382 | ]),
383 | hoistProperty('metadata', 'createdAt'),
384 | ])
385 |
386 | assert.deepEqual(newSchema.properties, {
387 | ...v1Schema.properties,
388 | metadata: {
389 | type: 'object',
390 | default: {},
391 | properties: {
392 | editedAt: {
393 | type: 'number',
394 | default: 0,
395 | },
396 | },
397 | required: ['editedAt'],
398 | },
399 | createdAt: {
400 | type: 'number',
401 | default: 0,
402 | },
403 | })
404 | })
405 |
406 | it('hoists up an object with child properties', () => {
407 | // hoist up a details object out of metadata
408 | const newSchema = updateSchema(v1Schema, [
409 | addProperty({ name: 'metadata', type: 'object' }),
410 | inside('metadata', [
411 | addProperty({ name: 'details', type: 'object' }),
412 | inside('details', [addProperty({ name: 'title', type: 'string' })]),
413 | ]),
414 | hoistProperty('metadata', 'details'),
415 | ])
416 |
417 | assert.deepEqual(newSchema.properties, {
418 | ...v1Schema.properties,
419 | metadata: {
420 | type: 'object',
421 | default: {},
422 | properties: {},
423 | required: [],
424 | },
425 | details: {
426 | type: 'object',
427 | default: {},
428 | properties: {
429 | title: { type: 'string', default: '' },
430 | },
431 | required: ['title'],
432 | },
433 | })
434 | })
435 | })
436 |
437 | describe('plungeProperty', () => {
438 | it('plunges the property down in the schema', () => {
439 | // move the existing summary down into a metadata object
440 | const newSchema = updateSchema(v1Schema, [
441 | addProperty({ name: 'metadata', type: 'object' }),
442 | inside('metadata', [
443 | addProperty({ name: 'createdAt', type: 'number' }),
444 | addProperty({ name: 'editedAt', type: 'number' }),
445 | ]),
446 | plungeProperty('metadata', 'summary'),
447 | ])
448 |
449 | assert.deepEqual(newSchema.properties, {
450 | name: v1Schema.properties?.name,
451 | metadata: {
452 | type: 'object',
453 | default: {},
454 | properties: {
455 | createdAt: {
456 | type: 'number',
457 | default: 0,
458 | },
459 | editedAt: {
460 | type: 'number',
461 | default: 0,
462 | },
463 | summary: {
464 | type: 'string',
465 | default: '',
466 | },
467 | },
468 | required: ['createdAt', 'editedAt', 'summary'],
469 | },
470 | })
471 | })
472 |
473 | it('fails when presented with invalid data', () => {
474 | assert.throws(() => {
475 | updateSchema(v1Schema, [plungeProperty('metadata', 'nosaj-thing')])
476 | }, /Could not find a property called nosaj-thing among/)
477 | })
478 |
479 | it.skip('plunges an object down with its child properties', () => {
480 | // plunge metadata object into a container object
481 | const newSchema = updateSchema(v1Schema, [
482 | addProperty({ name: 'container', type: 'object' }),
483 | addProperty({ name: 'metadata', type: 'object' }),
484 | inside('metadata', [
485 | addProperty({ name: 'createdAt', type: 'number' }),
486 | addProperty({ name: 'editedAt', type: 'number' }),
487 | ]),
488 | plungeProperty('container', 'metadata'),
489 | ])
490 |
491 | assert.deepEqual(newSchema.properties, {
492 | ...v1Schema.properties,
493 | container: {
494 | type: 'object',
495 | default: {},
496 | required: ['metadata'],
497 | properties: {
498 | metadata: {
499 | type: 'object',
500 | default: {},
501 | properties: {
502 | createdAt: {
503 | type: 'number',
504 | default: 0,
505 | },
506 | editedAt: {
507 | type: 'number',
508 | default: 0,
509 | },
510 | },
511 | required: ['createdAt', 'editedAt', 'summary'],
512 | },
513 | },
514 | },
515 | })
516 | })
517 | })
518 | })
519 |
--------------------------------------------------------------------------------
/test/lens-graph.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import { JSONSchema7 } from 'json-schema'
3 | import { updateSchema } from '../src/json-schema'
4 | import {
5 | addProperty,
6 | inside,
7 | map,
8 | headProperty,
9 | wrapProperty,
10 | hoistProperty,
11 | plungeProperty,
12 | renameProperty,
13 | convertValue,
14 | removeProperty,
15 | } from '../src/helpers'
16 |
17 | import {
18 | LensGraph,
19 | initLensGraph,
20 | registerLens,
21 | lensGraphSchemas,
22 | lensFromTo,
23 | } from '../src/lens-graph'
24 |
25 | const LensMutoV1 = [addProperty({ name: 'title', type: 'string' })]
26 | const LensV1toV2 = [
27 | addProperty({ name: 'metadata', type: 'object' }),
28 | inside('metadata', [
29 | addProperty({ name: 'createdAt', type: 'number' }),
30 | addProperty({ name: 'updatedAt', type: 'number' }),
31 | ]),
32 | ]
33 | const LensV2toV3 = [
34 | hoistProperty('metadata', 'createdAt'),
35 | addProperty({ name: 'metadata', type: 'object' }),
36 | ]
37 |
38 | const Lenses = [
39 | { from: 'mu', to: 'V1', lens: LensMutoV1 },
40 | { from: 'V1', to: 'V2', lens: LensV1toV2 },
41 | { from: 'V2', to: 'V3', lens: LensV2toV3 },
42 | ]
43 |
44 | describe('registering lenses', () => {
45 | it('should be able to create a graph', () => {
46 | const graph = initLensGraph()
47 | assert.deepEqual(lensGraphSchemas(graph), ['mu'])
48 | })
49 |
50 | it('should be able to register some lenses', () => {
51 | const graph = Lenses.reduce((graph, { from, to, lens }) => {
52 | return registerLens(graph, from, to, lens)
53 | }, initLensGraph())
54 | assert.deepEqual(lensGraphSchemas(graph), ['mu', 'V1', 'V2', 'V3'])
55 | })
56 |
57 | it('should compose a lens from a path', () => {
58 | const graph = Lenses.reduce(
59 | (graph, { from, to, lens }) => registerLens(graph, from, to, lens),
60 | initLensGraph()
61 | )
62 |
63 | const lens = lensFromTo(graph, 'V1', 'V3')
64 | assert.deepEqual(lens, [...LensV1toV2, ...LensV2toV3])
65 | })
66 | })
67 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "skipLibCheck": true,
5 | "strict": true,
6 | "noImplicitAny": false,
7 | "sourceMap": true,
8 | "noFallthroughCasesInSwitch": true,
9 | "lib": ["es2015", "es2017", "es2019", "dom", "webworker"],
10 | "target": "es2016",
11 | "module": "CommonJS",
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "jsx": "react",
15 | "pretty": true,
16 | "experimentalDecorators": true,
17 | "resolveJsonModule": true
18 | },
19 | "parserOptions": {
20 | "tsconfigRootDir": "."
21 | },
22 | "include": ["src/**/*.ts"]
23 | }
24 |
--------------------------------------------------------------------------------