@ankhorage/capability 0.0.0-stage → 0.2.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankhorage
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.
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
- # Temporary Holding Version
1
+ <!-- markdownlint-disable MD013 MD033 -->
2
+ <!-- This file is generated by Paradox. Do not edit manually. -->
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ # @ankhorage/capability
5
+
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.2.2](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: canonical](././paradox/badges/docs.svg)
7
+
8
+ Generic runtime validation, normalization, equality, and catalog operations for Ankhorage capabilities.
9
+
10
+ ## Generated documentation
11
+
12
+ - [Interactive documentation app](././paradox/index.html)
13
+ - [Public API reference](././paradox/exports.md)
14
+ - [Component registry](././paradox/components.md)
15
+ - [Architecture overview](././paradox/diagrams/architecture-overview.mmd)
16
+ - [Module relationships](././paradox/diagrams/module-relationships.mmd)
17
+ - [Export graph](././paradox/diagrams/export-graph.mmd)
18
+ - [areCapabilitiesEqual sequence](././paradox/diagrams/sequences/are-capabilities-equal.mmd)
19
+ - [areCapabilityCatalogsEqual sequence](././paradox/diagrams/sequences/are-capability-catalogs-equal.mmd)
20
+ - [isCapabilityCatalog sequence](././paradox/diagrams/sequences/is-capability-catalog.mmd)
21
+ - [isCapabilityId sequence](././paradox/diagrams/sequences/is-capability-id.mmd)
22
+ - [normalizeCapability sequence](././paradox/diagrams/sequences/normalize-capability.mmd)
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Compare capability descriptors independently of record keys and unordered collection ordering. */
3
+ export declare function areCapabilitiesEqual(left: Capability, right: Capability): boolean;
@@ -0,0 +1,6 @@
1
+ import { isDeepEqual } from '@ankhorage/utility/object';
2
+ import { normalizeCapability } from './normalizeCapability.js';
3
+ /*** Compare capability descriptors independently of record keys and unordered collection ordering. */
4
+ export function areCapabilitiesEqual(left, right) {
5
+ return isDeepEqual(normalizeCapability(left), normalizeCapability(right));
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Validate an untrusted value as one complete portable capability descriptor. */
3
+ export declare function isCapability(value: unknown): value is Capability;
@@ -0,0 +1,5 @@
1
+ import { parseCapability } from './parseCapability.js';
2
+ /*** Validate an untrusted value as one complete portable capability descriptor. */
3
+ export function isCapability(value) {
4
+ return parseCapability(value) !== null;
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Validate a stable dot-separated identifier for one capability. */
3
+ export declare function isCapabilityId(value: unknown): value is Capability['id'];
@@ -0,0 +1,7 @@
1
+ import { isNonEmptyString } from '@ankhorage/utility/string';
2
+ /*** Validate a stable dot-separated identifier for one capability. */
3
+ export function isCapabilityId(value) {
4
+ return (isNonEmptyString(value) &&
5
+ value.split('.').length >= 2 &&
6
+ value.split('.').every(isNonEmptyString));
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Return a capability with unordered access and binding-role collections sorted and deduplicated. */
3
+ export declare function normalizeCapability(capability: Capability): Capability;
@@ -0,0 +1,27 @@
1
+ import { uniqueSortedStrings } from '@ankhorage/utility/array';
2
+ const CAPABILITY_ACCESS = ['emit', 'invoke', 'read', 'subscribe', 'write'];
3
+ const CAPABILITY_BINDING_ROLES = ['source', 'target'];
4
+ /*** Return a capability with unordered access and binding-role collections sorted and deduplicated. */
5
+ export function normalizeCapability(capability) {
6
+ return {
7
+ id: capability.id,
8
+ owner: capability.owner,
9
+ access: uniqueSortedStrings(capability.access).filter(isCapabilityAccess),
10
+ binding: {
11
+ kind: capability.binding.kind,
12
+ bindableAs: uniqueSortedStrings(capability.binding.bindableAs).filter(isCapabilityBindingRole),
13
+ },
14
+ ...(capability.label === undefined ? {} : { label: capability.label }),
15
+ ...(capability.description === undefined ? {} : { description: capability.description }),
16
+ ...(capability.input === undefined ? {} : { input: capability.input }),
17
+ ...(capability.output === undefined ? {} : { output: capability.output }),
18
+ };
19
+ }
20
+ /*** Retain one known portable capability access operation after generic string normalization. */
21
+ function isCapabilityAccess(value) {
22
+ return CAPABILITY_ACCESS.includes(value);
23
+ }
24
+ /*** Retain one known portable capability binding direction after generic string normalization. */
25
+ function isCapabilityBindingRole(value) {
26
+ return CAPABILITY_BINDING_ROLES.includes(value);
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Parse one complete portable capability descriptor from an untrusted value. */
3
+ export declare function parseCapability(value: unknown): Capability | null;
@@ -0,0 +1,152 @@
1
+ import { isRecord, readOwnProperty } from '@ankhorage/utility/object';
2
+ import { isNonEmptyString, isOptionalString } from '@ankhorage/utility/string';
3
+ import { isCapabilityId } from './isCapabilityId.js';
4
+ const CAPABILITY_ACCESS = ['emit', 'invoke', 'read', 'subscribe', 'write'];
5
+ const CAPABILITY_BINDING_KINDS = [
6
+ 'action',
7
+ 'api',
8
+ 'auth',
9
+ 'authorization',
10
+ 'context',
11
+ 'event',
12
+ 'permission',
13
+ 'state',
14
+ 'storage',
15
+ ];
16
+ const CAPABILITY_BINDING_ROLES = ['source', 'target'];
17
+ /*** Parse one complete portable capability descriptor from an untrusted value. */
18
+ export function parseCapability(value) {
19
+ if (!isRecord(value))
20
+ return null;
21
+ if (!isCapabilityId(value.id) || !isNonEmptyString(value.owner))
22
+ return null;
23
+ if (!isCapabilityAccessList(value.access) || !isCapabilityBinding(value.binding))
24
+ return null;
25
+ if (!isOptionalString(value.label) || !isOptionalString(value.description))
26
+ return null;
27
+ if (!isOptionalDataSchemaSlot(value.input) || !isOptionalDataSchemaSlot(value.output))
28
+ return null;
29
+ return {
30
+ id: value.id,
31
+ owner: value.owner,
32
+ access: value.access,
33
+ binding: value.binding,
34
+ ...(value.label === undefined ? {} : { label: value.label }),
35
+ ...(value.description === undefined ? {} : { description: value.description }),
36
+ ...(value.input === undefined ? {} : { input: value.input }),
37
+ ...(value.output === undefined ? {} : { output: value.output }),
38
+ };
39
+ }
40
+ /*** Validate a non-empty list of supported capability access operations. */
41
+ function isCapabilityAccessList(value) {
42
+ return Array.isArray(value) && value.length > 0 && value.every(isCapabilityAccess);
43
+ }
44
+ /*** Validate one supported capability access operation. */
45
+ function isCapabilityAccess(value) {
46
+ return typeof value === 'string' && CAPABILITY_ACCESS.includes(value);
47
+ }
48
+ /*** Validate the portable binding shape attached to a capability descriptor. */
49
+ function isCapabilityBinding(value) {
50
+ return (isRecord(value) &&
51
+ isCapabilityBindingKind(value.kind) &&
52
+ Array.isArray(value.bindableAs) &&
53
+ value.bindableAs.every(isCapabilityBindingRole));
54
+ }
55
+ /*** Validate one supported portable capability binding family. */
56
+ function isCapabilityBindingKind(value) {
57
+ return typeof value === 'string' && CAPABILITY_BINDING_KINDS.includes(value);
58
+ }
59
+ /*** Validate one supported capability binding direction. */
60
+ function isCapabilityBindingRole(value) {
61
+ return typeof value === 'string' && CAPABILITY_BINDING_ROLES.includes(value);
62
+ }
63
+ /*** Validate an omitted or structurally valid portable data-schema slot. */
64
+ function isOptionalDataSchemaSlot(value) {
65
+ return value === undefined || isDataSchemaSlot(value);
66
+ }
67
+ /*** Validate one portable data-schema slot without importing an executable Contracts helper. */
68
+ function isDataSchemaSlot(value) {
69
+ return (isRecord(value) &&
70
+ (value.schema === undefined || isDataSchema(value.schema)) &&
71
+ (value.schemaRef === undefined || isDataSchemaRef(value.schemaRef)));
72
+ }
73
+ /*** Validate the serializable recursive data-schema shape used by capability slots. */
74
+ function isDataSchema(value) {
75
+ return (isRecord(value) &&
76
+ isDataSchemaMetadata(value) &&
77
+ isDataSchemaStructure(value) &&
78
+ isDataSchemaValues(value));
79
+ }
80
+ /*** Validate descriptive and object-shape metadata for one data schema. */
81
+ function isDataSchemaMetadata(value) {
82
+ return (isOptionalDataSchemaType(value.type) &&
83
+ isOptionalString(value.title) &&
84
+ isOptionalString(value.description) &&
85
+ isOptionalString(value.format) &&
86
+ (value.nullable === undefined || typeof value.nullable === 'boolean') &&
87
+ isOptionalStringArray(value.required) &&
88
+ isOptionalDataSchemaProperties(value.properties) &&
89
+ isOptionalAdditionalProperties(value.additionalProperties));
90
+ }
91
+ /*** Validate recursive composition and reference fields for one data schema. */
92
+ function isDataSchemaStructure(value) {
93
+ return ((value.items === undefined || isDataSchema(value.items)) &&
94
+ isOptionalDataSchemaComposition(value) &&
95
+ (value.ref === undefined || isDataSchemaRef(value.ref)));
96
+ }
97
+ /*** Validate serializable constant, default, and enum fields for one data schema. */
98
+ function isDataSchemaValues(value) {
99
+ return (isOptionalSerializableValue(value.const) &&
100
+ isOptionalSerializableValue(value.default) &&
101
+ isOptionalSerializableValueArray(value.enum));
102
+ }
103
+ /*** Validate an omitted data-schema type or supported primitive type declaration. */
104
+ function isOptionalDataSchemaType(value) {
105
+ const dataSchemaTypes = ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string'];
106
+ return (value === undefined ||
107
+ (typeof value === 'string' && dataSchemaTypes.includes(value)) ||
108
+ (Array.isArray(value) &&
109
+ value.every((entry) => typeof entry === 'string' && dataSchemaTypes.includes(entry))));
110
+ }
111
+ /*** Validate an omitted array of required property names. */
112
+ function isOptionalStringArray(value) {
113
+ return (value === undefined ||
114
+ (Array.isArray(value) && value.every((entry) => typeof entry === 'string')));
115
+ }
116
+ /*** Validate an omitted map of nested data schemas. */
117
+ function isOptionalDataSchemaProperties(value) {
118
+ return value === undefined || (isRecord(value) && Object.values(value).every(isDataSchema));
119
+ }
120
+ /*** Validate an omitted additional-properties declaration. */
121
+ function isOptionalAdditionalProperties(value) {
122
+ return value === undefined || typeof value === 'boolean' || isDataSchema(value);
123
+ }
124
+ /*** Validate optional schema intersections and alternatives from own properties only. */
125
+ function isOptionalDataSchemaComposition(value) {
126
+ return ['allOf', 'anyOf', 'oneOf'].every((key) => isOptionalDataSchemaArray(readOwnProperty(value, key)));
127
+ }
128
+ /*** Validate an omitted recursive data-schema collection. */
129
+ function isOptionalDataSchemaArray(value) {
130
+ return value === undefined || (Array.isArray(value) && value.every(isDataSchema));
131
+ }
132
+ /*** Validate a schema reference with the portable string identifier contract. */
133
+ function isDataSchemaRef(value) {
134
+ return isRecord(value) && typeof value.id === 'string';
135
+ }
136
+ /*** Validate an omitted JSON-serializable scalar, array, or record value. */
137
+ function isOptionalSerializableValue(value) {
138
+ return value === undefined || isSerializableValue(value);
139
+ }
140
+ /*** Validate an omitted array of JSON-serializable values. */
141
+ function isOptionalSerializableValueArray(value) {
142
+ return value === undefined || (Array.isArray(value) && value.every(isSerializableValue));
143
+ }
144
+ /*** Validate one JSON-serializable value recursively. */
145
+ function isSerializableValue(value) {
146
+ return (value === null ||
147
+ typeof value === 'string' ||
148
+ typeof value === 'number' ||
149
+ typeof value === 'boolean' ||
150
+ (Array.isArray(value) && value.every(isSerializableValue)) ||
151
+ (isRecord(value) && Object.values(value).every(isSerializableValue)));
152
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Compare capability catalogs independently of authored catalog and unordered descriptor collection ordering. */
3
+ export declare function areCapabilityCatalogsEqual(left: readonly Capability[], right: readonly Capability[]): boolean;
@@ -0,0 +1,6 @@
1
+ import { isDeepEqual } from '@ankhorage/utility/object';
2
+ import { normalizeCapabilityCatalog } from './normalizeCapabilityCatalog.js';
3
+ /*** Compare capability catalogs independently of authored catalog and unordered descriptor collection ordering. */
4
+ export function areCapabilityCatalogsEqual(left, right) {
5
+ return isDeepEqual(normalizeCapabilityCatalog(left), normalizeCapabilityCatalog(right));
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Validate a capability catalog with complete descriptors and unique stable identifiers. */
3
+ export declare function isCapabilityCatalog(value: unknown): value is readonly Capability[];
@@ -0,0 +1,5 @@
1
+ import { parseCapabilityCatalog } from './parseCapabilityCatalog.js';
2
+ /*** Validate a capability catalog with complete descriptors and unique stable identifiers. */
3
+ export function isCapabilityCatalog(value) {
4
+ return parseCapabilityCatalog(value) !== null;
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Normalize trusted catalog descriptors and sort them by stable capability identifier. */
3
+ export declare function normalizeCapabilityCatalog(catalog: readonly Capability[]): readonly Capability[];
@@ -0,0 +1,7 @@
1
+ import { normalizeCapability } from '../capability/normalizeCapability.js';
2
+ /*** Normalize trusted catalog descriptors and sort them by stable capability identifier. */
3
+ export function normalizeCapabilityCatalog(catalog) {
4
+ return catalog
5
+ .map(normalizeCapability)
6
+ .toSorted((left, right) => left.id.localeCompare(right.id));
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+ /*** Parse a capability catalog and reject invalid descriptors or duplicate stable identifiers. */
3
+ export declare function parseCapabilityCatalog(value: unknown): readonly Capability[] | null;
@@ -0,0 +1,12 @@
1
+ import { parseCapability } from '../capability/parseCapability.js';
2
+ /*** Parse a capability catalog and reject invalid descriptors or duplicate stable identifiers. */
3
+ export function parseCapabilityCatalog(value) {
4
+ if (!Array.isArray(value))
5
+ return null;
6
+ const capabilities = value.map(parseCapability);
7
+ if (capabilities.some((capability) => capability === null))
8
+ return null;
9
+ const parsedCapabilities = capabilities.filter((capability) => capability !== null);
10
+ const ids = new Set(parsedCapabilities.map((capability) => capability.id));
11
+ return ids.size === parsedCapabilities.length ? parsedCapabilities : null;
12
+ }
@@ -0,0 +1,9 @@
1
+ export { areCapabilitiesEqual } from './features/capability/areCapabilitiesEqual.js';
2
+ export { isCapability } from './features/capability/isCapability.js';
3
+ export { isCapabilityId } from './features/capability/isCapabilityId.js';
4
+ export { normalizeCapability } from './features/capability/normalizeCapability.js';
5
+ export { parseCapability } from './features/capability/parseCapability.js';
6
+ export { areCapabilityCatalogsEqual } from './features/catalog/areCapabilityCatalogsEqual.js';
7
+ export { isCapabilityCatalog } from './features/catalog/isCapabilityCatalog.js';
8
+ export { normalizeCapabilityCatalog } from './features/catalog/normalizeCapabilityCatalog.js';
9
+ export { parseCapabilityCatalog } from './features/catalog/parseCapabilityCatalog.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { areCapabilitiesEqual } from './features/capability/areCapabilitiesEqual.js';
2
+ export { isCapability } from './features/capability/isCapability.js';
3
+ export { isCapabilityId } from './features/capability/isCapabilityId.js';
4
+ export { normalizeCapability } from './features/capability/normalizeCapability.js';
5
+ export { parseCapability } from './features/capability/parseCapability.js';
6
+ export { areCapabilityCatalogsEqual } from './features/catalog/areCapabilityCatalogsEqual.js';
7
+ export { isCapabilityCatalog } from './features/catalog/isCapabilityCatalog.js';
8
+ export { normalizeCapabilityCatalog } from './features/catalog/normalizeCapabilityCatalog.js';
9
+ export { parseCapabilityCatalog } from './features/catalog/parseCapabilityCatalog.js';
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@ankhorage/capability",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.2",
4
+ "description": "Generic runtime validation, normalization, equality, and catalog operations for Ankhorage capabilities.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/ankhorage/capability#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/ankhorage/capability/issues"
9
+ },
10
+ "keywords": [
11
+ "ankhorage",
12
+ "capability",
13
+ "validation",
14
+ "typescript"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ankhorage/capability.git"
19
+ },
20
+ "type": "module",
21
+ "sideEffects": false,
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "typecheck": "tsc --noEmit -p tsconfig.json",
41
+ "test": "bun test",
42
+ "test:standalone": "bun run build && bun test tests/standalone.test.ts",
43
+ "docs": "bunx @ankhorage/paradox && ankhorage-prettier --write README.md paradox",
44
+ "lint": "ankhorage-eslint . --max-warnings=0",
45
+ "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
46
+ "format": "ankhorage-prettier --write .",
47
+ "format:check": "ankhorage-prettier --check .",
48
+ "knip:check": "ankhorage-knip",
49
+ "changeset": "ankhorage-changeset",
50
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
51
+ "version-packages": "ankhorage-changeset version"
52
+ },
53
+ "dependencies": {
54
+ "@ankhorage/contracts": "^26.0.0",
55
+ "@ankhorage/utility": "^1.9.85"
56
+ },
57
+ "devDependencies": {
58
+ "@ankhorage/doctor": "^0.14.5",
59
+ "@ankhorage/devtools": "^2.3.6",
60
+ "@ankhorage/paradox": "^0.2.28",
61
+ "@types/bun": "^1.4.2",
62
+ "@types/node": "^26.6.4",
63
+ "typescript": "^6.0.3"
64
+ },
65
+ "packageManager": "bun@1.4.2"
66
+ }