kitcn 0.16.0 → 0.17.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.
Files changed (38) hide show
  1. package/dist/aggregate/index.d.ts +1 -1
  2. package/dist/auth/client/index.js +1 -1
  3. package/dist/auth/index.js +19 -21
  4. package/dist/auth/nextjs/index.d.ts +1 -1
  5. package/dist/auth/nextjs/index.js +4 -4
  6. package/dist/{auth-store-ssZDPa37.js → auth-store-BnGZxmnY.js} +4 -1
  7. package/dist/{backend-core-DqPydYyx.mjs → backend-core-BsKP1LVg.mjs} +204 -164
  8. package/dist/{builder-DBgto1yn.js → builder-f4F_NRvK.js} +245 -153
  9. package/dist/{caller-factory-NEfgD5E0.js → caller-factory-DHywSoGZ.js} +7 -5
  10. package/dist/cli.mjs +14 -7
  11. package/dist/crpc/index.js +1 -127
  12. package/dist/{middleware-Bg-PdtrI.js → middleware-Cgrv2jIu.js} +1 -1
  13. package/dist/orm/index.d.ts +1 -1
  14. package/dist/orm/index.js +486 -121
  15. package/dist/plugins/index.js +1 -1
  16. package/dist/{procedure-caller-9m6NBxQu.js → procedure-caller-Rj6z3ai7.js} +1 -1
  17. package/dist/{procedure-name-Cy1AxayA.d.ts → procedure-name-Bo5KMcqc.d.ts} +15 -3
  18. package/dist/{query-context-ydn9kb6P.js → query-context-C90vNlc9.js} +131 -30
  19. package/dist/query-options-C_eBSIXG.js +247 -0
  20. package/dist/ratelimit/index.d.ts +26 -6
  21. package/dist/ratelimit/index.js +427 -100
  22. package/dist/ratelimit/react/index.d.ts +14 -0
  23. package/dist/ratelimit/react/index.js +149 -16
  24. package/dist/react/index.d.ts +3 -1
  25. package/dist/react/index.js +48 -15
  26. package/dist/rsc/index.js +22 -33
  27. package/dist/server/index.d.ts +1 -1
  28. package/dist/server/index.js +3 -3
  29. package/dist/solid/index.js +19 -5
  30. package/dist/watcher.mjs +2 -2
  31. package/dist/{where-clause-compiler-WF9UcrAB.d.ts → where-clause-compiler-BRhLW1dp.d.ts} +51 -0
  32. package/package.json +1 -1
  33. package/skills/kitcn/SKILL.md +1 -0
  34. package/skills/kitcn/references/features/create-plugins.md +1 -1
  35. package/skills/kitcn/references/features/orm.md +11 -1
  36. package/skills/kitcn/references/features/ratelimit.md +105 -0
  37. package/skills/kitcn/references/setup/server.md +1 -1
  38. package/dist/query-options-C96zLANM.js +0 -121
@@ -3931,6 +3931,14 @@ async function runAnalyze(argv) {
3931
3931
 
3932
3932
  //#endregion
3933
3933
  //#region src/shared/meta-utils.ts
3934
+ /**
3935
+ * Suffix of a parse snapshot — a mirror of a Convex module that kitcn builds
3936
+ * predating in-memory evaluation could strand in a project. Codegen writes no
3937
+ * snapshot, so this exists only so readers of the Convex functions directory
3938
+ * skip a stranded file rather than parse it as a real module. Nothing deletes
3939
+ * one: kitcn cannot prove it owns a file it did not write.
3940
+ */
3941
+ const PARSE_SNAPSHOT_SUFFIX = ".kitcn-parse.ts";
3934
3942
  /** Files to exclude from meta generation */
3935
3943
  const EXCLUDED_FILES = new Set([
3936
3944
  "schema.ts",
@@ -3949,6 +3957,7 @@ const DIRECT_CODEGEN_META_CAPTURE_REGEX = /\b_crpc(?:Meta|HttpRoute)\b/;
3949
3957
  * Filters out private files/directories (prefixed with _) and config files.
3950
3958
  */
3951
3959
  function isValidConvexFile(file) {
3960
+ if (file.endsWith(PARSE_SNAPSHOT_SUFFIX)) return false;
3952
3961
  if (file.endsWith(".runtime.ts")) return false;
3953
3962
  if (file.endsWith(".test.ts")) return false;
3954
3963
  if (file.endsWith(".spec.ts")) return false;
@@ -5048,19 +5057,17 @@ function isCRPCHttpRouter(value) {
5048
5057
  async function parseModuleRuntime(filePath, jitiInstance) {
5049
5058
  const source = fs.readFileSync(filePath, "utf8");
5050
5059
  const rewrittenSource = source.replaceAll(/from\s+(['"])kitcn\/server\1/g, `from ${JSON.stringify(normalizeImportPath(getProjectServerParserShimPath()))}`);
5051
- const importPath = rewrittenSource === source ? filePath : (() => {
5052
- const tempFilePath = `${filePath}.kitcn-parse.ts`;
5053
- fs.writeFileSync(tempFilePath, rewrittenSource, "utf8");
5054
- return tempFilePath;
5055
- })();
5056
5060
  const result = {};
5057
5061
  const httpRoutes = {};
5058
5062
  const procedures = [];
5059
5063
  const isHttp = filePath.endsWith("http.ts");
5060
- const module = await jitiInstance.import(importPath);
5064
+ const module = rewrittenSource === source ? await jitiInstance.import(filePath) : await jitiInstance.evalModule(rewrittenSource, {
5065
+ async: true,
5066
+ ext: ".ts",
5067
+ filename: filePath
5068
+ });
5061
5069
  if (!module || typeof module !== "object") {
5062
5070
  if (isHttp) logger.error(" http.ts: module is empty or not an object");
5063
- if (importPath !== filePath) fs.rmSync(importPath, { force: true });
5064
5071
  return {
5065
5072
  meta: null,
5066
5073
  httpRoutes: {},
@@ -5095,7 +5102,6 @@ async function parseModuleRuntime(filePath, jitiInstance) {
5095
5102
  };
5096
5103
  }
5097
5104
  }
5098
- if (importPath !== filePath) fs.rmSync(importPath, { force: true });
5099
5105
  return {
5100
5106
  meta: Object.keys(result).length > 0 ? result : null,
5101
5107
  httpRoutes,
@@ -6939,6 +6945,92 @@ const normalizeLockfileScaffoldPath = (value) => {
6939
6945
  return normalized;
6940
6946
  };
6941
6947
 
6948
+ //#endregion
6949
+ //#region src/cli/utils/typescript-runtime.ts
6950
+ const require$1 = createRequire(import.meta.url);
6951
+ let cachedTypeScript = null;
6952
+ const loadTypeScript = () => {
6953
+ if (cachedTypeScript) return cachedTypeScript;
6954
+ const loaded = require$1("typescript");
6955
+ const resolved = "default" in loaded && loaded.default ? loaded.default : loaded;
6956
+ cachedTypeScript = resolved;
6957
+ return resolved;
6958
+ };
6959
+ const createTypeScriptProxy = () => new Proxy({}, { get(_target, property) {
6960
+ return loadTypeScript()[property];
6961
+ } });
6962
+
6963
+ //#endregion
6964
+ //#region src/cli/registry/schema-chain.ts
6965
+ const ts$3 = createTypeScriptProxy();
6966
+ const parse$2 = (source) => ts$3.createSourceFile("schema.ts", source, ts$3.ScriptTarget.Latest, true, ts$3.ScriptKind.TS);
6967
+ const isDefineSchemaCall = (node) => {
6968
+ if (!ts$3.isCallExpression(node)) return false;
6969
+ if (ts$3.isIdentifier(node.expression)) return node.expression.text === "defineSchema";
6970
+ return ts$3.isPropertyAccessExpression(node.expression) && node.expression.name.text === "defineSchema";
6971
+ };
6972
+ const findDefineSchemaCall = (sourceFile) => {
6973
+ let found = null;
6974
+ const visit = (node) => {
6975
+ if (found) return;
6976
+ if (isDefineSchemaCall(node)) {
6977
+ found = node;
6978
+ return;
6979
+ }
6980
+ ts$3.forEachChild(node, visit);
6981
+ };
6982
+ visit(sourceFile);
6983
+ return found;
6984
+ };
6985
+ /**
6986
+ * Resolve the real `defineSchema(...)` call and the methods chained onto it.
6987
+ *
6988
+ * Locating the call through the TypeScript AST is what keeps registration out
6989
+ * of comments and string literals that merely mention `defineSchema(`.
6990
+ */
6991
+ const findDefineSchemaChain = (source) => {
6992
+ const sourceFile = parse$2(source);
6993
+ const defineSchemaCall = findDefineSchemaCall(sourceFile);
6994
+ if (!defineSchemaCall) return null;
6995
+ const calls = [];
6996
+ let current = defineSchemaCall;
6997
+ while (current.parent && ts$3.isPropertyAccessExpression(current.parent) && current.parent.expression === current && current.parent.parent && ts$3.isCallExpression(current.parent.parent) && current.parent.parent.expression === current.parent) {
6998
+ const propertyAccess = current.parent;
6999
+ const call = current.parent.parent;
7000
+ const dotToken = propertyAccess.getChildren(sourceFile).find((child) => child.kind === ts$3.SyntaxKind.DotToken);
7001
+ if (!dotToken) throw new Error("Schema chain property access is missing its dot token.");
7002
+ calls.push({
7003
+ closeParenIndex: call.end - 1,
7004
+ dotIndex: dotToken.getStart(sourceFile),
7005
+ end: call.end,
7006
+ name: propertyAccess.name.text
7007
+ });
7008
+ current = call;
7009
+ }
7010
+ return {
7011
+ calls,
7012
+ defineSchemaEnd: defineSchemaCall.end
7013
+ };
7014
+ };
7015
+ /**
7016
+ * Whether `source` really calls `name()` somewhere, ignoring comments and
7017
+ * string literals.
7018
+ */
7019
+ const hasCallExpression = (source, name) => {
7020
+ const sourceFile = parse$2(source);
7021
+ let found = false;
7022
+ const visit = (node) => {
7023
+ if (found) return;
7024
+ if (ts$3.isCallExpression(node) && ts$3.isIdentifier(node.expression) && node.expression.text === name) {
7025
+ found = true;
7026
+ return;
7027
+ }
7028
+ ts$3.forEachChild(node, visit);
7029
+ };
7030
+ visit(sourceFile);
7031
+ return found;
7032
+ };
7033
+
6942
7034
  //#endregion
6943
7035
  //#region src/cli/registry/state.ts
6944
7036
  const getPluginLockfilePath = (functionsDir) => join(functionsDir, "plugins.lock.json");
@@ -7080,7 +7172,6 @@ const READ_OPTIONAL_RUNTIME_ENV_PROPERTY_RE = /\breadOptionalRuntimeEnv\s*:/m;
7080
7172
  const READ_OPTIONAL_RUNTIME_ENV_RE = /(\s*readOptionalRuntimeEnv\s*:\s*\[)([\s\S]*?)(\]\s*,?)/m;
7081
7173
  const LEADING_WHITESPACE_RE = /^\s*/;
7082
7174
  const STRING_LITERAL_ARRAY_ENTRY_RE = /^(['"])([^'"]+)\1$/;
7083
- const WHITESPACE_RE$2 = /\s/;
7084
7175
  const findMatchingObjectBraceIndex$1 = (source, openIndex) => {
7085
7176
  let depth = 0;
7086
7177
  let quote;
@@ -7390,79 +7481,28 @@ const getPlannedFileContent = (files, absolutePath) => {
7390
7481
  const normalizedPath = normalizePath$1(relative(process.cwd(), absolutePath));
7391
7482
  return files?.find((file) => file.path === normalizedPath)?.content;
7392
7483
  };
7393
- const skipWhitespace$2 = (source, start) => {
7394
- let index = start;
7395
- while (index < source.length && WHITESPACE_RE$2.test(source[index] ?? "")) index += 1;
7396
- return index;
7397
- };
7398
- const findBalancedParenEnd$1 = (source, openParenIndex) => {
7399
- let depth = 0;
7400
- for (let index = openParenIndex; index < source.length; index += 1) {
7401
- const char = source[index];
7402
- if (char === "(") {
7403
- depth += 1;
7404
- continue;
7405
- }
7406
- if (char !== ")") continue;
7407
- depth -= 1;
7408
- if (depth === 0) return index;
7409
- }
7410
- return -1;
7411
- };
7412
7484
  const findSchemaExtensionInsertIndex = (source) => {
7413
- const defineSchemaIndex = source.indexOf("defineSchema(");
7414
- if (defineSchemaIndex < 0) return {
7485
+ const chain = findDefineSchemaChain(source);
7486
+ if (!chain) return {
7415
7487
  closeParenIndex: -1,
7416
7488
  hasExtend: false,
7417
7489
  insertIndex: -1
7418
7490
  };
7419
- const defineSchemaOpenParenIndex = source.indexOf("(", defineSchemaIndex);
7420
- if (defineSchemaOpenParenIndex < 0) return {
7491
+ const firstCall = chain.calls[0];
7492
+ if (!firstCall) return {
7421
7493
  closeParenIndex: -1,
7422
7494
  hasExtend: false,
7423
- insertIndex: -1
7495
+ insertIndex: chain.defineSchemaEnd
7424
7496
  };
7425
- const defineSchemaCloseParenIndex = findBalancedParenEnd$1(source, defineSchemaOpenParenIndex);
7426
- if (defineSchemaCloseParenIndex < 0) return {
7427
- closeParenIndex: -1,
7428
- hasExtend: false,
7429
- insertIndex: -1
7497
+ if (firstCall.name === "extend") return {
7498
+ closeParenIndex: firstCall.closeParenIndex,
7499
+ hasExtend: true,
7500
+ insertIndex: firstCall.closeParenIndex
7430
7501
  };
7431
- const cursor = defineSchemaCloseParenIndex + 1;
7432
- while (cursor < source.length) {
7433
- const nextSegmentIndex = skipWhitespace$2(source, cursor);
7434
- if (source.startsWith(".relations(", nextSegmentIndex) || source.startsWith(".triggers(", nextSegmentIndex)) return {
7435
- closeParenIndex: -1,
7436
- hasExtend: false,
7437
- insertIndex: nextSegmentIndex
7438
- };
7439
- if (!source.startsWith(".extend(", nextSegmentIndex)) return {
7440
- closeParenIndex: -1,
7441
- hasExtend: false,
7442
- insertIndex: nextSegmentIndex
7443
- };
7444
- const extendOpenParenIndex = source.indexOf("(", nextSegmentIndex);
7445
- if (extendOpenParenIndex < 0) return {
7446
- closeParenIndex: -1,
7447
- hasExtend: false,
7448
- insertIndex: -1
7449
- };
7450
- const extendCloseParenIndex = findBalancedParenEnd$1(source, extendOpenParenIndex);
7451
- if (extendCloseParenIndex < 0) return {
7452
- closeParenIndex: -1,
7453
- hasExtend: false,
7454
- insertIndex: -1
7455
- };
7456
- return {
7457
- closeParenIndex: extendCloseParenIndex,
7458
- hasExtend: true,
7459
- insertIndex: extendCloseParenIndex
7460
- };
7461
- }
7462
7502
  return {
7463
7503
  closeParenIndex: -1,
7464
7504
  hasExtend: false,
7465
- insertIndex: cursor
7505
+ insertIndex: firstCall.dotIndex
7466
7506
  };
7467
7507
  };
7468
7508
  const buildSchemaRegistrationPlanFile = (functionsDir, descriptor, roots, bootstrapFiles) => {
@@ -7473,7 +7513,7 @@ const buildSchemaRegistrationPlanFile = (functionsDir, descriptor, roots, bootst
7473
7513
  const pluginFactory = schemaRegistration.importName;
7474
7514
  const pluginImportPath = resolveRelativeImportPath(schemaPath, join(schemaRegistration.target === "lib" ? roots.libRootDir : roots.functionsRootDir, schemaRegistration.path));
7475
7515
  let source = bootstrappedSchemaSource ?? fs.readFileSync(schemaPath, "utf8");
7476
- if (!source.includes(`${pluginFactory}()`)) {
7516
+ if (!hasCallExpression(source, pluginFactory)) {
7477
7517
  if (!new RegExp(`import\\s+\\{[^}]*\\b${pluginFactory}\\b[^}]*\\}\\s+from\\s+['"]${pluginImportPath}['"];?`).test(source)) source = `import { ${pluginFactory} } from '${pluginImportPath}';\n${source}`;
7478
7518
  const extensionTarget = findSchemaExtensionInsertIndex(source);
7479
7519
  if (extensionTarget.hasExtend && extensionTarget.closeParenIndex >= 0) source = `${source.slice(0, extensionTarget.closeParenIndex)}, ${pluginFactory}()${source.slice(extensionTarget.closeParenIndex)}`;
@@ -7654,27 +7694,11 @@ const buildPluginInstallPlan = async (params) => {
7654
7694
  };
7655
7695
  };
7656
7696
 
7657
- //#endregion
7658
- //#region src/cli/utils/typescript-runtime.ts
7659
- const require$1 = createRequire(import.meta.url);
7660
- let cachedTypeScript = null;
7661
- const loadTypeScript = () => {
7662
- if (cachedTypeScript) return cachedTypeScript;
7663
- const loaded = require$1("typescript");
7664
- const resolved = "default" in loaded && loaded.default ? loaded.default : loaded;
7665
- cachedTypeScript = resolved;
7666
- return resolved;
7667
- };
7668
- const createTypeScriptProxy = () => new Proxy({}, { get(_target, property) {
7669
- return loadTypeScript()[property];
7670
- } });
7671
-
7672
7697
  //#endregion
7673
7698
  //#region src/cli/registry/schema-ownership.ts
7674
7699
  const OBJECT_ENTRY_INDENT = " ";
7675
7700
  const LEADING_INDENT_RE = /^[ \t]*/;
7676
7701
  const LEGACY_MANAGED_COMMENT_RE = /^[ \t]*\/\* kitcn-managed [^*]+ \*\/\n?/gm;
7677
- const WHITESPACE_RE$1 = /\s/;
7678
7702
  const ts$2 = createTypeScriptProxy();
7679
7703
  let printer = null;
7680
7704
  const getPrinter = () => {
@@ -7920,13 +7944,15 @@ const mergeIndexEntries = (params) => {
7920
7944
  const targetParamName = params.existingInfo.indexParamName ?? params.existingInfo.varName ?? params.desiredInfo.indexParamName;
7921
7945
  const desiredParamName = params.desiredInfo.indexParamName ?? params.desiredInfo.varName;
7922
7946
  const desiredEntries = params.desiredInfo.indexEntries;
7947
+ const unparsedThirdArgText = params.existingInfo.thirdArgText && !params.existingInfo.indexEntries ? params.existingInfo.thirdArgText : null;
7923
7948
  if (!desiredEntries || desiredEntries.length === 0) return {
7924
7949
  changed: false,
7925
7950
  entries: params.existingInfo.indexEntries?.map((entry) => entry.getText(params.existingInfo.sourceFile)),
7926
7951
  indexParamName: targetParamName,
7927
- requiresIndexArg: Boolean(params.existingInfo.thirdArgText)
7952
+ requiresIndexArg: Boolean(params.existingInfo.thirdArgText),
7953
+ verbatimIndexArgText: unparsedThirdArgText
7928
7954
  };
7929
- if (params.existingInfo.thirdArgText && !params.existingInfo.indexEntries) throw new Error(`Schema patch conflict in ${params.displayPath}: ${params.pluginKey} indexes for table "${params.tableKey}" could not be merged into the existing schema callback.`);
7955
+ if (unparsedThirdArgText) throw new Error(`Schema patch conflict in ${params.displayPath}: ${params.pluginKey} indexes for table "${params.tableKey}" could not be merged into the existing schema callback.`);
7930
7956
  const existingEntries = params.existingInfo.indexEntries ?? [];
7931
7957
  const existingMap = new Map(existingEntries.map((entry) => [getIndexIdentity(entry, params.existingInfo.sourceFile), entry]));
7932
7958
  const nextEntries = existingEntries.map((entry) => entry.getText(params.existingInfo.sourceFile));
@@ -7946,13 +7972,14 @@ const mergeIndexEntries = (params) => {
7946
7972
  changed,
7947
7973
  entries: nextEntries,
7948
7974
  indexParamName: targetParamName,
7949
- requiresIndexArg: Boolean(params.existingInfo.thirdArgText ?? nextEntries.length > 0)
7975
+ requiresIndexArg: Boolean(params.existingInfo.thirdArgText ?? nextEntries.length > 0),
7976
+ verbatimIndexArgText: null
7950
7977
  };
7951
7978
  };
7952
7979
  const renderTableStatement = (params) => {
7953
7980
  const fieldsText = renderObjectLiteral(" ", params.fieldEntries.map(ensureTrailingComma));
7954
7981
  const indexEntries = params.indexEntries ?? [];
7955
- const indexBlock = indexEntries.length > 0 ? `,\n (${params.indexParamName ?? params.varName}) => ${renderArrayLiteral(" ", indexEntries)}` : "";
7982
+ const indexBlock = params.verbatimIndexArgText ? `,\n ${params.verbatimIndexArgText}` : indexEntries.length > 0 ? `,\n (${params.indexParamName ?? params.varName}) => ${renderArrayLiteral(" ", indexEntries)}` : "";
7956
7983
  return `export const ${params.varName} = convexTable(\n ${params.tableNameText},\n ${fieldsText}${indexBlock}\n);`;
7957
7984
  };
7958
7985
  const insertDeclaration = (source, declaration) => {
@@ -7992,7 +8019,8 @@ const mergeTableDeclaration = (params) => {
7992
8019
  indexEntries: indexMerge.requiresIndexArg ? indexMerge.entries : null,
7993
8020
  indexParamName: indexMerge.indexParamName,
7994
8021
  tableNameText: existingInfo.tableNameText,
7995
- varName: existingInfo.varName
8022
+ varName: existingInfo.varName,
8023
+ verbatimIndexArgText: indexMerge.verbatimIndexArgText
7996
8024
  });
7997
8025
  return {
7998
8026
  content: replaceRange(params.source, existingInfo.statement.getStart(existingInfo.sourceFile), existingInfo.statement.end, nextStatement),
@@ -8015,43 +8043,15 @@ const updateTablesObject = (source, registrations) => {
8015
8043
  if (!changed) return source;
8016
8044
  return replaceRange(source, info.object.getStart(info.sourceFile), info.object.end, renderObjectLiteral(getIndentAt(source, info.object.getStart(info.sourceFile)), existingEntries));
8017
8045
  };
8018
- const skipWhitespace$1 = (source, start) => {
8019
- let cursor = start;
8020
- while (cursor < source.length && WHITESPACE_RE$1.test(source[cursor])) cursor += 1;
8021
- return cursor;
8022
- };
8023
- const findBalancedParenEnd = (source, openParenIndex) => {
8024
- let depth = 0;
8025
- for (let index = openParenIndex; index < source.length; index += 1) {
8026
- const char = source[index];
8027
- if (char === "(") depth += 1;
8028
- else if (char === ")") {
8029
- depth -= 1;
8030
- if (depth === 0) return index;
8031
- }
8032
- }
8033
- return -1;
8034
- };
8035
8046
  const findRelationsInsertIndex = (source) => {
8036
- const defineSchemaIndex = source.indexOf("defineSchema(");
8037
- if (defineSchemaIndex < 0) return -1;
8038
- const defineSchemaOpenParenIndex = source.indexOf("(", defineSchemaIndex);
8039
- if (defineSchemaOpenParenIndex < 0) return -1;
8040
- const defineSchemaCloseParenIndex = findBalancedParenEnd(source, defineSchemaOpenParenIndex);
8041
- if (defineSchemaCloseParenIndex < 0) return -1;
8042
- let cursor = defineSchemaCloseParenIndex + 1;
8043
- while (cursor < source.length) {
8044
- const nextSegmentIndex = skipWhitespace$1(source, cursor);
8045
- if (source.startsWith(".relations(", nextSegmentIndex)) return nextSegmentIndex;
8046
- if (source.startsWith(".triggers(", nextSegmentIndex)) return nextSegmentIndex;
8047
- if (!source.startsWith(".extend(", nextSegmentIndex)) return nextSegmentIndex;
8048
- const extendOpenParenIndex = source.indexOf("(", nextSegmentIndex);
8049
- if (extendOpenParenIndex < 0) return -1;
8050
- const extendCloseParenIndex = findBalancedParenEnd(source, extendOpenParenIndex);
8051
- if (extendCloseParenIndex < 0) return -1;
8052
- cursor = extendCloseParenIndex + 1;
8053
- }
8054
- return cursor;
8047
+ const chain = findDefineSchemaChain(source);
8048
+ if (!chain) return -1;
8049
+ let insertIndex = chain.defineSchemaEnd;
8050
+ for (const call of chain.calls) {
8051
+ if (call.name !== "extend") return call.dotIndex;
8052
+ insertIndex = call.end;
8053
+ }
8054
+ return insertIndex;
8055
8055
  };
8056
8056
  const mergeRelationProperty = (params) => {
8057
8057
  const desiredInfo = readPropertyObject(params.desiredRelation, params.tableKey);
@@ -8171,13 +8171,32 @@ const readManagedChecksumFromSource = (source, unit) => {
8171
8171
  };
8172
8172
  const mergeOrmImports = (source, importNames) => {
8173
8173
  if (importNames.length === 0) return source;
8174
- const sourceFile = parseSource(source);
8175
- const ormImport = sourceFile.statements.find((statement) => ts$2.isImportDeclaration(statement) && isStringLiteralLike(statement.moduleSpecifier) && statement.moduleSpecifier.text === "kitcn/orm");
8176
- if (!ormImport) return `${`import {\n ${[...new Set(importNames)].sort().join(",\n ")},\n} from 'kitcn/orm';\n\n`}${source}`;
8177
- if (!ormImport.importClause?.namedBindings || !ts$2.isNamedImports(ormImport.importClause.namedBindings)) return source;
8178
- const existingImports = ormImport.importClause.namedBindings.elements.map((element) => element.getText(sourceFile));
8179
- const nextImport = `import {\n ${[...new Set([...existingImports, ...importNames])].sort((a, b) => a.localeCompare(b)).join(",\n ")},\n} from 'kitcn/orm';`;
8180
- return replaceRange(source, ormImport.getStart(sourceFile), ormImport.end, nextImport);
8174
+ const requiredNames = new Set(importNames);
8175
+ const initialSourceFile = parseSource(source);
8176
+ const typeOnlyReplacements = initialSourceFile.statements.flatMap((statement) => {
8177
+ if (!ts$2.isImportDeclaration(statement) || !isStringLiteralLike(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "kitcn/orm" || !statement.importClause?.isTypeOnly || !statement.importClause.namedBindings || !ts$2.isNamedImports(statement.importClause.namedBindings)) return [];
8178
+ const remainingElements = statement.importClause.namedBindings.elements.filter((element) => !requiredNames.has(element.name.text));
8179
+ if (remainingElements.length === statement.importClause.namedBindings.elements.length) return [];
8180
+ const namedBindings = remainingElements.length > 0 ? `{ ${remainingElements.map((element) => element.getText(initialSourceFile)).join(", ")} }` : null;
8181
+ const bindings = [statement.importClause.name?.text, namedBindings].filter((value) => Boolean(value));
8182
+ const replacement = bindings.length > 0 ? `import type ${bindings.join(", ")} from ${statement.moduleSpecifier.getText(initialSourceFile)};` : "";
8183
+ return [{
8184
+ end: statement.end,
8185
+ replacement,
8186
+ start: statement.getStart(initialSourceFile)
8187
+ }];
8188
+ });
8189
+ let workingSource = source;
8190
+ for (const replacement of typeOnlyReplacements.sort((a, b) => b.start - a.start)) workingSource = replaceRange(workingSource, replacement.start, replacement.end, replacement.replacement);
8191
+ const sourceFile = parseSource(workingSource);
8192
+ const ormImport = sourceFile.statements.find((statement) => ts$2.isImportDeclaration(statement) && isStringLiteralLike(statement.moduleSpecifier) && statement.moduleSpecifier.text === "kitcn/orm" && statement.importClause !== void 0 && !statement.importClause.isTypeOnly && statement.importClause.namedBindings !== void 0 && ts$2.isNamedImports(statement.importClause.namedBindings));
8193
+ if (!ormImport) return `${`import {\n ${[...new Set(importNames)].sort().join(",\n ")},\n} from 'kitcn/orm';\n\n`}${workingSource}`;
8194
+ const namedBindings = ormImport.importClause?.namedBindings;
8195
+ const specifiersByLocalName = /* @__PURE__ */ new Map();
8196
+ for (const element of namedBindings.elements) specifiersByLocalName.set(element.name.text, element.getText(sourceFile));
8197
+ for (const name of importNames) specifiersByLocalName.set(name, name);
8198
+ const nextImport = `import {\n ${[...specifiersByLocalName.values()].sort((a, b) => a.localeCompare(b)).join(",\n ")},\n} from 'kitcn/orm';`;
8199
+ return replaceRange(workingSource, ormImport.getStart(sourceFile), ormImport.end, nextImport);
8181
8200
  };
8182
8201
  const hasSchemaFragment = (source, tableKey) => {
8183
8202
  if (new RegExp(`convexTable\\(\\s*['"]${escapeRegex(tableKey)}['"]`).test(source)) return true;
@@ -8258,6 +8277,39 @@ const reconcileRootSchemaOwnership = async (params) => {
8258
8277
  };
8259
8278
  };
8260
8279
 
8280
+ //#endregion
8281
+ //#region src/cli/registry/items/ratelimit/ratelimit-crpc.ts
8282
+ const CRPC_META_RATELIMIT_RE = /ratelimit\?: string;/;
8283
+ const CRPC_RATELIMIT_BUCKET_RE = /ratelimit\?: RatelimitBucket;/;
8284
+ const CRPC_CREATE_LINE_RE = /const c = initCRPC\.create\(\);/;
8285
+ const CRPC_META_CREATE_RE = /const c = initCRPC\s*\.meta<\{\s*([\s\S]*?)\s*\}>\(\)\s*\.create\(\);/;
8286
+ const PUBLIC_MUTATION_LINE_RE = /export const publicMutation = c\.mutation(?:\.use\(ratelimit\.middleware\(\)\))?;/;
8287
+ /**
8288
+ * Wire ratelimit middleware into an existing `crpc.ts` source.
8289
+ *
8290
+ * Shared so other plugins can reproduce the ratelimit-patched baseline and
8291
+ * recognize it as managed content rather than a user edit.
8292
+ */
8293
+ const patchRatelimitCrpcSource = (input) => {
8294
+ let source = input;
8295
+ if (!source.includes("from './plugins/ratelimit/plugin'")) if (source.includes("import type { ActionCtx, MutationCtx, QueryCtx }")) source = source.replace("import type { ActionCtx, MutationCtx, QueryCtx } from '../functions/generated/server';", `import { type RatelimitBucket, ratelimit } from './plugins/ratelimit/plugin';
8296
+ import type { ActionCtx, MutationCtx, QueryCtx } from '../functions/generated/server';`);
8297
+ else source = `import { type RatelimitBucket, ratelimit } from './plugins/ratelimit/plugin';\n${source}`;
8298
+ if (CRPC_META_RATELIMIT_RE.test(source)) source = source.replace(CRPC_META_RATELIMIT_RE, "ratelimit?: RatelimitBucket;");
8299
+ if (!CRPC_RATELIMIT_BUCKET_RE.test(source)) {
8300
+ if (CRPC_META_CREATE_RE.test(source)) source = source.replace(CRPC_META_CREATE_RE, (_match, fields) => {
8301
+ return `const c = initCRPC\n .meta<{\n${[...fields.split("\n").map((line) => line.trim()).filter(Boolean), "ratelimit?: RatelimitBucket;"].map((line) => ` ${line}`).join("\n")}\n }>()\n .create();`;
8302
+ });
8303
+ else if (CRPC_CREATE_LINE_RE.test(source)) source = source.replace(CRPC_CREATE_LINE_RE, `const c = initCRPC
8304
+ .meta<{
8305
+ ratelimit?: RatelimitBucket;
8306
+ }>()
8307
+ .create();`);
8308
+ }
8309
+ if (PUBLIC_MUTATION_LINE_RE.test(source)) source = source.replace(PUBLIC_MUTATION_LINE_RE, "export const publicMutation = c.mutation.use(ratelimit.middleware());");
8310
+ return source;
8311
+ };
8312
+
8261
8313
  //#endregion
8262
8314
  //#region src/cli/registry/items/auth/auth.template.ts
8263
8315
  const AUTH_TEMPLATE = `import { convex } from 'kitcn/auth';
@@ -10358,7 +10410,7 @@ function buildAuthCrpcRegistrationPlanFile(params) {
10358
10410
  kind: "scaffold",
10359
10411
  filePath: crpcPath,
10360
10412
  content: renderAuthCrpcTemplate({ withRatelimit: source.includes("from './plugins/ratelimit/plugin'") || source.includes("RatelimitBucket") || source.includes("ratelimit.middleware()") }),
10361
- managedBaselineContent: baselineCrpcSource,
10413
+ managedBaselineContent: [baselineCrpcSource, patchRatelimitCrpcSource(baselineCrpcSource)],
10362
10414
  createReason: "Create crpc.ts with auth-aware procedures.",
10363
10415
  updateReason: "Register auth-aware procedures in crpc.ts.",
10364
10416
  skipReason: "Auth-aware procedures are already registered in crpc.ts."
@@ -10398,6 +10450,7 @@ app.use(
10398
10450
  filePath: httpPath,
10399
10451
  content: source,
10400
10452
  managedBaselineContent: baselineHttpSource,
10453
+ requiresExplicitOverwrite: false,
10401
10454
  createReason: "Create http.ts with auth middleware.",
10402
10455
  updateReason: "Register auth middleware in http.ts.",
10403
10456
  skipReason: "Auth middleware is already registered in http.ts."
@@ -10555,6 +10608,7 @@ export default http;
10555
10608
  kind: "scaffold",
10556
10609
  filePath: httpPath,
10557
10610
  content: source,
10611
+ requiresExplicitOverwrite: false,
10558
10612
  createReason: "Create Convex http.ts with auth routes.",
10559
10613
  updateReason: "Register auth routes in Convex http.ts.",
10560
10614
  skipReason: "Convex http.ts already registers auth routes."
@@ -10592,6 +10646,7 @@ function buildAuthConvexNextProviderPlanFile(params) {
10592
10646
  kind: "scaffold",
10593
10647
  filePath: providerPath,
10594
10648
  content: source,
10649
+ requiresExplicitOverwrite: false,
10595
10650
  createReason: "Create auth-aware Convex client provider.",
10596
10651
  updateReason: "Update Convex client provider with auth.",
10597
10652
  skipReason: "Convex client provider already includes auth."
@@ -10616,6 +10671,7 @@ function buildAuthConvexStartProviderPlanFile(params) {
10616
10671
  kind: "scaffold",
10617
10672
  filePath: providerPath,
10618
10673
  content: patchAuthConvexProviderSource(fs.readFileSync(providerPath, "utf8")),
10674
+ requiresExplicitOverwrite: false,
10619
10675
  createReason: "Create auth-aware Start provider.",
10620
10676
  updateReason: "Update Start provider with auth.",
10621
10677
  skipReason: "Start provider already includes auth."
@@ -10634,6 +10690,7 @@ function buildAuthConvexReactEntryPlanFile(params) {
10634
10690
  kind: "scaffold",
10635
10691
  filePath: entryPath,
10636
10692
  content: source,
10693
+ requiresExplicitOverwrite: false,
10637
10694
  createReason: "Create auth-aware client entry.",
10638
10695
  updateReason: "Update client entry with auth.",
10639
10696
  skipReason: "Client entry already includes auth."
@@ -10897,11 +10954,6 @@ export function ratelimitExtension() {
10897
10954
 
10898
10955
  //#endregion
10899
10956
  //#region src/cli/registry/items/ratelimit/ratelimit-item.ts
10900
- const CRPC_META_RATELIMIT_RE = /ratelimit\?: string;/;
10901
- const CRPC_RATELIMIT_BUCKET_RE = /ratelimit\?: RatelimitBucket;/;
10902
- const CRPC_CREATE_LINE_RE = /const c = initCRPC\.create\(\);/;
10903
- const CRPC_META_CREATE_RE = /const c = initCRPC\s*\.meta<\{\s*([\s\S]*?)\s*\}>\(\)\s*\.create\(\);/;
10904
- const PUBLIC_MUTATION_LINE_RE = /export const publicMutation = c\.mutation(?:\.use\(ratelimit\.middleware\(\)\))?;/;
10905
10957
  const RATELIMIT_FILES = [createRegistryFile({
10906
10958
  id: "ratelimit-schema",
10907
10959
  path: "schema.ts",
@@ -10922,27 +10974,12 @@ function buildRatelimitCrpcRegistrationPlanFile(params) {
10922
10974
  functionsDir: params.functionsDir,
10923
10975
  crpcFilePath: crpcPath
10924
10976
  });
10925
- let source = fs.existsSync(crpcPath) ? fs.readFileSync(crpcPath, "utf8") : baselineCrpcSource;
10926
- if (!source.includes("from './plugins/ratelimit/plugin'")) if (source.includes("import type { ActionCtx, MutationCtx, QueryCtx }")) source = source.replace("import type { ActionCtx, MutationCtx, QueryCtx } from '../functions/generated/server';", `import { type RatelimitBucket, ratelimit } from './plugins/ratelimit/plugin';
10927
- import type { ActionCtx, MutationCtx, QueryCtx } from '../functions/generated/server';`);
10928
- else source = `import { type RatelimitBucket, ratelimit } from './plugins/ratelimit/plugin';\n${source}`;
10929
- if (CRPC_META_RATELIMIT_RE.test(source)) source = source.replace(CRPC_META_RATELIMIT_RE, "ratelimit?: RatelimitBucket;");
10930
- if (!CRPC_RATELIMIT_BUCKET_RE.test(source)) {
10931
- if (CRPC_META_CREATE_RE.test(source)) source = source.replace(CRPC_META_CREATE_RE, (_match, fields) => {
10932
- return `const c = initCRPC\n .meta<{\n${[...fields.split("\n").map((line) => line.trim()).filter(Boolean), "ratelimit?: RatelimitBucket;"].map((line) => ` ${line}`).join("\n")}\n }>()\n .create();`;
10933
- });
10934
- else if (CRPC_CREATE_LINE_RE.test(source)) source = source.replace(CRPC_CREATE_LINE_RE, `const c = initCRPC
10935
- .meta<{
10936
- ratelimit?: RatelimitBucket;
10937
- }>()
10938
- .create();`);
10939
- }
10940
- if (PUBLIC_MUTATION_LINE_RE.test(source)) source = source.replace(PUBLIC_MUTATION_LINE_RE, "export const publicMutation = c.mutation.use(ratelimit.middleware());");
10941
10977
  return createPlanFile({
10942
10978
  kind: "scaffold",
10943
10979
  filePath: crpcPath,
10944
- content: source,
10980
+ content: patchRatelimitCrpcSource(fs.existsSync(crpcPath) ? fs.readFileSync(crpcPath, "utf8") : baselineCrpcSource),
10945
10981
  managedBaselineContent: baselineCrpcSource,
10982
+ requiresExplicitOverwrite: false,
10946
10983
  createReason: "Create crpc.ts with ratelimit middleware.",
10947
10984
  updateReason: "Register ratelimit middleware in crpc.ts.",
10948
10985
  skipReason: "Ratelimit middleware is already registered in crpc.ts."
@@ -12263,6 +12300,7 @@ function buildResendHttpRegistrationPlanFile(params) {
12263
12300
  filePath: httpPath,
12264
12301
  content: source,
12265
12302
  managedBaselineContent: baselineHttpSource,
12303
+ requiresExplicitOverwrite: false,
12266
12304
  createReason: "Create http.ts with resend webhook route.",
12267
12305
  updateReason: "Register resend webhook in http.ts.",
12268
12306
  skipReason: "Resend webhook is already registered in http.ts."
@@ -15541,7 +15579,8 @@ async function runScaffoldCommandFlow(params) {
15541
15579
  cwd: scaffoldProjectDir,
15542
15580
  created: applyResult.created,
15543
15581
  updated: applyResult.updated,
15544
- skipped: applyResult.skipped,
15582
+ skipped: [...applyResult.skipped, ...applyResult.refused],
15583
+ refused: applyResult.refused,
15545
15584
  usedShadcn: params.template !== void 0 && params.template !== "expo",
15546
15585
  template: params.template ?? null,
15547
15586
  codegen: codegenResult.codegen,
@@ -15673,8 +15712,9 @@ async function applyPluginInstallPlanFiles(files, options) {
15673
15712
  const result = {
15674
15713
  created: [],
15675
15714
  manualActions: [],
15676
- updated: [],
15677
- skipped: []
15715
+ refused: [],
15716
+ skipped: [],
15717
+ updated: []
15678
15718
  };
15679
15719
  for (const file of files) {
15680
15720
  if (file.manualActions?.length) result.manualActions.push(...file.manualActions);
@@ -15690,7 +15730,7 @@ async function applyPluginInstallPlanFiles(files, options) {
15690
15730
  continue;
15691
15731
  }
15692
15732
  const managedBaselines = Array.isArray(file.managedBaselineContent) ? file.managedBaselineContent : typeof file.managedBaselineContent === "string" ? [file.managedBaselineContent] : [];
15693
- const requiresExplicitOverwrite = file.requiresExplicitOverwrite ?? (file.kind === "config" || file.kind === "scaffold" && file.templateId !== void 0 || file.kind === "env" && file.templateId !== LOCAL_CONVEX_ENV_TEMPLATE_ID && file.templateId !== KITCN_ENV_HELPER_TEMPLATE_ID);
15733
+ const requiresExplicitOverwrite = file.requiresExplicitOverwrite ?? (file.kind === "config" || file.kind === "scaffold" || file.kind === "env" && file.templateId !== LOCAL_CONVEX_ENV_TEMPLATE_ID && file.templateId !== KITCN_ENV_HELPER_TEMPLATE_ID);
15694
15734
  const existingContent = file.existingContent;
15695
15735
  const matchesManagedBaseline = typeof existingContent === "string" && managedBaselines.some((managedBaselineContent) => isContentEquivalent({
15696
15736
  filePath: file.path,
@@ -15700,7 +15740,7 @@ async function applyPluginInstallPlanFiles(files, options) {
15700
15740
  let shouldOverwrite = options.overwrite || !requiresExplicitOverwrite || matchesManagedBaseline;
15701
15741
  if (!shouldOverwrite && !options.yes && options.promptAdapter.isInteractive()) shouldOverwrite = await options.promptAdapter.confirm(`Overwrite ${file.path}?`);
15702
15742
  if (!shouldOverwrite) {
15703
- result.skipped.push(file.path);
15743
+ result.refused.push(file.path);
15704
15744
  continue;
15705
15745
  }
15706
15746
  fs.mkdirSync(dirname(absolutePath), { recursive: true });
@@ -16970,4 +17010,4 @@ function isEntryPoint(entry, filename) {
16970
17010
  }
16971
17011
 
16972
17012
  //#endregion
16973
- export { promptForScaffoldTemplateSelection as $, resolveCodegenTrimSegments as A, highlighter as At, runConfiguredCodegen as B, isEntryPoint as C, formatDependencyInstallCommand as Ct, parseInitCommandArgs as D, generateMeta as Dt, parseBackendRunJson as E, stripConvexCommandNoise as Et, resolveRunDeps as F, runMigrationFlow as G, runDevSchemaBackfillIfNeeded as H, runAfterScaffoldScript as I, withWorkingDirectory as J, trackProcess as K, runAggregateBackfillFlow as L, resolveDocTopic as M, resolveInitProjectDir as N, readPackageVersions as O, getConvexConfig as Ot, resolveMigrationConfig as P, promptForPluginSelection as Q, runAggregatePruneFlow as R, isConvexDevPreRunConflictFlag as S, detectPackageManager as St, parseArgs as T, serializeEnvValue as Tt, runInitCommandFlow as U, runConvexInitIfNeeded as V, runMigrationCreate as W, collectPluginScaffoldTemplates as X, createSpinner as Y, filterScaffoldTemplatePathMap as Z, formatInfoOutput as _, applyPlanningDependencyInstall as _t, cleanup as a, getPluginCatalogEntry as at, getDevAggregateBackfillStatePath as b, resolveSupportedDependencyWarnings as bt, createCommandEnv as c, buildPluginInstallPlan as ct, extractBackfillCliOptions as d, collectInstalledPluginKeys as dt, resolveAddTemplateDefaults as et, extractConcaveRunTargetArgs as f, getPluginLockfilePath as ft, formatDocsOutput as g, applyDependencyHintsInstall as gt, extractResetCliOptions as h, resolveSchemaInstalledPlugins as ht, buildInitializationPlan as i, resolveTemplatesByIdOrThrow as it, resolveConfiguredBackend as j, resolveBackfillConfig as k, logger as kt, ensureConvexGitignoreEntry as l, resolvePluginScaffoldRoots as lt, extractMigrationDownOptions as m, readPluginLockfile as mt, applyPluginInstallPlanFiles as n, resolvePresetScaffoldTemplates as nt, createBackendAdapter as o, getSupportedPluginKeys as ot, extractMigrationCliOptions as p, getSchemaFilePath as pt, withLocalCodegenEnv as q, assertNoRemovedDevPreRunFlag as r, resolveTemplateSelectionSource as rt, createBackendCommandEnv as s, isSupportedPluginKey as st, applyDependencyInstallPlan as t, resolvePluginPreset as tt, extractBackendRunTargetArgs as u, assertSchemaFileExists as ut, getAggregateBackfillDeploymentKey as v, applyPluginDependencyInstall as vt, isInitialized as w, resolveAuthEnvState as wt, hasRemoteConvexDeploymentEnv as x, resolveProjectScaffoldContext as xt, getConvexDeploymentCommandEnv as y, inspectPluginDependencyInstall as yt, runBackendFunction as z };
17013
+ export { promptForScaffoldTemplateSelection as $, resolveCodegenTrimSegments as A, PARSE_SNAPSHOT_SUFFIX as At, runConfiguredCodegen as B, isEntryPoint as C, formatDependencyInstallCommand as Ct, parseInitCommandArgs as D, generateMeta as Dt, parseBackendRunJson as E, stripConvexCommandNoise as Et, resolveRunDeps as F, runMigrationFlow as G, runDevSchemaBackfillIfNeeded as H, runAfterScaffoldScript as I, withWorkingDirectory as J, trackProcess as K, runAggregateBackfillFlow as L, resolveDocTopic as M, resolveInitProjectDir as N, readPackageVersions as O, getConvexConfig as Ot, resolveMigrationConfig as P, promptForPluginSelection as Q, runAggregatePruneFlow as R, isConvexDevPreRunConflictFlag as S, detectPackageManager as St, parseArgs as T, serializeEnvValue as Tt, runInitCommandFlow as U, runConvexInitIfNeeded as V, runMigrationCreate as W, collectPluginScaffoldTemplates as X, createSpinner as Y, filterScaffoldTemplatePathMap as Z, formatInfoOutput as _, applyPlanningDependencyInstall as _t, cleanup as a, getPluginCatalogEntry as at, getDevAggregateBackfillStatePath as b, resolveSupportedDependencyWarnings as bt, createCommandEnv as c, buildPluginInstallPlan as ct, extractBackfillCliOptions as d, collectInstalledPluginKeys as dt, resolveAddTemplateDefaults as et, extractConcaveRunTargetArgs as f, getPluginLockfilePath as ft, formatDocsOutput as g, applyDependencyHintsInstall as gt, extractResetCliOptions as h, resolveSchemaInstalledPlugins as ht, buildInitializationPlan as i, resolveTemplatesByIdOrThrow as it, resolveConfiguredBackend as j, highlighter as jt, resolveBackfillConfig as k, logger as kt, ensureConvexGitignoreEntry as l, resolvePluginScaffoldRoots as lt, extractMigrationDownOptions as m, readPluginLockfile as mt, applyPluginInstallPlanFiles as n, resolvePresetScaffoldTemplates as nt, createBackendAdapter as o, getSupportedPluginKeys as ot, extractMigrationCliOptions as p, getSchemaFilePath as pt, withLocalCodegenEnv as q, assertNoRemovedDevPreRunFlag as r, resolveTemplateSelectionSource as rt, createBackendCommandEnv as s, isSupportedPluginKey as st, applyDependencyInstallPlan as t, resolvePluginPreset as tt, extractBackendRunTargetArgs as u, assertSchemaFileExists as ut, getAggregateBackfillDeploymentKey as v, applyPluginDependencyInstall as vt, isInitialized as w, resolveAuthEnvState as wt, hasRemoteConvexDeploymentEnv as x, resolveProjectScaffoldContext as xt, getConvexDeploymentCommandEnv as y, inspectPluginDependencyInstall as yt, runBackendFunction as z };