@samva/markup 0.1.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/AGENTS.md +8 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/ast-DAhnAvQN.d.ts +120 -0
- package/dist/ast.d.ts +2 -0
- package/dist/ast.js +159 -0
- package/dist/ast.js.map +1 -0
- package/dist/candidate.d.ts +29 -0
- package/dist/candidate.js +88 -0
- package/dist/candidate.js.map +1 -0
- package/dist/checks-CW0nFj5n.js +58922 -0
- package/dist/checks-CW0nFj5n.js.map +1 -0
- package/dist/checks.d.ts +26 -0
- package/dist/checks.js +2 -0
- package/dist/classes.d.ts +27 -0
- package/dist/classes.js +184 -0
- package/dist/classes.js.map +1 -0
- package/dist/compile-sms.d.ts +28 -0
- package/dist/compile-sms.js +44 -0
- package/dist/compile-sms.js.map +1 -0
- package/dist/compile-whatsapp.d.ts +56 -0
- package/dist/compile-whatsapp.js +186 -0
- package/dist/compile-whatsapp.js.map +1 -0
- package/dist/compile.d.ts +93 -0
- package/dist/compile.js +938 -0
- package/dist/compile.js.map +1 -0
- package/dist/components.d.ts +289 -0
- package/dist/components.js +106 -0
- package/dist/components.js.map +1 -0
- package/dist/conditionals.d.ts +10 -0
- package/dist/conditionals.js +42 -0
- package/dist/conditionals.js.map +1 -0
- package/dist/diagnostics-oveE-laV.d.ts +79 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +62 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/emit.d.ts +50 -0
- package/dist/emit.js +151 -0
- package/dist/emit.js.map +1 -0
- package/dist/footer.d.ts +66 -0
- package/dist/footer.js +90 -0
- package/dist/footer.js.map +1 -0
- package/dist/interpolate.d.ts +27 -0
- package/dist/interpolate.js +61 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/jsx-dev-runtime.d.ts +10 -0
- package/dist/jsx-dev-runtime.js +7 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime-fSxcvlx4.d.ts +57 -0
- package/dist/jsx-runtime.d.ts +2 -0
- package/dist/jsx-runtime.js +102 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/lexer.d.ts +60 -0
- package/dist/lexer.js +269 -0
- package/dist/lexer.js.map +1 -0
- package/dist/model.d.ts +39 -0
- package/dist/model.js +90 -0
- package/dist/model.js.map +1 -0
- package/dist/ops.d.ts +208 -0
- package/dist/ops.js +546 -0
- package/dist/ops.js.map +1 -0
- package/dist/parser-yzjUOGhc.d.ts +387 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +821 -0
- package/dist/parser.js.map +1 -0
- package/dist/predicate-DTtVtLK7.d.ts +49 -0
- package/dist/predicate.d.ts +2 -0
- package/dist/predicate.js +417 -0
- package/dist/predicate.js.map +1 -0
- package/dist/presets/category-mapping.d.ts +13 -0
- package/dist/presets/category-mapping.js +49 -0
- package/dist/presets/category-mapping.js.map +1 -0
- package/dist/presets/public-gallery.d.ts +490 -0
- package/dist/presets/public-gallery.js +563 -0
- package/dist/presets/public-gallery.js.map +1 -0
- package/dist/presets/public-handoff.d.ts +68 -0
- package/dist/presets/public-handoff.js +105 -0
- package/dist/presets/public-handoff.js.map +1 -0
- package/dist/presets/public-preview-artifacts.d.ts +41 -0
- package/dist/presets/public-preview-artifacts.js +109 -0
- package/dist/presets/public-preview-artifacts.js.map +1 -0
- package/dist/presets/public-preview.d.ts +1168 -0
- package/dist/presets/public-preview.js +1158 -0
- package/dist/presets/public-preview.js.map +1 -0
- package/dist/presets/sources.d.ts +7 -0
- package/dist/presets/sources.js +54 -0
- package/dist/presets/sources.js.map +1 -0
- package/dist/presets.d.ts +350 -0
- package/dist/presets.js +1251 -0
- package/dist/presets.js.map +1 -0
- package/dist/printer.d.ts +6 -0
- package/dist/printer.js +122 -0
- package/dist/printer.js.map +1 -0
- package/dist/registry.d.ts +42 -0
- package/dist/registry.js +646 -0
- package/dist/registry.js.map +1 -0
- package/dist/render-template.d.ts +42 -0
- package/dist/render-template.js +74 -0
- package/dist/render-template.js.map +1 -0
- package/dist/sms-segments-y4FpPilJ.d.ts +13 -0
- package/dist/sms-segments.d.ts +2 -0
- package/dist/sms-segments.js +46 -0
- package/dist/sms-segments.js.map +1 -0
- package/dist/theme-6ApjjiBo.d.ts +116 -0
- package/dist/theme-css.d.ts +24 -0
- package/dist/theme-css.js +509 -0
- package/dist/theme-css.js.map +1 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +95 -0
- package/dist/theme.js.map +1 -0
- package/dist/tokens.d.ts +6 -0
- package/dist/tokens.js +417 -0
- package/dist/tokens.js.map +1 -0
- package/dist/variables-3NGLpNTB.d.ts +77 -0
- package/dist/variables.d.ts +2 -0
- package/dist/variables.js +141 -0
- package/dist/variables.js.map +1 -0
- package/dist/version-BITr58kk.js +96 -0
- package/dist/version-BITr58kk.js.map +1 -0
- package/generated/sml-agent-contract.json +917 -0
- package/generated/sml-email-agent-contract.json +790 -0
- package/generated/sml-email-language-reference.md +160 -0
- package/generated/sml-language-reference.md +220 -0
- package/package.json +203 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { printPredicate } from "./predicate.js";
|
|
2
|
+
import { Predicate } from "effect";
|
|
3
|
+
//#region src/variables.ts
|
|
4
|
+
const PLACEHOLDER_SPAN = {
|
|
5
|
+
start: 0,
|
|
6
|
+
end: 0
|
|
7
|
+
};
|
|
8
|
+
/** Brand carried by every VariableRef, keyed off a private symbol. */
|
|
9
|
+
const VARIABLE_REF = Symbol.for("@samva/markup/VariableRef");
|
|
10
|
+
/** Brand carried by every PredicateRef. */
|
|
11
|
+
const PREDICATE_REF = Symbol.for("@samva/markup/PredicateRef");
|
|
12
|
+
const COERCION_MESSAGE = "variables are send-time values; branch on build-time data instead";
|
|
13
|
+
const isVariableRef = (value) => Predicate.hasProperty(value, VARIABLE_REF);
|
|
14
|
+
const isPredicateRef = (value) => Predicate.hasProperty(value, PREDICATE_REF);
|
|
15
|
+
const literalFor = (value) => typeof value === "number" ? {
|
|
16
|
+
kind: "number",
|
|
17
|
+
value
|
|
18
|
+
} : {
|
|
19
|
+
kind: "string",
|
|
20
|
+
value
|
|
21
|
+
};
|
|
22
|
+
const makePredicateRef = (ast) => ({
|
|
23
|
+
[PREDICATE_REF]: { ast },
|
|
24
|
+
and: (other) => makePredicateRef({
|
|
25
|
+
kind: "and",
|
|
26
|
+
left: ast,
|
|
27
|
+
right: predicateAst(other),
|
|
28
|
+
span: PLACEHOLDER_SPAN
|
|
29
|
+
}),
|
|
30
|
+
or: (other) => makePredicateRef({
|
|
31
|
+
kind: "or",
|
|
32
|
+
left: ast,
|
|
33
|
+
right: predicateAst(other),
|
|
34
|
+
span: PLACEHOLDER_SPAN
|
|
35
|
+
}),
|
|
36
|
+
not: () => makePredicateRef({
|
|
37
|
+
kind: "not",
|
|
38
|
+
operand: ast,
|
|
39
|
+
span: PLACEHOLDER_SPAN
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
const predicateAst = (ref) => ref[PREDICATE_REF].ast;
|
|
43
|
+
/** The predicate source string (`test="..."`) for a PredicateRef. */
|
|
44
|
+
const printPredicateRef = (ref) => printPredicate(predicateAst(ref));
|
|
45
|
+
/**
|
|
46
|
+
* Shared across every ref produced from one template invocation. Records which
|
|
47
|
+
* variables were touched and the strongest kind evidence seen for each (a
|
|
48
|
+
* numeric comparison implies `number`; a string comparison implies `string`;
|
|
49
|
+
* bare interpolation/exists leaves it `unknown`).
|
|
50
|
+
*/
|
|
51
|
+
var VariableRecorder = class {
|
|
52
|
+
kinds = /* @__PURE__ */ new Map();
|
|
53
|
+
observe(name, kind) {
|
|
54
|
+
const existing = this.kinds.get(name);
|
|
55
|
+
if (existing === void 0 || existing === "unknown") this.kinds.set(name, kind);
|
|
56
|
+
}
|
|
57
|
+
collected() {
|
|
58
|
+
return [...this.kinds].map(([name, kind]) => ({
|
|
59
|
+
name,
|
|
60
|
+
kind
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const makeComparison = (brand, op, value) => {
|
|
65
|
+
brand.recorder.observe(brand.path, typeof value === "number" ? "number" : "string");
|
|
66
|
+
return makePredicateRef({
|
|
67
|
+
kind: "comparison",
|
|
68
|
+
name: brand.path,
|
|
69
|
+
op,
|
|
70
|
+
literal: literalFor(value),
|
|
71
|
+
span: PLACEHOLDER_SPAN
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const makeVariableRef = (path, recorder) => {
|
|
75
|
+
const brand = {
|
|
76
|
+
path,
|
|
77
|
+
recorder
|
|
78
|
+
};
|
|
79
|
+
const builders = {
|
|
80
|
+
eq: (value) => makeComparison(brand, "==", value),
|
|
81
|
+
ne: (value) => makeComparison(brand, "!=", value),
|
|
82
|
+
gt: (value) => makeComparison(brand, ">", value),
|
|
83
|
+
gte: (value) => makeComparison(brand, ">=", value),
|
|
84
|
+
lt: (value) => makeComparison(brand, "<", value),
|
|
85
|
+
lte: (value) => makeComparison(brand, "<=", value),
|
|
86
|
+
exists: () => {
|
|
87
|
+
recorder.observe(path, "unknown");
|
|
88
|
+
return makePredicateRef({
|
|
89
|
+
kind: "name",
|
|
90
|
+
name: path,
|
|
91
|
+
span: PLACEHOLDER_SPAN
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
notExists: () => {
|
|
95
|
+
recorder.observe(path, "unknown");
|
|
96
|
+
return makePredicateRef({
|
|
97
|
+
kind: "not",
|
|
98
|
+
operand: {
|
|
99
|
+
kind: "name",
|
|
100
|
+
name: path,
|
|
101
|
+
span: PLACEHOLDER_SPAN
|
|
102
|
+
},
|
|
103
|
+
span: PLACEHOLDER_SPAN
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const target = {
|
|
108
|
+
[VARIABLE_REF]: brand,
|
|
109
|
+
...builders
|
|
110
|
+
};
|
|
111
|
+
return new Proxy(target, { get(base, key, receiver) {
|
|
112
|
+
if (key === Symbol.toPrimitive || key === "valueOf" || key === "toString") return () => {
|
|
113
|
+
throw new TypeError(COERCION_MESSAGE);
|
|
114
|
+
};
|
|
115
|
+
if (key === VARIABLE_REF || key in base) return Reflect.get(base, key, receiver);
|
|
116
|
+
if (key === Symbol.toStringTag) return "VariableRef";
|
|
117
|
+
if (typeof key === "string") return makeVariableRef(`${path}.${key}`, recorder);
|
|
118
|
+
return Reflect.get(base, key, receiver);
|
|
119
|
+
} });
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Builds the recording proxy for a template invocation. Every top-level
|
|
123
|
+
* property access records the variable (kind `unknown` until a typed comparison
|
|
124
|
+
* refines it) and returns a VariableRef. Returns the proxy plus the shared
|
|
125
|
+
* recorder so renderTemplate can read the collected schema.
|
|
126
|
+
*/
|
|
127
|
+
const createVariablesProxy = () => {
|
|
128
|
+
const recorder = new VariableRecorder();
|
|
129
|
+
return {
|
|
130
|
+
proxy: new Proxy({}, { get(_base, key) {
|
|
131
|
+
if (typeof key !== "string") return void 0;
|
|
132
|
+
recorder.observe(key, "unknown");
|
|
133
|
+
return makeVariableRef(key, recorder);
|
|
134
|
+
} }),
|
|
135
|
+
recorder
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
export { PREDICATE_REF, VARIABLE_REF, VariableRecorder, createVariablesProxy, isPredicateRef, isVariableRef, printPredicateRef };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=variables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variables.js","names":[],"sources":["../src/variables.ts"],"sourcesContent":["import { Predicate } from \"effect\";\n\nimport type { ComparisonOp, PredicateAst, PredicateLiteral } from \"./predicate\";\nimport { printPredicate } from \"./predicate\";\n\n// Recording proxy + typed predicate builders for the TSX authoring layer\n// (design brief §7b/§7c). A template function is invoked with `Variables<T>`;\n// every property access returns a VariableRef that:\n//\n// • prints as a `{{name}}` interpolation when used as a child (see\n// jsx-runtime), and\n// • carries typed predicate builders (eq/ne for all fields, gt/gte/lt/lte for\n// number fields, exists/notExists) that build PredicateAst wrapped in a\n// PredicateRef for `<If test={...}>`.\n//\n// Coercion is a build-time error: variables are SEND-TIME holes, so comparing/\n// concatenating/doing arithmetic on a VariableRef with plain operators cannot\n// mean anything — branch on build-time data (plain JS) or use the predicate\n// builders instead. `Symbol.toPrimitive`/`valueOf`/`toString` therefore throw.\n//\n// The declared `T` doubles as the template's variable schema: renderTemplate\n// reads which keys were accessed and their declared kinds.\n\nconst PLACEHOLDER_SPAN = { start: 0, end: 0 } as const;\n\n/** Brand carried by every VariableRef, keyed off a private symbol. */\nexport const VARIABLE_REF = Symbol.for(\"@samva/markup/VariableRef\");\n\n/** Brand carried by every PredicateRef. */\nexport const PREDICATE_REF = Symbol.for(\"@samva/markup/PredicateRef\");\n\nexport interface VariableRefBrand {\n readonly path: string;\n /** The recorder shared across every ref/proxy from one template invocation. */\n readonly recorder: VariableRecorder;\n}\n\nconst COERCION_MESSAGE = \"variables are send-time values; branch on build-time data instead\";\n\n// ── Predicate builders ──────────────────────────────────────────────────────────\n\n/** Builders present on every VariableRef regardless of field type. */\ninterface CommonPredicateBuilders {\n eq(value: string | number): PredicateRef;\n ne(value: string | number): PredicateRef;\n exists(): PredicateRef;\n notExists(): PredicateRef;\n}\n\n/** Ordering builders — only surfaced (by the `Variables<T>` mapping) on number fields. */\ninterface NumericPredicateBuilders {\n gt(value: number): PredicateRef;\n gte(value: number): PredicateRef;\n lt(value: number): PredicateRef;\n lte(value: number): PredicateRef;\n}\n\n/** A recorded reference to a send-time variable. See module docs. */\nexport type VariableRef<T> = {\n readonly [VARIABLE_REF]: VariableRefBrand;\n} & CommonPredicateBuilders &\n (T extends number ? NumericPredicateBuilders : Record<never, never>);\n\n/** A recorded predicate expression for `<If test={...}>`; chains with and/or/not. */\nexport interface PredicateRef {\n readonly [PREDICATE_REF]: { readonly ast: PredicateAst };\n and(other: PredicateRef): PredicateRef;\n or(other: PredicateRef): PredicateRef;\n not(): PredicateRef;\n}\n\nexport const isVariableRef = (value: unknown): value is VariableRef<unknown> =>\n Predicate.hasProperty(value, VARIABLE_REF);\n\nexport const isPredicateRef = (value: unknown): value is PredicateRef =>\n Predicate.hasProperty(value, PREDICATE_REF);\n\nconst literalFor = (value: string | number): PredicateLiteral =>\n typeof value === \"number\" ? { kind: \"number\", value } : { kind: \"string\", value };\n\nconst makePredicateRef = (ast: PredicateAst): PredicateRef => ({\n [PREDICATE_REF]: { ast },\n and: (other) =>\n makePredicateRef({\n kind: \"and\",\n left: ast,\n right: predicateAst(other),\n span: PLACEHOLDER_SPAN,\n }),\n or: (other) =>\n makePredicateRef({ kind: \"or\", left: ast, right: predicateAst(other), span: PLACEHOLDER_SPAN }),\n not: () => makePredicateRef({ kind: \"not\", operand: ast, span: PLACEHOLDER_SPAN }),\n});\n\nconst predicateAst = (ref: PredicateRef): PredicateAst => ref[PREDICATE_REF].ast;\n\n/** The predicate source string (`test=\"...\"`) for a PredicateRef. */\nexport const printPredicateRef = (ref: PredicateRef): string => printPredicate(predicateAst(ref));\n\n// ── The recorder + proxy factory ────────────────────────────────────────────────\n\nexport type VariableKind = \"string\" | \"number\" | \"unknown\";\n\nexport interface CollectedVariable {\n readonly name: string;\n readonly kind: VariableKind;\n}\n\n/**\n * Shared across every ref produced from one template invocation. Records which\n * variables were touched and the strongest kind evidence seen for each (a\n * numeric comparison implies `number`; a string comparison implies `string`;\n * bare interpolation/exists leaves it `unknown`).\n */\nexport class VariableRecorder {\n private readonly kinds = new Map<string, VariableKind>();\n\n observe(name: string, kind: VariableKind): void {\n const existing = this.kinds.get(name);\n if (existing === undefined || existing === \"unknown\") {\n this.kinds.set(name, kind);\n }\n }\n\n collected(): ReadonlyArray<CollectedVariable> {\n return [...this.kinds].map(([name, kind]) => ({ name, kind }));\n }\n}\n\nconst makeComparison = (\n brand: VariableRefBrand,\n op: ComparisonOp,\n value: string | number,\n): PredicateRef => {\n brand.recorder.observe(brand.path, typeof value === \"number\" ? \"number\" : \"string\");\n return makePredicateRef({\n kind: \"comparison\",\n name: brand.path,\n op,\n literal: literalFor(value),\n span: PLACEHOLDER_SPAN,\n });\n};\n\nconst makeVariableRef = (path: string, recorder: VariableRecorder): VariableRef<unknown> => {\n const brand: VariableRefBrand = { path, recorder };\n const builders: CommonPredicateBuilders & NumericPredicateBuilders = {\n eq: (value) => makeComparison(brand, \"==\", value),\n ne: (value) => makeComparison(brand, \"!=\", value),\n gt: (value) => makeComparison(brand, \">\", value),\n gte: (value) => makeComparison(brand, \">=\", value),\n lt: (value) => makeComparison(brand, \"<\", value),\n lte: (value) => makeComparison(brand, \"<=\", value),\n exists: () => {\n recorder.observe(path, \"unknown\");\n return makePredicateRef({ kind: \"name\", name: path, span: PLACEHOLDER_SPAN });\n },\n notExists: () => {\n recorder.observe(path, \"unknown\");\n return makePredicateRef({\n kind: \"not\",\n operand: { kind: \"name\", name: path, span: PLACEHOLDER_SPAN },\n span: PLACEHOLDER_SPAN,\n });\n },\n };\n\n const target = { [VARIABLE_REF]: brand, ...builders };\n return new Proxy(target, {\n get(base, key, receiver) {\n // Coercion attempts (comparison/concat/arithmetic/template literals) route\n // through these — checked BEFORE own/inherited props so we shadow\n // Object.prototype's valueOf/toString. Variables have no build-time value,\n // so reject loudly with the exact guard message.\n if (key === Symbol.toPrimitive || key === \"valueOf\" || key === \"toString\") {\n return () => {\n // oxlint-disable-next-line samva/no-try-catch-or-throw, samva/no-error-constructor -- User-build authoring boundary: coercing a send-time variable at build time is a programmer error that must fail the user's TSX build loudly, not become a typed Effect failure.\n throw new TypeError(COERCION_MESSAGE);\n };\n }\n if (key === VARIABLE_REF || key in base) return Reflect.get(base, key, receiver);\n if (key === Symbol.toStringTag) return \"VariableRef\";\n // A nested access (`v.user.name`) extends the path — interpolation names\n // may be dotted; predicate builders on a dotted path fail print → parse.\n if (typeof key === \"string\") {\n return makeVariableRef(`${path}.${key}`, recorder);\n }\n return Reflect.get(base, key, receiver);\n },\n }) as unknown as VariableRef<unknown>;\n};\n\n/**\n * Maps a declared variable type to the recording-proxy shape a template\n * receives: `export default function Welcome(v: Variables<{ firstName: string }>)`.\n * Number fields additionally expose ordering builders.\n */\nexport type Variables<T extends Record<string, unknown>> = {\n readonly [K in keyof T]: VariableRef<T[K]>;\n};\n\n/**\n * Builds the recording proxy for a template invocation. Every top-level\n * property access records the variable (kind `unknown` until a typed comparison\n * refines it) and returns a VariableRef. Returns the proxy plus the shared\n * recorder so renderTemplate can read the collected schema.\n */\nexport const createVariablesProxy = <T extends Record<string, unknown>>(): {\n readonly proxy: Variables<T>;\n readonly recorder: VariableRecorder;\n} => {\n const recorder = new VariableRecorder();\n const proxy = new Proxy(\n {},\n {\n get(_base, key) {\n if (typeof key !== \"string\") return undefined;\n recorder.observe(key, \"unknown\");\n return makeVariableRef(key, recorder);\n },\n },\n ) as Variables<T>;\n return { proxy, recorder };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { stringAttribute } from "./ast.js";
|
|
2
|
+
//#region src/channel-text.ts
|
|
3
|
+
const interpolationText = (node, width) => {
|
|
4
|
+
if (width === "literal") return node.syntax === "curly" ? `{{${node.name}}}` : `\${${node.name}}`;
|
|
5
|
+
return "x".repeat(width.worstCaseChars);
|
|
6
|
+
};
|
|
7
|
+
const ifBranches = (node) => {
|
|
8
|
+
const elseNode = node.children.find((child) => child.kind === "element" && child.tag === "Else");
|
|
9
|
+
return {
|
|
10
|
+
then: node.children.filter((child) => !(child.kind === "element" && child.tag === "Else")),
|
|
11
|
+
otherwise: elseNode?.children ?? []
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** Verbatim flow → compiled text with `sml:` conditional markers embedded. */
|
|
15
|
+
const flattenChannelText = (nodes) => {
|
|
16
|
+
let result = "";
|
|
17
|
+
for (const node of nodes) {
|
|
18
|
+
if (node.kind === "text") {
|
|
19
|
+
result += node.value;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (node.kind === "interpolation") {
|
|
23
|
+
result += interpolationText(node, "literal");
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const test = stringAttribute(node, "test") ?? "";
|
|
27
|
+
const { then, otherwise } = ifBranches(node);
|
|
28
|
+
result += `<!--sml:if:${node.id} ${test}-->${flattenChannelText(then)}`;
|
|
29
|
+
if (otherwise.length > 0) result += `<!--sml:else:${node.id}-->${flattenChannelText(otherwise)}`;
|
|
30
|
+
result += `<!--sml:endif:${node.id}-->`;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
/** Safety valve: beyond this many branch combinations, stop forking. */
|
|
35
|
+
const MAX_VARIANTS = 256;
|
|
36
|
+
/**
|
|
37
|
+
* Every possible send-time rendering of a verbatim flow (each `<If>` resolved
|
|
38
|
+
* both ways), marker-free. Selected branches are trimmed exactly like
|
|
39
|
+
* resolveArtifactConditionals trims them, so measured lengths match what a
|
|
40
|
+
* recipient can actually receive. Capped at MAX_VARIANTS combinations —
|
|
41
|
+
* beyond the cap later `<If>`s stop forking (their true branch is kept),
|
|
42
|
+
* which keeps worst-case analysis approximate instead of exponential.
|
|
43
|
+
*/
|
|
44
|
+
const channelTextVariants = (nodes, width) => {
|
|
45
|
+
let variants = [""];
|
|
46
|
+
const append = (suffixes) => {
|
|
47
|
+
if (suffixes.length === 1) {
|
|
48
|
+
variants = variants.map((variant) => variant + suffixes[0]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const next = [];
|
|
52
|
+
for (const variant of variants) {
|
|
53
|
+
for (const suffix of suffixes) {
|
|
54
|
+
next.push(variant + suffix);
|
|
55
|
+
if (next.length >= MAX_VARIANTS) break;
|
|
56
|
+
}
|
|
57
|
+
if (next.length >= MAX_VARIANTS) break;
|
|
58
|
+
}
|
|
59
|
+
variants = next;
|
|
60
|
+
};
|
|
61
|
+
for (const node of nodes) {
|
|
62
|
+
if (node.kind === "text") {
|
|
63
|
+
append([node.value]);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (node.kind === "interpolation") {
|
|
67
|
+
append([interpolationText(node, width)]);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const { then, otherwise } = ifBranches(node);
|
|
71
|
+
const thenVariants = channelTextVariants(then, width).map((variant) => variant.trim());
|
|
72
|
+
const elseVariants = channelTextVariants(otherwise, width).map((variant) => variant.trim());
|
|
73
|
+
const branchVariants = [.../* @__PURE__ */ new Set([...thenVariants, ...elseVariants])];
|
|
74
|
+
append(variants.length * branchVariants.length > MAX_VARIANTS ? [thenVariants[0] ?? ""] : branchVariants);
|
|
75
|
+
}
|
|
76
|
+
return [...new Set(variants)];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The primary send-time rendering: every `<If>` takes its true branch (the
|
|
80
|
+
* branch that carries the main copy by convention). Used for the single
|
|
81
|
+
* `estimatedSegments` number and the opt-out check.
|
|
82
|
+
*/
|
|
83
|
+
const primaryChannelText = (nodes, width) => {
|
|
84
|
+
let result = "";
|
|
85
|
+
for (const node of nodes) if (node.kind === "text") result += node.value;
|
|
86
|
+
else if (node.kind === "interpolation") result += interpolationText(node, width);
|
|
87
|
+
else result += primaryChannelText(ifBranches(node).then, width).trim();
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/version.ts
|
|
92
|
+
const COMPILER_VERSION = "0.1.0";
|
|
93
|
+
//#endregion
|
|
94
|
+
export { primaryChannelText as i, channelTextVariants as n, flattenChannelText as r, COMPILER_VERSION as t };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=version-BITr58kk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-BITr58kk.js","names":[],"sources":["../src/channel-text.ts","../src/version.ts"],"sourcesContent":["import type { ElementNode, SmlNode } from \"./ast\";\nimport { stringAttribute } from \"./ast\";\n\n// Shared text-slot lowering for the structured channels (sms body, whatsapp\n// component text). Two views of the same verbatim flow:\n//\n// - `flattenChannelText` is the COMPILED form: text verbatim, interpolations\n// canonical, <If>/<Else> lowered to the same `<!--sml:if:id test-->` comment\n// markers the email compiler emits — so resolveArtifactConditionals selects\n// branches identically on every channel's compiled content at send time.\n// - `channelTextVariants` enumerates every branch combination MARKER-FREE, for\n// analysis that needs concrete rendered lengths (sms segment math, whatsapp\n// Meta limits). It mirrors the send-time semantics exactly, including the\n// `.trim()` resolveArtifactConditionals applies to a selected branch.\n\n/** How an interpolation counts when measuring a variant's rendered text. */\nexport type InterpolationWidth = \"literal\" | { readonly worstCaseChars: number };\n\nconst interpolationText = (\n node: Extract<SmlNode, { kind: \"interpolation\" }>,\n width: InterpolationWidth,\n): string => {\n if (width === \"literal\") {\n return node.syntax === \"curly\" ? `{{${node.name}}}` : `\\${${node.name}}`;\n }\n // A GSM-7-safe filler: variables are send-time holes, so they count at the\n // documented worst-case width and never flip the encoding to UCS-2.\n return \"x\".repeat(width.worstCaseChars);\n};\n\nconst ifBranches = (\n node: ElementNode,\n): { readonly then: ReadonlyArray<SmlNode>; readonly otherwise: ReadonlyArray<SmlNode> } => {\n const elseNode = node.children.find(\n (child): child is ElementNode => child.kind === \"element\" && child.tag === \"Else\",\n );\n return {\n then: node.children.filter((child) => !(child.kind === \"element\" && child.tag === \"Else\")),\n otherwise: elseNode?.children ?? [],\n };\n};\n\n/** Verbatim flow → compiled text with `sml:` conditional markers embedded. */\nexport const flattenChannelText = (nodes: ReadonlyArray<SmlNode>): string => {\n let result = \"\";\n for (const node of nodes) {\n if (node.kind === \"text\") {\n result += node.value;\n continue;\n }\n if (node.kind === \"interpolation\") {\n result += interpolationText(node, \"literal\");\n continue;\n }\n // Grammar guarantees the only elements in a verbatim flow are <If> (with\n // an optional trailing <Else> child).\n const test = stringAttribute(node, \"test\") ?? \"\";\n const { then, otherwise } = ifBranches(node);\n result += `<!--sml:if:${node.id} ${test}-->${flattenChannelText(then)}`;\n if (otherwise.length > 0)\n result += `<!--sml:else:${node.id}-->${flattenChannelText(otherwise)}`;\n result += `<!--sml:endif:${node.id}-->`;\n }\n return result;\n};\n\n/** Safety valve: beyond this many branch combinations, stop forking. */\nconst MAX_VARIANTS = 256;\n\n/**\n * Every possible send-time rendering of a verbatim flow (each `<If>` resolved\n * both ways), marker-free. Selected branches are trimmed exactly like\n * resolveArtifactConditionals trims them, so measured lengths match what a\n * recipient can actually receive. Capped at MAX_VARIANTS combinations —\n * beyond the cap later `<If>`s stop forking (their true branch is kept),\n * which keeps worst-case analysis approximate instead of exponential.\n */\nexport const channelTextVariants = (\n nodes: ReadonlyArray<SmlNode>,\n width: InterpolationWidth,\n): ReadonlyArray<string> => {\n let variants: Array<string> = [\"\"];\n const append = (suffixes: ReadonlyArray<string>): void => {\n if (suffixes.length === 1) {\n variants = variants.map((variant) => variant + suffixes[0]);\n return;\n }\n const next: Array<string> = [];\n for (const variant of variants) {\n for (const suffix of suffixes) {\n next.push(variant + suffix);\n if (next.length >= MAX_VARIANTS) break;\n }\n if (next.length >= MAX_VARIANTS) break;\n }\n variants = next;\n };\n\n for (const node of nodes) {\n if (node.kind === \"text\") {\n append([node.value]);\n continue;\n }\n if (node.kind === \"interpolation\") {\n append([interpolationText(node, width)]);\n continue;\n }\n const { then, otherwise } = ifBranches(node);\n const thenVariants = channelTextVariants(then, width).map((variant) => variant.trim());\n const elseVariants = channelTextVariants(otherwise, width).map((variant) => variant.trim());\n const branchVariants = [...new Set([...thenVariants, ...elseVariants])];\n append(\n variants.length * branchVariants.length > MAX_VARIANTS\n ? [thenVariants[0] ?? \"\"]\n : branchVariants,\n );\n }\n return [...new Set(variants)];\n};\n\n/**\n * The primary send-time rendering: every `<If>` takes its true branch (the\n * branch that carries the main copy by convention). Used for the single\n * `estimatedSegments` number and the opt-out check.\n */\nexport const primaryChannelText = (\n nodes: ReadonlyArray<SmlNode>,\n width: InterpolationWidth,\n): string => {\n let result = \"\";\n for (const node of nodes) {\n if (node.kind === \"text\") result += node.value;\n else if (node.kind === \"interpolation\") result += interpolationText(node, width);\n else result += primaryChannelText(ifBranches(node).then, width).trim();\n }\n return result;\n};\n","// Version stamp shared by every compile backend. Bump on any change that\n// alters compiled bytes for identical input — content addressing depends on it.\nexport const COMPILER_VERSION = \"0.1.0\";\n"],"mappings":";;AAkBA,MAAM,qBACJ,MACA,UACW;CACX,IAAI,UAAU,WACZ,OAAO,KAAK,WAAW,UAAU,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK;CAIxE,OAAO,IAAI,OAAO,MAAM,cAAc;AACxC;AAEA,MAAM,cACJ,SAC0F;CAC1F,MAAM,WAAW,KAAK,SAAS,MAC5B,UAAgC,MAAM,SAAS,aAAa,MAAM,QAAQ,MAC7E;CACA,OAAO;EACL,MAAM,KAAK,SAAS,QAAQ,UAAU,EAAE,MAAM,SAAS,aAAa,MAAM,QAAQ,OAAO;EACzF,WAAW,UAAU,YAAY,CAAC;CACpC;AACF;;AAGA,MAAa,sBAAsB,UAA0C;CAC3E,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,QAAQ;GACxB,UAAU,KAAK;GACf;EACF;EACA,IAAI,KAAK,SAAS,iBAAiB;GACjC,UAAU,kBAAkB,MAAM,SAAS;GAC3C;EACF;EAGA,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;EAC9C,MAAM,EAAE,MAAM,cAAc,WAAW,IAAI;EAC3C,UAAU,cAAc,KAAK,GAAG,GAAG,KAAK,KAAK,mBAAmB,IAAI;EACpE,IAAI,UAAU,SAAS,GACrB,UAAU,gBAAgB,KAAK,GAAG,KAAK,mBAAmB,SAAS;EACrE,UAAU,iBAAiB,KAAK,GAAG;CACrC;CACA,OAAO;AACT;;AAGA,MAAM,eAAe;;;;;;;;;AAUrB,MAAa,uBACX,OACA,UAC0B;CAC1B,IAAI,WAA0B,CAAC,EAAE;CACjC,MAAM,UAAU,aAA0C;EACxD,IAAI,SAAS,WAAW,GAAG;GACzB,WAAW,SAAS,KAAK,YAAY,UAAU,SAAS,EAAE;GAC1D;EACF;EACA,MAAM,OAAsB,CAAC;EAC7B,KAAK,MAAM,WAAW,UAAU;GAC9B,KAAK,MAAM,UAAU,UAAU;IAC7B,KAAK,KAAK,UAAU,MAAM;IAC1B,IAAI,KAAK,UAAU,cAAc;GACnC;GACA,IAAI,KAAK,UAAU,cAAc;EACnC;EACA,WAAW;CACb;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,QAAQ;GACxB,OAAO,CAAC,KAAK,KAAK,CAAC;GACnB;EACF;EACA,IAAI,KAAK,SAAS,iBAAiB;GACjC,OAAO,CAAC,kBAAkB,MAAM,KAAK,CAAC,CAAC;GACvC;EACF;EACA,MAAM,EAAE,MAAM,cAAc,WAAW,IAAI;EAC3C,MAAM,eAAe,oBAAoB,MAAM,KAAK,CAAC,CAAC,KAAK,YAAY,QAAQ,KAAK,CAAC;EACrF,MAAM,eAAe,oBAAoB,WAAW,KAAK,CAAC,CAAC,KAAK,YAAY,QAAQ,KAAK,CAAC;EAC1F,MAAM,iBAAiB,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,cAAc,GAAG,YAAY,CAAC,CAAC;EACtE,OACE,SAAS,SAAS,eAAe,SAAS,eACtC,CAAC,aAAa,MAAM,EAAE,IACtB,cACN;CACF;CACA,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AAC9B;;;;;;AAOA,MAAa,sBACX,OACA,UACW;CACX,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,QAAQ,UAAU,KAAK;MACpC,IAAI,KAAK,SAAS,iBAAiB,UAAU,kBAAkB,MAAM,KAAK;MAC1E,UAAU,mBAAmB,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK;CAEvE,OAAO;AACT;;;ACtIA,MAAa,mBAAmB"}
|