@toa.io/norm 1.0.0-alpha.291 → 1.0.0-alpha.292
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +7 -5
- package/src/.component/.normalize/events.js +3 -1
- package/src/.component/extensions.js +2 -5
- package/src/.component/merge.js +12 -11
- package/src/.component/validate.js +2 -1
- package/src/.context/.dependencies/extensions.js +18 -4
- package/src/.context/.dependencies/resolve.js +3 -4
- package/src/.context/normalize.js +3 -3
- package/src/definition.js +56 -0
- package/src/index.js +1 -0
- package/test/context/normalize.test.js +2 -2
- package/test/definition.test.js +35 -0
- package/test/dummies/bare/index.js +1 -0
- package/test/dummies/defined/definition.js +1 -0
- package/test/dummies/defined/index.js +1 -0
- package/test/dummies/defined/package.json +1 -0
- package/types/index.d.ts +8 -0
- package/src/.context/.dependencies/load.js +0 -39
- package/test/context/dependencies.load.test.js +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **cli:** the deployment library is a peer, and the binary is the CLI's ([a923f81](https://github.com/toa-io/toa/commit/a923f8173a55ed1c091213a7c7db1863ca1d0ba6))
|
|
11
|
+
* **definitions:** the version of Toa is the definitions package's ([3d0272e](https://github.com/toa-io/toa/commit/3d0272ea0ab862f4b6c2e58a2f003f92d0f8a87f))
|
|
12
|
+
* **definitions:** what a package declares is read from one package ([7052341](https://github.com/toa-io/toa/commit/70523411b5d9e2b204c999aa02365173f3bb518d))
|
|
13
|
+
* **norm:** a package's definition is read by one resolver ([bffd41c](https://github.com/toa-io/toa/commit/bffd41c8d18c42786b0bf321623088b9dabc4724))
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
# [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @toa.io/norm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.292",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,11 +21,13 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/
|
|
26
|
-
"@toa.io/
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.292",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.292",
|
|
26
|
+
"@toa.io/generic": "1.0.0-alpha.292",
|
|
27
|
+
"@toa.io/prototype": "1.0.0-alpha.289",
|
|
28
|
+
"@toa.io/schemas": "1.0.0-alpha.292",
|
|
27
29
|
"fast-glob": "3.3.3",
|
|
28
30
|
"js-yaml": "5.4.1"
|
|
29
31
|
},
|
|
30
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "0f6aa3ad9dbec86bb632a7975ea61d1dde1d8fc5"
|
|
31
33
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { definition } from '../../definition.js'
|
|
2
|
+
|
|
1
3
|
export const events = async (component) => {
|
|
2
4
|
if (component.events === undefined) return
|
|
3
5
|
|
|
@@ -14,7 +16,7 @@ export const events = async (component) => {
|
|
|
14
16
|
/** The first binding that carries events, which only its module can say. */
|
|
15
17
|
async function asynchronous(bindings) {
|
|
16
18
|
for (const binding of bindings) {
|
|
17
|
-
const { properties } = await
|
|
19
|
+
const { properties } = (await definition(binding)).module
|
|
18
20
|
|
|
19
21
|
if (properties.async === true) return binding
|
|
20
22
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { find } from '@toa.io/generic'
|
|
2
2
|
import { resolve } from '../shortcuts.js'
|
|
3
|
-
|
|
4
|
-
const cache = {}
|
|
3
|
+
import { definition } from '../definition.js'
|
|
5
4
|
|
|
6
5
|
export const extensions = async (manifest) => {
|
|
7
6
|
manifest.extensions = Object.assign({}, PREDEFINED, manifest.extensions)
|
|
@@ -14,9 +13,7 @@ export const extensions = async (manifest) => {
|
|
|
14
13
|
// relative path
|
|
15
14
|
if (key[0] === '.') key = find(key, manifest.path)
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const extension = await cache[key]
|
|
16
|
+
const { module: extension } = await definition(key)
|
|
20
17
|
|
|
21
18
|
if (extension.manifest !== undefined) {
|
|
22
19
|
declaration = extension.manifest(declaration, manifest)
|
package/src/.component/merge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { merge } from '@toa.io/generic'
|
|
2
|
+
import { definition } from '../definition.js'
|
|
2
3
|
|
|
3
4
|
const bridge = async (root, manifest) => {
|
|
4
5
|
await Promise.all([
|
|
@@ -18,7 +19,7 @@ const define = async (root, manifest, property) => {
|
|
|
18
19
|
if (item.bridge === undefined || item.bridge === manifest.bridge) continue // default bridge later
|
|
19
20
|
|
|
20
21
|
const bridge = item.bridge || manifest.bridge
|
|
21
|
-
const { define } = await
|
|
22
|
+
const { define } = await bridged(bridge)
|
|
22
23
|
const definition = await define[singular](root, endpoint)
|
|
23
24
|
|
|
24
25
|
merge(item, definition)
|
|
@@ -53,20 +54,20 @@ const define = async (root, manifest, property) => {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
const cache = {}
|
|
57
|
-
|
|
58
|
-
// the promise is what is remembered, so a bridge is loaded once
|
|
59
|
-
function req(mod) {
|
|
60
|
-
cache[mod] ??= import(mod)
|
|
61
|
-
|
|
62
|
-
return cache[mod]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
57
|
const scan = async (bridge, root, property) => {
|
|
66
|
-
const { define } = await
|
|
58
|
+
const { define } = await bridged(bridge)
|
|
67
59
|
|
|
68
60
|
if (property in define) return define[property](root)
|
|
69
61
|
else return undefined
|
|
70
62
|
}
|
|
71
63
|
|
|
64
|
+
async function bridged(reference) {
|
|
65
|
+
const { module } = await definition(reference)
|
|
66
|
+
|
|
67
|
+
if (module.define === undefined)
|
|
68
|
+
throw new Error(`Bridge '${reference}' defines nothing: it exports no 'define'`)
|
|
69
|
+
|
|
70
|
+
return module
|
|
71
|
+
}
|
|
72
|
+
|
|
72
73
|
export { bridge as merge }
|
|
@@ -3,6 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
4
|
import { yaml } from '@toa.io/generic'
|
|
5
5
|
import * as schemas from '@toa.io/schemas'
|
|
6
|
+
import { definition } from '../definition.js'
|
|
6
7
|
|
|
7
8
|
const object = yaml.load(
|
|
8
9
|
readFileSync(path.resolve(import.meta.dirname, 'schema.yaml'), 'utf8')
|
|
@@ -34,7 +35,7 @@ const entity = (manifest) => {
|
|
|
34
35
|
|
|
35
36
|
const events = async (manifest) => {
|
|
36
37
|
for (const [label, event] of Object.entries(manifest.events)) {
|
|
37
|
-
const { properties } = await
|
|
38
|
+
const { properties } = (await definition(event.binding)).module
|
|
38
39
|
|
|
39
40
|
if (properties.async !== true) {
|
|
40
41
|
throw new Error(`Event '${label}' binding '${event.binding}' is not async`)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Locator } from '@toa.io/core'
|
|
1
2
|
import { component as load } from '../../component.js'
|
|
2
|
-
import {
|
|
3
|
+
import { definition } from '../../definition.js'
|
|
3
4
|
|
|
4
5
|
export const extensions = async (context) => {
|
|
5
6
|
const extensions = {}
|
|
@@ -84,15 +85,28 @@ async function extractExtensionComponents(components, extensions, annotations) {
|
|
|
84
85
|
async function extract(reference, extensions, annotations) {
|
|
85
86
|
extensions[reference] = []
|
|
86
87
|
|
|
87
|
-
const {
|
|
88
|
+
const { name, module: mod } = await definition(reference)
|
|
88
89
|
|
|
89
90
|
if (mod.components === undefined) return []
|
|
90
91
|
|
|
91
92
|
// the annotation decides whether an extension contributes components at all
|
|
92
|
-
const annotation = annotations?.[
|
|
93
|
+
const annotation = annotations?.[name]
|
|
94
|
+
const { manifests, paths } = mod.components(annotation)
|
|
95
|
+
|
|
96
|
+
// a definition carries the manifests, read where the extension is not installed;
|
|
97
|
+
// an entry names the directories, where it is
|
|
98
|
+
if (manifests !== undefined) return manifests.map(revive)
|
|
99
|
+
|
|
93
100
|
const extracted = []
|
|
94
101
|
|
|
95
|
-
for (const path of
|
|
102
|
+
for (const path of paths) extracted.push(await load(path))
|
|
96
103
|
|
|
97
104
|
return extracted
|
|
98
105
|
}
|
|
106
|
+
|
|
107
|
+
/** A manifest as a digest carries it, which is one norm read, without the locator. */
|
|
108
|
+
function revive(manifest) {
|
|
109
|
+
manifest.locator = new Locator(manifest.name, manifest.namespace)
|
|
110
|
+
|
|
111
|
+
return manifest
|
|
112
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definition } from '../../definition.js'
|
|
2
2
|
|
|
3
3
|
export const resolve = async (references, annotations = {}) => {
|
|
4
4
|
const dependencies = {}
|
|
5
5
|
|
|
6
6
|
for (const [dependency, components] of Object.entries(references)) {
|
|
7
|
-
const {
|
|
8
|
-
const id = metadata.name
|
|
7
|
+
const { name: id, module } = await definition(dependency)
|
|
9
8
|
|
|
10
9
|
const instances = components.map((component) => ({
|
|
11
10
|
locator: component.locator,
|
|
@@ -40,7 +39,7 @@ export const resolve = async (references, annotations = {}) => {
|
|
|
40
39
|
*/
|
|
41
40
|
async function optional(reference) {
|
|
42
41
|
try {
|
|
43
|
-
return (await
|
|
42
|
+
return (await definition(reference)).module
|
|
44
43
|
} catch {
|
|
45
44
|
return null
|
|
46
45
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as definitions from '@toa.io/definitions'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {toa.norm.context.Declaration | Object} context
|
|
5
5
|
*/
|
|
6
6
|
export const normalize = (context) => {
|
|
7
|
-
if (context.runtime === undefined) context.runtime = { version:
|
|
7
|
+
if (context.runtime === undefined) context.runtime = { version: definitions.version }
|
|
8
8
|
if (typeof context.runtime === 'string') context.runtime = { version: context.runtime }
|
|
9
9
|
|
|
10
10
|
if (context.runtime.version === undefined || context.runtime.version === '.') {
|
|
11
|
-
context.runtime.version =
|
|
11
|
+
context.runtime.version = definitions.version
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
if (typeof context.registry === 'string') context.registry = { base: context.registry }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import * as definitions from '@toa.io/definitions'
|
|
6
|
+
|
|
7
|
+
// import.meta.resolve takes no paths, and a dependency is named rather than
|
|
8
|
+
// written as a path
|
|
9
|
+
const require = createRequire(import.meta.url)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What a package declares, read without running it.
|
|
13
|
+
*
|
|
14
|
+
* A first-party package's definition is in `@toa.io/definitions`. Another package's is
|
|
15
|
+
* its `definition.js`, or its entry where it ships none — so an extension that exports
|
|
16
|
+
* its `manifest` and `deployment` beside its `Factory` is read as it always was.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} reference a package name, or a directory
|
|
19
|
+
* @returns {Promise<toa.norm.Definition>}
|
|
20
|
+
*/
|
|
21
|
+
export function definition(reference) {
|
|
22
|
+
cache[reference] ??= load(reference)
|
|
23
|
+
|
|
24
|
+
return cache[reference]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cache = {}
|
|
28
|
+
|
|
29
|
+
async function load(reference) {
|
|
30
|
+
const known = await definitions.definition(reference)
|
|
31
|
+
|
|
32
|
+
if (known !== undefined) return { name: reference, module: known }
|
|
33
|
+
|
|
34
|
+
const name = metadata(reference)?.name ?? reference
|
|
35
|
+
const module = await import(pathToFileURL(resolve(reference)).href)
|
|
36
|
+
|
|
37
|
+
return { name, module }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolve(reference) {
|
|
41
|
+
try {
|
|
42
|
+
return require.resolve(join(reference, 'definition.js'))
|
|
43
|
+
} catch {
|
|
44
|
+
return require.resolve(reference)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function metadata(reference) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(
|
|
51
|
+
readFileSync(require.resolve(join(reference, 'package.json')), 'utf8')
|
|
52
|
+
)
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as definitions from '@toa.io/definitions'
|
|
2
2
|
import { it, beforeEach } from 'node:test'
|
|
3
3
|
import assert from 'node:assert/strict'
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ it('should resolve local version', () => {
|
|
|
20
20
|
normalize(context)
|
|
21
21
|
|
|
22
22
|
assert.notDeepStrictEqual(context.runtime, '.')
|
|
23
|
-
assert.deepStrictEqual(context.runtime.version,
|
|
23
|
+
assert.deepStrictEqual(context.runtime.version, definitions.version)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
it('should expand registry', () => {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { definition } from '../src/definition.js'
|
|
6
|
+
|
|
7
|
+
it('reads a definition.js where a package ships one', async () => {
|
|
8
|
+
const { name, module } = await definition(join(import.meta.dirname, 'dummies/defined'))
|
|
9
|
+
|
|
10
|
+
assert.equal(name, 'dummies.defined')
|
|
11
|
+
assert.equal(module.declares, true)
|
|
12
|
+
assert.equal(module.runs, undefined)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('reads the entry where a package ships no definition.js', async () => {
|
|
16
|
+
const path = join(import.meta.dirname, '../')
|
|
17
|
+
const { name, module } = await definition(path)
|
|
18
|
+
|
|
19
|
+
assert.equal(name, '@toa.io/norm')
|
|
20
|
+
assert.ok(module.context instanceof Function)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('names a directory without package.json by its reference', async () => {
|
|
24
|
+
const path = join(import.meta.dirname, 'dummies/bare')
|
|
25
|
+
const { name, module } = await definition(path)
|
|
26
|
+
|
|
27
|
+
assert.equal(name, path)
|
|
28
|
+
assert.equal(module.declares, true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('loads a definition once', async () => {
|
|
32
|
+
const path = join(import.meta.dirname, 'dummies/bare')
|
|
33
|
+
|
|
34
|
+
assert.equal(await definition(path), await definition(path))
|
|
35
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const declares = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const declares = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const runs = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "name": "dummies.defined", "type": "module", "main": "index.js" }
|
package/types/index.d.ts
CHANGED
|
@@ -3,3 +3,11 @@ export * as component from './component.js'
|
|
|
3
3
|
|
|
4
4
|
export { Manifest } from './component.js'
|
|
5
5
|
export * as entity from './entity.js'
|
|
6
|
+
|
|
7
|
+
export interface Definition {
|
|
8
|
+
/** The package's name, or the reference where it has no package.json */
|
|
9
|
+
name: string
|
|
10
|
+
module: Record<string, any>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function definition(reference: string): Promise<Definition>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'node:module'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { pathToFileURL } from 'node:url'
|
|
5
|
-
|
|
6
|
-
// import.meta.resolve takes no paths, and a dependency is named rather than
|
|
7
|
-
// written as a path
|
|
8
|
-
const require = createRequire(import.meta.url)
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @param {string} path
|
|
12
|
-
* @returns {Promise<{ metadata: object, module: object }>}
|
|
13
|
-
*/
|
|
14
|
-
export async function load(path) {
|
|
15
|
-
const metadata = loadMetadata(path)
|
|
16
|
-
const module = await loadModule(path)
|
|
17
|
-
|
|
18
|
-
return { metadata, module }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function loadMetadata(reference) {
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(
|
|
24
|
-
readFileSync(require.resolve(join(reference, 'package.json')), 'utf8')
|
|
25
|
-
)
|
|
26
|
-
} catch {
|
|
27
|
-
return null
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {string} reference
|
|
33
|
-
* @returns {Promise<object>}
|
|
34
|
-
*/
|
|
35
|
-
async function loadModule(reference) {
|
|
36
|
-
const path = require.resolve(reference)
|
|
37
|
-
|
|
38
|
-
return await import(pathToFileURL(path).href)
|
|
39
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { load } from '../../src/.context/.dependencies/load.js'
|
|
6
|
-
|
|
7
|
-
it('should be', async () => {
|
|
8
|
-
assert.ok(load instanceof Function)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('should load module', async () => {
|
|
12
|
-
const path = join(import.meta.dirname, '../../')
|
|
13
|
-
const { metadata, module } = await load(path)
|
|
14
|
-
|
|
15
|
-
assert.deepStrictEqual(metadata.name, '@toa.io/norm')
|
|
16
|
-
assert.ok(module.context instanceof Function)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should return null metadata if no package.json', async () => {
|
|
20
|
-
const path = join(import.meta.dirname, '../../src')
|
|
21
|
-
const { metadata, module } = await load(path)
|
|
22
|
-
|
|
23
|
-
assert.strictEqual(metadata, null)
|
|
24
|
-
assert.ok(module.context instanceof Function)
|
|
25
|
-
})
|