@lucidkiln/core 1.0.0 → 1.2.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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { $ as silence, $t as ownerOf, A as addSlotFlags, At as atFront, B as fail, Bt as checkLate, C as scanHtml, Ct as truthy, D as getAttr, Dt as BLOCKED_NAMES, E as toTime, Et as valueLabel, F as consumeAt, Ft as passesFrom, G as indentedText, Gt as Run, H as holdData, Ht as recordAddition, I as embed, It as runAhead, J as locate, Jt as endCapture, K as isPromise, Kt as blockScope, L as embedKeyed, Lt as runSequence, M as all, Mt as isRoot, N as checkLength, Nt as mayRunAhead, O as getMethod, Ot as expose, P as consume, Pt as passesAhead, Q as renderTemplate, Qt as lookup$1, R as embedScope, Rt as OUTLET_INDENT, S as readProperty, St as toStringArgument, T as toPlainData, Tt as unwrap, U as importsFor, Ut as takeOutput, V as hold, Vt as closeOutlets, W as indentText, Wt as RenderState, X as markCaller, Xt as startCapture, Y as locating, Yt as fillScope, Z as passData, Zt as childScope, _ as integerArgument, _t as matchesType, a as ESCAPE_STRATEGIES, an as isSafe, at as FORMATTERS, b as mapContents, bt as toNumber, c as contains, cn as escapeUrl, ct as describeType, d as Kept, dn as resolveTemplateName, dt as hashKeys, en as sectionCopy, et as startPlace, f as checkDepth, fn as KilnIssue, ft as isEmpty, g as insertedPiece, gn as issue, gt as mapKeys, h as functionArgument, hn as createDiagnostic, ht as literalOptions, i as DEFAULT_SLOT, in as Markup, it as write, j as after, jt as contentSets, k as readableNames, kt as lookup, l as operatorFunction, ln as escapeWith, lt as describeValue, m as fromPieces, mn as KilnSyntaxError, mt as isPlainObject, n as parse, nn as variableNames, nt as unhold, o as builtinFunctions, on as safe, ot as MISSING, p as entriesOf, pn as KilnRenderError, pt as isHashLike, q as loadTemplate, qt as emptyOut, r as scanParts, rn as suggest, rt as withCaller, s as builtinTests, sn as escapeHtml, st as compare, t as Source, tn as sectionScope, tt as step, u as rangeOperator, un as TEMPLATE_EXTENSION, ut as equals, v as isHtmlStrategy, vt as setOwn, w as toPieces, wt as typeName, x as markupText, xt as toStr, y as listOf$1, yt as templateFunctions, z as endPlace, zt as additionsIn } from "./source-BQxJGo5Y.js";
1
+ import { $ as compare, $t as escapeUrl, A as consume, At as additionsIn, B as loadTemplate, Bt as fillScope, C as getAttr, Ct as contentSets, D as after, Dt as passesFrom, E as addSlotFlags, Et as passesAhead, F as fail, Ft as RenderState, G as renderTemplate, Gt as sectionCopy, H as locating, Ht as childScope, I as hold, It as Run, J as unhold, Jt as suggest, K as silence, Kt as sectionScope, L as holdData, Lt as blockScope, M as embed, Mt as closeOutlets, N as embedKeyed, Nt as recordAddition, O as all, Ot as runAhead, P as embedScope, Pt as takeOutput, Q as MISSING, Qt as escapeHtml, R as importsFor, Rt as emptyOut, S as toTime, St as atFront, T as readableNames, Tt as mayRunAhead, U as markCaller, Ut as lookup$1, V as locate, Vt as startCapture, W as passData, Wt as ownerOf, X as write, Xt as isSafe, Y as withCaller, Yt as Markup, Z as FORMATTERS, Zt as safe, _ as markupText, _t as unwrap, a as operatorFunction, an as KilnSyntaxError, at as isHashLike, b as toPieces, bt as expose, c as checkDepth, ct as mapKeys, d as functionArgument, dt as templateFunctions, en as escapeWith, et as describeType, f as insertedPiece, ft as toNumber, g as mapContents, gt as typeName, h as listOf$1, ht as truthy, i as contains, in as KilnRenderError, it as isEmpty, j as consumeAt, jt as checkLate, k as checkLength, kt as runSequence, l as entriesOf, lt as matchesType, m as isHtmlStrategy, mt as toStringArgument, n as builtinFunctions, nn as resolveTemplateName, nt as equals, o as rangeOperator, on as createDiagnostic, ot as isPlainObject, p as integerArgument, pt as toStr, q as step, qt as variableNames, r as builtinTests, rn as KilnIssue, rt as hashKeys, s as Kept, sn as issue, st as literalOptions, t as Source, tn as TEMPLATE_EXTENSION, tt as describeValue, u as fromPieces, ut as setOwn, v as readProperty, vt as valueLabel, w as getMethod, wt as isRoot, x as toPlainData, xt as lookup, y as scanHtml, yt as BLOCKED_NAMES, z as isPromise, zt as endCapture } from "./source-BbikGqIb.js";
2
+ import { a as ESCAPE_STRATEGIES, i as DEFAULT_SLOT, r as scanParts, t as parse } from "./parser-isboTef0.js";
2
3
  //#region src/analyzer.ts
3
4
  const MAX_DEPTH$1 = 1e3;
4
5
  /** The statement lists nested inside a statement. */
@@ -1565,9 +1566,9 @@ const checkReferences = async (env, template) => {
1565
1566
  };
1566
1567
  //#endregion
1567
1568
  //#region src/runtime/cache.ts
1568
- const VERSION = 2;
1569
+ const VERSION = 3;
1569
1570
  /** Makes the value a store saves. */
1570
- const packSaved = ({ html, additions, islands, islandScript }) => JSON.stringify({
1571
+ const packSaved = ({ html, additions, islands, islandScript, tags }) => JSON.stringify({
1571
1572
  v: VERSION,
1572
1573
  html,
1573
1574
  add: additions.map((addition) => [
@@ -1576,7 +1577,8 @@ const packSaved = ({ html, additions, islands, islandScript }) => JSON.stringify
1576
1577
  addition.once
1577
1578
  ]),
1578
1579
  isl: islands.map((island) => [island.template, island.props]),
1579
- scr: islandScript
1580
+ scr: islandScript,
1581
+ tag: tags
1580
1582
  });
1581
1583
  /** Reads a value a store returned, or returns null when it isn't one `packSaved` made. */
1582
1584
  const unpackSaved = (value) => {
@@ -1587,8 +1589,8 @@ const unpackSaved = (value) => {
1587
1589
  return null;
1588
1590
  }
1589
1591
  if (typeof parsed !== "object" || parsed === null) return null;
1590
- const { v, html, add, isl, scr } = parsed;
1591
- if (v !== VERSION || typeof html !== "string" || !Array.isArray(add) || !Array.isArray(isl) || typeof scr !== "boolean") return null;
1592
+ const { v, html, add, isl, scr, tag } = parsed;
1593
+ if (v !== VERSION || typeof html !== "string" || !Array.isArray(add) || !Array.isArray(isl) || typeof scr !== "boolean" || !Array.isArray(tag) || tag.some((item) => typeof item !== "string")) return null;
1592
1594
  const additions = [];
1593
1595
  for (const item of add) {
1594
1596
  if (!Array.isArray(item)) return null;
@@ -1614,7 +1616,8 @@ const unpackSaved = (value) => {
1614
1616
  html,
1615
1617
  additions,
1616
1618
  islands,
1617
- islandScript: scr
1619
+ islandScript: scr,
1620
+ tags: tag
1618
1621
  };
1619
1622
  };
1620
1623
  //#endregion
@@ -2094,18 +2097,14 @@ const printParts = (list) => {
2094
2097
  const parts = [];
2095
2098
  for (const part of list) {
2096
2099
  const last = parts.at(-1);
2097
- if (part.text !== null && last && last.text !== null) {
2098
- const offset = last.text.length;
2099
- const lines = part.lines ? [...last.lines ?? [], ...part.lines.map((start) => start + offset)] : last.lines;
2100
- parts[parts.length - 1] = {
2101
- text: last.text + part.text,
2102
- lines,
2103
- span: {
2104
- start: last.span.start,
2105
- end: part.span.end
2106
- }
2107
- };
2108
- } else parts.push(part);
2100
+ if (part.text !== null && last && last.text !== null) parts[parts.length - 1] = {
2101
+ text: last.text + part.text,
2102
+ span: {
2103
+ start: last.span.start,
2104
+ end: part.span.end
2105
+ }
2106
+ };
2107
+ else parts.push(part);
2109
2108
  }
2110
2109
  const first = parts[0];
2111
2110
  const span = {
@@ -2114,18 +2113,12 @@ const printParts = (list) => {
2114
2113
  };
2115
2114
  if (parts.length === 1 && first.text !== null) {
2116
2115
  const text = first.text;
2117
- if (!first.lines) return (run, _scope, out) => {
2118
- write(run, out, text, span);
2119
- };
2120
- const entry = indentedText(text, first.lines);
2121
2116
  return (run, _scope, out) => {
2122
- const indent = out.indent;
2123
- write(run, out, indent === "" ? text : indentText(entry, indent, out.inline), span);
2117
+ write(run, out, text, span);
2124
2118
  };
2125
2119
  }
2126
2120
  const count = parts.length;
2127
2121
  const texts = [];
2128
- const indented = [];
2129
2122
  const reads = [];
2130
2123
  const evs = [];
2131
2124
  const emits = [];
@@ -2143,7 +2136,6 @@ const printParts = (list) => {
2143
2136
  const root = compiled?.variable ?? path?.root.variable ?? null;
2144
2137
  const choice = compiled?.choice ? emitChoice(compiled.choice, emit) : null;
2145
2138
  texts.push(part.text);
2146
- indented.push(part.text !== null && part.lines ? indentedText(part.text, part.lines) : null);
2147
2139
  reads.push(root !== null ? "path" : choice ? "choice" : "other");
2148
2140
  evs.push(compiled?.ev ?? null);
2149
2141
  emits.push(emit);
@@ -2155,13 +2147,11 @@ const printParts = (list) => {
2155
2147
  spans.push(part.span);
2156
2148
  }
2157
2149
  return (run, scope, out) => {
2158
- const indent = out.indent;
2159
2150
  let text = "";
2160
2151
  for (let i = 0; i < count; i++) {
2161
2152
  const part = texts[i];
2162
2153
  if (part !== null) {
2163
- const entry = indented[i];
2164
- text += indent === "" || !entry ? part : entry.indent === indent && !out.inline ? entry.indented : indentText(entry, indent, text === "" && out.inline);
2154
+ text += part;
2165
2155
  continue;
2166
2156
  }
2167
2157
  const at = spans[i];
@@ -2645,7 +2635,6 @@ var Compiler = class {
2645
2635
  if (parts.length === 0) partsStart = i;
2646
2636
  parts.push({
2647
2637
  text: statement.value,
2648
- lines: statement.lines,
2649
2638
  span: statement.span
2650
2639
  });
2651
2640
  continue;
@@ -2777,15 +2766,13 @@ var Compiler = class {
2777
2766
  const env = this.#env;
2778
2767
  /**
2779
2768
  * Runs passes `index` to `end`. When a pass waits, the passes after it run as `passesFrom` runs them: ahead of
2780
- * it when they may. `ahead` is whether they may, found the first time a pass waits, and `indent` is the
2781
- * output's indentation when the loop started.
2769
+ * it when they may. `ahead` is whether they may, found the first time a pass waits.
2782
2770
  */
2783
- const iterate = (run, scope, out, values, keys, index, end, reused, indent, ahead) => {
2771
+ const iterate = (run, scope, out, values, keys, index, end, reused, ahead) => {
2784
2772
  const length = values.length;
2785
2773
  for (let i = index; i < end; i++) {
2786
2774
  step(run, span);
2787
2775
  const inner = reused ?? childScope(scope);
2788
- const inline = out.inline;
2789
2776
  inner[valueName] = values[i] ?? null;
2790
2777
  if (keyName !== null) inner[keyName] = keys ? keys[i] ?? null : i;
2791
2778
  if (usesLoop) inner.loop = {
@@ -2808,14 +2795,14 @@ var Compiler = class {
2808
2795
  };
2809
2796
  }
2810
2797
  const may = ahead ??= passesAhead(run, scope, effects.sets, effects.elsewhere);
2811
- return passesFrom(run, out, result, inline, i + 1, end, indent, (aheadRun, aheadOut, at) => iterate(aheadRun, scope, aheadOut, values, keys, at, at + 1, shared ? childScope(scope) : null, indent, may), span, may);
2798
+ return passesFrom(run, out, result, i + 1, end, (aheadRun, aheadOut, at) => iterate(aheadRun, scope, aheadOut, values, keys, at, at + 1, shared ? childScope(scope) : null, may), span, may);
2812
2799
  }
2813
2800
  };
2814
2801
  const start = (run, scope, out, value) => {
2815
2802
  const { values, keys } = loopItems(run, span, value, keyName !== null);
2816
2803
  if (values.length === 0) return alternate?.(run, scope, out);
2817
2804
  const reused = shared ? childScope(scope) : null;
2818
- return iterate(run, scope, out, values, keys, 0, values.length, reused, out.indent, null);
2805
+ return iterate(run, scope, out, values, keys, 0, values.length, reused, null);
2819
2806
  };
2820
2807
  const ev = iterable.ev;
2821
2808
  if (!iterable.async) return (run, scope, out) => {
@@ -2942,31 +2929,20 @@ var Compiler = class {
2942
2929
  }
2943
2930
  #blockStatement(node) {
2944
2931
  this.#defineBlock(node);
2945
- const { name, span, indent } = node;
2932
+ const { name, span } = node;
2946
2933
  return (run, scope, out) => {
2947
2934
  const entry = run.blocks.get(name)?.[0];
2948
2935
  if (!entry) return;
2949
2936
  const render = () => entry.def.body(entry.run.within(run.capture, run.blockDepth), blockScope(entry.def, scope), out);
2950
- const own = entry.run.template === run.template;
2951
- if (indent === "") return own ? render() : withCaller(run, span, render);
2952
- const saved = out.indent;
2953
- const inline = out.inline;
2954
- startPlace(out, indent);
2955
- return endPlace(out, saved, inline, own ? render() : withCaller(run, span, render));
2937
+ return entry.run.template === run.template ? render() : withCaller(run, span, render);
2956
2938
  };
2957
2939
  }
2958
2940
  #include(node) {
2959
- const { ignoreMissing, span, indent } = node;
2960
- return this.#embedding(node.template, node.with, span, (run, out, name, variables) => {
2961
- if (indent === "") return embed(run, out, span, name, variables, null, ignoreMissing);
2962
- const saved = out.indent;
2963
- const inline = out.inline;
2964
- startPlace(out, indent);
2965
- return endPlace(out, saved, inline, embed(run, out, span, name, variables, null, ignoreMissing));
2966
- });
2941
+ const { ignoreMissing, span } = node;
2942
+ return this.#embedding(node.template, node.with, span, (run, out, name, variables) => embed(run, out, span, name, variables, null, ignoreMissing));
2967
2943
  }
2968
2944
  #component(node) {
2969
- const { span, indent } = node;
2945
+ const { span } = node;
2970
2946
  const fills = node.fills.map((fill) => ({
2971
2947
  name: fill.name,
2972
2948
  body: this.#body(fill.body),
@@ -2988,11 +2964,7 @@ var Compiler = class {
2988
2964
  scope: callerScope,
2989
2965
  sets: fill.sets
2990
2966
  });
2991
- if (indent === "") return embed(run, out, span, name, variables, slots, false);
2992
- const saved = out.indent;
2993
- const inline = out.inline;
2994
- startPlace(out, indent);
2995
- return endPlace(out, saved, inline, embed(run, out, span, name, variables, slots, false));
2967
+ return embed(run, out, span, name, variables, slots, false);
2996
2968
  });
2997
2969
  }
2998
2970
  /**
@@ -3010,11 +2982,10 @@ var Compiler = class {
3010
2982
  opening = ISLAND_SCRIPT + opening;
3011
2983
  if (isRoot(state, out)) state.islandScript = true;
3012
2984
  }
3013
- const lead = (at) => (out.inline ? "" : out.indent) + at;
3014
- write(run, out, indent === void 0 ? opening : `${lead(indent)}${opening}${closing?.indent === null ? "" : "\n"}`, span);
2985
+ write(run, out, indent === void 0 ? opening : `${indent}${opening}${closing?.indent === null ? "" : "\n"}`, span);
3015
2986
  return after(fallback(run, childScope(scope), out), () => {
3016
2987
  if (!closing) return write(run, out, ISLAND_CLOSING, span);
3017
- const start = closing.indent === null ? "" : lead(closing.indent);
2988
+ const start = closing.indent ?? "";
3018
2989
  const end = closing.newline ? "\n" : "";
3019
2990
  return write(run, out, `${start}${ISLAND_CLOSING}${end}`, span);
3020
2991
  });
@@ -3037,11 +3008,7 @@ var Compiler = class {
3037
3008
  const state = run.state;
3038
3009
  const islands = state.islands;
3039
3010
  if (islands !== null && (islands.mode === "defer" || state.stream !== null && isRoot(state, out) && !run.capture?.section)) return after(loadTemplate(run, name, span), (template) => template ? render(run, out, scope, islands, template, props) : void 0);
3040
- if (indent === "") return embed(run, out, span, name, props, null, false);
3041
- const saved = out.indent;
3042
- const inline = out.inline;
3043
- startPlace(out, indent);
3044
- return endPlace(out, saved, inline, embed(run, out, span, name, props, null, false));
3011
+ return embed(run, out, span, name, props, null, false);
3045
3012
  };
3046
3013
  return this.#embedding(node.template, node.with, span, (run, out, name, variables, scope) => run.state.islands === null ? place(run, out, name, variables, scope) : atFront(run, () => place(run, out, name, variables, scope)));
3047
3014
  }
@@ -3068,8 +3035,8 @@ var Compiler = class {
3068
3035
  }
3069
3036
  #slot(node) {
3070
3037
  const fallback = this.#body(node.body);
3071
- const { name, span, indent } = node;
3072
- const render = (run, scope, out) => {
3038
+ const { name, span } = node;
3039
+ return (run, scope, out) => {
3073
3040
  const fill = run.slots?.get(name);
3074
3041
  if (!fill) return fallback(run, childScope(scope), out);
3075
3042
  try {
@@ -3082,13 +3049,6 @@ var Compiler = class {
3082
3049
  throw markCaller(run, span, error);
3083
3050
  }
3084
3051
  };
3085
- if (indent === "") return render;
3086
- return (run, scope, out) => {
3087
- const saved = out.indent;
3088
- const inline = out.inline;
3089
- startPlace(out, indent);
3090
- return endPlace(out, saved, inline, render(run, scope, out));
3091
- };
3092
3052
  }
3093
3053
  #with(node) {
3094
3054
  const value = this.#expr(node.value);
@@ -3131,11 +3091,10 @@ var Compiler = class {
3131
3091
  if (value instanceof Markup) return value.value;
3132
3092
  throw fail(run, span, "type-error", `"cache" keys are made of strings, numbers, booleans, dates and null, but got a ${typeName(value)}`);
3133
3093
  };
3134
- const fullKey = (run, out, value) => {
3094
+ const fullKey = (run, value) => {
3135
3095
  const parts = Array.isArray(value) ? value.map((item) => part(run, item)) : [part(run, value)];
3136
3096
  const { locale, timeZone, islands } = run.state;
3137
- let key = `${JSON.stringify(run.state.env.cacheNamespace)}|${templateName}|${locale}|${timeZone}|${JSON.stringify(parts)}`;
3138
- if (out.indent !== "") key += `|indent|${JSON.stringify([out.indent, out.inline])}`;
3097
+ const key = `${JSON.stringify(run.state.env.cacheNamespace)}|${templateName}|${locale}|${timeZone}|${JSON.stringify(parts)}`;
3139
3098
  return islands?.mode === "defer" ? `${key}|islands|${JSON.stringify([islands.url, islands.script])}` : key;
3140
3099
  };
3141
3100
  const lifetime = (run, value) => {
@@ -3144,7 +3103,7 @@ var Compiler = class {
3144
3103
  return value;
3145
3104
  };
3146
3105
  const save = (run, scope, out, keyValue, secondsValue) => {
3147
- const name = fullKey(run, out, keyValue);
3106
+ const name = fullKey(run, keyValue);
3148
3107
  const time = lifetime(run, secondsValue);
3149
3108
  const state = run.state;
3150
3109
  const store = state.env.cache;
@@ -3152,7 +3111,8 @@ var Compiler = class {
3152
3111
  const inner = sectionScope(scope);
3153
3112
  const capture = startCapture(run, {
3154
3113
  scope: inner,
3155
- deferred: []
3114
+ deferred: [],
3115
+ tags: /* @__PURE__ */ new Set()
3156
3116
  });
3157
3117
  return after(body(run.within(capture), inner, out), () => {
3158
3118
  endCapture(run, capture);
@@ -3164,6 +3124,8 @@ var Compiler = class {
3164
3124
  return after(found, (value) => {
3165
3125
  const saved = typeof value === "string" ? unpackSaved(value) : null;
3166
3126
  if (saved) {
3127
+ const around = run.capture?.section;
3128
+ if (around) for (const tag of saved.tags) around.tags.add(tag);
3167
3129
  const replay = (front) => {
3168
3130
  const islands = state.islands;
3169
3131
  if (islands?.mode === "defer" && saved.islands.length > 0) {
@@ -3185,17 +3147,20 @@ var Compiler = class {
3185
3147
  const start = out.s.length;
3186
3148
  const hadScript = state.islandScript;
3187
3149
  return after(fresh(), (capture) => {
3150
+ const tags = [...capture.section?.tags ?? []].sort();
3188
3151
  const manifest = state.islands?.mode === "defer" ? state.islands.manifest : null;
3189
3152
  const value = packSaved({
3190
3153
  html: out.s.slice(start),
3191
3154
  additions: additionsIn(capture.additions ?? []),
3192
3155
  islands: manifest ? (capture.section?.deferred ?? []).flatMap((id) => manifest[id] ?? []) : [],
3193
- islandScript: !hadScript && state.islandScript
3156
+ islandScript: !hadScript && state.islandScript,
3157
+ tags
3194
3158
  });
3195
3159
  return locating(run, span, () => store.set({
3196
3160
  key: name,
3197
3161
  value,
3198
- seconds: time
3162
+ seconds: time,
3163
+ tags
3199
3164
  }), "The cache store");
3200
3165
  });
3201
3166
  });
@@ -3220,7 +3185,7 @@ var Compiler = class {
3220
3185
  * so the outlet must write to that output, not to a capture or a cached section.
3221
3186
  */
3222
3187
  #outlet(node) {
3223
- const { name, span, indent } = node;
3188
+ const { name, span } = node;
3224
3189
  const mark = (run, out) => {
3225
3190
  const state = run.state;
3226
3191
  if (!isRoot(state, out) || run.capture?.section) throw fail(run, span, "misplaced-outlet", `Outlet "${name}" can't be inside "cache", "set", "apply" or a macro`);
@@ -3229,8 +3194,6 @@ var Compiler = class {
3229
3194
  state.outlets.set(name, {
3230
3195
  out,
3231
3196
  offset: out.s.length,
3232
- indent: indent === void 0 ? out.indent : out.indent + indent,
3233
- inline: indent === void 0,
3234
3197
  content: null
3235
3198
  });
3236
3199
  };
@@ -3258,9 +3221,8 @@ var Compiler = class {
3258
3221
  };
3259
3222
  }
3260
3223
  /**
3261
- * `add to`: renders its content and records it for the outlet. The content takes the outlet's indentation when the
3262
- * outlet has been reached. A `once` tag that already added doesn't render again, but is still recorded, so a cached
3263
- * section saves it.
3224
+ * `add to`: renders its content and records it for the outlet. A `once` tag that already added doesn't render again,
3225
+ * but is still recorded, so a cached section saves it.
3264
3226
  */
3265
3227
  #add(node) {
3266
3228
  const body = this.#body(node.body);
@@ -3276,12 +3238,7 @@ var Compiler = class {
3276
3238
  }, front);
3277
3239
  return;
3278
3240
  }
3279
- const capture = {
3280
- s: "",
3281
- indent: OUTLET_INDENT,
3282
- inline: false,
3283
- ahead: null
3284
- };
3241
+ const capture = emptyOut();
3285
3242
  return after(body(run, childScope(scope), capture), () => recordAddition(run, span, {
3286
3243
  outlet,
3287
3244
  html: capture.s,
@@ -4199,7 +4156,7 @@ const isPrecompiled = (value) => typeof value === "object" && value !== null &&
4199
4156
  const mismatch = (name, detail) => new KilnRenderError("precompiled-mismatch", `Precompiled template "${name}" ${detail}. Precompile it again`, name);
4200
4157
  /** Compiles a precompiled template: its source, with the generated statements in place of the ones they cover. */
4201
4158
  const compilePrecompiled = (env, name, template) => {
4202
- if (template.kiln !== 4) throw mismatch(name, "was made by another version of Kiln");
4159
+ if (template.kiln !== 5) throw mismatch(name, "was made by another version of Kiln");
4203
4160
  if (template.autoescape !== env.autoescape) throw mismatch(name, "was compiled with another \"autoescape\" option");
4204
4161
  const runs = /* @__PURE__ */ new Map();
4205
4162
  const create = template.create;
@@ -4279,30 +4236,29 @@ const runtime = {
4279
4236
  unhold,
4280
4237
  /**
4281
4238
  * Continues a generated run after a statement that returned a promise, `pending`: the rest of the run starts at
4282
- * once, ahead of it, when it may, or else once the statement is done. `inline` is `out.inline` from where the
4283
- * function started. See `runAhead`.
4239
+ * once, ahead of it, when it may, or else once the statement is done. See `runAhead`.
4284
4240
  */
4285
- restAhead: (run, out, pending, inline, indent, rest, span) => mayRunAhead(run.state, inline) ? runAhead(run, out, pending, indent, rest, span) : pending.then(() => rest(run, out)),
4241
+ restAhead: (run, out, pending, rest, span) => mayRunAhead(run.state) ? runAhead(run, out, pending, rest, span) : pending.then(() => rest(run, out)),
4286
4242
  contentSets,
4287
4243
  /**
4288
4244
  * Continues a generated loop after a pass that returned a promise, `pending`: passes `from` to `to` run one at a
4289
4245
  * time with `pass`, ahead of it when they may, see `passesAhead`. `sets` are the variables the loop's body sets,
4290
4246
  * and `elsewhere` whether it places content from elsewhere.
4291
4247
  */
4292
- passAhead: (run, scope, out, pending, inline, indent, sets, elsewhere, from, to, pass, span) => passesFrom(run, out, pending, inline, from, to, indent, pass, span, passesAhead(run, scope, sets, elsewhere)),
4248
+ passAhead: (run, scope, out, pending, sets, elsewhere, from, to, pass, span) => passesFrom(run, out, pending, from, to, pass, span, passesAhead(run, scope, sets, elsewhere)),
4293
4249
  /**
4294
4250
  * For a generated loop that pauses inside a pass: starts passes `from` to `to` ahead of it when they may, and returns
4295
4251
  * what the loop calls once the pass is done. Returns null when they may not, and the loop carries on as usual.
4296
4252
  */
4297
- startAhead: (run, scope, out, inline, indent, sets, elsewhere, from, to, pass, span) => {
4298
- if (!mayRunAhead(run.state, inline) || !passesAhead(run, scope, sets, elsewhere)) return null;
4253
+ startAhead: (run, scope, out, sets, elsewhere, from, to, pass, span) => {
4254
+ if (!mayRunAhead(run.state) || !passesAhead(run, scope, sets, elsewhere)) return null;
4299
4255
  let done = NOTHING;
4300
4256
  let failed = NOTHING;
4301
4257
  const pending = new Promise((resolve, reject) => {
4302
4258
  done = resolve;
4303
4259
  failed = reject;
4304
4260
  });
4305
- const joined = passesFrom(run, out, pending, false, from, to, indent, pass, span, true);
4261
+ const joined = passesFrom(run, out, pending, from, to, pass, span, true);
4306
4262
  return {
4307
4263
  join: () => {
4308
4264
  done();
@@ -4314,10 +4270,6 @@ const runtime = {
4314
4270
  }
4315
4271
  };
4316
4272
  },
4317
- startPlace,
4318
- endPlace,
4319
- indentText,
4320
- indentedText,
4321
4273
  embed,
4322
4274
  embedScope,
4323
4275
  embedKeyed,
@@ -4662,10 +4614,6 @@ const HELPERS = [
4662
4614
  "contentSets",
4663
4615
  "passAhead",
4664
4616
  "startAhead",
4665
- "startPlace",
4666
- "endPlace",
4667
- "indentText",
4668
- "indentedText",
4669
4617
  "truthy",
4670
4618
  "isEmpty",
4671
4619
  "equals",
@@ -4852,7 +4800,7 @@ var Generator = class {
4852
4800
  return [
4853
4801
  "// Precompiled by Kiln. Don't edit this file: precompile the template again.",
4854
4802
  "export default {",
4855
- `\tkiln: 4,`,
4803
+ `\tkiln: 5,`,
4856
4804
  `\tautoescape: ${JSON.stringify(this.#env.autoescape)},`,
4857
4805
  `\tsource: ${JSON.stringify(source)},`,
4858
4806
  " create(rt) {",
@@ -4979,7 +4927,7 @@ var Generator = class {
4979
4927
  const next = `${base}_${index + 1}(run, scope, out)`;
4980
4928
  const body = lines.map((line) => line.replace(WAIT_LINE, (_, tabs, result, ahead) => {
4981
4929
  const then = `${result}.then(() => ${next})`;
4982
- const rest = `restAhead(run, out, ${result}, inl, ind, (run, out) => ${next}, ${span})`;
4930
+ const rest = `restAhead(run, out, ${result}, (run, out) => ${next}, ${span})`;
4983
4931
  return `${tabs}if (${result} !== undefined) return ${ahead === AHEAD ? rest : ahead === GUARDED ? `contentSets(run) ? ${then} : ${rest}` : then};`;
4984
4932
  }));
4985
4933
  const last = index === segments.length - 1;
@@ -5030,8 +4978,6 @@ var Generator = class {
5030
4978
  "let steps = state.steps;",
5031
4979
  "const signal = state.signal;",
5032
4980
  `signal !== undefined && signal.aborted && aborted(run, ${span});`,
5033
- "const ind = out.indent;",
5034
- "const inl = out.inline;",
5035
4981
  "let s = \"\";",
5036
4982
  `let ${[
5037
4983
  "q",
@@ -5179,7 +5125,7 @@ var Generator = class {
5179
5125
  }
5180
5126
  #statement(statement, context) {
5181
5127
  switch (statement.type) {
5182
- case "Text": return [this.#text(statement)];
5128
+ case "Text": return [`s += ${JSON.stringify(statement.value)};`];
5183
5129
  case "Output": return this.#output(statement.expression, statement.span, context);
5184
5130
  case "If": return this.#if(statement, context);
5185
5131
  case "For": return this.#for(statement, context);
@@ -5196,26 +5142,6 @@ var Generator = class {
5196
5142
  default: return unsupported();
5197
5143
  }
5198
5144
  }
5199
- /** Text, with its lines indented to where it is inserted. See `indentText`. */
5200
- #text(statement) {
5201
- const text = JSON.stringify(statement.value);
5202
- if (!statement.lines) return `s += ${text};`;
5203
- const lines = JSON.stringify(statement.lines);
5204
- const entry = this.#constant(`text:${statement.span.start}:${statement.span.end}`, "T", `indentedText(${text}, ${lines})`);
5205
- return `s += ind === "" ? ${text} : ${entry}.indent === ind && !out.inline ? ${entry}.indented : indentText(${entry}, ind, s === "" && out.inline);`;
5206
- }
5207
- /**
5208
- * Inserts content at the indentation of the tag that inserts it, with `startPlace` and `endPlace`. Tags at the start
5209
- * of a line need nothing. Saved values go in temporaries rather than a closure, so the function's locals stay fast.
5210
- */
5211
- #place(at, call) {
5212
- if (at === "") return call;
5213
- const [saved, inline] = [this.#temp(), this.#temp()];
5214
- return `(${saved} = out.indent, ${inline} = out.inline, startPlace(out, ${this.#at(at)}), endPlace(out, ${saved}, ${inline}, ${call}))`;
5215
- }
5216
- #at(at) {
5217
- return at === void 0 ? "undefined" : JSON.stringify(at);
5218
- }
5219
5145
  /** Statements inside another, such as a loop's body. */
5220
5146
  #body(list, context) {
5221
5147
  const inner = {
@@ -5504,7 +5430,7 @@ var Generator = class {
5504
5430
  const call = (part, value) => `${name}_${part}(${partParams}${value === void 0 ? "" : `, ${value}`})`;
5505
5431
  const one = `(run, out, j) => ${name}(run, scope, out, ${loop.values}, ${loop.length}, j, j + 1${keys})`;
5506
5432
  const { sets, elsewhere } = this.#setsOf(statement);
5507
- const handOff = (from, tabs, rest) => from === 0 ? `${tabs}return ${loop.index} + 1 < stop ? passAhead(run, scope, out, ${rest}, inl, ind, ${sets}, ${elsewhere}, ${loop.index} + 1, stop, ${one}, ${loop.span}) : ${rest};` : `${tabs}return ${rest};`;
5433
+ const handOff = (from, tabs, rest) => from === 0 ? `${tabs}return ${loop.index} + 1 < stop ? passAhead(run, scope, out, ${rest}, ${sets}, ${elsewhere}, ${loop.index} + 1, stop, ${one}, ${loop.span}) : ${rest};` : `${tabs}return ${rest};`;
5508
5434
  const continued = (from) => segments.slice(from).flatMap((lines, offset) => this.#forgetLines(lines).flatMap((line) => {
5509
5435
  const next = from + offset + 1;
5510
5436
  const value = VALUE_LINE.exec(line);
@@ -5593,7 +5519,7 @@ var Generator = class {
5593
5519
  const ahead = `a${loop.id}`;
5594
5520
  const { index, span } = loop;
5595
5521
  const { sets, elsewhere } = this.#setsOf(statement);
5596
- const fork = `if (${ahead} === null && ${index} + 1 < stop) ${ahead} = startAhead(run, ${loop.outer}, out, inl, ind, ${sets}, ${elsewhere}, ${index} + 1, stop, (run, out, j) => drive(${name}(run, out, j, j + 1)), ${span});`;
5522
+ const fork = `if (${ahead} === null && ${index} + 1 < stop) ${ahead} = startAhead(run, ${loop.outer}, out, ${sets}, ${elsewhere}, ${index} + 1, stop, (run, out, j) => drive(${name}(run, out, j, j + 1)), ${span});`;
5597
5523
  const body = this.#forgetLines(this.#setFlags(lines)).flatMap((line) => {
5598
5524
  if (line.trim() === FORK) return [line.replace(FORK, fork)];
5599
5525
  const wait = WAIT_LINE.exec(line);
@@ -5758,7 +5684,7 @@ var Generator = class {
5758
5684
  ...target.lines,
5759
5685
  ...variables.lines,
5760
5686
  ...variables.charge(span),
5761
- ...this.#wait(span, this.#place(statement.indent, variables.embed(span, target.name, "null", statement.ignoreMissing)), context.top)
5687
+ ...this.#wait(span, variables.embed(span, target.name, "null", statement.ignoreMissing), context.top)
5762
5688
  ];
5763
5689
  }
5764
5690
  /**
@@ -5846,7 +5772,7 @@ var Generator = class {
5846
5772
  `${slots} = new Map();`,
5847
5773
  ...fills.map((fill) => `${slots}.set(${JSON.stringify(fill.name)}, { body: ${this.#nested(fill.body, statement.span, context)}, run, scope: ${context.scope}, sets: ${mayWrite(fill.body)} });`),
5848
5774
  ...variables.charge(span),
5849
- ...this.#wait(span, this.#place(statement.indent, variables.embed(span, target.name, slots, false)), context.top),
5775
+ ...this.#wait(span, variables.embed(span, target.name, slots, false), context.top),
5850
5776
  ...fills.some((fill) => setsVariables(fill.body)) ? this.#forgetAll() : fills.some((fill) => containsNode(fill.body, (record) => record.type === "Block")) ? this.#afterBlocks() : []
5851
5777
  ];
5852
5778
  }
@@ -5854,7 +5780,6 @@ var Generator = class {
5854
5780
  #slot(statement, context) {
5855
5781
  const span = this.#span(statement.span);
5856
5782
  const fill = this.#temp();
5857
- const at = statement.indent;
5858
5783
  const sets = [...setsIn(statement.body)];
5859
5784
  const scope = sets.length > 0 ? this.#temp() : context.scope;
5860
5785
  let fallback = this.#body(statement.body, {
@@ -5866,23 +5791,10 @@ var Generator = class {
5866
5791
  ...fallback,
5867
5792
  ...sets.flatMap((name) => this.#forget(name))
5868
5793
  ];
5869
- if (at !== "" && !statement.body.every(isBlank)) {
5870
- const [saved, inline] = [this.#temp(), this.#temp()];
5871
- fallback = [
5872
- `${saved} = out.indent;`,
5873
- `${inline} = out.inline;`,
5874
- `startPlace(out, ${this.#at(at)});`,
5875
- "{",
5876
- ...indent(["const ind = out.indent;", ...fallback]),
5877
- "}",
5878
- `out.indent = ${saved};`,
5879
- `if (out.inline) out.inline = ${inline};`
5880
- ];
5881
- }
5882
5794
  return [
5883
5795
  `${fill} = run.slots === null ? undefined : run.slots.get(${JSON.stringify(statement.name)});`,
5884
5796
  `if (${fill} !== undefined) {`,
5885
- ...indent(this.#wait(span, this.#place(at, `fillSlot(run, ${span}, ${fill}, out)`), context.top)),
5797
+ ...indent(this.#wait(span, `fillSlot(run, ${span}, ${fill}, out)`, context.top)),
5886
5798
  "} else {",
5887
5799
  ...indent(fallback),
5888
5800
  "}"
@@ -5896,7 +5808,7 @@ var Generator = class {
5896
5808
  if (context.inLoop) return unsupported();
5897
5809
  this.#pending.push([statement.body, context.strategy]);
5898
5810
  const span = this.#span(statement.span);
5899
- return [...this.#wait(span, this.#place(statement.indent, `placeBlock(run, ${span}, ${JSON.stringify(statement.name)}, ${context.scope}, out)`), context.top), ...this.#afterBlocks()];
5811
+ return [...this.#wait(span, `placeBlock(run, ${span}, ${JSON.stringify(statement.name)}, ${context.scope}, out)`, context.top), ...this.#afterBlocks()];
5900
5812
  }
5901
5813
  /** Forgets every variable lookup after a block is placed when the block, perhaps a child template's version, may set any variable. */
5902
5814
  #afterBlocks() {
@@ -8565,7 +8477,7 @@ const memoryLoader = (templates) => {
8565
8477
  };
8566
8478
  /**
8567
8479
  * A store for the `cache` tag that keeps output in memory, for a long-running server. Holds up to `max` copies, 1,000
8568
- * by default, and drops the oldest first.
8480
+ * by default, and drops the oldest first. `clear` removes every copy, or those with any of the tags passed.
8569
8481
  *
8570
8482
  * ```ts
8571
8483
  * const kiln = createKiln({ loader, cache: memoryCache() });
@@ -8581,14 +8493,20 @@ const memoryCache = ({ max = 1e3 } = {}) => {
8581
8493
  entries.delete(key);
8582
8494
  return null;
8583
8495
  },
8584
- set: ({ key, value, seconds }) => {
8496
+ set: ({ key, value, seconds, tags }) => {
8585
8497
  entries.delete(key);
8586
8498
  entries.set(key, {
8587
8499
  value,
8588
- expires: seconds === null ? Infinity : Date.now() + seconds * 1e3
8500
+ expires: seconds === null ? Infinity : Date.now() + seconds * 1e3,
8501
+ tags
8589
8502
  });
8590
8503
  const oldest = entries.keys().next();
8591
8504
  if (entries.size > max && !oldest.done) entries.delete(oldest.value);
8505
+ },
8506
+ /** Removes every copy, or with `tags`, the copies that have any of them. */
8507
+ clear: ({ tags } = {}) => {
8508
+ if (!tags) return entries.clear();
8509
+ for (const [key, entry] of entries) if (entry.tags.some((tag) => tags.includes(tag))) entries.delete(key);
8592
8510
  }
8593
8511
  };
8594
8512
  };