@jigs.design/host 0.1.2 → 0.3.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/README.md +13 -13
- package/dist/catalog.d.ts +1 -1
- package/dist/guidance.d.ts +1 -1
- package/dist/host.d.ts +18 -11
- package/dist/host.js +22 -17
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/jigs.d.ts +56 -0
- package/dist/jigs.js +117 -0
- package/dist/procedures.d.ts +1 -1
- package/package.json +2 -2
- package/dist/recipes.d.ts +0 -24
- package/dist/recipes.js +0 -79
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jigs.design/host
|
|
2
2
|
|
|
3
|
-
Defines a host: the contract between your product and the model. A host lists the components a spec may draw, the
|
|
3
|
+
Defines a host: the contract between your product and the model. A host lists the components a spec may draw, the jigs written from them (tags the model may use, pages the product serves), the procedures it may call, the image hosts it may load from, and the design guidance and situations the model designs by. It holds schemas, not the code that runs a procedure.
|
|
4
4
|
|
|
5
5
|
Import it from server code and config. No browser package imports it; the browser imports `ActionEvent` as a type at most.
|
|
6
6
|
|
|
@@ -28,25 +28,25 @@ export const host = defineHost({
|
|
|
28
28
|
|
|
29
29
|
## Exports
|
|
30
30
|
|
|
31
|
-
| Export | What it is
|
|
32
|
-
| ----------------------------------------------------------------------------------------------------- |
|
|
33
|
-
| `defineHost` | Builds a host from `components`, `procedures`, `
|
|
34
|
-
| `query`, `mutation`, `action` | The three kinds of procedure. An action is handled by the embedding page
|
|
35
|
-
| `
|
|
36
|
-
| `situation` | Design guidance for when the reader has a given job, with a checked example
|
|
37
|
-
| `defineCatalog`, `defineComponent` | Build your own catalog
|
|
38
|
-
| `string`, `number`, `boolean`, `oneOf`, `handler`, `image`, `link`, `list`, `block`, `inline`, `none` | Prop and children builders for components
|
|
39
|
-
| `contentSecurityPolicy` | The page's `frame-src
|
|
40
|
-
| `CatalogProps`, `PropsOf`, `ActionEvent` | Types for a registry and for a page's `onAction`
|
|
31
|
+
| Export | What it is |
|
|
32
|
+
| ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `defineHost` | Builds a host from `components`, `procedures`, `jigs`, `imageHosts`, `guidance` and `situations` |
|
|
34
|
+
| `query`, `mutation`, `action` | The three kinds of procedure. An action is handled by the embedding page |
|
|
35
|
+
| `jig` | A tag or a page written in Jigs from catalog components |
|
|
36
|
+
| `situation` | Design guidance for when the reader has a given job, with a checked example |
|
|
37
|
+
| `defineCatalog`, `defineComponent` | Build your own catalog |
|
|
38
|
+
| `string`, `number`, `boolean`, `oneOf`, `handler`, `image`, `link`, `list`, `block`, `inline`, `none` | Prop and children builders for components |
|
|
39
|
+
| `contentSecurityPolicy` | The page's `frame-src`, `img-src` and, for an endpoint on another origin, `connect-src`, merged into yours |
|
|
40
|
+
| `CatalogProps`, `PropsOf`, `ActionEvent` | Types for a registry and for a page's `onAction` |
|
|
41
41
|
|
|
42
42
|
## Source
|
|
43
43
|
|
|
44
44
|
- `src/host.ts`: `defineHost`, `contentSecurityPolicy` and `ActionEvent`.
|
|
45
45
|
- `src/catalog.ts`: catalogs, components and the prop builders.
|
|
46
46
|
- `src/procedures.ts`: `query`, `mutation`, `action` and invalidation scopes.
|
|
47
|
-
- `src/
|
|
47
|
+
- `src/jigs.ts`: `jig`, `DefineName` and `PageName`; the server compiles jigs against the host's catalog.
|
|
48
48
|
- `src/guidance.ts`: `situation` and the design guidance types; the host's replace the catalog's.
|
|
49
49
|
|
|
50
50
|
## Docs
|
|
51
51
|
|
|
52
|
-
[Host](../../apps/docs/docs/host/index.mdx), [Catalog](../../apps/docs/docs/host/catalog.mdx), [
|
|
52
|
+
[Host](../../apps/docs/docs/host/index.mdx), [Catalog](../../apps/docs/docs/host/catalog.mdx), [Jigs](../../apps/docs/docs/host/jigs.mdx), [Design guidance](../../apps/docs/docs/host/guidance.mdx), [Queries](../../apps/docs/docs/host/queries.mdx), [Mutations](../../apps/docs/docs/host/mutations.mdx), [Actions](../../apps/docs/docs/host/actions.mdx).
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DesignGuidance, GuidanceInput, SituationDefinition } from "./guidance.js";
|
|
2
|
-
import { ChildrenKind, ComponentSchema, PropSchema, Row } from "@jigs.design/
|
|
2
|
+
import { ChildrenKind, ComponentSchema, PropSchema, Row } from "@jigs.design/protocol";
|
|
3
3
|
//#region src/catalog.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* One prop of a catalog component. `schema` is what the browser checks;
|
package/dist/guidance.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Design guidance: how to design a good UI with a catalog, not only a valid
|
|
4
4
|
* one. Foundations apply to every UI; a situation applies when its `when`
|
|
5
5
|
* matches what the reader will do with the UI. See
|
|
6
|
-
*
|
|
6
|
+
* `.agent-docs/notes/design-guidance-2026-10.md`.
|
|
7
7
|
*/
|
|
8
8
|
/** Guidance for every placement (`shared`), and for one prompt mode each. */
|
|
9
9
|
export interface DesignGuidance {
|
package/dist/host.d.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { DesignGuidance, GuidanceInput, SituationDefinition } from "./guidance.js";
|
|
2
2
|
import { Catalog } from "./catalog.js";
|
|
3
3
|
import { ActionDefinition, MutationDefinition, Parsed, Procedures, QueryDefinition } from "./procedures.js";
|
|
4
|
-
import {
|
|
4
|
+
import { JigDefinition } from "./jigs.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/host.d.ts
|
|
7
|
-
export interface Host<TProcedures extends Procedures = Procedures, TCatalog extends Catalog = Catalog> {
|
|
7
|
+
export interface Host<TProcedures extends Procedures = Procedures, TCatalog extends Catalog = Catalog, TJigs extends ReadonlyArray<JigDefinition> = ReadonlyArray<JigDefinition>> {
|
|
8
8
|
components: TCatalog;
|
|
9
9
|
/** The host's design guidance, or the catalog's when the host sets none. */
|
|
10
10
|
guidance: DesignGuidance;
|
|
11
11
|
imageHosts: ReadonlyArray<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Jigs written for this host: tags the model may write, pages the
|
|
14
|
+
* product serves, or both. The server compiles them at startup, in
|
|
15
|
+
* order: a jig may use the ones before it.
|
|
16
|
+
*/
|
|
17
|
+
jigs: TJigs;
|
|
12
18
|
procedures: TProcedures;
|
|
13
|
-
/** Compound components written in Jigs; the server checks them at startup. */
|
|
14
|
-
recipes: ReadonlyArray<RecipeDefinition>;
|
|
15
19
|
/** The host's situations, or the catalog's when the host sets none. */
|
|
16
20
|
situations: ReadonlyArray<SituationDefinition>;
|
|
17
21
|
}
|
|
@@ -44,21 +48,23 @@ type CheckedProcedures<TProcedures extends Procedures> = { readonly [K in keyof
|
|
|
44
48
|
/**
|
|
45
49
|
* The product's boundary with the spec. Throws at startup when a schema has
|
|
46
50
|
* no JSON form, `invalidates` names something that is not a query, a
|
|
47
|
-
* procedure name is not an identifier, an image host is malformed,
|
|
48
|
-
* situation name is not kebab-case or is listed twice
|
|
51
|
+
* procedure name is not an identifier, an image host is malformed, a
|
|
52
|
+
* situation name is not kebab-case or is listed twice, or a jig is listed
|
|
53
|
+
* twice or has a component's name.
|
|
49
54
|
*
|
|
50
55
|
* `guidance` and `situations` replace the catalog's when set; to extend the
|
|
51
56
|
* catalog's, spread them in.
|
|
52
57
|
*/
|
|
53
|
-
export declare function defineHost<const TProcedures extends Procedures, const TCatalog extends Catalog>(definition: {
|
|
58
|
+
export declare function defineHost<const TProcedures extends Procedures, const TCatalog extends Catalog, const TJigs extends ReadonlyArray<JigDefinition> = readonly []>(definition: {
|
|
54
59
|
components: TCatalog;
|
|
55
60
|
/** A string for every placement, or `{ shared, chat, document }`. */
|
|
56
61
|
guidance?: GuidanceInput;
|
|
57
62
|
imageHosts?: ReadonlyArray<string>;
|
|
63
|
+
/** Tags and pages written in Jigs, from `jig()`. */
|
|
64
|
+
jigs?: TJigs;
|
|
58
65
|
procedures: TProcedures & CheckedProcedures<TProcedures>;
|
|
59
|
-
recipes?: ReadonlyArray<RecipeDefinition>;
|
|
60
66
|
situations?: ReadonlyArray<SituationDefinition>;
|
|
61
|
-
}): Host<TProcedures, TCatalog>;
|
|
67
|
+
}): Host<TProcedures, TCatalog, TJigs>;
|
|
62
68
|
/** A policy as directive names and their sources, e.g. `{ "img-src": ["'self'"] }`. */
|
|
63
69
|
export type PolicyDirectives = Readonly<Record<string, ReadonlyArray<string>>>;
|
|
64
70
|
/** The embedding page's policy, and where it must not be sent. */
|
|
@@ -76,8 +82,9 @@ export interface PagePolicy {
|
|
|
76
82
|
}
|
|
77
83
|
/**
|
|
78
84
|
* The embedding page's policy: the page's own `directives` plus what Jigs
|
|
79
|
-
* needs, `frame-src` for the sandbox frame
|
|
80
|
-
* hosts
|
|
85
|
+
* needs, `frame-src` for the sandbox frame, `img-src` for the host's image
|
|
86
|
+
* hosts, and `connect-src` for an endpoint on another origin, which calls,
|
|
87
|
+
* turns and the push stream fetch. Send `header` on every response `appliesTo` accepts; the endpoint's
|
|
81
88
|
* frame carries its own policy, and a page policy sent there replaces it.
|
|
82
89
|
*/
|
|
83
90
|
export declare function contentSecurityPolicy(host: Host, options: {
|
package/dist/host.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { checkSituations, normalizeGuidance } from "./guidance.js";
|
|
2
2
|
import { isCallPolicy } from "./procedures.js";
|
|
3
|
-
import { isImageHostEntry, normalizeImageHost } from "@jigs.design/
|
|
3
|
+
import { isImageHostEntry, normalizeImageHost } from "@jigs.design/protocol";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/host.ts
|
|
6
6
|
const PROCEDURE_NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
@@ -14,23 +14,24 @@ function checkJsonSchema(label, schema) {
|
|
|
14
14
|
/**
|
|
15
15
|
* The product's boundary with the spec. Throws at startup when a schema has
|
|
16
16
|
* no JSON form, `invalidates` names something that is not a query, a
|
|
17
|
-
* procedure name is not an identifier, an image host is malformed,
|
|
18
|
-
* situation name is not kebab-case or is listed twice
|
|
17
|
+
* procedure name is not an identifier, an image host is malformed, a
|
|
18
|
+
* situation name is not kebab-case or is listed twice, or a jig is listed
|
|
19
|
+
* twice or has a component's name.
|
|
19
20
|
*
|
|
20
21
|
* `guidance` and `situations` replace the catalog's when set; to extend the
|
|
21
22
|
* catalog's, spread them in.
|
|
22
23
|
*/
|
|
23
24
|
function defineHost(definition) {
|
|
24
25
|
const { procedures } = definition;
|
|
25
|
-
const
|
|
26
|
+
const jigs = definition.jigs ?? [];
|
|
26
27
|
const situations = definition.situations ?? definition.components.situations;
|
|
27
28
|
checkSituations(situations);
|
|
28
29
|
const components = new Set(definition.components.components.map((component) => component.name));
|
|
29
|
-
const
|
|
30
|
-
for (const entry of
|
|
31
|
-
if (components.has(entry.name)) throw new Error(`
|
|
32
|
-
if (
|
|
33
|
-
|
|
30
|
+
const jigNames = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const entry of jigs) {
|
|
32
|
+
if (components.has(entry.name)) throw new Error(`Jig "${entry.name}" has the name of a catalog component`);
|
|
33
|
+
if (jigNames.has(entry.name)) throw new Error(`Jig "${entry.name}" is listed twice`);
|
|
34
|
+
jigNames.add(entry.name);
|
|
34
35
|
}
|
|
35
36
|
for (const [name, procedure] of Object.entries(procedures)) {
|
|
36
37
|
if (!PROCEDURE_NAME.test(name)) throw new Error(`Procedure name "${name}" must be an identifier: letters, digits and underscores, starting with a letter`);
|
|
@@ -51,8 +52,8 @@ function defineHost(definition) {
|
|
|
51
52
|
components: definition.components,
|
|
52
53
|
guidance: definition.guidance === void 0 ? definition.components.guidance : normalizeGuidance(definition.guidance),
|
|
53
54
|
imageHosts,
|
|
55
|
+
jigs,
|
|
54
56
|
procedures,
|
|
55
|
-
recipes,
|
|
56
57
|
situations
|
|
57
58
|
};
|
|
58
59
|
}
|
|
@@ -65,7 +66,11 @@ function endpointSource(endpoint) {
|
|
|
65
66
|
* Where a directive Jigs adds takes its sources from when the page has not
|
|
66
67
|
* set it: once the directive exists, the browser stops falling back.
|
|
67
68
|
*/
|
|
68
|
-
const FALLBACKS = /* @__PURE__ */ new Map([
|
|
69
|
+
const FALLBACKS = /* @__PURE__ */ new Map([
|
|
70
|
+
["connect-src", ["default-src"]],
|
|
71
|
+
["frame-src", ["child-src", "default-src"]],
|
|
72
|
+
["img-src", ["default-src"]]
|
|
73
|
+
]);
|
|
69
74
|
/** Adds `extra` to `directives`, keeping each source once and dropping a `'none'` that now has company. */
|
|
70
75
|
function mergeDirectives(directives, extra) {
|
|
71
76
|
const merged = new Map(Object.entries(directives).map(([name, sources]) => [name, [...sources]]));
|
|
@@ -79,16 +84,16 @@ function mergeDirectives(directives, extra) {
|
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
81
86
|
* The embedding page's policy: the page's own `directives` plus what Jigs
|
|
82
|
-
* needs, `frame-src` for the sandbox frame
|
|
83
|
-
* hosts
|
|
87
|
+
* needs, `frame-src` for the sandbox frame, `img-src` for the host's image
|
|
88
|
+
* hosts, and `connect-src` for an endpoint on another origin, which calls,
|
|
89
|
+
* turns and the push stream fetch. Send `header` on every response `appliesTo` accepts; the endpoint's
|
|
84
90
|
* frame carries its own policy, and a page policy sent there replaces it.
|
|
85
91
|
*/
|
|
86
92
|
function contentSecurityPolicy(host, options) {
|
|
87
|
-
const directives = mergeDirectives(options.directives ?? {}, {
|
|
88
|
-
"frame-src": [endpointSource(options.endpoint)],
|
|
89
|
-
"img-src": ["'self'", ...host.imageHosts.map((entry) => `https://${entry}`)]
|
|
90
|
-
});
|
|
91
93
|
const absolute = !options.endpoint.startsWith("/");
|
|
94
|
+
const needed = /* @__PURE__ */ new Map([["frame-src", [endpointSource(options.endpoint)]], ["img-src", ["'self'", ...host.imageHosts.map((entry) => `https://${entry}`)]]]);
|
|
95
|
+
if (absolute) needed.set("connect-src", [endpointSource(options.endpoint)]);
|
|
96
|
+
const directives = mergeDirectives(options.directives ?? {}, Object.fromEntries(needed));
|
|
92
97
|
const endpoint = new URL(options.endpoint, "https://page.invalid");
|
|
93
98
|
const path = endpoint.pathname.replace(/\/$/, "");
|
|
94
99
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DesignGuidance, GuidanceInput, SituationDefinition, situation } from "./guidance.js";
|
|
2
2
|
import { Catalog, CatalogProps, ComponentDefinition, ComponentDesign, ComponentNamed, PropDefinition, PropDefinitions, PropsOf, block, boolean, componentSchema, defineCatalog, defineComponent, handler, image, inline, link, list, none, number, oneOf, string } from "./catalog.js";
|
|
3
3
|
import { ActionDefinition, CallPolicy, InputOf, InvalidationScope, InvalidationScopes, MutationDefinition, OutputOf, ProcedureDefinition, Procedures, Projection, QueryDefinition, action, dataSchema, isCallPolicy, mutation, query } from "./procedures.js";
|
|
4
|
-
import {
|
|
4
|
+
import { DefineName, JigDefinition, PageName, jig } from "./jigs.js";
|
|
5
5
|
import { ActionEvent, Host, PagePolicy, PolicyDirectives, contentSecurityPolicy, defineHost } from "./host.js";
|
|
6
|
-
export { type ActionDefinition, type ActionEvent, type CallPolicy, type Catalog, type CatalogProps, type ComponentDefinition, type ComponentDesign, type ComponentNamed, type DesignGuidance, type GuidanceInput, type Host, type InputOf, type InvalidationScope, type InvalidationScopes, type MutationDefinition, type OutputOf, type PagePolicy, type PolicyDirectives, type ProcedureDefinition, type Procedures, type Projection, type PropDefinition, type PropDefinitions, type PropsOf, type QueryDefinition, type
|
|
6
|
+
export { type ActionDefinition, type ActionEvent, type CallPolicy, type Catalog, type CatalogProps, type ComponentDefinition, type ComponentDesign, type ComponentNamed, type DefineName, type DesignGuidance, type GuidanceInput, type Host, type InputOf, type InvalidationScope, type InvalidationScopes, type JigDefinition, type MutationDefinition, type OutputOf, type PageName, type PagePolicy, type PolicyDirectives, type ProcedureDefinition, type Procedures, type Projection, type PropDefinition, type PropDefinitions, type PropsOf, type QueryDefinition, type SituationDefinition, action, block, boolean, componentSchema, contentSecurityPolicy, dataSchema, defineCatalog, defineComponent, defineHost, handler, image, inline, isCallPolicy, jig, link, list, mutation, none, number, oneOf, query, situation, string };
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,5 @@ import { situation } from "./guidance.js";
|
|
|
2
2
|
import { block, boolean, componentSchema, defineCatalog, defineComponent, handler, image, inline, link, list, none, number, oneOf, string } from "./catalog.js";
|
|
3
3
|
import { action, dataSchema, isCallPolicy, mutation, query } from "./procedures.js";
|
|
4
4
|
import { contentSecurityPolicy, defineHost } from "./host.js";
|
|
5
|
-
import {
|
|
6
|
-
export { action, block, boolean, componentSchema, contentSecurityPolicy, dataSchema, defineCatalog, defineComponent, defineHost, handler, image, inline, isCallPolicy, link, list, mutation, none, number, oneOf, query,
|
|
5
|
+
import { jig } from "./jigs.js";
|
|
6
|
+
export { action, block, boolean, componentSchema, contentSecurityPolicy, dataSchema, defineCatalog, defineComponent, defineHost, handler, image, inline, isCallPolicy, jig, link, list, mutation, none, number, oneOf, query, situation, string };
|
package/dist/jigs.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/jigs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A jig: one named piece of Jigs source the product writes, listed on the
|
|
4
|
+
* host. A jig the model may write by name is a **tag** (it has a
|
|
5
|
+
* `description`); one the product serves compiled, by name and with props,
|
|
6
|
+
* is a **page** (`page: true`). A jig may be both. Because it is written in
|
|
7
|
+
* catalog tags, it runs on every renderer whose catalog has those tags, and
|
|
8
|
+
* the server checks it against the host at startup.
|
|
9
|
+
*/
|
|
10
|
+
export interface JigDefinition<TName extends string = string, TPage extends boolean = boolean> {
|
|
11
|
+
/** One line for the model: when to reach for this tag. Set on every tag. */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** A one-line use, shown to the model. */
|
|
14
|
+
example?: string;
|
|
15
|
+
/** "define": one top-level `<define>` with props. "document": a whole document with none. */
|
|
16
|
+
kind: "define" | "document";
|
|
17
|
+
/** The define's name, or the name a document jig was given. */
|
|
18
|
+
name: TName;
|
|
19
|
+
/** Served compiled by `jigs.page(name, props)`. */
|
|
20
|
+
page: TPage;
|
|
21
|
+
/** Jigs source; it may start with a pragma line such as `{@jigs 0 catalog=3}`. */
|
|
22
|
+
source: string;
|
|
23
|
+
/** The model may write it by name: true when it has a `description`. */
|
|
24
|
+
tag: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The name of the `<define>` in a source written as a literal, or `string`
|
|
28
|
+
* when the type cannot read it (a source built with `${}`, or a braced
|
|
29
|
+
* name). The runtime always reads the name from the source itself.
|
|
30
|
+
*/
|
|
31
|
+
export type DefineName<TSource extends string> = TSource extends `${string}<define${string}name="${infer N}"${string}` ? N : TSource extends `${string}<define${string}name='${infer N}'${string}` ? N : string;
|
|
32
|
+
/** The names of the pages among `TJigs`, for `jigs.page`. */
|
|
33
|
+
export type PageName<TJigs extends ReadonlyArray<JigDefinition>> = Extract<TJigs[number], {
|
|
34
|
+
page: true;
|
|
35
|
+
}>["name"];
|
|
36
|
+
/**
|
|
37
|
+
* A jig whose source is one `<define>`: named by it. A `description` makes
|
|
38
|
+
* it a tag the model may write; `page: true` makes it a page the product
|
|
39
|
+
* serves with props.
|
|
40
|
+
*/
|
|
41
|
+
export declare function jig<const TSource extends string, const TPage extends boolean = false>(definition: {
|
|
42
|
+
description?: string;
|
|
43
|
+
example?: string;
|
|
44
|
+
page?: TPage;
|
|
45
|
+
source: TSource;
|
|
46
|
+
}): JigDefinition<DefineName<TSource>, TPage>;
|
|
47
|
+
/**
|
|
48
|
+
* A jig whose source is a whole document, such as a pinned chat block: named
|
|
49
|
+
* here, since it has no `<define>`, and always a page.
|
|
50
|
+
*/
|
|
51
|
+
export declare function jig<const TName extends string>(definition: {
|
|
52
|
+
name: TName;
|
|
53
|
+
page?: true;
|
|
54
|
+
source: string;
|
|
55
|
+
}): JigDefinition<TName, true>;
|
|
56
|
+
//#endregion
|
package/dist/jigs.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/jigs.ts
|
|
2
|
+
/** Tag names compile to bindings in the program. */
|
|
3
|
+
const TAG_NAME = /^[a-z][A-Za-z0-9]*$/;
|
|
4
|
+
/** A document jig's name is a chat block's id, so a pinned block keeps it. */
|
|
5
|
+
const DOCUMENT_NAME = /^[a-z][a-z0-9-]*$/;
|
|
6
|
+
const PRAGMA = /^\{@jigs\s+\d+(?:\s+catalog=\d+)?\}[ \t]*(?:\r?\n|$)/;
|
|
7
|
+
const DEFINE_TAG = /<define(?=[\s/>])/g;
|
|
8
|
+
const ATTR_NAME = /[A-Za-z_][\w-]*/y;
|
|
9
|
+
/** Index just past the `}` matching the `{` at `start`, or -1 if unbalanced. */
|
|
10
|
+
function skipBraced(source, start) {
|
|
11
|
+
let depth = 0;
|
|
12
|
+
let quote = null;
|
|
13
|
+
for (let index = start; index < source.length; index++) {
|
|
14
|
+
const char = source.charAt(index);
|
|
15
|
+
if (quote !== null) {
|
|
16
|
+
if (char === "\\") index++;
|
|
17
|
+
else if (char === quote) quote = null;
|
|
18
|
+
} else if (char === "\"" || char === "'" || char === "`") quote = char;
|
|
19
|
+
else if (char === "{") depth++;
|
|
20
|
+
else if (char === "}") {
|
|
21
|
+
depth--;
|
|
22
|
+
if (depth === 0) return index + 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return -1;
|
|
26
|
+
}
|
|
27
|
+
/** A literal attribute value: `"x"`, `'x'` or `{"x"}`. */
|
|
28
|
+
function literal(value) {
|
|
29
|
+
const quoted = /^\s*\{?\s*(?:"([^"\\]*)"|'([^'\\]*)')\s*\}?\s*$/.exec(value);
|
|
30
|
+
return quoted === null ? null : quoted[1] ?? quoted[2] ?? null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The `name` attribute of the opening tag at `start`, read the way the
|
|
34
|
+
* compiler reads it. The compiler checks the whole source again at startup.
|
|
35
|
+
*/
|
|
36
|
+
function nameAttribute(source, start) {
|
|
37
|
+
let cursor = start + 7;
|
|
38
|
+
while (cursor < source.length) {
|
|
39
|
+
const char = source.charAt(cursor);
|
|
40
|
+
if (/\s/.test(char)) {
|
|
41
|
+
cursor++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === ">" || char === "/") return null;
|
|
45
|
+
ATTR_NAME.lastIndex = cursor;
|
|
46
|
+
const attr = ATTR_NAME.exec(source)?.[0];
|
|
47
|
+
if (attr === void 0) return null;
|
|
48
|
+
cursor += attr.length;
|
|
49
|
+
if (source.charAt(cursor) !== "=") continue;
|
|
50
|
+
cursor++;
|
|
51
|
+
const opener = source.charAt(cursor);
|
|
52
|
+
let end;
|
|
53
|
+
if (opener === "\"" || opener === "'") end = source.indexOf(opener, cursor + 1) + 1;
|
|
54
|
+
else if (opener === "{") end = skipBraced(source, cursor);
|
|
55
|
+
else return null;
|
|
56
|
+
if (end <= 0) return null;
|
|
57
|
+
if (attr === "name") return literal(source.slice(cursor, end));
|
|
58
|
+
cursor = end;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The start of the one top-level `<define>` when `source` is that define and
|
|
64
|
+
* nothing else but whitespace and a leading pragma line; null for a
|
|
65
|
+
* document. A scan, not a parse: the compiler checks the source again at
|
|
66
|
+
* startup.
|
|
67
|
+
*/
|
|
68
|
+
function defineStart(source) {
|
|
69
|
+
const pragma = PRAGMA.exec(source)?.[0].length ?? 0;
|
|
70
|
+
const starts = [...source.matchAll(DEFINE_TAG)].map((match) => match.index);
|
|
71
|
+
const [start] = starts;
|
|
72
|
+
const close = source.lastIndexOf("</define>");
|
|
73
|
+
if (start === void 0 || starts.length > 1 || close === -1 || source.slice(pragma, start).trim() !== "" || source.slice(close + 9).trim() !== "") return null;
|
|
74
|
+
return start;
|
|
75
|
+
}
|
|
76
|
+
/** The name of a define jig, read the way the compiler reads it. */
|
|
77
|
+
function defineName(source, start) {
|
|
78
|
+
const name = nameAttribute(source, start);
|
|
79
|
+
if (name === null) throw new Error("A jig's <define> needs a literal name, e.g. <define name=\"orderRow\">");
|
|
80
|
+
if (!TAG_NAME.test(name)) throw new Error(`Jig name "${name}" must start with a lowercase letter and use letters and digits only`);
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
function jig(definition) {
|
|
84
|
+
const start = defineStart(definition.source);
|
|
85
|
+
if (start === null) return documentJig(definition);
|
|
86
|
+
const name = defineName(definition.source, start);
|
|
87
|
+
if (definition.name !== void 0) throw new Error(`Jig "${name}" has a <define>, which names it: remove name: "${definition.name}"`);
|
|
88
|
+
const page = definition.page ?? false;
|
|
89
|
+
const tag = definition.description !== void 0;
|
|
90
|
+
if (!tag && !page) throw new Error(`Jig "${name}" is neither a tag nor a page: give it a description for the model, or page: true`);
|
|
91
|
+
const built = {
|
|
92
|
+
kind: "define",
|
|
93
|
+
name,
|
|
94
|
+
page,
|
|
95
|
+
source: definition.source,
|
|
96
|
+
tag
|
|
97
|
+
};
|
|
98
|
+
if (definition.description !== void 0) built.description = definition.description;
|
|
99
|
+
if (definition.example !== void 0) built.example = definition.example;
|
|
100
|
+
return built;
|
|
101
|
+
}
|
|
102
|
+
function documentJig(definition) {
|
|
103
|
+
const { name } = definition;
|
|
104
|
+
if (name === void 0) throw new Error("A jig whose source is not one <define> is a document and needs a name, e.g. jig({ name: \"monthly-spending\", source })");
|
|
105
|
+
if (!DOCUMENT_NAME.test(name)) throw new Error(`Jig name "${name}" must be kebab-case: a lowercase letter, then lowercase letters, digits and hyphens`);
|
|
106
|
+
if (definition.description !== void 0) throw new Error(`Jig "${name}" is a document, and only a <define> can be a tag: wrap it in <define name="…"> to make one`);
|
|
107
|
+
if (definition.page === false) throw new Error(`Jig "${name}" is a document, which can only be a page: drop page: false`);
|
|
108
|
+
return {
|
|
109
|
+
kind: "document",
|
|
110
|
+
name,
|
|
111
|
+
page: true,
|
|
112
|
+
source: definition.source,
|
|
113
|
+
tag: false
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { jig };
|
package/dist/procedures.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jigs.design/host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Define a host: the catalog a spec may draw, the procedures it may call, and the image hosts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@jigs.design/
|
|
28
|
+
"@jigs.design/protocol": "0.3.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"zod": "^4.0.0"
|
package/dist/recipes.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
//#region src/recipes.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* A recipe: a compound component the product writes in Jigs, from catalog
|
|
4
|
-
* components and other recipes. The model uses it like any tag. Because it is
|
|
5
|
-
* written in catalog tags, it runs on every renderer whose catalog has those
|
|
6
|
-
* tags, and the server checks it against the host at startup.
|
|
7
|
-
*/
|
|
8
|
-
export interface RecipeDefinition {
|
|
9
|
-
/** One line: when the model should reach for this recipe. */
|
|
10
|
-
description: string;
|
|
11
|
-
/** A one-line use, shown to the model. */
|
|
12
|
-
example?: string;
|
|
13
|
-
/** The name of the `<define>` in `source`. */
|
|
14
|
-
name: string;
|
|
15
|
-
/** Jigs source with exactly one `<define name="…">`. */
|
|
16
|
-
source: string;
|
|
17
|
-
}
|
|
18
|
-
/** A recipe named by the `<define>` in its source. */
|
|
19
|
-
export declare function recipe(definition: {
|
|
20
|
-
description: string;
|
|
21
|
-
example?: string;
|
|
22
|
-
source: string;
|
|
23
|
-
}): RecipeDefinition;
|
|
24
|
-
//#endregion
|
package/dist/recipes.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
//#region src/recipes.ts
|
|
2
|
-
const RECIPE_NAME = /^[a-z][A-Za-z0-9]*$/;
|
|
3
|
-
const DEFINE_TAG = /<define(?=[\s/>])/g;
|
|
4
|
-
const ATTR_NAME = /[A-Za-z_][\w-]*/y;
|
|
5
|
-
/** Index just past the `}` matching the `{` at `start`, or -1 if unbalanced. */
|
|
6
|
-
function skipBraced(source, start) {
|
|
7
|
-
let depth = 0;
|
|
8
|
-
let quote = null;
|
|
9
|
-
for (let index = start; index < source.length; index++) {
|
|
10
|
-
const char = source.charAt(index);
|
|
11
|
-
if (quote !== null) {
|
|
12
|
-
if (char === "\\") index++;
|
|
13
|
-
else if (char === quote) quote = null;
|
|
14
|
-
} else if (char === "\"" || char === "'" || char === "`") quote = char;
|
|
15
|
-
else if (char === "{") depth++;
|
|
16
|
-
else if (char === "}") {
|
|
17
|
-
depth--;
|
|
18
|
-
if (depth === 0) return index + 1;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return -1;
|
|
22
|
-
}
|
|
23
|
-
/** A literal attribute value: `"x"`, `'x'` or `{"x"}`. */
|
|
24
|
-
function literal(value) {
|
|
25
|
-
const quoted = /^\s*\{?\s*(?:"([^"\\]*)"|'([^'\\]*)')\s*\}?\s*$/.exec(value);
|
|
26
|
-
return quoted === null ? null : quoted[1] ?? quoted[2] ?? null;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* The `name` attribute of the opening tag at `start`, read the way the
|
|
30
|
-
* compiler reads it. The compiler checks the whole source again at startup.
|
|
31
|
-
*/
|
|
32
|
-
function nameAttribute(source, start) {
|
|
33
|
-
let cursor = start + 7;
|
|
34
|
-
while (cursor < source.length) {
|
|
35
|
-
const char = source.charAt(cursor);
|
|
36
|
-
if (/\s/.test(char)) {
|
|
37
|
-
cursor++;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (char === ">" || char === "/") return null;
|
|
41
|
-
ATTR_NAME.lastIndex = cursor;
|
|
42
|
-
const attr = ATTR_NAME.exec(source)?.[0];
|
|
43
|
-
if (attr === void 0) return null;
|
|
44
|
-
cursor += attr.length;
|
|
45
|
-
if (source.charAt(cursor) !== "=") continue;
|
|
46
|
-
cursor++;
|
|
47
|
-
const opener = source.charAt(cursor);
|
|
48
|
-
let end;
|
|
49
|
-
if (opener === "\"" || opener === "'") end = source.indexOf(opener, cursor + 1) + 1;
|
|
50
|
-
else if (opener === "{") end = skipBraced(source, cursor);
|
|
51
|
-
else return null;
|
|
52
|
-
if (end <= 0) return null;
|
|
53
|
-
if (attr === "name") return literal(source.slice(cursor, end));
|
|
54
|
-
cursor = end;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
/** The name of the one `<define>` in a recipe's source. */
|
|
59
|
-
function defineName(source) {
|
|
60
|
-
const starts = [...source.matchAll(DEFINE_TAG)].map((match) => match.index);
|
|
61
|
-
const [start] = starts;
|
|
62
|
-
if (start === void 0 || starts.length > 1) throw new Error(`A recipe's source must contain exactly one <define>; found ${starts.length}`);
|
|
63
|
-
const name = nameAttribute(source, start);
|
|
64
|
-
if (name === null) throw new Error("A recipe's <define> needs a literal name, e.g. <define name=\"orderRow\">");
|
|
65
|
-
if (!RECIPE_NAME.test(name)) throw new Error(`Recipe name "${name}" must start with a lowercase letter and use letters and digits only`);
|
|
66
|
-
return name;
|
|
67
|
-
}
|
|
68
|
-
/** A recipe named by the `<define>` in its source. */
|
|
69
|
-
function recipe(definition) {
|
|
70
|
-
const built = {
|
|
71
|
-
description: definition.description,
|
|
72
|
-
name: defineName(definition.source),
|
|
73
|
-
source: definition.source
|
|
74
|
-
};
|
|
75
|
-
if (definition.example !== void 0) built.example = definition.example;
|
|
76
|
-
return built;
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
export { recipe };
|