@bakers-build/message 0.0.0-stage → 0.1.0-next.38
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 +21 -0
- package/README.md +4 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/schema.d.ts +43 -0
- package/dist/src/schema.js +113 -0
- package/package.json +28 -4
- package/schemas/message-definition.v2.schema.json +42 -0
- package/schemas/message-result.v2.schema.json +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 keytec GmbH
|
|
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,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bakers-build/message
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The InboxMessageHandler contract: a message type's definition (label, access, optional input) and its handler's one-Markdown result. Owns only the message shape, independent of addon/workspace/conductor concepts.
|
|
4
|
+
|
|
5
|
+
Part of the bakers.build CLI. Install the meta package `@bakers-build/bakers` to get the `bakers` CLI with all addons. Source: https://git.key-tec.de/bakers-build/bakers-build (bakers-cli/).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type FieldType, MESSAGE_CONTRACT_VERSION, type MessageDefinition, type MessageField, type MessageInput, type MessageResult, payloadSchema, type Validation, validateArgs, validateMessageDefinition, validateResult, } from './schema.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MESSAGE_CONTRACT_VERSION, payloadSchema, validateArgs, validateMessageDefinition, validateResult, } from './schema.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const MESSAGE_CONTRACT_VERSION = 2;
|
|
2
|
+
export type FieldType = 'string' | 'integer' | 'number' | 'boolean';
|
|
3
|
+
export interface MessageField {
|
|
4
|
+
type: FieldType;
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
default?: unknown;
|
|
8
|
+
enum?: Array<string | number>;
|
|
9
|
+
minimum?: number;
|
|
10
|
+
maximum?: number;
|
|
11
|
+
minLength?: number;
|
|
12
|
+
maxLength?: number;
|
|
13
|
+
pattern?: string;
|
|
14
|
+
format?: 'uri' | 'multiline';
|
|
15
|
+
}
|
|
16
|
+
export interface MessageInput {
|
|
17
|
+
type: 'object';
|
|
18
|
+
required?: string[];
|
|
19
|
+
properties: Record<string, MessageField>;
|
|
20
|
+
}
|
|
21
|
+
/** What an addon registers for one message type. */
|
|
22
|
+
export interface MessageDefinition {
|
|
23
|
+
label: string;
|
|
24
|
+
/** Required access the sender must hold; defaults to the strictest, `update`. */
|
|
25
|
+
access?: 'view' | 'update';
|
|
26
|
+
/** Absent means a parameterless message: no form field, no payload. */
|
|
27
|
+
input?: MessageInput;
|
|
28
|
+
}
|
|
29
|
+
/** What every handler returns: one Markdown answer, nothing else. */
|
|
30
|
+
export interface MessageResult {
|
|
31
|
+
markdown: string;
|
|
32
|
+
}
|
|
33
|
+
export type Validation = {
|
|
34
|
+
ok: true;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
errors: string[];
|
|
38
|
+
};
|
|
39
|
+
export declare function validateMessageDefinition(def: unknown): Validation;
|
|
40
|
+
/** The message's input as a closed schema; no input means an empty object. */
|
|
41
|
+
export declare function payloadSchema(input: MessageInput | undefined): object;
|
|
42
|
+
export declare function validateArgs(input: MessageInput | undefined, args: unknown): Validation;
|
|
43
|
+
export declare function validateResult(result: unknown): Validation;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The message contract v2: a MessageDefinition an addon registers under a
|
|
3
|
+
* message type key, and the MessageResult its handler answers with. Owns
|
|
4
|
+
* only the message shape — no addon, workspace or conductor concept lives
|
|
5
|
+
* here.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { Ajv } from 'ajv';
|
|
11
|
+
export const MESSAGE_CONTRACT_VERSION = 2;
|
|
12
|
+
/** Walks up from this module to the `@bakers-build/message` package root (src or dist). */
|
|
13
|
+
function schemaDir() {
|
|
14
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
for (let i = 0; i < 6; i++) {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
18
|
+
if (pkg.name === '@bakers-build/message')
|
|
19
|
+
return join(dir, 'schemas');
|
|
20
|
+
}
|
|
21
|
+
catch { }
|
|
22
|
+
dir = dirname(dir);
|
|
23
|
+
}
|
|
24
|
+
throw new Error('message/schema: @bakers-build/message package root not found');
|
|
25
|
+
}
|
|
26
|
+
function loadSchema(file) {
|
|
27
|
+
return JSON.parse(readFileSync(join(schemaDir(), file), 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
let compiled;
|
|
30
|
+
function validators() {
|
|
31
|
+
if (compiled)
|
|
32
|
+
return compiled;
|
|
33
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
34
|
+
compiled = {
|
|
35
|
+
ajv,
|
|
36
|
+
definition: ajv.compile(loadSchema('message-definition.v2.schema.json')),
|
|
37
|
+
result: ajv.compile(loadSchema('message-result.v2.schema.json')),
|
|
38
|
+
};
|
|
39
|
+
return compiled;
|
|
40
|
+
}
|
|
41
|
+
function ajvErrors(errors) {
|
|
42
|
+
return (errors ?? []).map((e) => `${e.instancePath.replace(/^\//, '').replaceAll('/', '.') || '(root)'}: ${e.message ?? 'invalid'}`);
|
|
43
|
+
}
|
|
44
|
+
function matchesType(type, value) {
|
|
45
|
+
switch (type) {
|
|
46
|
+
case 'integer':
|
|
47
|
+
return Number.isInteger(value);
|
|
48
|
+
case 'number':
|
|
49
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
50
|
+
case 'boolean':
|
|
51
|
+
return typeof value === 'boolean';
|
|
52
|
+
case 'string':
|
|
53
|
+
return typeof value === 'string';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The checks JSON Schema cannot express: values and keywords fit the field type. */
|
|
57
|
+
function consistencyErrors(input) {
|
|
58
|
+
if (!input)
|
|
59
|
+
return [];
|
|
60
|
+
const errors = [];
|
|
61
|
+
for (const [name, field] of Object.entries(input.properties)) {
|
|
62
|
+
const at = `input.properties.${name}`;
|
|
63
|
+
const numeric = field.type === 'integer' || field.type === 'number';
|
|
64
|
+
if ('default' in field && !matchesType(field.type, field.default))
|
|
65
|
+
errors.push(`${at}.default: does not match type ${field.type}`);
|
|
66
|
+
if (field.enum?.some((v) => !matchesType(field.type, v)))
|
|
67
|
+
errors.push(`${at}.enum: items must match type ${field.type}`);
|
|
68
|
+
for (const kw of ['minimum', 'maximum'])
|
|
69
|
+
if (kw in field && !numeric)
|
|
70
|
+
errors.push(`${at}.${kw}: only allowed on integer or number`);
|
|
71
|
+
for (const kw of ['minLength', 'maxLength', 'pattern', 'format'])
|
|
72
|
+
if (kw in field && field.type !== 'string')
|
|
73
|
+
errors.push(`${at}.${kw}: only allowed on string`);
|
|
74
|
+
if (field.pattern !== undefined) {
|
|
75
|
+
try {
|
|
76
|
+
new RegExp(field.pattern, 'u');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
errors.push(`${at}.pattern: does not compile`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const name of input.required ?? [])
|
|
84
|
+
if (!(name in input.properties))
|
|
85
|
+
errors.push(`input.required: ${name} is not a property`);
|
|
86
|
+
return errors;
|
|
87
|
+
}
|
|
88
|
+
export function validateMessageDefinition(def) {
|
|
89
|
+
const { definition } = validators();
|
|
90
|
+
if (!definition(def))
|
|
91
|
+
return { ok: false, errors: ajvErrors(definition.errors) };
|
|
92
|
+
const errors = consistencyErrors(def.input);
|
|
93
|
+
return errors.length ? { ok: false, errors } : { ok: true };
|
|
94
|
+
}
|
|
95
|
+
/** The message's input as a closed schema; no input means an empty object. */
|
|
96
|
+
export function payloadSchema(input) {
|
|
97
|
+
return {
|
|
98
|
+
...(input ?? { type: 'object', properties: {} }),
|
|
99
|
+
additionalProperties: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function validateArgs(input, args) {
|
|
103
|
+
const { ajv } = validators();
|
|
104
|
+
const validate = ajv.compile(payloadSchema(input));
|
|
105
|
+
const candidate = args === undefined ? {} : args;
|
|
106
|
+
return validate(candidate)
|
|
107
|
+
? { ok: true }
|
|
108
|
+
: { ok: false, errors: ajvErrors(validate.errors) };
|
|
109
|
+
}
|
|
110
|
+
export function validateResult(result) {
|
|
111
|
+
const v = validators().result;
|
|
112
|
+
return v(result) ? { ok: true } : { ok: false, errors: ajvErrors(v.errors) };
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakers-build/message",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-next.38",
|
|
4
|
+
"description": "The InboxMessageHandler contract: a message type's definition (label, access, optional input) and its handler's one-Markdown result. Owns only the message shape, independent of addon/workspace/conductor concepts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.19"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/src/index.js",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/src",
|
|
16
|
+
"schemas",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://git.key-tec.de/bakers-build/bakers-build.git",
|
|
25
|
+
"directory": "bakers-cli/message"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"ajv": "^8.20.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://app.bakers.build/schemas/message-definition.v2.schema.json",
|
|
4
|
+
"title": "MessageDefinition",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["label"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"label": { "type": "string", "minLength": 1 },
|
|
10
|
+
"access": { "enum": ["view", "update"] },
|
|
11
|
+
"input": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["type", "properties"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"type": { "const": "object" },
|
|
17
|
+
"required": { "type": "array", "items": { "type": "string" } },
|
|
18
|
+
"properties": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["type"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"type": { "enum": ["string", "integer", "number", "boolean"] },
|
|
26
|
+
"title": { "type": "string" },
|
|
27
|
+
"description": { "type": "string" },
|
|
28
|
+
"default": {},
|
|
29
|
+
"enum": { "type": "array" },
|
|
30
|
+
"minimum": { "type": "number" },
|
|
31
|
+
"maximum": { "type": "number" },
|
|
32
|
+
"minLength": { "type": "integer" },
|
|
33
|
+
"maxLength": { "type": "integer" },
|
|
34
|
+
"pattern": { "type": "string" },
|
|
35
|
+
"format": { "enum": ["uri", "multiline"] }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://app.bakers.build/schemas/message-result.v2.schema.json",
|
|
4
|
+
"title": "MessageResult",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["markdown"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"markdown": { "type": "string", "maxLength": 1048576 }
|
|
10
|
+
}
|
|
11
|
+
}
|