@plitzi/sdk-server 0.32.14 → 0.32.16
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/CHANGELOG.md +20 -0
- package/dist/adapters/jsonAdapters.js +2 -1
- package/dist/core/createServer.js +10 -2
- package/dist/core/http/stages/middlewares.js +3 -2
- package/dist/core/http/stages/pluginAssets.js +3 -2
- package/dist/core/http/stages/static.js +3 -2
- package/dist/core/requestParser.js +1 -1
- package/dist/core/server/{ssrServer.js → pageServer.js} +15 -6
- package/dist/core/services/registry.js +2 -2
- package/dist/helpers/cache/defaults.js +3 -3
- package/dist/index.js +1 -1
- package/dist/modules/mcp/catalogs/cssCatalog/border.js +30 -0
- package/dist/modules/mcp/catalogs/cssCatalog/box.js +31 -0
- package/dist/modules/mcp/catalogs/cssCatalog/helpers.js +185 -0
- package/dist/modules/mcp/catalogs/cssCatalog/index.js +131 -0
- package/dist/modules/mcp/catalogs/cssCatalog/layout.js +98 -0
- package/dist/modules/mcp/catalogs/cssCatalog/visual.js +144 -0
- package/dist/modules/mcp/catalogs/registry.js +1 -1
- package/dist/modules/mcp/helpers/guide.js +23 -13
- package/dist/modules/mcp/resources/core.js +5 -2
- package/dist/modules/mcp/resources/primer.js +2 -1
- package/dist/modules/mcp/resources/register.js +6 -3
- package/dist/modules/mcp/server.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +3 -2
- package/dist/modules/mcp/tools/operations/style/shared.js +2 -2
- package/dist/modules/mcp/tools/operations/style/write.js +2 -2
- package/dist/modules/mcp/tools/shared/validator/audit.js +1 -4
- package/dist/modules/mcp/tools/shared/validator/css.js +3 -11
- package/dist/modules/mcp/tools/shared/validator/elements.js +4 -1
- package/dist/modules/mcp/tools/shared/validator/index.js +15 -15
- package/dist/modules/mcp/tools/shared/validator/interactions.js +1 -4
- package/dist/modules/oauth/authorize.js +2 -1
- package/dist/modules/oauth/metadata.js +8 -2
- package/dist/modules/oauth/records.js +11 -2
- package/dist/modules/oauth/register.js +15 -8
- package/dist/modules/oauth/respond.js +4 -4
- package/dist/modules/oauth/token.js +2 -2
- package/dist/modules/ssr/registerExternalPlugins.js +1 -1
- package/dist/plugins/manager.js +1 -1
- package/dist/src/core/createServer.d.ts +9 -1
- package/dist/src/core/server/pageServer.d.ts +10 -0
- package/dist/src/core/services/registry.d.ts +1 -1
- package/dist/src/modules/mcp/catalogs/cssCatalog/border.d.ts +4 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/box.d.ts +4 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/helpers.d.ts +34 -0
- package/dist/src/modules/mcp/catalogs/{cssCatalog.d.ts → cssCatalog/index.d.ts} +10 -5
- package/dist/src/modules/mcp/catalogs/cssCatalog/layout.d.ts +9 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/visual.d.ts +8 -0
- package/dist/src/modules/mcp/helpers/guide.d.ts +2 -2
- package/dist/src/modules/mcp/resources/index.d.ts +1 -1
- package/dist/src/modules/mcp/tests/apply.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/bindings.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/css.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/helpers.d.ts +13 -0
- package/dist/src/modules/mcp/tests/interactions.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/pages.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/reads.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/search.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/styles.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/tools.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/validation.test.d.ts +1 -0
- package/dist/src/modules/mcp/tools/screenshot.d.ts +1 -1
- package/dist/src/modules/oauth/metadata.d.ts +7 -1
- package/dist/src/modules/oauth/records.d.ts +6 -0
- package/package.json +9 -9
- package/dist/modules/mcp/catalogs/cssCatalog.js +0 -205
- package/dist/src/core/server/ssrServer.d.ts +0 -2
- /package/dist/src/modules/mcp/{mcp.test.d.ts → catalogs/cssCatalog/cssCatalog.test.d.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { FLEX_DIRECTIONS, FLEX_WRAPS, LENGTH_RE, NUMBER_RE, QUOTE_RE, splitOn, splitPair, splitTokens } from "./helpers.js";
|
|
2
|
+
//#region src/modules/mcp/catalogs/cssCatalog/layout.ts
|
|
3
|
+
var expandOverflow = (value, out) => {
|
|
4
|
+
const [x, y = x] = splitTokens(value);
|
|
5
|
+
out["overflow-x"] = x;
|
|
6
|
+
out["overflow-y"] = y;
|
|
7
|
+
};
|
|
8
|
+
var FLEX_KEYWORDS = /* @__PURE__ */ new Map([
|
|
9
|
+
["none", [
|
|
10
|
+
"0",
|
|
11
|
+
"0",
|
|
12
|
+
"auto"
|
|
13
|
+
]],
|
|
14
|
+
["auto", [
|
|
15
|
+
"1",
|
|
16
|
+
"1",
|
|
17
|
+
"auto"
|
|
18
|
+
]],
|
|
19
|
+
["initial", [
|
|
20
|
+
"0",
|
|
21
|
+
"1",
|
|
22
|
+
"auto"
|
|
23
|
+
]]
|
|
24
|
+
]);
|
|
25
|
+
var writeFlex = (out, grow, shrink, basis) => {
|
|
26
|
+
out["flex-grow"] = grow;
|
|
27
|
+
out["flex-shrink"] = shrink;
|
|
28
|
+
out["flex-basis"] = basis;
|
|
29
|
+
};
|
|
30
|
+
var expandFlex = (value, out) => {
|
|
31
|
+
const tokens = splitTokens(value);
|
|
32
|
+
if (tokens.length === 1) {
|
|
33
|
+
const [only] = tokens;
|
|
34
|
+
const keyword = FLEX_KEYWORDS.get(only);
|
|
35
|
+
if (keyword) writeFlex(out, ...keyword);
|
|
36
|
+
else if (NUMBER_RE.test(only)) writeFlex(out, only, "1", "0%");
|
|
37
|
+
else writeFlex(out, "1", "1", only);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const [grow, second, third] = tokens;
|
|
41
|
+
if (!NUMBER_RE.test(grow)) return;
|
|
42
|
+
if (tokens.length === 2) if (NUMBER_RE.test(second)) writeFlex(out, grow, second, "0%");
|
|
43
|
+
else writeFlex(out, grow, "1", second);
|
|
44
|
+
else writeFlex(out, grow, second, third);
|
|
45
|
+
};
|
|
46
|
+
var expandFlexFlow = (value, out) => {
|
|
47
|
+
for (const token of splitTokens(value)) if (FLEX_DIRECTIONS.has(token)) out["flex-direction"] = token;
|
|
48
|
+
else if (FLEX_WRAPS.has(token)) out["flex-wrap"] = token;
|
|
49
|
+
};
|
|
50
|
+
var expandPlacePair = (prefix, value, out) => {
|
|
51
|
+
const [align, justify = align] = splitTokens(value);
|
|
52
|
+
const prop = prefix.slice(6);
|
|
53
|
+
out[`align-${prop}`] = align;
|
|
54
|
+
out[`justify-${prop}`] = justify;
|
|
55
|
+
};
|
|
56
|
+
var expandGridLine = (key, value, out) => {
|
|
57
|
+
const axis = key.slice(5);
|
|
58
|
+
const [start, end = "auto"] = splitOn(value, "/");
|
|
59
|
+
out[`grid-${axis}-start`] = start;
|
|
60
|
+
out[`grid-${axis}-end`] = end;
|
|
61
|
+
};
|
|
62
|
+
var expandGridArea = (value, out) => {
|
|
63
|
+
const [rowStart, colStart = rowStart, rowEnd = colStart, colEnd = rowEnd] = splitOn(value, "/");
|
|
64
|
+
out["grid-row-start"] = rowStart;
|
|
65
|
+
out["grid-column-start"] = colStart;
|
|
66
|
+
out["grid-row-end"] = rowEnd;
|
|
67
|
+
out["grid-column-end"] = colEnd;
|
|
68
|
+
};
|
|
69
|
+
var expandColumns = (value, out) => {
|
|
70
|
+
const tokens = splitTokens(value);
|
|
71
|
+
if (tokens.length === 1) {
|
|
72
|
+
const [only] = tokens;
|
|
73
|
+
if (NUMBER_RE.test(only)) out["column-count"] = only;
|
|
74
|
+
else out["column-width"] = only;
|
|
75
|
+
} else if (tokens.length >= 2) {
|
|
76
|
+
const [first, second] = tokens;
|
|
77
|
+
const [width, count] = NUMBER_RE.test(first) ? [second, first] : [first, second];
|
|
78
|
+
out["column-width"] = width;
|
|
79
|
+
out["column-count"] = count;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var isTrackList = (tokens) => tokens.some((token) => token === "none" || token === "auto" || token === "min-content" || token === "max-content" || token.startsWith("[") || token.startsWith("minmax(") || token.startsWith("repeat(") || token.startsWith("fit-content(") || LENGTH_RE.test(token) || /^-?[\d.]+fr$/.test(token));
|
|
83
|
+
var expandGridTemplate = (value, out) => {
|
|
84
|
+
const [first, second] = splitPair(value, "/");
|
|
85
|
+
const rowTokens = splitTokens(first);
|
|
86
|
+
const areas = rowTokens.filter((token) => QUOTE_RE.test(token));
|
|
87
|
+
const sizes = rowTokens.filter((token) => !QUOTE_RE.test(token));
|
|
88
|
+
if (second !== void 0) {
|
|
89
|
+
if (second !== "") out["grid-template-columns"] = second;
|
|
90
|
+
} else if (areas.length === 0) {
|
|
91
|
+
if (isTrackList(rowTokens)) out["grid-template-columns"] = first;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (areas.length > 0) out["grid-template-areas"] = areas.join(" ");
|
|
95
|
+
if (sizes.length > 0) out["grid-template-rows"] = sizes.join(" ");
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { expandColumns, expandFlex, expandFlexFlow, expandGridArea, expandGridLine, expandGridTemplate, expandOverflow, expandPlacePair };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ANIMATION_DIRECTIONS, ANIMATION_FILL_MODES, ANIMATION_PLAY_STATES, FONT_STYLE_KEYWORDS, FONT_VARIANT_KEYWORDS, FONT_WEIGHT_KEYWORDS, LENGTH_RE, LIST_STYLE_POSITIONS, LIST_STYLE_TYPES, NUMBER_RE, TEXT_DECORATION_LINES, TEXT_DECORATION_STYLES, TIME_RE, isTimingFunction, splitOn, splitPair, splitTokens } from "./helpers.js";
|
|
2
|
+
import { classifyBorderTokens } from "./border.js";
|
|
3
|
+
//#region src/modules/mcp/catalogs/cssCatalog/visual.ts
|
|
4
|
+
var expandLayers = (value, out, expandLayer) => {
|
|
5
|
+
const layers = splitOn(value, ",");
|
|
6
|
+
if (layers.length === 1) {
|
|
7
|
+
expandLayer(layers[0], out);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const perLayer = layers.map((layer) => {
|
|
11
|
+
const layerOut = {};
|
|
12
|
+
expandLayer(layer, layerOut);
|
|
13
|
+
return layerOut;
|
|
14
|
+
});
|
|
15
|
+
const keys = new Set(perLayer.flatMap((layer) => Object.keys(layer)));
|
|
16
|
+
for (const key of keys) out[key] = perLayer.map((layer) => String(layer[key] ?? "initial")).join(", ");
|
|
17
|
+
};
|
|
18
|
+
var expandOutline = (value, out) => {
|
|
19
|
+
for (const [kind, token] of Object.entries(classifyBorderTokens(value))) out[`outline-${kind}`] = token;
|
|
20
|
+
};
|
|
21
|
+
var expandListStyle = (value, out) => {
|
|
22
|
+
for (const token of splitTokens(value)) if (token.startsWith("url(")) out["list-style-image"] = token;
|
|
23
|
+
else if (LIST_STYLE_POSITIONS.has(token)) out["list-style-position"] = token;
|
|
24
|
+
else if (LIST_STYLE_TYPES.has(token)) out["list-style-type"] = token;
|
|
25
|
+
};
|
|
26
|
+
var expandTextDecoration = (value, out) => {
|
|
27
|
+
for (const token of splitTokens(value)) if (TEXT_DECORATION_LINES.has(token)) out["text-decoration-line"] = token;
|
|
28
|
+
else if (TEXT_DECORATION_STYLES.has(token)) out["text-decoration-style"] = token;
|
|
29
|
+
else out["text-decoration-color"] = token;
|
|
30
|
+
};
|
|
31
|
+
var expandTransitionLayer = (layer, out) => {
|
|
32
|
+
let foundDuration = false;
|
|
33
|
+
for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["transition-timing-function"] = token;
|
|
34
|
+
else if (TIME_RE.test(token) || NUMBER_RE.test(token)) if (foundDuration) out["transition-delay"] = token;
|
|
35
|
+
else {
|
|
36
|
+
out["transition-duration"] = token;
|
|
37
|
+
foundDuration = true;
|
|
38
|
+
}
|
|
39
|
+
else if (!out["transition-property"]) out["transition-property"] = token;
|
|
40
|
+
};
|
|
41
|
+
var expandTransition = (value, out) => expandLayers(value, out, expandTransitionLayer);
|
|
42
|
+
var expandAnimationLayer = (layer, out) => {
|
|
43
|
+
let foundDuration = false;
|
|
44
|
+
for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["animation-timing-function"] = token;
|
|
45
|
+
else if (TIME_RE.test(token)) if (foundDuration) out["animation-delay"] = token;
|
|
46
|
+
else {
|
|
47
|
+
out["animation-duration"] = token;
|
|
48
|
+
foundDuration = true;
|
|
49
|
+
}
|
|
50
|
+
else if (token === "infinite" || NUMBER_RE.test(token)) out["animation-iteration-count"] = token;
|
|
51
|
+
else if (ANIMATION_DIRECTIONS.has(token) && !out["animation-direction"]) out["animation-direction"] = token;
|
|
52
|
+
else if (ANIMATION_FILL_MODES.has(token) && !out["animation-fill-mode"]) out["animation-fill-mode"] = token;
|
|
53
|
+
else if (ANIMATION_PLAY_STATES.has(token) && !out["animation-play-state"]) out["animation-play-state"] = token;
|
|
54
|
+
else if (!out["animation-name"]) out["animation-name"] = token;
|
|
55
|
+
};
|
|
56
|
+
var expandAnimation = (value, out) => expandLayers(value, out, expandAnimationLayer);
|
|
57
|
+
var BACKGROUND_REPEATS = /* @__PURE__ */ new Set([
|
|
58
|
+
"repeat",
|
|
59
|
+
"no-repeat",
|
|
60
|
+
"repeat-x",
|
|
61
|
+
"repeat-y",
|
|
62
|
+
"space",
|
|
63
|
+
"round"
|
|
64
|
+
]);
|
|
65
|
+
var BACKGROUND_ATTACHMENTS = /* @__PURE__ */ new Set([
|
|
66
|
+
"scroll",
|
|
67
|
+
"fixed",
|
|
68
|
+
"local"
|
|
69
|
+
]);
|
|
70
|
+
var BACKGROUND_BOXES = /* @__PURE__ */ new Set([
|
|
71
|
+
"border-box",
|
|
72
|
+
"padding-box",
|
|
73
|
+
"content-box"
|
|
74
|
+
]);
|
|
75
|
+
var BACKGROUND_POSITIONS = /* @__PURE__ */ new Set([
|
|
76
|
+
"center",
|
|
77
|
+
"top",
|
|
78
|
+
"bottom",
|
|
79
|
+
"left",
|
|
80
|
+
"right"
|
|
81
|
+
]);
|
|
82
|
+
var isImage = (token) => token.startsWith("url(") || token.includes("-gradient(");
|
|
83
|
+
var isPosition = (token) => BACKGROUND_POSITIONS.has(token) || LENGTH_RE.test(token);
|
|
84
|
+
var isSize = (token) => token === "cover" || token === "contain" || token === "auto" || LENGTH_RE.test(token);
|
|
85
|
+
var expandBackgroundLayer = (layer, out) => {
|
|
86
|
+
const [beforeSlash, afterSlash] = splitPair(layer, "/");
|
|
87
|
+
const tokens = splitTokens(beforeSlash);
|
|
88
|
+
if (afterSlash !== void 0) {
|
|
89
|
+
let positionStart = tokens.length;
|
|
90
|
+
while (positionStart > 0 && isPosition(tokens[positionStart - 1])) positionStart -= 1;
|
|
91
|
+
const position = tokens.splice(positionStart, tokens.length - positionStart);
|
|
92
|
+
if (position.length > 0) out["background-position"] = position.join(" ");
|
|
93
|
+
const afterTokens = splitTokens(afterSlash);
|
|
94
|
+
let sizeEnd = 0;
|
|
95
|
+
while (sizeEnd < afterTokens.length && isSize(afterTokens[sizeEnd])) sizeEnd += 1;
|
|
96
|
+
if (sizeEnd > 0) out["background-size"] = afterTokens.slice(0, sizeEnd).join(" ");
|
|
97
|
+
tokens.push(...afterTokens.slice(sizeEnd));
|
|
98
|
+
}
|
|
99
|
+
const positions = [];
|
|
100
|
+
for (const token of tokens) if (isImage(token)) out["background-image"] = token;
|
|
101
|
+
else if (BACKGROUND_REPEATS.has(token)) out["background-repeat"] = token;
|
|
102
|
+
else if (token === "cover" || token === "contain") out["background-size"] = token;
|
|
103
|
+
else if (BACKGROUND_ATTACHMENTS.has(token)) out["background-attachment"] = token;
|
|
104
|
+
else if (BACKGROUND_BOXES.has(token)) if (out["background-origin"]) out["background-clip"] = token;
|
|
105
|
+
else out["background-origin"] = token;
|
|
106
|
+
else if (isPosition(token)) positions.push(token);
|
|
107
|
+
else if (!out["background-color"]) out["background-color"] = token;
|
|
108
|
+
if (positions.length > 0) out["background-position"] = positions.join(" ");
|
|
109
|
+
};
|
|
110
|
+
var expandBackground = (value, out) => expandLayers(value, out, expandBackgroundLayer);
|
|
111
|
+
var expandFont = (value, out) => {
|
|
112
|
+
const [beforeSlash, afterSlash] = splitPair(value, "/");
|
|
113
|
+
const tokens = splitTokens(beforeSlash);
|
|
114
|
+
if (afterSlash !== void 0) {
|
|
115
|
+
const [lineHeight, ...family] = splitTokens(afterSlash);
|
|
116
|
+
out["line-height"] = lineHeight;
|
|
117
|
+
if (family.length > 0) out["font-family"] = family.join(" ");
|
|
118
|
+
}
|
|
119
|
+
const family = [];
|
|
120
|
+
let foundSize = false;
|
|
121
|
+
for (const token of tokens) {
|
|
122
|
+
if (foundSize) {
|
|
123
|
+
family.push(token);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (token === "normal") {
|
|
127
|
+
const slot = [
|
|
128
|
+
"font-style",
|
|
129
|
+
"font-variant",
|
|
130
|
+
"font-weight"
|
|
131
|
+
].find((prop) => !out[prop]);
|
|
132
|
+
if (slot) out[slot] = token;
|
|
133
|
+
} else if (FONT_STYLE_KEYWORDS.has(token) && !out["font-style"]) out["font-style"] = token;
|
|
134
|
+
else if (FONT_VARIANT_KEYWORDS.has(token) && !out["font-variant"]) out["font-variant"] = token;
|
|
135
|
+
else if (FONT_WEIGHT_KEYWORDS.has(token) && !out["font-weight"]) out["font-weight"] = token;
|
|
136
|
+
else if (LENGTH_RE.test(token)) {
|
|
137
|
+
out["font-size"] = token;
|
|
138
|
+
foundSize = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (family.length > 0) out["font-family"] = family.join(" ");
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
export { expandAnimation, expandBackground, expandFont, expandListStyle, expandOutline, expandTextDecoration, expandTransition };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BUILTIN_COMPONENTS } from "./builtinComponents.js";
|
|
2
|
-
import { cssProperties } from "./cssCatalog.js";
|
|
2
|
+
import { cssProperties } from "./cssCatalog/index.js";
|
|
3
3
|
//#region src/modules/mcp/catalogs/registry.ts
|
|
4
4
|
var enrichType = (typeName, info, catalog) => {
|
|
5
5
|
const entry = catalog?.[typeName];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/modules/mcp/helpers/guide.ts
|
|
2
|
-
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is kebab-case
|
|
2
|
+
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS refs by label/type/attribute; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
|
|
3
3
|
var guideQuickstart = `# Plitzi AI MCP — quickstart
|
|
4
4
|
This is the condensed guide; read \`plitzi://guide\` for the full reference (every resource, op and example).
|
|
5
5
|
|
|
@@ -19,10 +19,12 @@ interactions target by it. A dot would split that path; an **underscore is fine*
|
|
|
19
19
|
from the idRef (element types have none), so \`list_food_item\` reads unambiguously as type \`list\`, idRef \`food_item\`.
|
|
20
20
|
|
|
21
21
|
**Styling:** CSS keys are **kebab-case** (\`background-color\`); \`var(--token)\` for style vars, \`{{name}}\` for schema
|
|
22
|
-
vars.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
vars. **Write plain CSS** — shorthands (\`border\`, \`padding\`, \`margin\`, \`gap\`, \`overflow\`, \`flex\`, \`background\`,
|
|
23
|
+
\`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-row\`/\`grid-column\`, \`place-*\`, \`outline\`, \`columns\`,
|
|
24
|
+
\`list-style\`, \`text-decoration\`) are expanded to their longhands for you, so \`border: 1px solid red\` is stored as
|
|
25
|
+
\`border-top-color\`/\`border-top-width\`/\`border-top-style\`/… That is also how you read them back. Flex layout is
|
|
26
|
+
still \`display: flex\` + \`flex-direction\`/\`align-items\`/…, not a \`flex\` value. Mind a type's \`defaultStyle\`
|
|
27
|
+
(\`text\` is \`display: inline\`). Global styles (\`button {…}\`) and id styles (\`#id\`) have their own ops.
|
|
26
28
|
|
|
27
29
|
**Data bindings** (\`upsertBinding\`, category attributes|style|initialState): connect a \`source\` to a \`to\` field.
|
|
28
30
|
A source \`<type>_<idRef>\` is scoped to the provider's **DESCENDANTS only** — bind inside the provider's subtree
|
|
@@ -202,14 +204,22 @@ pointed at the old name is repointed with it, so the element stays wired. You do
|
|
|
202
204
|
- A definition lives in the **style schema**; an element's \`style.base\` (element schema) is the link that applies
|
|
203
205
|
it. Styling an element = upsertDefinition + upsertElement with that ref in \`style.base\`, in one batch.
|
|
204
206
|
- CSS keys are **kebab-case** (\`background-color\`). camelCase is rejected — read \`plitzi://css-properties\`.
|
|
205
|
-
-
|
|
206
|
-
\`padding\`, \`margin\`, \`inset\`, \`gap\`
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
\`
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
\`
|
|
207
|
+
- **Write normal CSS — shorthands are accepted and expanded for you.** \`border\`, \`border-{side}\`,
|
|
208
|
+
\`border-width\`/\`-color\`/\`-style\`, \`border-radius\`, \`padding\`, \`margin\`, \`inset\`, \`gap\`, \`overflow\`,
|
|
209
|
+
\`flex\`, \`flex-flow\`, \`background\`, \`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-template\`,
|
|
210
|
+
\`grid-area\`, \`grid-row\`, \`grid-column\`, \`place-content\`, \`place-items\`, \`place-self\`, \`outline\`,
|
|
211
|
+
\`columns\`, \`list-style\`, \`text-decoration\` — the full list is in \`plitzi://css-properties\` under
|
|
212
|
+
\`shorthands\`. They **persist as longhand keys** (that is what a read gives back), so a breakpoint/state/variant
|
|
213
|
+
can still override one property at a time.
|
|
214
|
+
- \`border: 1px solid red\` → \`border-top-width: 1px\`, \`border-top-style: solid\`, \`border-top-color: red\`, …
|
|
215
|
+
for all four sides. A shorthand also RESETS what it omits, so \`border: none\` clears a width a previous
|
|
216
|
+
definition set.
|
|
217
|
+
- Comma-separated layers are kept per longhand: \`transition: opacity 200ms, transform 300ms\` →
|
|
218
|
+
\`transition-property: opacity, transform\` + \`transition-duration: 200ms, 300ms\`.
|
|
219
|
+
- In a **patch**, a shorthand replaces the longhands it controls (\`padding: 8px\` overwrites a previous
|
|
220
|
+
\`padding-left\`), and \`"border": null\` removes all twelve border longhands.
|
|
221
|
+
- **Flex layout is not a \`flex\` value**: set \`display: flex\` **plus** \`flex-direction\`, \`align-items\`,
|
|
222
|
+
\`justify-content\` as separate properties.
|
|
213
223
|
- CSS is grouped by breakpoint: \`desktop\`, \`tablet\`, \`mobile\`.
|
|
214
224
|
- Reference a style variable in CSS as \`var(--name)\`; a schema variable in a prop as \`{{name}}\`.
|
|
215
225
|
- \`element.style.base\` is a **list** of definition refs; other slots go under \`element.style.slots\`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { buildTypeRegistry } from "../catalogs/registry.js";
|
|
3
3
|
import "../helpers/uris.js";
|
|
4
4
|
import { guideText } from "../helpers/guide.js";
|
|
@@ -9,7 +9,10 @@ import { envelope } from "./envelope.js";
|
|
|
9
9
|
var readCoreResource = (space, uri) => {
|
|
10
10
|
if (uri === "plitzi://guide") return envelope(guideText);
|
|
11
11
|
if (uri === "plitzi://types") return envelope(buildTypeRegistry(space.schema, space.catalog));
|
|
12
|
-
if (uri === "plitzi://css-properties") return envelope(
|
|
12
|
+
if (uri === "plitzi://css-properties") return envelope({
|
|
13
|
+
properties: cssProperties,
|
|
14
|
+
shorthands: cssShorthands
|
|
15
|
+
});
|
|
13
16
|
};
|
|
14
17
|
//#endregion
|
|
15
18
|
export { readCoreResource };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { buildDataSourceCatalog, buildInteractionCatalog } from "../catalogs/observed.js";
|
|
3
3
|
import { buildTypeRegistry } from "../catalogs/registry.js";
|
|
4
4
|
import { guideQuickstart } from "../helpers/guide.js";
|
|
@@ -16,6 +16,7 @@ var readPrimerResource = (space, env, uri) => {
|
|
|
16
16
|
guide: guideQuickstart,
|
|
17
17
|
types: buildTypeRegistry(space.schema, space.catalog),
|
|
18
18
|
cssProperties,
|
|
19
|
+
cssShorthands,
|
|
19
20
|
pages: pageSummariesToAI(space.schema),
|
|
20
21
|
folders: foldersToAI(space.schema),
|
|
21
22
|
definitions: definitionRefs(space.style),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { guideText } from "../helpers/guide.js";
|
|
3
3
|
import { resourceErrorMessage } from "./canonical.js";
|
|
4
4
|
import { envelope, jsonContents } from "./envelope.js";
|
|
@@ -36,9 +36,12 @@ var registerResources = (server, getSpace, env, log) => {
|
|
|
36
36
|
text: guideText
|
|
37
37
|
}] })));
|
|
38
38
|
server.registerResource("CSS properties", "plitzi://css-properties", {
|
|
39
|
-
description: "Valid kebab-case CSS property keys",
|
|
39
|
+
description: "Valid kebab-case CSS property keys, plus the shorthands that are accepted and auto-expanded",
|
|
40
40
|
mimeType: "application/json"
|
|
41
|
-
}, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope(
|
|
41
|
+
}, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope({
|
|
42
|
+
properties: cssProperties,
|
|
43
|
+
shorthands: cssShorthands
|
|
44
|
+
}))));
|
|
42
45
|
registerRenderResources(server, log);
|
|
43
46
|
const fixed = [
|
|
44
47
|
[
|
|
@@ -43,7 +43,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) =>
|
|
|
43
43
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
44
44
|
const server = new McpServer({
|
|
45
45
|
name: "plitzi-mcp",
|
|
46
|
-
version: "0.32.
|
|
46
|
+
version: "0.32.16"
|
|
47
47
|
}, { instructions: serverInstructions });
|
|
48
48
|
registerResources(server, getSpace, MCP_ENV, log);
|
|
49
49
|
registerApps(server);
|
|
@@ -16,13 +16,14 @@ var upsertInteractionFlow = (space, env, op) => {
|
|
|
16
16
|
const found = resolveElement(space, env, op.pageRef, op.ref);
|
|
17
17
|
if ("error" in found) return found.error;
|
|
18
18
|
const ownerRef = ensureIdRef(space, found.el);
|
|
19
|
-
const
|
|
19
|
+
const nodes = (op.flowId && !op.nodes[0].id ? [{
|
|
20
20
|
...op.nodes[0],
|
|
21
21
|
id: op.flowId
|
|
22
22
|
}, ...op.nodes.slice(1)] : op.nodes).map((node) => node.elementId === void 0 || node.nodeType === "utility" ? node : {
|
|
23
23
|
...node,
|
|
24
24
|
elementId: resolveTargetRef(space, node.elementId)
|
|
25
|
-
})
|
|
25
|
+
});
|
|
26
|
+
const { flowId, record } = materializeFlow(nodes, ownerRef);
|
|
26
27
|
const interactions = found.el.definition.interactions ?? {};
|
|
27
28
|
const targetFlow = op.flowId ?? flowId;
|
|
28
29
|
let replaced = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
//#region src/modules/mcp/tools/operations/style/shared.ts
|
|
3
|
-
var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("kebab-case CSS
|
|
3
|
+
var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("Plain kebab-case CSS. Shorthands are welcome — `border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`, `transition: opacity 200ms ease` — and are stored as their longhands so a breakpoint/state/variant can override each property on its own. Use var(--name) for style tokens and {{name}} for schema vars.");
|
|
4
4
|
var displayModeCss = z.object({
|
|
5
5
|
desktop: cssMap.optional(),
|
|
6
6
|
tablet: cssMap.optional(),
|
|
@@ -14,7 +14,7 @@ var cssPatchMap = z.record(z.string(), z.union([
|
|
|
14
14
|
z.string(),
|
|
15
15
|
z.number(),
|
|
16
16
|
z.null()
|
|
17
|
-
])).describe("kebab-case CSS
|
|
17
|
+
])).describe("Plain kebab-case CSS merged onto the existing declarations; shorthands are accepted and expanded, so `padding: 8px` replaces all four sides. A value of null removes the property — null on a shorthand (`border: null`) removes every longhand it controls.");
|
|
18
18
|
var displayModeCssPatch = z.object({
|
|
19
19
|
desktop: cssPatchMap.optional(),
|
|
20
20
|
tablet: cssPatchMap.optional(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandShorthand } from "../../../catalogs/cssCatalog.js";
|
|
1
|
+
import { expandShorthand, expandShorthandPatch } from "../../../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { defUri, defsUri, globalUri, globalsUri, idUri, idsUri, styleVarUri, styleVarsUri } from "../../../helpers/uris.js";
|
|
3
3
|
import { fail } from "../../../helpers/opResult.js";
|
|
4
4
|
import processSelector from "@plitzi/sdk-style/helpers/processSelector";
|
|
@@ -65,7 +65,7 @@ var writeStyleItem = (style, ref, base, slots, itemType, componentType) => {
|
|
|
65
65
|
};
|
|
66
66
|
var mergeCss = (base, patch) => {
|
|
67
67
|
const merged = { ...base };
|
|
68
|
-
for (const [key, value] of Object.entries(patch ?? {})) if (value === null) Reflect.deleteProperty(merged, key);
|
|
68
|
+
for (const [key, value] of Object.entries(expandShorthandPatch(patch ?? {}))) if (value === null) Reflect.deleteProperty(merged, key);
|
|
69
69
|
else merged[key] = value;
|
|
70
70
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
71
71
|
};
|
|
@@ -83,10 +83,7 @@ var collectTouched = (ops) => {
|
|
|
83
83
|
touched.globalStyles.add(op.componentType);
|
|
84
84
|
break;
|
|
85
85
|
case "upsertIdStyle":
|
|
86
|
-
case "patchIdStyle":
|
|
87
|
-
touched.idStyles.add(op.targetId);
|
|
88
|
-
break;
|
|
89
|
-
default: break;
|
|
86
|
+
case "patchIdStyle": touched.idStyles.add(op.targetId);
|
|
90
87
|
}
|
|
91
88
|
return touched;
|
|
92
89
|
};
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { checkStyleVarRefs, checkVarRefs
|
|
1
|
+
import { expandShorthand, isCssProperty, suggestCssProperty } from "../../../catalogs/cssCatalog/index.js";
|
|
2
|
+
import { checkStyleVarRefs, checkVarRefs } from "./context.js";
|
|
3
3
|
//#region src/modules/mcp/tools/shared/validator/css.ts
|
|
4
4
|
var checkCss = (css, path, ctx) => {
|
|
5
5
|
if (!css) return;
|
|
6
6
|
const declared = {};
|
|
7
7
|
for (const [key, value] of Object.entries(css)) if (value !== null) declared[key] = value;
|
|
8
8
|
for (const [key, value] of Object.entries(expandShorthand(declared))) {
|
|
9
|
-
|
|
10
|
-
if (longhands) if (!isCssProperty(key)) ctx.errors.push({
|
|
11
|
-
path: `${path}.${key}`,
|
|
12
|
-
message: `Compound CSS shorthand "${key}" is not supported — write its atomic longhand properties`,
|
|
13
|
-
hint: `Use: ${longhands.join(", ")}. (Note: flex layout is display + flex-direction + flex-grow/shrink/basis, not "flex".)`,
|
|
14
|
-
validValues: longhands
|
|
15
|
-
});
|
|
16
|
-
else warnOnce(ctx, `CSS shorthand "${key}" at ${path}.${key} is accepted but stored as one compound value. Plitzi styling is atomic — prefer the longhands (${longhands.join(", ")}) so a breakpoint/state/variant can override each property independently.`);
|
|
17
|
-
else if (!isCssProperty(key)) {
|
|
9
|
+
if (!isCssProperty(key)) {
|
|
18
10
|
const suggestion = suggestCssProperty(key);
|
|
19
11
|
ctx.errors.push({
|
|
20
12
|
path: `${path}.${key}`,
|
|
@@ -25,7 +25,10 @@ var checkVariantApplication = (initialState, path, ctx) => {
|
|
|
25
25
|
const batch = ctx.batchVariants.get(cls);
|
|
26
26
|
for (const [selector, variant] of Object.entries(selectors)) {
|
|
27
27
|
const names = Array.isArray(variant) ? variant : [variant];
|
|
28
|
-
for (const name of names) if (!((declared?.[selector]?.includes(name) ?? false) || (batch?.has(name) ?? false)))
|
|
28
|
+
for (const name of names) if (!((declared?.[selector]?.includes(name) ?? false) || (batch?.has(name) ?? false))) {
|
|
29
|
+
const avail = declared ? ` (declares: ${Object.entries(declared).map(([s, v]) => `${s}:${v.join("/")}`).join(", ")})` : "";
|
|
30
|
+
warnOnce(ctx, `Element applies variant "${name}" on class "${cls}" (${selector}) at ${path}, but that class defines no such variant${avail}. Create it via upsertDefinition/patchDefinition "variants", or fix the name.`);
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
};
|
|
@@ -191,19 +191,21 @@ var validateOperations = (space, ops) => {
|
|
|
191
191
|
case "patchInteractionNode": {
|
|
192
192
|
checkRef(op.ref, `${base}.ref`, ctx);
|
|
193
193
|
const existing = findElementByRef(space.schema, op.ref)?.definition.interactions?.[op.nodeId];
|
|
194
|
-
if (existing)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
194
|
+
if (existing) {
|
|
195
|
+
const merged = {
|
|
196
|
+
id: existing.id,
|
|
197
|
+
title: op.title ?? existing.title,
|
|
198
|
+
nodeType: existing.type,
|
|
199
|
+
action: op.action ?? existing.action,
|
|
200
|
+
params: {
|
|
201
|
+
...existing.params,
|
|
202
|
+
...op.params ?? {}
|
|
203
|
+
},
|
|
204
|
+
enabled: op.enabled ?? existing.enabled,
|
|
205
|
+
elementId: op.elementId ?? existing.elementId ?? void 0
|
|
206
|
+
};
|
|
207
|
+
checkInteractionNode(merged, base, ctx, op.ref);
|
|
208
|
+
} else checkObservedName(op.action, ctx.observedActions, "Interaction action", "plitzi://interactions", `${base}.action`, ctx);
|
|
207
209
|
break;
|
|
208
210
|
}
|
|
209
211
|
case "deleteInteraction":
|
|
@@ -213,8 +215,6 @@ var validateOperations = (space, ops) => {
|
|
|
213
215
|
message: "Provide exactly one of flowId or nodeId",
|
|
214
216
|
hint: "flowId removes a whole flow; nodeId removes a single step"
|
|
215
217
|
});
|
|
216
|
-
break;
|
|
217
|
-
default: break;
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
220
|
return {
|
|
@@ -96,10 +96,7 @@ var checkInteractionNode = (node, base, ctx, hostRef) => {
|
|
|
96
96
|
case "callback":
|
|
97
97
|
checkElementCallback(node, base, ctx, hostRef);
|
|
98
98
|
break;
|
|
99
|
-
case "utility":
|
|
100
|
-
checkUtility(node, base, ctx);
|
|
101
|
-
break;
|
|
102
|
-
default: break;
|
|
99
|
+
case "utility": checkUtility(node, base, ctx);
|
|
103
100
|
}
|
|
104
101
|
};
|
|
105
102
|
//#endregion
|
|
@@ -28,7 +28,8 @@ var hiddenFieldsFor = (request, pendingId) => {
|
|
|
28
28
|
return hidden;
|
|
29
29
|
};
|
|
30
30
|
var renderConsent = async (config, res, view) => {
|
|
31
|
-
|
|
31
|
+
const html = await (config.renderConsent ?? renderConsentPage)(view);
|
|
32
|
+
sendHtml(res, 200, html);
|
|
32
33
|
};
|
|
33
34
|
/** Validates the parts of an authorization request that decide WHERE a failure may be reported. Until the client
|
|
34
35
|
* and its redirect target check out, nothing may be sent back to the client. */
|
|
@@ -22,11 +22,17 @@ var resourceMetadataUrl = (config, req) => `${issuerOf(config, req)}${PROTECTED_
|
|
|
22
22
|
*
|
|
23
23
|
* `resource` echoes the path the document was asked for: Claude requires it to match the server URL the user
|
|
24
24
|
* typed, path included, and a dedicated MCP server answers JSON-RPC on every path — so `/.well-known/…/mcp`
|
|
25
|
-
* describes `https://host/mcp` while the bare path describes the origin.
|
|
25
|
+
* describes `https://host/mcp` while the bare path describes the origin.
|
|
26
|
+
*
|
|
27
|
+
* A server mounted at the ORIGIN is `https://host/`, WITH the trailing slash: a host parses the URL the user
|
|
28
|
+
* typed before it does anything with it, and an empty path renders as `/` — that normalized form is what the
|
|
29
|
+
* client sends back as the `resource` parameter (asserted in the connector e2e). A document claiming to be
|
|
30
|
+
* `https://host` then disagrees with the request made against it, and Claude's connector drops the grant it
|
|
31
|
+
* just completed — it takes the token and never opens the session. */
|
|
26
32
|
var protectedResourceMetadata = (config, req) => {
|
|
27
33
|
const issuer = issuerOf(config, req);
|
|
28
34
|
return {
|
|
29
|
-
resource: `${issuer}${canonicalPath(req.path.slice(37))}`,
|
|
35
|
+
resource: `${issuer}${canonicalPath(req.path.slice(37)) || "/"}`,
|
|
30
36
|
authorization_servers: [issuer],
|
|
31
37
|
scopes_supported: scopesOf(config),
|
|
32
38
|
bearer_methods_supported: ["header"]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/modules/oauth/records.ts
|
|
2
|
-
var CLIENT_TTL_SECONDS =
|
|
2
|
+
var CLIENT_TTL_SECONDS = 7776e3;
|
|
3
3
|
var PENDING_TTL_SECONDS = 600;
|
|
4
4
|
var keyOf = (kind, id) => `oauth:${kind}:${id}`;
|
|
5
5
|
var readJson = async (store, kind, id) => {
|
|
@@ -16,6 +16,15 @@ var writeJson = async (store, kind, id, value, ttlSeconds) => {
|
|
|
16
16
|
};
|
|
17
17
|
var putClient = (store, client) => writeJson(store, "client", client.clientId, client, CLIENT_TTL_SECONDS);
|
|
18
18
|
var getClient = (store, clientId) => readJson(store, "client", clientId);
|
|
19
|
+
/** The id already handed out for an identical registration. Keyed by what the client asked to be registered AS,
|
|
20
|
+
* so the same request always resolves to the same client — see handleRegister for why that matters. */
|
|
21
|
+
var getClientByFingerprint = async (store, fingerprint) => {
|
|
22
|
+
const clientId = await store.get(keyOf("clientfp", fingerprint));
|
|
23
|
+
return clientId ? getClient(store, clientId) : void 0;
|
|
24
|
+
};
|
|
25
|
+
var putClientFingerprint = async (store, fingerprint, clientId) => {
|
|
26
|
+
await store.put(keyOf("clientfp", fingerprint), clientId, CLIENT_TTL_SECONDS);
|
|
27
|
+
};
|
|
19
28
|
var putPending = (store, id, pending) => writeJson(store, "pending", id, pending, PENDING_TTL_SECONDS);
|
|
20
29
|
var getPending = (store, id) => readJson(store, "pending", id);
|
|
21
30
|
var dropPending = async (store, id) => {
|
|
@@ -34,4 +43,4 @@ var dropRefresh = async (store, token) => {
|
|
|
34
43
|
var putAccess = (store, token, record, ttlSeconds) => writeJson(store, "access", token, record, ttlSeconds);
|
|
35
44
|
var getAccess = (store, token) => readJson(store, "access", token);
|
|
36
45
|
//#endregion
|
|
37
|
-
export { dropCode, dropPending, dropRefresh, getAccess, getClient, getCode, getPending, getRefresh, putAccess, putClient, putCode, putPending, putRefresh };
|
|
46
|
+
export { dropCode, dropPending, dropRefresh, getAccess, getClient, getClientByFingerprint, getCode, getPending, getRefresh, putAccess, putClient, putClientFingerprint, putCode, putPending, putRefresh };
|