kreftjs 0.0.20-beta → 0.0.22-beta

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.
Files changed (64) hide show
  1. package/README.md +10 -1
  2. package/bin/artisan.js +4 -2
  3. package/bin/backend/cache.js +31 -5
  4. package/bin/backend/gc.js +193 -43
  5. package/bin/backend/http.js +6 -2
  6. package/bin/backend/index.js +6 -0
  7. package/bin/backend/mcp-client.js +133 -0
  8. package/bin/backend/mcp.js +17 -0
  9. package/bin/backend/model.js +15 -1
  10. package/bin/backend/shield.js +266 -0
  11. package/bin/backend/stream.js +74 -0
  12. package/bin/build.js +12 -5
  13. package/bin/db-api.js +9 -1
  14. package/bin/kreftjs.js +50 -12
  15. package/book/ar/16-virtual-list.md +131 -0
  16. package/book/ar/17-combobox.md +169 -0
  17. package/book/ar/18-tabs-offline.md +168 -0
  18. package/book/ar/19-wizard.md +196 -0
  19. package/book/ar/20-pipes-match-props.md +75 -0
  20. package/book/ar/README.md +12 -1
  21. package/book/kreft-book-ar.docx +0 -0
  22. package/docs/README.md +209 -13
  23. package/editors/sublime/Comments.tmPreferences +26 -0
  24. package/editors/sublime/Component.sublime-snippet +20 -0
  25. package/editors/sublime/Kreft.sublime-syntax +39 -0
  26. package/editors/sublime/LICENSE +95 -0
  27. package/editors/sublime/README.md +7 -0
  28. package/editors/sublime/action.sublime-snippet +10 -0
  29. package/editors/sublime/derive.sublime-snippet +8 -0
  30. package/editors/sublime/for.sublime-snippet +12 -0
  31. package/editors/sublime/k-case.sublime-snippet +8 -0
  32. package/editors/sublime/k-if.sublime-snippet +8 -0
  33. package/editors/sublime/k-match.sublime-snippet +12 -0
  34. package/editors/sublime/kreft-wait.sublime-snippet +12 -0
  35. package/editors/sublime/layout.sublime-snippet +8 -0
  36. package/editors/sublime/load.sublime-snippet +8 -0
  37. package/editors/sublime/on.sublime-snippet +10 -0
  38. package/editors/sublime/pipe.sublime-snippet +8 -0
  39. package/editors/sublime/prop-typed.sublime-snippet +8 -0
  40. package/editors/sublime/saved.sublime-snippet +8 -0
  41. package/editors/sublime/section.sublime-snippet +10 -0
  42. package/editors/sublime/state.sublime-snippet +8 -0
  43. package/editors/sublime/watch.sublime-snippet +10 -0
  44. package/editors/sublime/when.sublime-snippet +10 -0
  45. package/editors/sublime/yield.sublime-snippet +8 -0
  46. package/editors/vscode/snippets/kreftjs.json +143 -15
  47. package/editors/zed/LICENSE +95 -0
  48. package/editors/zed/README.md +17 -0
  49. package/editors/zed/extension.toml +11 -0
  50. package/editors/zed/languages/kreft/brackets.scm +4 -0
  51. package/editors/zed/languages/kreft/config.toml +17 -0
  52. package/editors/zed/languages/kreft/highlights.scm +18 -0
  53. package/editors/zed/languages/kreft/indents.scm +5 -0
  54. package/editors/zed/languages/kreft/injections.scm +14 -0
  55. package/editors/zed/languages/kreft/outline.scm +3 -0
  56. package/kreftjs.d.ts +1 -1
  57. package/kreftjs.js +219 -14
  58. package/package.json +3 -2
  59. package/server.d.ts +44 -0
  60. package/templates/app/backend/app/Auth/Session.js +8 -4
  61. package/templates/app/backend/app/Http/Middleware/Authenticate.js +7 -4
  62. package/templates/app/backend/database/seeders/DatabaseSeeder.js +4 -0
  63. package/templates/app/frontend/kreftjs.d.ts +1 -1
  64. package/templates/app/frontend/kreftjs.js +219 -14
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Kreft v0.0.20-beta — reactive by nature
2
+ * Kreft v0.0.22-beta — reactive by nature
3
3
  * The .kreft component language (state · derive · action · when · watch · on),
4
4
  * { } views with k-if / k-each / k-model, components, slots, router, stores,
5
5
  * share / receive, event bus and a template engine.
@@ -1026,6 +1026,7 @@
1026
1026
  onceDepth++;
1027
1027
  try { return compile(el, scope, inst); } finally { onceDepth--; }
1028
1028
  }
1029
+ if (el.hasAttribute('k-match')) return compileMatch(el, scope, inst); // becomes k-if / k-elif / k-else on its children
1029
1030
  if (el.hasAttribute('k-if')) return compileIf(el, scope, inst); // k-if is checked before k-each
1030
1031
  if (el.hasAttribute('k-elif') || el.hasAttribute('k-else')) {
1031
1032
  warn('k-elif / k-else has no k-if right before it.', el);
@@ -1060,6 +1061,157 @@
1060
1061
  compileElement(el, scope, inst, needAttrs, hasAnim, info);
1061
1062
  }
1062
1063
 
1064
+ /* ---------- pipes: { price | currency:'EUR' | upper } ---------- */
1065
+ // positions of `want` at the top level of an expression: not inside (), [], {}, strings or regular expressions.
1066
+ // For '|' a lone bar only: `a || b` and `a |= b` are not pipes.
1067
+ function topLevel(s, want) {
1068
+ const hits = [];
1069
+ let depth = 0, prev = '';
1070
+ for (let i = 0; i < s.length; i++) {
1071
+ const c = s[i];
1072
+ if (c === '"' || c === "'" || c === '`') {
1073
+ for (i++; i < s.length && s[i] !== c; i++) if (s[i] === '\\') i++;
1074
+ prev = c;
1075
+ continue;
1076
+ }
1077
+ if (c === '/' && s[i + 1] !== '/' && s[i + 1] !== '*' && (prev === '' || '(,=:?!&|{[;+-*%<>~^'.includes(prev))) { // a regular expression
1078
+ let cls = false;
1079
+ for (i++; i < s.length; i++) {
1080
+ if (s[i] === '\\') i++;
1081
+ else if (s[i] === '[') cls = true;
1082
+ else if (s[i] === ']') cls = false;
1083
+ else if (s[i] === '/' && !cls) break;
1084
+ }
1085
+ prev = '/';
1086
+ continue;
1087
+ }
1088
+ if (c === '(' || c === '[' || c === '{') depth++;
1089
+ else if (c === ')' || c === ']' || c === '}') depth--;
1090
+ else if (depth === 0 && c === want && (want !== '|' || (s[i + 1] !== '|' && s[i - 1] !== '|' && s[i + 1] !== '='))) hits.push(i);
1091
+ if (!/\s/.test(c)) prev = c;
1092
+ }
1093
+ return hits;
1094
+ }
1095
+ const cutAt = (s, cuts) => { const out = []; let from = 0; for (const i of cuts) { out.push(s.slice(from, i)); from = i + 1; } out.push(s.slice(from)); return out; };
1096
+ const pipeCache = new Map();
1097
+ // "price | currency:'EUR' | upper" → Kreftjs.__pipe("upper")(Kreftjs.__pipe("currency")((price), 'EUR'))
1098
+ // A pipe is a function in your component (a local function wins) or one added with Kreftjs.pipe(name, fn).
1099
+ function applyPipes(expr) {
1100
+ if (typeof expr !== 'string' || expr.indexOf('|') < 0) return expr;
1101
+ let r = pipeCache.get(expr);
1102
+ if (r !== undefined) return r;
1103
+ r = expr;
1104
+ const cuts = topLevel(expr, '|');
1105
+ if (cuts.length) {
1106
+ const segs = cutAt(expr, cuts);
1107
+ let code = '(' + segs[0].trim() + ')', ok = true;
1108
+ for (const seg of segs.slice(1)) {
1109
+ const m = /^\s*([A-Za-z_$][\w$]*)\s*([\s\S]*)$/.exec(seg);
1110
+ if (!m) { ok = false; break; }
1111
+ const name = m[1];
1112
+ const rest = m[2].trim();
1113
+ let args = '';
1114
+ if (rest[0] === ':') args = cutAt(rest.slice(1), topLevel(rest.slice(1), ':')).map((a) => a.trim()).join(', ');
1115
+ else if (rest[0] === '(' && rest[rest.length - 1] === ')') args = rest.slice(1, -1).trim();
1116
+ else if (rest) { ok = false; break; }
1117
+ code = `Kreftjs.__pipe(${JSON.stringify(name)}, typeof ${name} === "function" ? ${name} : null)(${code}${args ? ', ' + args : ''})`;
1118
+ }
1119
+ if (ok) r = code;
1120
+ }
1121
+ if (pipeCache.size > 5000) pipeCache.clear();
1122
+ pipeCache.set(expr, r);
1123
+ return r;
1124
+ }
1125
+ const pipes = Object.create(null);
1126
+ const asText = (v) => (v == null ? '' : String(v));
1127
+ // "2026-11-05 15:39:58" (a date from the Kreft database) has no time zone: the database stores UTC, so read it as UTC
1128
+ const toDate = (v) => (v instanceof Date ? v : typeof v === 'string' && /^\d{4}-\d\d-\d\d \d\d:\d\d(:\d\d)?$/.test(v) ? new Date(v.replace(' ', 'T') + 'Z') : new Date(v));
1129
+ Object.assign(pipes, {
1130
+ upper: (v) => asText(v).toUpperCase(),
1131
+ lower: (v) => asText(v).toLowerCase(),
1132
+ capitalize: (v) => { const t = asText(v); return t.charAt(0).toUpperCase() + t.slice(1); },
1133
+ trim: (v) => asText(v).trim(),
1134
+ truncate: (v, n = 50, tail = '…') => { const t = asText(v); return t.length > n ? t.slice(0, Math.max(0, n)).trimEnd() + tail : t; },
1135
+ currency: (v, code = 'USD') => (v == null || v === '' ? '' : formatCurrency(Number(v), code)),
1136
+ number: (v, digits) => (v == null || v === '' ? '' : formatNumber(Number(v), digits && typeof digits === 'object' ? digits : digits == null ? undefined : { maximumFractionDigits: digits, minimumFractionDigits: digits })),
1137
+ percent: (v, digits = 0) => (v == null || v === '' ? '' : formatNumber(Number(v), { style: 'percent', maximumFractionDigits: digits, minimumFractionDigits: digits })),
1138
+ date: (v, style = 'medium') => (v == null || v === '' ? '' : formatDate(toDate(v), typeof style === 'object' ? style : { dateStyle: style })),
1139
+ time: (v, style = 'short') => (v == null || v === '' ? '' : formatDate(toDate(v), typeof style === 'object' ? style : { timeStyle: style })),
1140
+ ago: (v) => {
1141
+ if (v == null || v === '') return '';
1142
+ const secs = Math.round((toDate(v).getTime() - Date.now()) / 1000);
1143
+ if (isNaN(secs)) return '';
1144
+ const rtf = new Intl.RelativeTimeFormat(currentLocaleRef.value, { numeric: 'auto' });
1145
+ const steps = [['year', 31536000], ['month', 2592000], ['day', 86400], ['hour', 3600], ['minute', 60]];
1146
+ for (const [unit, size] of steps) if (Math.abs(secs) >= size) return rtf.format(Math.round(secs / size), unit);
1147
+ return rtf.format(secs, 'second');
1148
+ },
1149
+ json: (v, indent = 2) => { try { return JSON.stringify(v, null, indent); } catch (_) { return String(v); } },
1150
+ fallback: (v, d = '') => (v == null || v === '' ? d : v),
1151
+ join: (v, sep = ', ') => (isArray(v) ? v.join(sep) : asText(v)),
1152
+ first: (v, n) => (n == null ? (v == null ? v : v[0]) : (v || []).slice(0, n)),
1153
+ last: (v, n) => (n == null ? (v == null ? v : v[v.length - 1]) : (v || []).slice(-n)),
1154
+ slice: (v, a, b) => (v == null ? v : v.slice(a, b)),
1155
+ reverse: (v) => (isArray(v) ? [...v].reverse() : [...asText(v)].reverse().join('')),
1156
+ sortBy: (v, key, dir = 'asc') => (isArray(v) ? [...v].sort((x, y) => { const a = key ? x[key] : x, b = key ? y[key] : y; return (a > b ? 1 : a < b ? -1 : 0) * (dir === 'desc' ? -1 : 1); }) : v),
1157
+ count: (v) => (v == null ? 0 : typeof v === 'object' && !isArray(v) ? Object.keys(v).length : v.length || 0),
1158
+ bytes: (v) => {
1159
+ let n = Number(v);
1160
+ if (!isFinite(n)) return '';
1161
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
1162
+ let u = 0;
1163
+ while (Math.abs(n) >= 1024 && u < units.length - 1) { n /= 1024; u++; }
1164
+ return (u ? n.toFixed(n < 10 ? 1 : 0) : n) + ' ' + units[u];
1165
+ },
1166
+ });
1167
+ const definePipe = (name, fn) => { if (!isFunction(fn)) throw new Error(`Kreftjs.pipe("${name}", fn): fn must be a function.`); pipes[name] = fn; };
1168
+ function lookupPipe(name, local) {
1169
+ const f = local || pipes[name];
1170
+ if (f) return f;
1171
+ warn(`Unknown pipe "${name}". Use a function of your own named ${name}, or Kreftjs.pipe('${name}', fn).`);
1172
+ return (v) => v;
1173
+ }
1174
+
1175
+ /* ---------- prop types: prop size: 'sm' | 'lg' = 'sm' ---------- */
1176
+ // TypeScript type text → (value) => boolean, or null when it can't be checked (your own type names, any)
1177
+ function typeCheck(text) {
1178
+ const members = cutAt(String(text), topLevel(String(text), '|')).map((t) => t.trim()).filter(Boolean);
1179
+ if (!members.length) return null;
1180
+ const checks = [];
1181
+ for (const m of members) {
1182
+ let c;
1183
+ if (m === 'string') c = (v) => typeof v === 'string';
1184
+ else if (m === 'number') c = (v) => typeof v === 'number' && !isNaN(v);
1185
+ else if (m === 'boolean') c = (v) => typeof v === 'boolean';
1186
+ else if (m === 'null') c = (v) => v === null;
1187
+ else if (m === 'undefined' || m === 'void') c = (v) => v === undefined;
1188
+ else if (m === 'true' || m === 'false') c = (v) => v === (m === 'true');
1189
+ else if (m === 'object') c = (v) => v !== null && typeof v === 'object';
1190
+ else if (m === 'Date') c = (v) => v instanceof Date;
1191
+ else if (m === 'Function' || /=>/.test(m)) c = isFunction;
1192
+ else if (/\[\]$/.test(m) || /^(?:readonly\s+)?Array\s*</.test(m) || /^\[/.test(m)) c = isArray;
1193
+ else if (/^(?:Record|Partial|Map|Set)\s*</.test(m) || m[0] === '{') c = (v) => v !== null && typeof v === 'object';
1194
+ else if (/^(['"`])[\s\S]*\1$/.test(m)) { const lit = m.slice(1, -1); c = (v) => v === lit; }
1195
+ else if (/^-?\d+(?:\.\d+)?$/.test(m)) { const n = Number(m); c = (v) => v === n; }
1196
+ else return null; // any, unknown, User, … : not checked
1197
+ checks.push(c);
1198
+ }
1199
+ return (v) => checks.some((c) => c(v));
1200
+ }
1201
+ const simpleType = (t) => ({ string: String, number: Number, boolean: Boolean }[String(t).trim()]);
1202
+ const describeValue = (v) => (v === null ? 'null' : isArray(v) ? 'an array' : typeof v === 'string' ? `"${v.length > 20 ? v.slice(0, 20) + '…' : v}"` : typeof v === 'object' ? 'an object' : typeof v + (typeof v === 'number' || typeof v === 'boolean' ? ' ' + v : ''));
1203
+ function checkProp(comp, k, opt, v) {
1204
+ if (opt.check && v !== undefined && !opt.check(v)) warn(`<${comp}> prop "${k}" should be ${opt.typeText}, but got ${describeValue(v)}.`);
1205
+ }
1206
+ function typedProp(opt, info) {
1207
+ opt.typeText = info.type;
1208
+ const chk = typeCheck(info.type);
1209
+ if (chk) opt.check = chk;
1210
+ if (info.required) opt.required = true;
1211
+ if (!opt.type) { const t = simpleType(info.type); if (t) opt.type = t; }
1212
+ return opt;
1213
+ }
1214
+
1063
1215
  /* ---------- { expr } interpolation ---------- */
1064
1216
  // "Hi {name}!" → [{ text: 'Hi ' }, { expr: 'name' }, { text: '!' }]; write \{ for a literal brace
1065
1217
  const interpCache = new Map();
@@ -1087,7 +1239,7 @@
1087
1239
  if (!expr) { buf += str.slice(i, end); i = end; continue; }
1088
1240
  if (buf) parts.push({ text: buf });
1089
1241
  buf = '';
1090
- parts.push({ expr });
1242
+ parts.push({ expr: applyPipes(expr) });
1091
1243
  i = end;
1092
1244
  continue;
1093
1245
  }
@@ -1108,7 +1260,7 @@
1108
1260
  function exactBraceRaw(v) {
1109
1261
  const t = (v || '').trim();
1110
1262
  if (t[0] !== '{') return null;
1111
- try { return matchBrace(t, 0) === t.length ? t.slice(1, -1).trim() : null; } catch (_) { return null; }
1263
+ try { return matchBrace(t, 0) === t.length ? applyPipes(t.slice(1, -1).trim()) : null; } catch (_) { return null; }
1112
1264
  }
1113
1265
  const stripBrace = (v) => { const e = exactBrace(v); return e != null ? e : v; };
1114
1266
  // "btn {active ? 'on' : ''}" → "`btn ${…}`"; null when there is nothing to interpolate
@@ -1791,6 +1943,41 @@
1791
1943
  };
1792
1944
  }
1793
1945
 
1946
+ /* ---------- k-match / k-case / k-default ---------- */
1947
+ // <div k-match="status">
1948
+ // <b k-case="'ok'">Fine</b>
1949
+ // <i k-case="'wait', 'queued'">Waiting</i> several values: one of them
1950
+ // <u k-default>Unknown</u>
1951
+ // </div>
1952
+ // The children turn into k-if / k-elif / k-else, so animations, components and templates work as they do there.
1953
+ function caseCond(subject, raw) {
1954
+ const list = cutAt(raw, topLevel(raw, ',')).map((v) => v.trim()).filter(Boolean);
1955
+ return list.length > 1 ? `[${list.join(', ')}].includes(${subject})` : `(${subject}) === (${list[0] || 'undefined'})`;
1956
+ }
1957
+ function compileMatch(el, scope, inst) {
1958
+ const subject = stripBrace(el.getAttribute('k-match'));
1959
+ el.removeAttribute('k-match');
1960
+ let started = false, fallback = null;
1961
+ for (const child of [...el.children]) {
1962
+ if (child.hasAttribute('k-case')) {
1963
+ const raw = stripBrace(child.getAttribute('k-case'));
1964
+ child.removeAttribute('k-case');
1965
+ child.setAttribute(started ? 'k-elif' : 'k-if', caseCond(subject, raw));
1966
+ started = true;
1967
+ } else if (child.hasAttribute('k-default')) {
1968
+ child.removeAttribute('k-default');
1969
+ if (fallback) warn('k-match has more than one k-default; only the first is used.', child);
1970
+ else fallback = child;
1971
+ }
1972
+ }
1973
+ if (fallback) {
1974
+ if (fallback.nextElementSibling) warn('k-default belongs last in k-match.', fallback);
1975
+ if (started) { fallback.setAttribute('k-else', ''); el.appendChild(fallback); } // right after the last case
1976
+ else fallback.setAttribute('k-if', 'true');
1977
+ }
1978
+ return compile(el, scope, inst);
1979
+ }
1980
+
1794
1981
  /* ---------- k-if / k-elif / k-else ---------- */
1795
1982
  function compileIf(el, scope, inst) {
1796
1983
  const anchor = text();
@@ -2811,12 +2998,13 @@
2811
2998
  if (hasType(opt, Number) && v !== '' && !isNaN(v)) v = Number(v);
2812
2999
  } else if (hasOwn(extra, k)) { v = extra[k]; present = true; }
2813
3000
  props[k] = resolvePropValue(opt, v, present);
3001
+ checkProp(def.__name || def.name || 'component', k, opt, props[k]);
2814
3002
  if (opt.required && !present) warn(`Missing required prop: "${k}"`);
2815
3003
  }
2816
3004
  for (const k in extra) if (!hasOwn(propsOptions, k)) attrs[k] = extra[k];
2817
3005
  dynamicProps.forEach(([k, expr]) => {
2818
3006
  if (!hasOwn(propsOptions, k)) return void dynamicAttrs.push([k, expr]);
2819
- renderEffect(() => { props[k] = resolvePropValue(propsOptions[k], evaluate(parentScope, expr, el), true); });
3007
+ renderEffect(() => { props[k] = resolvePropValue(propsOptions[k], evaluate(parentScope, expr, el), true); checkProp(def.__name || def.name || 'component', k, propsOptions[k], props[k]); });
2820
3008
  });
2821
3009
  dynamicAttrs.forEach(([k, expr]) => renderEffect(() => { attrs[k] = evaluate(parentScope, expr, el); }));
2822
3010
  if (options.liveProps) renderEffect(() => { // props that follow the route (<kreft-view>)
@@ -3993,7 +4181,17 @@
3993
4181
  try { return parseKreftScriptInner(src, url); } finally { scanScript = false; }
3994
4182
  }
3995
4183
  function parseKreftScriptInner(src, url) {
3996
- const out = [], imports = [], props = {}, events = [];
4184
+ const out = [], imports = [], props = {}, events = [], propTypes = {};
4185
+ // `prop size: 'sm' | 'lg' = 'sm'` keeps its type, to warn when a parent sends something else
4186
+ const notePropType = (lhs, hasDefault) => {
4187
+ const c = lhs.indexOf(':');
4188
+ if (c < 0) return;
4189
+ const raw = lhs.slice(0, c).trim();
4190
+ const name = raw.replace(/\?$/, '').trim();
4191
+ const type = lhs.slice(c + 1).trim();
4192
+ if (!type || !IDENT.test(name)) return;
4193
+ propTypes[name] = { type, required: !hasDefault && !/\?$/.test(raw) && !/(?:^|[|\s])(?:null|undefined|any|unknown)(?:[|\s]|$)/.test(type) };
4194
+ };
3997
4195
  const kw = /(prop|state|derive|derived|action|when|watch|on|use|share|receive|event|import|saved|load|layout|interface|type)(?![\w$])/y;
3998
4196
  let layout;
3999
4197
  // `saved` and `load` are keywords when followed by `name =` or `name: Type =`, so `load(…)` stays a normal call
@@ -4089,6 +4287,7 @@
4089
4287
  if (word === 'prop' && eq > 0) {
4090
4288
  const { name, value } = nameValue(j, end, 'prop');
4091
4289
  props[name] = value;
4290
+ notePropType(text.slice(0, eq), true);
4092
4291
  } else if (word === 'use' && eq > 0) { // use mouse = trackMouse() or use mouse: MousePos = trackMouse()
4093
4292
  const { name, value } = nameValue(j, end, 'use');
4094
4293
  out.push(`$h.use(${JSON.stringify(name)}, () => (${value}));`);
@@ -4097,7 +4296,7 @@
4097
4296
  const colon = text.indexOf(':');
4098
4297
  const name = text.slice(0, colon).replace(/\?\s*$/, '').trim();
4099
4298
  if (!IDENT.test(name) && word !== 'event') fail(j, `Invalid name "${name}"`);
4100
- if (word === 'prop') props[name] = null;
4299
+ if (word === 'prop') { props[name] = null; notePropType(text, false); }
4101
4300
  else events.push(name);
4102
4301
  } else {
4103
4302
  text.split(',').map((s) => s.trim()).filter(Boolean).forEach((rawN) => {
@@ -4194,7 +4393,7 @@
4194
4393
  fail(i, e.message);
4195
4394
  }
4196
4395
  }
4197
- return { code: out.join('\n'), imports, props, events, layout };
4396
+ return { code: out.join('\n'), imports, props, propTypes, events, layout };
4198
4397
  }
4199
4398
 
4200
4399
  // a CSS file imported by a .kreft file: one <link> for the whole page, before the components' own styles
@@ -4451,6 +4650,7 @@
4451
4650
  code: parsed.code,
4452
4651
  imports: parsed.imports,
4453
4652
  props: parsed.props, // name → default expression (or null)
4653
+ propTypes: parsed.propTypes, // name → { type, required } from `prop name: Type`
4454
4654
  events: parsed.events,
4455
4655
  layout: parsed.layout, // undefined: not said; false: none; or a name / path
4456
4656
  styles: styles.map((m) => ({ css: m[2], scoped: /\bscoped\b/.test(m[1] || '') })),
@@ -4483,11 +4683,14 @@
4483
4683
  const props = {};
4484
4684
  for (const p in sp.props) {
4485
4685
  const expr = sp.props[p];
4486
- if (expr == null) { props[p] = {}; continue; }
4487
- const make = new Function(`return (${expr})`);
4488
- let sample;
4489
- try { sample = make(); } catch (_) { /* default uses something only known later */ }
4490
- props[p] = isFunction(sample) ? { type: Function, default: sample } : { type: propType(sample), default: make };
4686
+ if (expr == null) props[p] = {};
4687
+ else {
4688
+ const make = new Function(`return (${expr})`);
4689
+ let sample;
4690
+ try { sample = make(); } catch (_) { /* default uses something only known later */ }
4691
+ props[p] = isFunction(sample) ? { type: Function, default: sample } : { type: propType(sample), default: make };
4692
+ }
4693
+ if (sp.propTypes && sp.propTypes[p]) typedProp(props[p], sp.propTypes[p]);
4491
4694
  }
4492
4695
 
4493
4696
  let scopeId = null;
@@ -5398,7 +5601,7 @@
5398
5601
  stores, storeLog, busLog,
5399
5602
  };
5400
5603
 
5401
- const VERSION = '0.0.20-beta';
5604
+ const VERSION = '0.0.22-beta';
5402
5605
  const Kreftjs = {
5403
5606
  version: VERSION,
5404
5607
  // PUBLIC_ values from .env (kreft dev and kreft build put them here); never secrets
@@ -5450,6 +5653,8 @@
5450
5653
  choice: choiceTranslation, transChoice: choiceTranslation, trans_choice: choiceTranslation,
5451
5654
  hasTranslation,
5452
5655
  formatNumber, formatCurrency, formatDate,
5656
+ // pipes: { price | currency }; add yours with Kreftjs.pipe('name', fn)
5657
+ pipe: definePipe, pipes, __pipe: lookupPipe, __typeCheck: typeCheck,
5453
5658
  // reacting to changes
5454
5659
  watch, effect: watchEffect, tick: nextTick, onCleanup: onScopeDispose,
5455
5660
  // sharing data
@@ -5464,7 +5669,7 @@
5464
5669
 
5465
5670
  // the text-only helpers the build tool reuses, so built code matches the browser exactly
5466
5671
  Object.defineProperty(Kreftjs, '__internal', {
5467
- value: { expandBlocks, prepareTemplate, splitKreft, splitInterp, exactBrace, stripBrace, interpToExpr, quoteBraceAttrs, expandSelfClosing, scopeCss, hashString, propType, simplePathRE, fnExpRE, forAliasRE, makeKreftDef, compileFn, evaluate, stripTypeScript, stripTsParams },
5672
+ value: { expandBlocks, prepareTemplate, splitKreft, splitInterp, exactBrace, stripBrace, interpToExpr, quoteBraceAttrs, expandSelfClosing, scopeCss, hashString, propType, typeCheck, caseCond, simplePathRE, fnExpRE, forAliasRE, makeKreftDef, compileFn, evaluate, stripTypeScript, stripTsParams },
5468
5673
  });
5469
5674
 
5470
5675
  if (typeof module !== 'undefined' && module.exports) module.exports = Kreftjs;