DefStream 0.1.2__py3-none-any.whl
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.
- DefStream/Broker/Configuration.py +18 -0
- DefStream/Broker/Connection.py +97 -0
- DefStream/Broker/Consumer.py +59 -0
- DefStream/Broker/Errors.py +14 -0
- DefStream/Broker/Event.py +35 -0
- DefStream/Broker/Implements/__init__.py +1 -0
- DefStream/Broker/Policies.py +25 -0
- DefStream/Broker/Protocol.py +45 -0
- DefStream/Broker/__init__.py +47 -0
- DefStream/Descriptor/DataObject.py +384 -0
- DefStream/Descriptor/Types.py +229 -0
- DefStream/Descriptor/Value.py +132 -0
- DefStream/Descriptor/__init__.py +172 -0
- DefStream/Inspector/Extension.py +69 -0
- DefStream/Inspector/Extensions/Console.html +373 -0
- DefStream/Inspector/Extensions/Console.py +396 -0
- DefStream/Inspector/Extensions/Dashboard.html +206 -0
- DefStream/Inspector/Extensions/Dashboard.py +114 -0
- DefStream/Inspector/Extensions/HealthCheck.py +92 -0
- DefStream/Inspector/Extensions/Metrics.py +281 -0
- DefStream/Inspector/Extensions/Specifications.py +160 -0
- DefStream/Inspector/Extensions/__init__.py +13 -0
- DefStream/Inspector/Inspector.py +216 -0
- DefStream/Inspector/Response.py +14 -0
- DefStream/Inspector/__init__.py +9 -0
- DefStream/Validator/Pattern.py +36 -0
- DefStream/Validator/Patterns/Array.py +30 -0
- DefStream/Validator/Patterns/Common.py +93 -0
- DefStream/Validator/Patterns/Compare.py +180 -0
- DefStream/Validator/Patterns/Condition.py +60 -0
- DefStream/Validator/Patterns/DataObject.py +85 -0
- DefStream/Validator/Patterns/DateTime.py +111 -0
- DefStream/Validator/Patterns/Number.py +43 -0
- DefStream/Validator/Patterns/Object.py +150 -0
- DefStream/Validator/Patterns/Size.py +110 -0
- DefStream/Validator/Patterns/String.py +85 -0
- DefStream/Validator/Patterns/Type.py +350 -0
- DefStream/Validator/Patterns/__init__.py +137 -0
- DefStream/Validator/Validate.py +29 -0
- DefStream/Validator/Validator.py +30 -0
- DefStream/Validator/__init__.py +11 -0
- DefStream/Worker/Executor.py +298 -0
- DefStream/Worker/Message.py +103 -0
- DefStream/Worker/Pool.py +203 -0
- DefStream/Worker/Run.py +187 -0
- DefStream/Worker/Specification.py +91 -0
- DefStream/Worker/Worker.py +321 -0
- DefStream/Worker/__init__.py +49 -0
- DefStream/__init__.py +0 -0
- defstream-0.1.2.dist-info/METADATA +1197 -0
- defstream-0.1.2.dist-info/RECORD +54 -0
- defstream-0.1.2.dist-info/WHEEL +5 -0
- defstream-0.1.2.dist-info/licenses/LICENSE +674 -0
- defstream-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{{console_title}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light; font: 14px/1.55 system-ui, -apple-system, sans-serif; color: #263343; background: #f7f8fa; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; }
|
|
11
|
+
button, input, textarea, select { font: inherit; }
|
|
12
|
+
button, summary, select { cursor: pointer; }
|
|
13
|
+
button, input, textarea, select, summary { transition: border-color .15s, background-color .15s; }
|
|
14
|
+
:focus-visible { outline: 3px solid #89b8ff; outline-offset: 3px; }
|
|
15
|
+
.topbar { background: #fff; border-bottom: 1px solid #e1e5eb; }
|
|
16
|
+
.topbar-inner, .workspace { max-width: 1440px; margin: 0 auto; padding: 24px 32px; }
|
|
17
|
+
.topbar-inner { display: flex; align-items: center; gap: 16px; }
|
|
18
|
+
h1 { margin: 0; font-size: 22px; letter-spacing: -.6px; line-height: 1.3; overflow-wrap: anywhere; }
|
|
19
|
+
h2 { margin: 0; font-size: 25px; letter-spacing: -.6px; overflow-wrap: anywhere; }
|
|
20
|
+
h3 { margin: 0; font-size: 15px; }
|
|
21
|
+
p { margin: 6px 0 0; }
|
|
22
|
+
.muted { color: #657184; }
|
|
23
|
+
.subtitle { font-size: 13px; }
|
|
24
|
+
.subtitle:empty { display: none; }
|
|
25
|
+
.version { margin-top: 5px; color: #657184; font: 11px/1.6 Consolas, monospace; }
|
|
26
|
+
.badge { display: inline-flex; align-items: center; padding: 4px 9px; border: 1px solid #c6dfcf; border-radius: 5px; color: #286f40; background: #f0faf3; font-size: 11px; font-weight: 700; letter-spacing: .6px; }
|
|
27
|
+
.topbar .badge { margin-left: auto; white-space: nowrap; }
|
|
28
|
+
.warning { margin: 0 0 20px; padding: 12px 16px; border: 1px solid #eadbb7; border-radius: 6px; background: #fffcf4; color: #796023; font-size: 12px; }
|
|
29
|
+
.warning strong { margin-right: 8px; }
|
|
30
|
+
.auth { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; padding: 16px 20px; margin-bottom: 24px; background: #fff; border: 1px solid #e1e5eb; border-radius: 7px; }
|
|
31
|
+
.auth label { flex-basis: 100%; margin: 0; }
|
|
32
|
+
.auth input { flex: 1 1 240px; max-width: 400px; }
|
|
33
|
+
label { display: block; font-size: 12px; font-weight: 650; margin-bottom: 7px; }
|
|
34
|
+
input, textarea, select { width: 100%; border: 1px solid #cdd5df; border-radius: 5px; color: inherit; background: #fff; }
|
|
35
|
+
input { padding: 9px 12px; }
|
|
36
|
+
input:hover, textarea:hover, select:hover { border-color: #93a4b7; }
|
|
37
|
+
button { padding: 9px 17px; border: 1px solid #cdd5df; border-radius: 5px; font-weight: 650; color: #38485b; background: #fff; }
|
|
38
|
+
button:hover { background: #f0f3f7; border-color: #a5b2c2; }
|
|
39
|
+
button:disabled { opacity: .45; cursor: not-allowed; }
|
|
40
|
+
#notice { flex: 1 1 240px; align-self: center; margin: 0; color: #657184; font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
41
|
+
.grid { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 24px; align-items: start; }
|
|
42
|
+
.panel { min-width: 0; background: #fff; border: 1px solid #e1e5eb; border-radius: 7px; overflow: hidden; }
|
|
43
|
+
.sidebar { position: sticky; top: 24px; }
|
|
44
|
+
.sidebar-heading { padding: 18px 18px 12px; border-bottom: 1px solid #edf0f4; }
|
|
45
|
+
.sidebar-heading label { margin: 0; font-size: 13px; }
|
|
46
|
+
.sidebar-heading p { font-size: 12px; }
|
|
47
|
+
#tasks { display: block; border: 0; border-radius: 0; padding: 10px; min-height: 280px; font-size: 13px; }
|
|
48
|
+
#tasks option { padding: 11px 12px; border-radius: 4px; margin: 3px 0; color: #000; -webkit-text-fill-color: #000; }
|
|
49
|
+
#tasks option:checked { color: #000; background: #eaf7ee linear-gradient(#eaf7ee, #eaf7ee); font-weight: 650; }
|
|
50
|
+
#tasks option:hover, #tasks option:focus, #tasks:focus option:checked { color: #000; -webkit-text-fill-color: #000; }
|
|
51
|
+
.operation { border-top: 3px solid #49a66a; }
|
|
52
|
+
.operation-header { padding: 24px; background: #f6fbf8; border-bottom: 1px solid #dcebe1; }
|
|
53
|
+
.operation-heading { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
|
|
54
|
+
#description:empty { display: none; }
|
|
55
|
+
#description { color: #536374; }
|
|
56
|
+
#queue { margin-top: 14px; font: 12px/1.7 Consolas, monospace; overflow-wrap: anywhere; white-space: pre-line; }
|
|
57
|
+
.section { padding: 24px; }
|
|
58
|
+
.section + .section { border-top: 1px solid #e8ecf1; }
|
|
59
|
+
.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
|
|
60
|
+
.section-heading span { color: #768295; font-size: 11px; letter-spacing: .7px; font-weight: 600; }
|
|
61
|
+
.schemas { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
62
|
+
details { min-width: 0; border: 1px solid #e1e5eb; border-radius: 5px; background: #fafbfc; }
|
|
63
|
+
summary { padding: 12px 14px; font-size: 12px; font-weight: 650; }
|
|
64
|
+
details[open] summary { border-bottom: 1px solid #e1e5eb; }
|
|
65
|
+
pre { margin: 0; padding: 16px; font: 12px/1.75 Consolas, "Liberation Mono", monospace; white-space: pre-wrap; overflow-wrap: anywhere; overflow: auto; background: #f7f9fb; }
|
|
66
|
+
details pre { max-height: 320px; border-radius: 0 0 5px 5px; }
|
|
67
|
+
.schema-card { min-width: 0; border: 1px solid #e1e5eb; border-radius: 5px; background: #fff; }
|
|
68
|
+
.schema-card > h4 { margin: 0; padding: 12px 14px; border-bottom: 1px solid #e1e5eb; background: #f7f9fb; font-size: 12px; }
|
|
69
|
+
.schema-model { padding: 4px 14px; }
|
|
70
|
+
.schema-node { padding: 10px 0; min-width: 0; }
|
|
71
|
+
.schema-node + .schema-node { border-top: 1px solid #edf0f4; }
|
|
72
|
+
details.schema-node { border: 0; border-radius: 0; background: transparent; }
|
|
73
|
+
details.schema-node > summary { padding: 0; border: 0; }
|
|
74
|
+
.schema-line { display: inline; font-size: 12px; line-height: 2; overflow-wrap: anywhere; }
|
|
75
|
+
.schema-name { color: #263343; font: 650 12px Consolas, monospace; margin-right: 8px; }
|
|
76
|
+
.schema-type { padding: 2px 6px; margin-right: 7px; border-radius: 4px; background: #edf3ff; color: #345a9a; font: 11px Consolas, monospace; }
|
|
77
|
+
.schema-required { color: #b33b45; font-size: 10px; font-weight: 650; margin-right: 7px; }
|
|
78
|
+
.schema-description { margin-top: 4px; color: #536374; font-size: 12px; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
79
|
+
.schema-constraints { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
|
|
80
|
+
.schema-constraint { max-width: 100%; padding: 2px 6px; border: 1px solid #e1e5eb; border-radius: 4px; background: #fafbfc; color: #58677a; font: 10px/1.7 Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
81
|
+
.schema-children { margin: 8px 0 0 5px; padding-left: 13px; border-left: 2px solid #e8edf3; }
|
|
82
|
+
.schema-empty { padding: 12px 0; color: #657184; font-size: 12px; }
|
|
83
|
+
.schema-raw { margin: 0 14px 14px; }
|
|
84
|
+
.schema-raw summary { color: #657184; font-size: 11px; }
|
|
85
|
+
.editor-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 9px 14px; border: 1px solid #cdd5df; border-bottom: 0; border-radius: 5px 5px 0 0; background: #f7f9fb; }
|
|
86
|
+
.editor-header label { margin: 0; }
|
|
87
|
+
#example { padding: 3px 8px; border-color: transparent; background: transparent; font-size: 11px; }
|
|
88
|
+
textarea { display: block; min-height: 220px; resize: vertical; padding: 16px; border-radius: 0 0 5px 5px; font: 13px/1.8 Consolas, "Liberation Mono", monospace; tab-size: 2; }
|
|
89
|
+
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
|
|
90
|
+
#send { min-width: 120px; background: #27834c; border-color: #27834c; color: #fff; }
|
|
91
|
+
#send:hover { background: #216e40; }
|
|
92
|
+
button.rpc { min-width: 120px; background: #edf5ff; border-color: #9abfe9; color: #225c9d; }
|
|
93
|
+
button.rpc:hover { background: #deedff; }
|
|
94
|
+
#rpc-note { margin-top: 12px; font-size: 12px; }
|
|
95
|
+
#result { min-height: 100px; border: 1px solid #dce3eb; border-radius: 5px; }
|
|
96
|
+
.footer { margin-top: 24px; text-align: right; color: #7b8797; font-size: 11px; }
|
|
97
|
+
[hidden] { display: none !important; }
|
|
98
|
+
body[data-auth-required="false"] label[for="token"], body[data-auth-required="false"] #token, body[data-auth-required="false"] #clear { display: none; }
|
|
99
|
+
@media(max-width: 800px) {
|
|
100
|
+
.topbar-inner, .workspace { padding: 20px 16px; }
|
|
101
|
+
.grid { grid-template-columns: 1fr; gap: 16px; }
|
|
102
|
+
.sidebar { position: static; }
|
|
103
|
+
#tasks { min-height: 0; height: 140px; }
|
|
104
|
+
.schemas { grid-template-columns: 1fr; }
|
|
105
|
+
.section, .operation-header { padding: 18px; }
|
|
106
|
+
.topbar .badge { display: none; }
|
|
107
|
+
.section-heading span { letter-spacing: 0; }
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
110
|
+
</head>
|
|
111
|
+
<body data-auth-required="true">
|
|
112
|
+
<header class="topbar"><div class="topbar-inner">
|
|
113
|
+
<div><h1 id="title">{{console_title}}</h1>
|
|
114
|
+
<p class="muted subtitle">{{console_description}}</p>
|
|
115
|
+
<p id="version" class="version" hidden></p></div>
|
|
116
|
+
<span class="badge">ASYNCAPI 3.1</span>
|
|
117
|
+
</div></header>
|
|
118
|
+
<div class="workspace">
|
|
119
|
+
<p class="warning"><strong>Live broker</strong>These actions publish real messages and may have side effects. RPC timeout does not cancel work. Use HTTPS for remote access.</p>
|
|
120
|
+
<section class="auth" aria-label="Connection">
|
|
121
|
+
<label for="token">Bearer token</label>
|
|
122
|
+
<input id="token" type="password" autocomplete="off" spellcheck="false" placeholder="Token is kept in memory only">
|
|
123
|
+
<button id="connect" type="button">Load tasks</button>
|
|
124
|
+
<button id="clear" type="button">Clear token</button>
|
|
125
|
+
<p id="notice" role="status" aria-live="polite"></p>
|
|
126
|
+
</section>
|
|
127
|
+
<main id="main" class="grid" hidden>
|
|
128
|
+
<aside class="panel sidebar">
|
|
129
|
+
<div class="sidebar-heading"><label for="tasks">Registered Tasks</label><p class="muted">Select an operation to explore</p></div>
|
|
130
|
+
<select id="tasks" size="12"></select>
|
|
131
|
+
</aside>
|
|
132
|
+
<section class="panel operation" aria-labelledby="task-title">
|
|
133
|
+
<div class="operation-header">
|
|
134
|
+
<div class="operation-heading"><span class="badge">RECEIVE</span><h2 id="task-title"></h2></div>
|
|
135
|
+
<p id="description"></p><p id="queue" class="muted"></p>
|
|
136
|
+
</div>
|
|
137
|
+
<section class="section" aria-labelledby="contracts-title">
|
|
138
|
+
<div class="section-heading"><h3 id="contracts-title">Message contracts</h3><span>SCHEMAS</span></div>
|
|
139
|
+
<div class="schemas">
|
|
140
|
+
<section class="schema-card" aria-labelledby="request-schema-title">
|
|
141
|
+
<h4 id="request-schema-title">Request schema</h4>
|
|
142
|
+
<div id="request-schema" class="schema-model"></div>
|
|
143
|
+
<details class="schema-raw"><summary>Raw JSON schema</summary><pre id="request-schema-raw"></pre></details>
|
|
144
|
+
</section>
|
|
145
|
+
<section class="schema-card" aria-labelledby="response-schema-title">
|
|
146
|
+
<h4 id="response-schema-title">Response schema</h4>
|
|
147
|
+
<div id="response-schema" class="schema-model"></div>
|
|
148
|
+
<details class="schema-raw"><summary>Raw JSON schema</summary><pre id="response-schema-raw"></pre></details>
|
|
149
|
+
</section>
|
|
150
|
+
</div>
|
|
151
|
+
</section>
|
|
152
|
+
<section class="section" aria-labelledby="request-title">
|
|
153
|
+
<div class="section-heading"><h3 id="request-title">Request</h3><span>APPLICATION / JSON</span></div>
|
|
154
|
+
<div class="editor-header">
|
|
155
|
+
<label for="body">Request body (JSON object)</label>
|
|
156
|
+
<button id="example" type="button">Reset example</button>
|
|
157
|
+
</div>
|
|
158
|
+
<textarea id="body" spellcheck="false"></textarea>
|
|
159
|
+
<div class="actions">
|
|
160
|
+
<button id="send" type="button">Send</button>
|
|
161
|
+
<button id="rpc" class="rpc" type="button">RPC</button>
|
|
162
|
+
</div>
|
|
163
|
+
<p id="rpc-note" class="muted"></p>
|
|
164
|
+
</section>
|
|
165
|
+
<section class="section" aria-labelledby="result-title">
|
|
166
|
+
<div class="section-heading"><h3 id="result-title">Result</h3><span>BROKER RESPONSE</span></div>
|
|
167
|
+
<pre id="result" role="status" aria-live="polite">No request sent.</pre>
|
|
168
|
+
</section>
|
|
169
|
+
</section>
|
|
170
|
+
</main>
|
|
171
|
+
<footer class="footer">DefStream Inspector · Console</footer>
|
|
172
|
+
</div>
|
|
173
|
+
<script>
|
|
174
|
+
'use strict';
|
|
175
|
+
const el = id => document.getElementById(id);
|
|
176
|
+
let catalog = null;
|
|
177
|
+
let current = null;
|
|
178
|
+
let busy = false;
|
|
179
|
+
let loadVersion = 0;
|
|
180
|
+
const endpoint = window.location.pathname;
|
|
181
|
+
const authRequired = document.body.dataset.authRequired === 'true';
|
|
182
|
+
const show = value => JSON.stringify(value, null, 2);
|
|
183
|
+
function resolve(ref) {
|
|
184
|
+
return ref.$ref.slice(2).split('/').reduce((value, part) => value[part.replace(/~1/g, '/').replace(/~0/g, '~')], catalog.specification);
|
|
185
|
+
}
|
|
186
|
+
function example() {
|
|
187
|
+
const value = current.request.examples?.[0]?.payload ?? {};
|
|
188
|
+
el('body').value = show(value);
|
|
189
|
+
}
|
|
190
|
+
function schemaElement(tag, className, text) {
|
|
191
|
+
const element = document.createElement(tag);
|
|
192
|
+
element.className = className;
|
|
193
|
+
if (text !== undefined) element.textContent = text;
|
|
194
|
+
return element;
|
|
195
|
+
}
|
|
196
|
+
function schemaNode(schema, name, required, root, depth = 0, references = []) {
|
|
197
|
+
const object = schema !== null && typeof schema === 'object' && !Array.isArray(schema);
|
|
198
|
+
const children = [];
|
|
199
|
+
const notes = [];
|
|
200
|
+
let type = object ? schema.type : undefined;
|
|
201
|
+
if (!type && object) {
|
|
202
|
+
if (schema.properties || schema.additionalProperties !== undefined) type = 'object';
|
|
203
|
+
else if (schema.items !== undefined || schema.prefixItems) type = 'array';
|
|
204
|
+
else if (schema.oneOf) type = 'oneOf';
|
|
205
|
+
else if (schema.anyOf) type = 'anyOf';
|
|
206
|
+
else if (schema.allOf) type = 'allOf';
|
|
207
|
+
else if (schema.$ref) type = 'reference';
|
|
208
|
+
}
|
|
209
|
+
if (depth < 12 && object) {
|
|
210
|
+
const add = (value, label, mandatory = false) => {
|
|
211
|
+
children.push(schemaNode(value, label, mandatory, root, depth + 1, references));
|
|
212
|
+
};
|
|
213
|
+
if (schema.$ref) {
|
|
214
|
+
if (references.includes(schema.$ref)) notes.push('Recursive reference: ' + schema.$ref);
|
|
215
|
+
else if (schema.$ref === '#' || schema.$ref.startsWith('#/')) {
|
|
216
|
+
try {
|
|
217
|
+
const target = schema.$ref === '#' ? root : schema.$ref.slice(2).split('/').reduce((value, part) => {
|
|
218
|
+
const key = decodeURIComponent(part).replace(/~1/g, '/').replace(/~0/g, '~');
|
|
219
|
+
if (value === null || typeof value !== 'object' || !Object.hasOwn(value, key)) throw new Error('Unresolved reference');
|
|
220
|
+
return value[key];
|
|
221
|
+
}, root);
|
|
222
|
+
children.push(schemaNode(target, 'Referenced schema', false, root, depth + 1, [...references, schema.$ref]));
|
|
223
|
+
} catch (error) { notes.push('Unresolved reference: ' + schema.$ref); }
|
|
224
|
+
} else notes.push('External reference: ' + schema.$ref);
|
|
225
|
+
}
|
|
226
|
+
for (const [key, value] of Object.entries(schema.properties ?? {})) {
|
|
227
|
+
add(value, key, Array.isArray(schema.required) && schema.required.includes(key));
|
|
228
|
+
}
|
|
229
|
+
if (schema.items !== undefined) {
|
|
230
|
+
if (Array.isArray(schema.items)) schema.items.forEach((value, index) => add(value, 'Item ' + (index + 1)));
|
|
231
|
+
else add(schema.items, 'items');
|
|
232
|
+
}
|
|
233
|
+
(schema.prefixItems ?? []).forEach((value, index) => add(value, 'Item ' + (index + 1)));
|
|
234
|
+
for (const [keyword, label] of [['allOf', 'All of'], ['oneOf', 'One of'], ['anyOf', 'Any of']]) {
|
|
235
|
+
(schema[keyword] ?? []).forEach((value, index) => add(value, label + ' / ' + (index + 1)));
|
|
236
|
+
}
|
|
237
|
+
for (const keyword of ['not', 'contains', 'if', 'then', 'else']) {
|
|
238
|
+
if (schema[keyword] !== undefined) add(schema[keyword], keyword);
|
|
239
|
+
}
|
|
240
|
+
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) {
|
|
241
|
+
add(schema.additionalProperties, '[additional property]');
|
|
242
|
+
}
|
|
243
|
+
} else if (object) notes.push('Expand further in Raw JSON schema (display depth limit).');
|
|
244
|
+
const node = schemaElement(children.length ? 'details' : 'div', 'schema-node');
|
|
245
|
+
const heading = schemaElement(children.length ? 'summary' : 'div', '');
|
|
246
|
+
const line = schemaElement('span', 'schema-line');
|
|
247
|
+
line.append(
|
|
248
|
+
schemaElement('span', 'schema-name', name),
|
|
249
|
+
' ',
|
|
250
|
+
schemaElement('span', 'schema-type', Array.isArray(type) ? type.join(' | ') : type ?? (schema === false ? 'never' : 'any'))
|
|
251
|
+
);
|
|
252
|
+
if (required) line.append(' ', schemaElement('span', 'schema-required', 'required'));
|
|
253
|
+
heading.append(line);
|
|
254
|
+
node.append(heading);
|
|
255
|
+
if (children.length) node.open = depth < 2;
|
|
256
|
+
if (object && (schema.title || schema.description)) {
|
|
257
|
+
node.append(schemaElement('div', 'schema-description', [schema.title, schema.description].filter(Boolean).join(' - ')));
|
|
258
|
+
}
|
|
259
|
+
if (schema === true || (object && !Object.keys(schema).length)) notes.push('Any value allowed.');
|
|
260
|
+
if (schema === false) notes.push('No value allowed.');
|
|
261
|
+
const constraints = schemaElement('div', 'schema-constraints');
|
|
262
|
+
if (object) {
|
|
263
|
+
const labels = {
|
|
264
|
+
format: 'format', enum: 'enum', const: 'const', default: 'default',
|
|
265
|
+
minimum: 'min', maximum: 'max', exclusiveMinimum: 'exclusive min', exclusiveMaximum: 'exclusive max',
|
|
266
|
+
multipleOf: 'multiple of', minLength: 'min length', maxLength: 'max length', pattern: 'pattern',
|
|
267
|
+
minItems: 'min items', maxItems: 'max items', uniqueItems: 'unique items',
|
|
268
|
+
minProperties: 'min properties', maxProperties: 'max properties',
|
|
269
|
+
readOnly: 'read only', writeOnly: 'write only', deprecated: 'deprecated', examples: 'examples'
|
|
270
|
+
};
|
|
271
|
+
for (const [key, label] of Object.entries(labels)) {
|
|
272
|
+
if (Object.hasOwn(schema, key)) constraints.append(schemaElement('span', 'schema-constraint', label + ': ' + JSON.stringify(schema[key])));
|
|
273
|
+
}
|
|
274
|
+
if (typeof schema.additionalProperties === 'boolean') {
|
|
275
|
+
constraints.append(schemaElement('span', 'schema-constraint', 'additional properties: ' + (schema.additionalProperties ? 'allowed' : 'not allowed')));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (constraints.childNodes.length) node.append(constraints);
|
|
279
|
+
for (const note of notes) node.append(schemaElement('div', 'schema-description', note));
|
|
280
|
+
if (children.length) {
|
|
281
|
+
const group = schemaElement('div', 'schema-children');
|
|
282
|
+
group.append(...children);
|
|
283
|
+
node.append(group);
|
|
284
|
+
}
|
|
285
|
+
return node;
|
|
286
|
+
}
|
|
287
|
+
function renderSchema(id, schema) {
|
|
288
|
+
const container = el(id);
|
|
289
|
+
container.replaceChildren(schema === undefined
|
|
290
|
+
? schemaElement('p', 'schema-empty', 'No response contract.')
|
|
291
|
+
: schemaNode(schema, 'payload', false, schema));
|
|
292
|
+
el(id + '-raw').textContent = schema === undefined ? 'No response contract.' : show(schema);
|
|
293
|
+
}
|
|
294
|
+
function selectTask() {
|
|
295
|
+
const operation = catalog.specification.operations[el('tasks').value];
|
|
296
|
+
const channel = resolve(operation.channel);
|
|
297
|
+
current = { operation, channel, request: resolve(operation.messages[0]) };
|
|
298
|
+
el('task-title').textContent = el('tasks').value;
|
|
299
|
+
el('description').textContent = operation.description ?? '';
|
|
300
|
+
el('queue').textContent = 'Queue: ' + channel.address + '\nContent-Type: ' + (current.request.contentType ?? catalog.specification.defaultContentType);
|
|
301
|
+
renderSchema('request-schema', current.request.payload);
|
|
302
|
+
renderSchema('response-schema', operation.reply ? resolve(operation.reply.messages[0]).payload : undefined);
|
|
303
|
+
el('rpc').disabled = !catalog.rpc || busy;
|
|
304
|
+
el('rpc-note').textContent = catalog.rpc ? 'RPC supported. Timeout: ' + catalog.timeout + ' seconds.' : 'This broker does not support RPC. Send is available.';
|
|
305
|
+
el('result').textContent = 'No request sent.';
|
|
306
|
+
example();
|
|
307
|
+
}
|
|
308
|
+
async function api(options = {}) {
|
|
309
|
+
const response = await fetch(endpoint, {
|
|
310
|
+
...options,
|
|
311
|
+
credentials: 'omit',
|
|
312
|
+
redirect: 'error',
|
|
313
|
+
headers: { Accept: 'application/json', ...(authRequired ? { Authorization: 'Bearer ' + el('token').value } : {}), ...(options.headers ?? {}) }
|
|
314
|
+
});
|
|
315
|
+
const data = await response.json();
|
|
316
|
+
if (!response.ok) throw new Error('HTTP ' + response.status + ': ' + (data.error ?? show(data)));
|
|
317
|
+
return data;
|
|
318
|
+
}
|
|
319
|
+
el('connect').onclick = async () => {
|
|
320
|
+
if (busy) return;
|
|
321
|
+
const version = ++loadVersion;
|
|
322
|
+
el('connect').disabled = true;
|
|
323
|
+
el('main').hidden = true;
|
|
324
|
+
el('notice').textContent = 'Loading...';
|
|
325
|
+
try {
|
|
326
|
+
const data = await api();
|
|
327
|
+
if (version !== loadVersion) return;
|
|
328
|
+
catalog = data;
|
|
329
|
+
el('version').textContent = 'Version ' + catalog.specification.info.version;
|
|
330
|
+
el('version').hidden = false;
|
|
331
|
+
el('tasks').replaceChildren(...Object.keys(catalog.specification.operations).map(name => {
|
|
332
|
+
const option = document.createElement('option');
|
|
333
|
+
option.value = name; option.textContent = name; return option;
|
|
334
|
+
}));
|
|
335
|
+
if (!el('tasks').options.length) throw new Error('No tasks registered.');
|
|
336
|
+
el('tasks').selectedIndex = 0;
|
|
337
|
+
selectTask();
|
|
338
|
+
el('main').hidden = false;
|
|
339
|
+
el('notice').textContent = 'Ready. Requests execute on the real worker.';
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (version === loadVersion) el('notice').textContent = error.message;
|
|
342
|
+
} finally {
|
|
343
|
+
if (version === loadVersion) el('connect').disabled = busy;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
el('clear').onclick = () => { ++loadVersion; el('token').value = ''; catalog = null; current = null; el('main').hidden = true; el('connect').disabled = busy; el('notice').textContent = 'Token cleared.'; };
|
|
347
|
+
el('tasks').onchange = selectTask;
|
|
348
|
+
el('example').onclick = example;
|
|
349
|
+
async function execute(mode) {
|
|
350
|
+
if (busy) return;
|
|
351
|
+
try {
|
|
352
|
+
const body = JSON.parse(el('body').value);
|
|
353
|
+
if (body === null || Array.isArray(body) || typeof body !== 'object') throw new Error('Body must be a JSON object.');
|
|
354
|
+
busy = true; el('send').disabled = true; el('rpc').disabled = true; el('tasks').disabled = true; el('connect').disabled = true;
|
|
355
|
+
el('result').textContent = 'Waiting...';
|
|
356
|
+
const data = await api({
|
|
357
|
+
method: 'POST',
|
|
358
|
+
headers: { 'Content-Type': 'application/json' },
|
|
359
|
+
body: JSON.stringify({ task: el('tasks').value, mode, body })
|
|
360
|
+
});
|
|
361
|
+
el('result').textContent = show(data);
|
|
362
|
+
} catch (error) { el('result').textContent = error.message; }
|
|
363
|
+
finally {
|
|
364
|
+
busy = false; el('send').disabled = false; el('tasks').disabled = false; el('connect').disabled = false;
|
|
365
|
+
el('rpc').disabled = !catalog?.rpc;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
el('send').onclick = () => execute('send');
|
|
369
|
+
el('rpc').onclick = () => execute('request');
|
|
370
|
+
if (!authRequired) el('connect').click();
|
|
371
|
+
</script>
|
|
372
|
+
</body>
|
|
373
|
+
</html>
|