@hozu/cli 0.21.1 → 0.23.0
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/bin/hozu.js +2 -2
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +10 -7
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/app.d.ts +3 -0
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +23 -0
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/browse-page.d.ts.map +1 -1
- package/dist/commands/browse-page.js +6 -1
- package/dist/commands/browse-page.js.map +1 -1
- package/dist/commands/browse-tab.d.ts +4 -2
- package/dist/commands/browse-tab.d.ts.map +1 -1
- package/dist/commands/browse-tab.js +36 -5
- package/dist/commands/browse-tab.js.map +1 -1
- package/dist/commands/browse-world.d.ts +3 -0
- package/dist/commands/browse-world.d.ts.map +1 -1
- package/dist/commands/browse-world.js +2 -1
- package/dist/commands/browse-world.js.map +1 -1
- package/dist/commands/browse.d.ts +10 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +107 -13
- package/dist/commands/browse.js.map +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +6 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/gen.d.ts +6 -0
- package/dist/commands/gen.d.ts.map +1 -0
- package/dist/commands/gen.js +56 -0
- package/dist/commands/gen.js.map +1 -0
- package/dist/commands/request.d.ts +6 -1
- package/dist/commands/request.d.ts.map +1 -1
- package/dist/commands/request.js +60 -6
- package/dist/commands/request.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +11 -3
- package/dist/commands/serve.js.map +1 -1
- package/dist/contract.d.ts +50 -3
- package/dist/contract.d.ts.map +1 -1
- package/dist/gen/go.d.ts +10 -0
- package/dist/gen/go.d.ts.map +1 -0
- package/dist/gen/go.js +443 -0
- package/dist/gen/go.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +28 -2
- package/dist/main.js.map +1 -1
- package/dist/migrate/steps.d.ts.map +1 -1
- package/dist/migrate/steps.js +24 -0
- package/dist/migrate/steps.js.map +1 -1
- package/dist/remote.d.ts +15 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +66 -0
- package/dist/remote.js.map +1 -0
- package/package.json +8 -8
- package/schema/browse.schema.json +77 -5
- package/schema/check.schema.json +2 -1
- package/schema/gen.schema.json +78 -0
- package/schema/inspect.schema.json +20 -0
- package/schema/migrate.schema.json +2 -1
- package/schema/render.schema.json +2 -1
- package/schema/request.schema.json +35 -1
- package/skill/SKILL.md +2 -2
- package/skill/example/features/bookmarks/views.ts +1 -2
- package/skill/topics/data.md +77 -0
- package/skill/topics/diagnostics.md +1 -0
- package/skill/topics/http.md +2 -1
- package/skill/topics/machine.md +4 -5
- package/skill/topics/pages.md +1 -1
- package/skill/topics/recipes.md +23 -0
- package/skill/topics/testing.md +10 -4
- package/skill/topics/views.md +15 -3
package/dist/gen/go.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { canonicalStringify } from '@hozu/core/ir';
|
|
2
|
+
const pascal = (text) => {
|
|
3
|
+
const name = text
|
|
4
|
+
.replace(/(^|[^A-Za-z0-9])([A-Za-z0-9])/g, (_, __, c) => c.toUpperCase())
|
|
5
|
+
.replace(/[^A-Za-z0-9]/g, '');
|
|
6
|
+
return /^[0-9]/.test(name) ? `X${name}` : name || 'X';
|
|
7
|
+
};
|
|
8
|
+
const methodOf = (ref) => ref.split('.').map(pascal).join('');
|
|
9
|
+
const isRef = (t) => /^(\[\]|map\[|\*|json\.)/.test(t);
|
|
10
|
+
/** Columns padded the way gofmt aligns consecutive struct fields. */
|
|
11
|
+
const aligned = (rows) => {
|
|
12
|
+
const widths = [];
|
|
13
|
+
for (const row of rows)
|
|
14
|
+
row.slice(0, -1).forEach((cell, i) => (widths[i] = Math.max(widths[i] ?? 0, cell.length)));
|
|
15
|
+
return rows.map((row) => `\t${row.map((cell, i) => (i < row.length - 1 ? cell.padEnd(widths[i]) : cell)).join(' ')}`.trimEnd());
|
|
16
|
+
};
|
|
17
|
+
const render = (s) => {
|
|
18
|
+
if (s.members)
|
|
19
|
+
return `type ${s.name} string\n\nconst (\n${aligned(s.members.map(([name, value]) => [name, s.name, `= ${value}`])).join('\n')}\n)`;
|
|
20
|
+
return s.fields.length
|
|
21
|
+
? `type ${s.name} struct {\n${aligned(s.fields).join('\n')}\n}`
|
|
22
|
+
: `type ${s.name} struct{}`;
|
|
23
|
+
};
|
|
24
|
+
class Types {
|
|
25
|
+
structs = [];
|
|
26
|
+
#byShape = new Map();
|
|
27
|
+
#names = new Set();
|
|
28
|
+
constructor(reserved) {
|
|
29
|
+
for (const r of reserved)
|
|
30
|
+
this.#names.add(r);
|
|
31
|
+
}
|
|
32
|
+
/** The Go type of a schema; `name` is used when it needs a new struct and has no `title`. */
|
|
33
|
+
type(schema, name, root = schema) {
|
|
34
|
+
const ref = schema.$ref;
|
|
35
|
+
if (typeof ref === 'string') {
|
|
36
|
+
const key = ref.replace(/^#\/\$defs\//, '');
|
|
37
|
+
const def = root.$defs?.[key];
|
|
38
|
+
return def ? this.type(def, pascal(key), root) : 'json.RawMessage';
|
|
39
|
+
}
|
|
40
|
+
const any = (schema.anyOf ?? schema.oneOf);
|
|
41
|
+
if (any) {
|
|
42
|
+
const rest = any.filter((s) => s.type !== 'null');
|
|
43
|
+
if (rest.length === 1) {
|
|
44
|
+
const t = this.type(rest[0], name, root);
|
|
45
|
+
return rest.length < any.length && !isRef(t) ? `*${t}` : t;
|
|
46
|
+
}
|
|
47
|
+
return 'json.RawMessage';
|
|
48
|
+
}
|
|
49
|
+
const values = (schema.enum ?? (schema.const !== undefined ? [schema.const] : null));
|
|
50
|
+
if (schema.enum && values?.every((v) => typeof v === 'string'))
|
|
51
|
+
return this.#enum(values, typeof schema.title === 'string' ? pascal(schema.title) : name);
|
|
52
|
+
if (values)
|
|
53
|
+
return values.every((v) => typeof v === 'string') ? 'string' : 'json.RawMessage';
|
|
54
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
55
|
+
const nullable = types.includes('null');
|
|
56
|
+
const base = this.#base(types.find((t) => t !== 'null'), schema, name, root);
|
|
57
|
+
return nullable && !isRef(base) ? `*${base}` : base;
|
|
58
|
+
}
|
|
59
|
+
#base(type, schema, name, root) {
|
|
60
|
+
switch (type) {
|
|
61
|
+
case 'string':
|
|
62
|
+
return 'string';
|
|
63
|
+
case 'boolean':
|
|
64
|
+
return 'bool';
|
|
65
|
+
case 'integer':
|
|
66
|
+
return 'int64';
|
|
67
|
+
case 'number':
|
|
68
|
+
return 'float64';
|
|
69
|
+
case 'array':
|
|
70
|
+
return schema.items
|
|
71
|
+
? `[]${this.type(schema.items, `${name}Item`, root)}`
|
|
72
|
+
: '[]json.RawMessage';
|
|
73
|
+
case 'object': {
|
|
74
|
+
const props = schema.properties;
|
|
75
|
+
const extra = schema.additionalProperties;
|
|
76
|
+
if (!props && extra && typeof extra === 'object')
|
|
77
|
+
return `map[string]${this.type(extra, `${name}Value`, root)}`;
|
|
78
|
+
return this.struct(schema, name, root);
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
return 'json.RawMessage';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
struct(schema, name, root = schema, fixed = false) {
|
|
85
|
+
const { title, description: _, ...shape } = schema;
|
|
86
|
+
const props = Object.entries(schema.properties ?? {});
|
|
87
|
+
if (!fixed && !props.length)
|
|
88
|
+
return this.#empty();
|
|
89
|
+
if (!fixed && typeof title === 'string')
|
|
90
|
+
return this.#named(pascal(title), () => this.#fields(schema, pascal(title), root), canonicalStringify(shape));
|
|
91
|
+
return this.#add(name, (unique) => this.#fields(schema, unique, root));
|
|
92
|
+
}
|
|
93
|
+
#enum(values, name) {
|
|
94
|
+
return this.#named(name, () => [], JSON.stringify(values), (unique) => {
|
|
95
|
+
const used = new Set();
|
|
96
|
+
return values.map((v) => {
|
|
97
|
+
let constant = `${unique}${pascal(v)}`;
|
|
98
|
+
for (let n = 2; used.has(constant) || this.#names.has(constant); n++)
|
|
99
|
+
constant = `${unique}${pascal(v)}${n}`;
|
|
100
|
+
used.add(constant);
|
|
101
|
+
this.#names.add(constant);
|
|
102
|
+
return [constant, JSON.stringify(v)];
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
#empty() {
|
|
107
|
+
if (!this.structs.some((s) => s.name === 'Empty'))
|
|
108
|
+
this.structs.push({ name: 'Empty', fields: [] });
|
|
109
|
+
return 'Empty';
|
|
110
|
+
}
|
|
111
|
+
/** A titled shape is one Go type wherever it appears; the same title with another shape gets a number. */
|
|
112
|
+
#named(name, fields, shape = '{}', members) {
|
|
113
|
+
const key = `${name} ${shape}`;
|
|
114
|
+
const known = this.#byShape.get(key);
|
|
115
|
+
if (known)
|
|
116
|
+
return known;
|
|
117
|
+
const unique = this.#add(name, () => fields(), members);
|
|
118
|
+
this.#byShape.set(key, unique);
|
|
119
|
+
return unique;
|
|
120
|
+
}
|
|
121
|
+
#add(wanted, fields, members) {
|
|
122
|
+
let unique = wanted;
|
|
123
|
+
for (let n = 2; this.#names.has(unique); n++)
|
|
124
|
+
unique = `${wanted}${n}`;
|
|
125
|
+
this.#names.add(unique);
|
|
126
|
+
const struct = { name: unique, fields: [] };
|
|
127
|
+
this.structs.push(struct);
|
|
128
|
+
if (members)
|
|
129
|
+
struct.members = members(unique);
|
|
130
|
+
else
|
|
131
|
+
struct.fields = fields(unique);
|
|
132
|
+
return unique;
|
|
133
|
+
}
|
|
134
|
+
#fields(schema, owner, root) {
|
|
135
|
+
const required = new Set(schema.required ?? []);
|
|
136
|
+
return Object.entries(schema.properties ?? {}).map(([prop, s]) => {
|
|
137
|
+
const t = this.type(s, `${owner}${pascal(prop)}`, root);
|
|
138
|
+
const optional = !required.has(prop);
|
|
139
|
+
const values = (s.enum ?? null);
|
|
140
|
+
const named = values?.every((v) => typeof v === 'string');
|
|
141
|
+
return [
|
|
142
|
+
pascal(prop),
|
|
143
|
+
`${optional && !isRef(t) ? '*' : ''}${t}`,
|
|
144
|
+
`\`json:"${prop}${optional ? ',omitempty' : ''}"\``,
|
|
145
|
+
...(values && !named ? [`// one of ${values.map((v) => JSON.stringify(v)).join(', ')}`] : []),
|
|
146
|
+
];
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const describe = (e, errors) => {
|
|
151
|
+
const parts = [
|
|
152
|
+
`${e.ref} (${e.kind}${e.session ? ', with the session' : ', no session: public data'})`,
|
|
153
|
+
...(errors.length ? [`errors: ${errors.join(', ')}`] : []),
|
|
154
|
+
...(e.invalid ? ['may return Invalid'] : []),
|
|
155
|
+
];
|
|
156
|
+
return `\t// ${parts.join('; ')}`;
|
|
157
|
+
};
|
|
158
|
+
/** The Go contract of one `remote()` group: types, the Resolvers interface and the HTTP handler (ADR 0068). */
|
|
159
|
+
const tidy = (go) => go.replace(/\n{3,}/g, '\n\n');
|
|
160
|
+
export function goContract(contract, pkg) {
|
|
161
|
+
const types = new Types([
|
|
162
|
+
'Invalid',
|
|
163
|
+
'Ctx',
|
|
164
|
+
'Options',
|
|
165
|
+
'Resolvers',
|
|
166
|
+
'Handler',
|
|
167
|
+
'Fingerprint',
|
|
168
|
+
'Empty',
|
|
169
|
+
'Upload',
|
|
170
|
+
'Forbidden',
|
|
171
|
+
]);
|
|
172
|
+
const session = contract.session ? types.struct(contract.session, 'Session', contract.session, true) : null;
|
|
173
|
+
const errorTypes = [];
|
|
174
|
+
const methods = contract.effects.map((e) => {
|
|
175
|
+
const base = methodOf(e.ref);
|
|
176
|
+
const input = types.type(e.input, `${base}Input`);
|
|
177
|
+
const output = types.type(e.output, `${base}Output`);
|
|
178
|
+
const errors = Object.entries(e.errors).map(([name, schema]) => {
|
|
179
|
+
const type = types.struct(schema, `${base}${pascal(name)}`, schema, true);
|
|
180
|
+
errorTypes.push(`func (e ${type}) Error() string { return "${e.ref}: ${name}" }\nfunc (e ${type}) failure() (string, string, any) {\n\treturn "${e.ref}", "${name}", e\n}`);
|
|
181
|
+
return type;
|
|
182
|
+
});
|
|
183
|
+
return { e, base, input, output, errors };
|
|
184
|
+
});
|
|
185
|
+
const sessionType = session ?? 'struct{}';
|
|
186
|
+
return tidy(`// Code generated by hozu gen from the Hozu declarations. DO NOT EDIT.
|
|
187
|
+
// Change the declarations in TypeScript, then run hozu gen (ADR 0068).
|
|
188
|
+
|
|
189
|
+
package ${pkg}
|
|
190
|
+
|
|
191
|
+
import (
|
|
192
|
+
\t"context"
|
|
193
|
+
\t"crypto/subtle"
|
|
194
|
+
\t"encoding/json"
|
|
195
|
+
\t"errors"
|
|
196
|
+
\t"log"
|
|
197
|
+
\t"net/http"
|
|
198
|
+
\t"reflect"
|
|
199
|
+
\t"strings"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Fingerprint is the contract of one effect this file was generated from, or "" for another effect. Every call
|
|
203
|
+
// carries its effect's, so changing one declaration answers 409 for that effect alone.
|
|
204
|
+
func Fingerprint(effect string) string {
|
|
205
|
+
\tswitch effect {
|
|
206
|
+
${contract.effects.map((e) => `\tcase "${e.ref}":\n\t\treturn "${e.fingerprint}"`).join('\n')}
|
|
207
|
+
\t}
|
|
208
|
+
\treturn ""
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
${types.structs.map(render).join('\n\n')}
|
|
212
|
+
|
|
213
|
+
${errorTypes.join('\n\n')}
|
|
214
|
+
|
|
215
|
+
// Invalid is the framework error of mutations and endpoints: a message and one message per input field.
|
|
216
|
+
type Invalid struct {
|
|
217
|
+
\tMessage string \`json:"message"\`
|
|
218
|
+
\tFields map[string]string \`json:"fields"\`
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func (e Invalid) Error() string { return "Invalid: " + e.Message }
|
|
222
|
+
func (e Invalid) failure() (string, string, any) { return "", "Invalid", e }
|
|
223
|
+
|
|
224
|
+
// Forbidden answers as access does when the visitor may not have it (user-scoped queries and mutations).
|
|
225
|
+
type Forbidden struct {
|
|
226
|
+
\tMessage string \`json:"message,omitempty"\`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func (e Forbidden) Error() string { return "Forbidden: " + e.Message }
|
|
230
|
+
func (e Forbidden) failure() (string, string, any) { return "", "Forbidden", e }
|
|
231
|
+
|
|
232
|
+
// Ctx is one call. Session is nil when the visitor is signed out or the effect reads no session. Preview is
|
|
233
|
+
// true in preview mode; Header holds an endpoint's request headers (no cookie).
|
|
234
|
+
type Ctx struct {
|
|
235
|
+
\tContext context.Context
|
|
236
|
+
\tSession *${sessionType}
|
|
237
|
+
\tPreview bool
|
|
238
|
+
\tHeader http.Header
|
|
239
|
+
\tfiles map[string]Upload
|
|
240
|
+
\tchanged bool
|
|
241
|
+
\tnext *${sessionType}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Upload is a file a form sent with the call; Data holds its bytes.
|
|
245
|
+
type Upload struct {
|
|
246
|
+
\tName string \`json:"name"\`
|
|
247
|
+
\tType string \`json:"type"\`
|
|
248
|
+
\tSize int64 \`json:"size"\`
|
|
249
|
+
\tData []byte \`json:"data"\`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// File returns the upload a form field's token names, or nil (mutations and endpoints only).
|
|
253
|
+
func (c *Ctx) File(token string) *Upload {
|
|
254
|
+
\tif f, ok := c.files[token]; ok {
|
|
255
|
+
\t\treturn &f
|
|
256
|
+
\t}
|
|
257
|
+
\treturn nil
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// SetSession signs the visitor in as s (mutations and endpoints only).
|
|
261
|
+
func (c *Ctx) SetSession(s ${sessionType}) { c.changed, c.next = true, &s }
|
|
262
|
+
|
|
263
|
+
// SignOut ends the visitor's session (mutations and endpoints only).
|
|
264
|
+
func (c *Ctx) SignOut() { c.changed, c.next = true, nil }
|
|
265
|
+
|
|
266
|
+
// Resolvers implements the effects that app.ts lists in remote(). Return a declared error as the error value.
|
|
267
|
+
type Resolvers interface {
|
|
268
|
+
${methods.map((m) => `${describe(m.e, m.errors)}\n\t${m.base}(ctx *Ctx, in ${m.input}) (${m.output}, error)`).join('\n')}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Options configures Handler. Secret (16 characters or more) must equal the x-hozu-secret header of every call:
|
|
272
|
+
// the handler trusts the session a call carries, so it answers only the Hozu server.
|
|
273
|
+
type Options struct {
|
|
274
|
+
\tSecret string
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
type failure interface {
|
|
278
|
+
\terror
|
|
279
|
+
\tfailure() (effect string, name string, data any)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
type request struct {
|
|
283
|
+
${aligned([
|
|
284
|
+
['Effect', 'string', '`json:"effect"`'],
|
|
285
|
+
['Input', 'json.RawMessage', '`json:"input"`'],
|
|
286
|
+
['Session', `*${sessionType}`, '`json:"session"`'],
|
|
287
|
+
['Preview', 'bool', '`json:"preview"`'],
|
|
288
|
+
['Headers', 'map[string]string', '`json:"headers"`'],
|
|
289
|
+
['Files', 'map[string]Upload', '`json:"files"`'],
|
|
290
|
+
]).join('\n')}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func call[I any, O any](ctx *Ctx, raw json.RawMessage, f func(*Ctx, I) (O, error)) (any, error) {
|
|
294
|
+
\tvar in I
|
|
295
|
+
\tif err := json.Unmarshal(raw, &in); err != nil {
|
|
296
|
+
\t\treturn nil, err
|
|
297
|
+
\t}
|
|
298
|
+
\tout, err := f(ctx, in)
|
|
299
|
+
\treturn filled(out), err
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// filled replaces nil slices and maps with empty ones, so a list answers [] and never null.
|
|
303
|
+
func filled(v any) any {
|
|
304
|
+
\trv := reflect.New(reflect.TypeOf(v)).Elem()
|
|
305
|
+
\trv.Set(reflect.ValueOf(v))
|
|
306
|
+
\tfill(rv)
|
|
307
|
+
\treturn rv.Interface()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func fill(v reflect.Value) {
|
|
311
|
+
\tswitch v.Kind() {
|
|
312
|
+
\tcase reflect.Slice:
|
|
313
|
+
\t\tif v.IsNil() && v.CanSet() {
|
|
314
|
+
\t\t\tv.Set(reflect.MakeSlice(v.Type(), 0, 0))
|
|
315
|
+
\t\t}
|
|
316
|
+
\t\tfor i := 0; i < v.Len(); i++ {
|
|
317
|
+
\t\t\tfill(v.Index(i))
|
|
318
|
+
\t\t}
|
|
319
|
+
\tcase reflect.Map:
|
|
320
|
+
\t\tif v.IsNil() && v.CanSet() {
|
|
321
|
+
\t\t\tv.Set(reflect.MakeMap(v.Type()))
|
|
322
|
+
\t\t}
|
|
323
|
+
\tcase reflect.Pointer:
|
|
324
|
+
\t\tif !v.IsNil() {
|
|
325
|
+
\t\t\tfill(v.Elem())
|
|
326
|
+
\t\t}
|
|
327
|
+
\tcase reflect.Struct:
|
|
328
|
+
\t\tfor i := 0; i < v.NumField(); i++ {
|
|
329
|
+
\t\t\tif v.Type().Field(i).IsExported() {
|
|
330
|
+
\t\t\t\tfill(v.Field(i))
|
|
331
|
+
\t\t\t}
|
|
332
|
+
\t\t}
|
|
333
|
+
\t}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handler answers the calls of the Hozu server: POST { effect, input, session } → { ok } | { fail } (+ session).
|
|
337
|
+
func Handler(r Resolvers, o Options) http.Handler {
|
|
338
|
+
\tif len(o.Secret) < 16 {
|
|
339
|
+
\t\tpanic("hozu: Options.Secret must hold the 16 or more characters of the app's remote() secret")
|
|
340
|
+
\t}
|
|
341
|
+
\treturn http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
342
|
+
\t\tif req.Method != http.MethodPost {
|
|
343
|
+
\t\t\thttp.Error(w, "POST only", http.StatusMethodNotAllowed)
|
|
344
|
+
\t\t\treturn
|
|
345
|
+
\t\t}
|
|
346
|
+
\t\tif subtle.ConstantTimeCompare([]byte(req.Header.Get("X-Hozu-Secret")), []byte(o.Secret)) != 1 {
|
|
347
|
+
\t\t\thttp.Error(w, "wrong x-hozu-secret", http.StatusUnauthorized)
|
|
348
|
+
\t\t\treturn
|
|
349
|
+
\t\t}
|
|
350
|
+
\t\tvar body request
|
|
351
|
+
\t\tif err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
352
|
+
\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)
|
|
353
|
+
\t\t\treturn
|
|
354
|
+
\t\t}
|
|
355
|
+
\t\twant := Fingerprint(body.Effect)
|
|
356
|
+
\t\tif want == "" {
|
|
357
|
+
\t\t\thttp.Error(w, "unknown effect "+body.Effect, http.StatusNotFound)
|
|
358
|
+
\t\t\treturn
|
|
359
|
+
\t\t}
|
|
360
|
+
\t\tif got := req.Header.Get("X-Hozu-Fingerprint"); got != want {
|
|
361
|
+
\t\t\thttp.Error(w, body.Effect+" is "+want+" here, not "+got+": run hozu gen and rebuild", http.StatusConflict)
|
|
362
|
+
\t\t\treturn
|
|
363
|
+
\t\t}
|
|
364
|
+
\t\tid := req.Header.Get("X-Hozu-Call")
|
|
365
|
+
\t\tctx := &Ctx{Context: req.Context(), Session: body.Session, Preview: body.Preview, Header: http.Header{}, files: body.Files}
|
|
366
|
+
\t\tfor k, v := range body.Headers {
|
|
367
|
+
\t\t\tctx.Header.Set(k, v)
|
|
368
|
+
\t\t}
|
|
369
|
+
\t\tvar out any
|
|
370
|
+
\t\tvar err error
|
|
371
|
+
\t\tswitch body.Effect {
|
|
372
|
+
${methods.map((m) => `\t\tcase "${m.e.ref}":\n\t\t\tout, err = call(ctx, body.Input, r.${m.base})`).join('\n')}
|
|
373
|
+
\t\t}
|
|
374
|
+
\t\tres := map[string]any{}
|
|
375
|
+
\t\tvar f failure
|
|
376
|
+
\t\tswitch {
|
|
377
|
+
\t\tcase err == nil:
|
|
378
|
+
\t\t\tres["ok"] = out
|
|
379
|
+
\t\tcase errors.As(err, &f):
|
|
380
|
+
\t\t\teffect, name, data := f.failure()
|
|
381
|
+
\t\t\tif effect != "" && effect != body.Effect {
|
|
382
|
+
\t\t\t\tlog.Printf("hozu: %s (call %s) returned %s, an error of %s", body.Effect, id, name, effect)
|
|
383
|
+
\t\t\t\thttp.Error(w, "resolver failed: "+name+" is an error of "+effect, http.StatusInternalServerError)
|
|
384
|
+
\t\t\t\treturn
|
|
385
|
+
\t\t\t}
|
|
386
|
+
\t\t\tres["fail"] = map[string]any{"name": name, "data": data}
|
|
387
|
+
\t\tdefault:
|
|
388
|
+
\t\t\tlog.Printf("hozu: %s (call %s): %v", body.Effect, id, err)
|
|
389
|
+
\t\t\tline, _, _ := strings.Cut(err.Error(), "\\n")
|
|
390
|
+
\t\t\thttp.Error(w, "resolver failed: "+line, http.StatusInternalServerError)
|
|
391
|
+
\t\t\treturn
|
|
392
|
+
\t\t}
|
|
393
|
+
\t\tif ctx.changed {
|
|
394
|
+
\t\t\tres["session"] = ctx.next
|
|
395
|
+
\t\t}
|
|
396
|
+
\t\tw.Header().Set("Content-Type", "application/json")
|
|
397
|
+
\t\tif err := json.NewEncoder(w).Encode(res); err != nil {
|
|
398
|
+
\t\t\tlog.Printf("hozu: %s (call %s): %v", body.Effect, id, err)
|
|
399
|
+
\t\t}
|
|
400
|
+
\t})
|
|
401
|
+
}
|
|
402
|
+
`);
|
|
403
|
+
}
|
|
404
|
+
/** The fingerprint of each effect a generated Go contract declares, by ref. */
|
|
405
|
+
export const goFingerprints = (text) => Object.fromEntries([...text.matchAll(/^\tcase "([^"]+)":\n\t\treturn "([0-9a-f]+)"$/gm)].map((m) => [m[1], m[2]]));
|
|
406
|
+
const counted = /^(id|count|qty|quantity|cents)$|(Id|_id|ID|Count|_count|Qty|Quantity|Cents|_cents)$/;
|
|
407
|
+
/**
|
|
408
|
+
* Number fields named like an id or a count: `z.number()` is float64 in Go, so they are notes suggesting `z.int()`
|
|
409
|
+
* (ADR 0070 C4), not diagnostics.
|
|
410
|
+
*/
|
|
411
|
+
export function goNotes(contract) {
|
|
412
|
+
const notes = new Set();
|
|
413
|
+
const walk = (schema, path, root, seen) => {
|
|
414
|
+
const ref = schema.$ref;
|
|
415
|
+
if (typeof ref === 'string') {
|
|
416
|
+
const key = ref.replace(/^#\/\$defs\//, '');
|
|
417
|
+
const def = root.$defs?.[key];
|
|
418
|
+
if (def && !seen.has(key))
|
|
419
|
+
walk(def, path, root, new Set([...seen, key]));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
for (const s of (schema.anyOf ?? schema.oneOf ?? []))
|
|
423
|
+
walk(s, path, root, seen);
|
|
424
|
+
if (schema.items)
|
|
425
|
+
walk(schema.items, `${path}[]`, root, seen);
|
|
426
|
+
for (const [prop, s] of Object.entries(schema.properties ?? {})) {
|
|
427
|
+
const number = [s, ...(s.anyOf ?? s.oneOf ?? [])].some((x) => (Array.isArray(x.type) ? x.type : [x.type]).includes('number'));
|
|
428
|
+
if (number && counted.test(prop))
|
|
429
|
+
notes.add(`${path}.${prop} is a number (float64 in Go); if it holds whole numbers, declare it z.int() (int64)`);
|
|
430
|
+
walk(s, `${path}.${prop}`, root, seen);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
for (const e of contract.effects) {
|
|
434
|
+
walk(e.input, `${e.ref}: input`, e.input, new Set());
|
|
435
|
+
walk(e.output, `${e.ref}: output`, e.output, new Set());
|
|
436
|
+
for (const [name, schema] of Object.entries(e.errors))
|
|
437
|
+
walk(schema, `${e.ref}: ${name}`, schema, new Set());
|
|
438
|
+
}
|
|
439
|
+
if (contract.session)
|
|
440
|
+
walk(contract.session, 'session', contract.session, new Set());
|
|
441
|
+
return [...notes];
|
|
442
|
+
}
|
|
443
|
+
//# sourceMappingURL=go.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"go.js","sourceRoot":"","sources":["../../src/gen/go.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ type Token = {
|
|
|
11
11
|
value?: string | undefined;
|
|
12
12
|
};
|
|
13
13
|
export declare function browsePlan(tokens: Token[]): Pick<BrowseOptions, 'actors' | 'plan'>;
|
|
14
|
+
export declare function cli(argv: string[]): Promise<void>;
|
|
14
15
|
export declare function main(argv: string[], cwd?: string, out?: (s: string) => void): Promise<number>;
|
|
15
16
|
export {};
|
|
16
17
|
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,sBAAsB,CAAA;AA4H7B,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAaxE;AAED,eAAO,MAAM,cAAc,UAAW,MAAM,GAAG,SAAS,KAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAQzF,CAAA;AAQD,KAAK,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAExE,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,MAAM,CAAC,CA8ClF;AAMD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASvD;AAED,wBAAsB,IAAI,CACxB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,SAAgB,EACnB,GAAG,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAqC,GACxD,OAAO,CAAC,MAAM,CAAC,CAqWjB"}
|
package/dist/main.js
CHANGED
|
@@ -3,7 +3,7 @@ import { relative, resolve } from 'node:path';
|
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import { describeAdd, runAddFeature } from './commands/add.js';
|
|
5
5
|
import { describeAddComponent, runAddComponent } from './commands/add-component.js';
|
|
6
|
-
import { BuildFailed } from './commands/app.js';
|
|
6
|
+
import { BuildFailed, disposeApps } from './commands/app.js';
|
|
7
7
|
import { browseFailed, describeBrowse, runBrowse, stepsOf, } from './commands/browse.js';
|
|
8
8
|
import { runBuild } from './commands/build.js';
|
|
9
9
|
import { describeCall, runCall } from './commands/call.js';
|
|
@@ -14,6 +14,7 @@ import { describeDevtoolsMessages, messagesFileOf, runDevtoolsMessages } from '.
|
|
|
14
14
|
import { runDocs } from './commands/docs.js';
|
|
15
15
|
import { describeEnv, runEnv } from './commands/env.js';
|
|
16
16
|
import { describeExport, runExport } from './commands/export.js';
|
|
17
|
+
import { describeGen, runGen } from './commands/gen.js';
|
|
17
18
|
import { runInspect } from './commands/inspect.js';
|
|
18
19
|
import { describeAddKit, runAddKit } from './commands/kits.js';
|
|
19
20
|
import { describeMap, runMap } from './commands/map.js';
|
|
@@ -64,6 +65,9 @@ Commands:
|
|
|
64
65
|
get <path>... Request pages in-process (no server): status, title, alerts, visible text, forms
|
|
65
66
|
env [--example] Every env variable the app reads: side, required, default, set now, internal URL;
|
|
66
67
|
--example writes .env.example
|
|
68
|
+
gen Write the contract of every remote() in app.ts (a Go file: types, the Resolvers
|
|
69
|
+
interface, the HTTP handler) for resolvers in another language; hozu check reports a
|
|
70
|
+
stale one (HZ093)
|
|
67
71
|
call <feature>.<effect> Run one query, mutation or endpoint in-process (no server) through the app's own
|
|
68
72
|
handler: --input '<json>', --session '<json>', --header 'Name: value' (endpoints);
|
|
69
73
|
a mutation or a POST endpoint writes real data and needs --write
|
|
@@ -202,6 +206,18 @@ export function browsePlan(tokens) {
|
|
|
202
206
|
}
|
|
203
207
|
return { actors, plan };
|
|
204
208
|
}
|
|
209
|
+
let running = false;
|
|
210
|
+
const flushed = (stream) => new Promise((done) => stream.write('', () => done()));
|
|
211
|
+
export async function cli(argv) {
|
|
212
|
+
const code = await main(argv);
|
|
213
|
+
if (running) {
|
|
214
|
+
process.exitCode = code;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
await disposeApps(undefined, (line) => process.stderr.write(`hozu: ${line}\n`));
|
|
218
|
+
await Promise.all([flushed(process.stdout), flushed(process.stderr)]);
|
|
219
|
+
process.exit(code);
|
|
220
|
+
}
|
|
205
221
|
export async function main(argv, cwd = process.cwd(), out = (s) => process.stdout.write(s)) {
|
|
206
222
|
let asJson = false;
|
|
207
223
|
try {
|
|
@@ -272,6 +288,7 @@ export async function main(argv, cwd = process.cwd(), out = (s) => process.stdou
|
|
|
272
288
|
'get',
|
|
273
289
|
'call',
|
|
274
290
|
'env',
|
|
291
|
+
'gen',
|
|
275
292
|
'browse',
|
|
276
293
|
'add',
|
|
277
294
|
'inspect',
|
|
@@ -390,6 +407,7 @@ export async function main(argv, cwd = process.cwd(), out = (s) => process.stdou
|
|
|
390
407
|
await runDev(loaded, values['no-devtools'] === true ? false : (values.devtools ?? 'builder'), (line) => out(`${line}\n`), values['no-devtools'] === true
|
|
391
408
|
? null
|
|
392
409
|
: messagesFileOf(values['devtools-messages'], cwd, (line) => out(`${line}\n`)));
|
|
410
|
+
running = true;
|
|
393
411
|
return 0;
|
|
394
412
|
}
|
|
395
413
|
if (command === 'devtools') {
|
|
@@ -432,7 +450,10 @@ export async function main(argv, cwd = process.cwd(), out = (s) => process.stdou
|
|
|
432
450
|
return result.ok ? 0 : 1;
|
|
433
451
|
}
|
|
434
452
|
if (command === 'serve') {
|
|
435
|
-
await runServe(loaded, (line) => out(`${line}\n`));
|
|
453
|
+
const server = await runServe(loaded, (line) => out(`${line}\n`));
|
|
454
|
+
running = true;
|
|
455
|
+
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
456
|
+
process.once(signal, () => void server.close().finally(() => process.exit(0)));
|
|
436
457
|
return 0;
|
|
437
458
|
}
|
|
438
459
|
if (command === 'map') {
|
|
@@ -445,6 +466,11 @@ export async function main(argv, cwd = process.cwd(), out = (s) => process.stdou
|
|
|
445
466
|
out(asJson ? json(result) : describeEnv(result));
|
|
446
467
|
return 0;
|
|
447
468
|
}
|
|
469
|
+
if (command === 'gen') {
|
|
470
|
+
const result = await runGen(loaded, cwd);
|
|
471
|
+
out(asJson ? json(result) : describeGen(result));
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
448
474
|
if (command === 'call') {
|
|
449
475
|
const result = await runCall(loaded, {
|
|
450
476
|
target,
|