@toa.io/norm 1.0.0-alpha.307 → 1.0.0-alpha.308
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 +8 -0
- package/package.json +4 -4
- package/src/.component/.expand/version.js +51 -22
- package/src/.component/schema.yaml +12 -0
- package/src/map.js +11 -8
- package/types/component.d.ts +4 -0
- package/types/context.d.ts +1 -1
- package/types/index.d.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.308](https://github.com/toa-io/toa/compare/v1.0.0-alpha.307...v1.0.0-alpha.308) (2026-09-15)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/norm
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# [1.0.0-alpha.307](https://github.com/toa-io/toa/compare/v1.0.0-alpha.306...v1.0.0-alpha.307) (2026-09-15)
|
|
7
15
|
|
|
8
16
|
**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.308",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,12 +21,12 @@
|
|
|
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/definitions": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.308",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.308",
|
|
26
26
|
"@toa.io/generic": "1.0.0-alpha.304",
|
|
27
27
|
"@toa.io/prototype": "1.0.0-alpha.301",
|
|
28
28
|
"@toa.io/schemas": "1.0.0-alpha.304",
|
|
29
29
|
"fast-glob": "3.3.3"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "66eb0d54032f60e6ead9e13fd9e21fb60c611277"
|
|
32
32
|
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
|
+
import glob from 'fast-glob'
|
|
5
|
+
|
|
6
|
+
import { definition } from '../../definition.js'
|
|
4
7
|
|
|
5
8
|
export async function version(manifest) {
|
|
6
|
-
manifest.version ??= await hash(manifest
|
|
9
|
+
manifest.version ??= await hash(manifest)
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Identifies a build of a component: it becomes the tag of its image, so it must be the
|
|
11
|
-
* same for the same sources and different for different ones — on any machine
|
|
14
|
+
* same for the same sources and different for different ones — on any machine, and whichever
|
|
15
|
+
* build produced them.
|
|
12
16
|
*
|
|
13
|
-
* What
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* What a build leaves out cannot change it: the bridge states what no build ships, a component
|
|
18
|
+
* adds to that with `ignore` and puts one back with `!`, and `files` names what its version is
|
|
19
|
+
* made of where a build copies only some of it. Its manifest is always one of them, because
|
|
20
|
+
* what it declares is what a caller is held to.
|
|
16
21
|
*/
|
|
17
|
-
async function hash(
|
|
18
|
-
const files = (await list(
|
|
19
|
-
const digests = await Promise.all(files.map((file) => digest(join(path, file))))
|
|
22
|
+
async function hash(manifest) {
|
|
23
|
+
const files = (await list(manifest)).sort()
|
|
24
|
+
const digests = await Promise.all(files.map((file) => digest(join(manifest.path, file))))
|
|
20
25
|
const total = createHash('sha256')
|
|
21
26
|
|
|
22
27
|
// the path is part of it: moving a file changes the build even if no content did
|
|
@@ -26,24 +31,48 @@ async function hash(path) {
|
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
* @param {
|
|
30
|
-
* @
|
|
31
|
-
* @param {string[]} [acc]
|
|
32
|
-
* @returns {Promise<string[]>} paths relative to `root`
|
|
34
|
+
* @param {toa.norm.Component} manifest
|
|
35
|
+
* @returns {Promise<string[]>} paths relative to the component
|
|
33
36
|
*/
|
|
34
|
-
async function list(
|
|
35
|
-
const
|
|
37
|
+
async function list(manifest) {
|
|
38
|
+
const files = await glob(patterns(manifest.files), {
|
|
39
|
+
cwd: manifest.path,
|
|
40
|
+
dot: true,
|
|
41
|
+
onlyFiles: true,
|
|
42
|
+
followSymbolicLinks: false,
|
|
43
|
+
ignore: await ignore(manifest)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// what a component declares is what a caller is held to, so its manifest is always one of them
|
|
47
|
+
if (!files.includes(MANIFEST) && (await exists(join(manifest.path, MANIFEST))))
|
|
48
|
+
files.push(MANIFEST)
|
|
49
|
+
|
|
50
|
+
return files
|
|
51
|
+
}
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
/** A path names what is under it, and a pattern is taken as it is. */
|
|
54
|
+
function patterns(files = ['**']) {
|
|
55
|
+
return files.flatMap((file) => (file.includes('*') ? [file] : [file, file + '/**']))
|
|
56
|
+
}
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
async function ignore(manifest) {
|
|
59
|
+
const { module } = await definition(manifest.bridge)
|
|
60
|
+
const stated = manifest.ignore ?? []
|
|
61
|
+
const back = new Set(
|
|
62
|
+
stated.filter((pattern) => pattern[0] === '!').map((pattern) => pattern.slice(1))
|
|
63
|
+
)
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
return [
|
|
66
|
+
...(module.ignore ?? []).filter((pattern) => !back.has(pattern)),
|
|
67
|
+
...stated.filter((pattern) => pattern[0] !== '!')
|
|
68
|
+
]
|
|
69
|
+
}
|
|
45
70
|
|
|
46
|
-
|
|
71
|
+
async function exists(path) {
|
|
72
|
+
return await fs
|
|
73
|
+
.access(path)
|
|
74
|
+
.then(() => true)
|
|
75
|
+
.catch(() => false)
|
|
47
76
|
}
|
|
48
77
|
|
|
49
78
|
async function digest(path) {
|
|
@@ -52,4 +81,4 @@ async function digest(path) {
|
|
|
52
81
|
.digest()
|
|
53
82
|
}
|
|
54
83
|
|
|
55
|
-
const
|
|
84
|
+
const MANIFEST = 'manifest.toa.yaml'
|
|
@@ -34,6 +34,18 @@ properties:
|
|
|
34
34
|
path:
|
|
35
35
|
type: string
|
|
36
36
|
|
|
37
|
+
files:
|
|
38
|
+
# what this component's version is made of, where a build of it copies only some of it
|
|
39
|
+
type: array
|
|
40
|
+
items:
|
|
41
|
+
type: string
|
|
42
|
+
|
|
43
|
+
ignore:
|
|
44
|
+
# what its version is not made of, added to what the bridge states; `!` puts one of those back
|
|
45
|
+
type: array
|
|
46
|
+
items:
|
|
47
|
+
type: string
|
|
48
|
+
|
|
37
49
|
locator:
|
|
38
50
|
type: object
|
|
39
51
|
properties:
|
package/src/map.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
+
import { contract } from '@toa.io/core'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Every component a Context has, with the version it runs
|
|
3
|
-
* bring, and the ones it evicts. A map is not rendered into a
|
|
4
|
-
* an evicted component is called like any other, so it is
|
|
4
|
+
* Every component a Context has, with the version it runs and the contract of that version: its
|
|
5
|
+
* own, the ones its extensions bring, and the ones it evicts. A map is not rendered into a
|
|
6
|
+
* workload the way variables are, and an evicted component is called like any other, so it is
|
|
7
|
+
* described like any other.
|
|
5
8
|
*
|
|
6
9
|
* @param {toa.norm.Context} context
|
|
7
|
-
* @returns {Record<string,
|
|
10
|
+
* @returns {Record<string, import('@toa.io/core').Contract>}
|
|
8
11
|
*/
|
|
9
12
|
export const map = (context) => {
|
|
10
|
-
const
|
|
13
|
+
const components = new Map()
|
|
11
14
|
|
|
12
15
|
for (const component of context.components ?? [])
|
|
13
|
-
|
|
16
|
+
components.set(component.locator.id, contract.component(component))
|
|
14
17
|
|
|
15
18
|
// what an extension brings reaches a context as an instance of what it depends on
|
|
16
19
|
for (const instances of Object.values(context.dependencies ?? {}))
|
|
17
20
|
for (const { component } of instances)
|
|
18
|
-
|
|
21
|
+
components.set(component.locator.id, contract.component(component))
|
|
19
22
|
|
|
20
|
-
return Object.fromEntries([...
|
|
23
|
+
return Object.fromEntries([...components].sort(([one], [other]) => one.localeCompare(other)))
|
|
21
24
|
}
|
package/types/component.d.ts
CHANGED
|
@@ -58,6 +58,10 @@ export type Entity = {
|
|
|
58
58
|
|
|
59
59
|
type Declaration = {
|
|
60
60
|
prototype?: string
|
|
61
|
+
/** what this component's version is made of; everything under it by default */
|
|
62
|
+
files?: string[]
|
|
63
|
+
/** what its version is not made of, beside what the bridge states */
|
|
64
|
+
ignore?: string[]
|
|
61
65
|
namespace: string
|
|
62
66
|
name: string
|
|
63
67
|
version: string
|
package/types/context.d.ts
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { Contract } from '@toa.io/core'
|
|
2
|
+
import type { Context } from './context.d.ts'
|
|
3
|
+
|
|
1
4
|
export * as context from './context.d.ts'
|
|
2
5
|
export * as component from './component.d.ts'
|
|
3
6
|
|
|
4
7
|
export { Manifest } from './component.d.ts'
|
|
5
8
|
export * as entity from './entity.d.ts'
|
|
6
9
|
|
|
10
|
+
/** Every component a Context has, by id, with the contract of the version it runs. */
|
|
11
|
+
export function map(context: Context): Record<string, Contract>
|
|
12
|
+
|
|
7
13
|
export interface Definition {
|
|
8
14
|
/** The package's name, or the reference where it has no package.json */
|
|
9
15
|
name: string
|