kitcn 0.23.0 → 0.25.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/aggregate/index.d.ts +11 -1
- package/dist/aggregate/index.js +1 -1
- package/dist/auth/generated/index.d.ts +1 -1
- package/dist/auth/generated/index.js +1 -1
- package/dist/auth/http/index.d.ts +1 -1
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/index.js +10 -9
- package/dist/auth/nextjs/index.d.ts +2 -2
- package/dist/auth/nextjs/index.js +3 -3
- package/dist/auth/start/server/index.d.ts +1 -1
- package/dist/auth/start/server/index.js +1 -1
- package/dist/{builder-CsxVc5xC.js → builder-1Vx-tEnS.js} +3 -3
- package/dist/{caller-factory-CWa0ELLD.js → caller-factory-Dd3H7j3V.js} +3 -3
- package/dist/{where-clause-compiler-B_H3oio5.d.ts → capabilities-Bem7xvGK.d.ts} +2958 -4145
- package/dist/cli.mjs +155 -60
- package/dist/{convex-plugin-D8B0oCFq.js → convex-plugin-DfOhBU9g.js} +1 -1
- package/dist/{create-schema-orm-B3f2Kc8O.js → create-schema-orm-Bu2JCiBS.js} +1 -1
- package/dist/crpc/index.d.ts +3 -3
- package/dist/crpc/index.js +4 -4
- package/dist/{customFunctions-DxEEO4Dq.js → customFunctions-BbhgdRGl.js} +2 -2
- package/dist/definitions-D9vhJ6OV.js +215 -0
- package/dist/extensions-DzjCJXYl.js +55 -0
- package/dist/filter-expression-Dydt8wS0.js +402 -0
- package/dist/{generated-contract-disabled-B-nmd7Ne.d.ts → generated-contract-disabled-BEc4d98x.d.ts} +2 -2
- package/dist/{http-types-BoSDAh4Y.d.ts → http-types-90mapMP9.d.ts} +33 -3
- package/dist/id-Bibg34Yb.js +37 -0
- package/dist/{local-env-yFKub75x.mjs → local-env-Du24tdbp.mjs} +2243 -2113
- package/dist/{middleware-Br5zubu7.d.ts → middleware-CE_MC6Gi.d.ts} +1 -1
- package/dist/{middleware-DUd1Sj39.js → middleware-cJDRHTRY.js} +1 -1
- package/dist/orm/aggregate-index/index.d.ts +20 -0
- package/dist/orm/aggregate-index/index.js +2136 -0
- package/dist/orm/index.d.ts +5 -4
- package/dist/orm/index.js +126 -4292
- package/dist/orm/migrations/index.d.ts +3 -0
- package/dist/orm/migrations/index.js +506 -0
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{procedure-caller-DQxnLBS_.js → procedure-caller-DIzXfX2E.js} +2 -2
- package/dist/{procedure-name-C55TynK3.d.ts → procedure-name-B09-cf1-.d.ts} +3 -3
- package/dist/{query-context-BzihIpnM.js → query-context-DOm5Xm3H.js} +1 -402
- package/dist/{query-options-C_eBSIXG.js → query-options-DUiOj01a.js} +33 -1
- package/dist/ratelimit/index.d.ts +47 -11
- package/dist/ratelimit/index.js +256 -102
- package/dist/react/index.d.ts +9 -6
- package/dist/react/index.js +36 -17
- package/dist/rsc/index.d.ts +6 -9
- package/dist/rsc/index.js +7 -11
- package/dist/runtime-DfGsZtx3.js +1074 -0
- package/dist/schema-Besl-rpv.js +1411 -0
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +4 -4
- package/dist/solid/index.d.ts +9 -6
- package/dist/solid/index.js +36 -17
- package/dist/{runtime-6DJxpDfb.js → table-rmJm5Qio.js} +3 -1095
- package/dist/{token-xlpENMVn.js → token-DcV_0fkF.js} +1 -1
- package/dist/{types-C0Xl7P8K.d.ts → types-In-ZZOsd.d.ts} +1 -1
- package/dist/{validators-C7LelqTN.js → validators-Dmbhsd3U.js} +1 -1
- package/dist/watcher.mjs +1 -1
- package/dist/where-clause-compiler-mohWFiG0.d.ts +2042 -0
- package/package.json +3 -2
- package/skills/kitcn/SKILL.md +19 -18
- package/skills/kitcn/references/features/aggregates.md +21 -0
- package/skills/kitcn/references/features/auth-organizations.md +3 -3
- package/skills/kitcn/references/features/auth-polar.md +2 -1
- package/skills/kitcn/references/features/auth.md +8 -1
- package/skills/kitcn/references/features/http.md +26 -1
- package/skills/kitcn/references/features/ratelimit.md +11 -0
- package/skills/kitcn/references/features/react.md +1 -1
- package/skills/kitcn/references/setup/auth.md +52 -42
- package/skills/kitcn/references/setup/index.md +1 -1
- package/skills/kitcn/references/setup/server.md +37 -6
- /package/dist/{api-entry-N3nBOlI2.js → api-entry-CkDpGYVg.js} +0 -0
- /package/dist/{context-utils-BBUtBqjN.d.ts → context-utils-DwZ3Cam1.d.ts} +0 -0
- /package/dist/{create-schema-BXrKE2YY.js → create-schema-BRFjdgRr.js} +0 -0
- /package/dist/{error-Bvo7YEhk.js → error-CMLeCadS.js} +0 -0
- /package/dist/{generated-contract-disabled-BXaz7JCE.js → generated-contract-disabled-CZa0iyV0.js} +0 -0
- /package/dist/{meta-utils-D9K4fICl.js → meta-utils-D2lqN2Lg.js} +0 -0
- /package/dist/{query-context-CNo9ffvI.d.ts → query-context-DJONf8X5.d.ts} +0 -0
- /package/dist/{token-B9Bjcqug.d.ts → token-kQaqFby4.d.ts} +0 -0
- /package/dist/{transformer-yZuBWo8v.js → transformer-D8wO-kEj.js} +0 -0
- /package/dist/{types-BCl8gfGy.d.ts → types-Bf3XQex5.d.ts} +0 -0
- /package/dist/{types-DF2cg_w0.d.ts → types-MHYXboyy.d.ts} +0 -0
- /package/dist/{types-CnTpHR1F.d.ts → types-eNzQmbqM.d.ts} +0 -0
- /package/dist/{upstream-BR6sBLg3.js → upstream-BCgGZX6q.js} +0 -0
- /package/dist/{validators-BhsByJeg.d.ts → validators-V4eM2-Jg.d.ts} +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as createSystemFields, S as
|
|
2
|
+
import { C as Columns, D as TableName, E as RlsPolicies, O as createSystemFields, S as getRankIndexes, T as OrmSchemaExtensions, _ as loadEsbuild, a as generateMeta, b as getAggregateIndexes, c as resolveSchemaDefaultExport, f as createProjectJiti, g as loadDotenv, h as loadClackPrompts, i as resolveConfiguredBackend, l as schemaDeclaresAggregateIndexes, m as loadBabelParser, n as withLocalCodegenEnv, o as getConvexConfig, p as CRPC_BUILDER_STUB_SOURCE, r as loadCliConfig, s as collectSchemaTables, t as getLocalBackendEnvVars, u as logger, v as highlighter, w as EnableRLS, x as getIndexes, y as isColorEnabled } from "./local-env-Du24tdbp.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import fs, { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
@@ -12,21 +12,6 @@ import { diffWords, structuredPatch } from "diff";
|
|
|
12
12
|
import { parseEnv } from "node:util";
|
|
13
13
|
import { createServer } from "node:http";
|
|
14
14
|
|
|
15
|
-
//#region src/orm/index-utils.ts
|
|
16
|
-
function getIndexes(table) {
|
|
17
|
-
const indexes = table.getIndexes?.();
|
|
18
|
-
return Array.isArray(indexes) ? indexes : [];
|
|
19
|
-
}
|
|
20
|
-
function getAggregateIndexes(table) {
|
|
21
|
-
const indexes = table.getAggregateIndexes?.();
|
|
22
|
-
return Array.isArray(indexes) ? indexes : [];
|
|
23
|
-
}
|
|
24
|
-
function getRankIndexes(table) {
|
|
25
|
-
const indexes = table.getRankIndexes?.();
|
|
26
|
-
return Array.isArray(indexes) ? indexes : [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
//#endregion
|
|
30
15
|
//#region src/orm/mutation-utils.ts
|
|
31
16
|
const UTF8_ENCODER = new TextEncoder();
|
|
32
17
|
function getTableName(table) {
|
|
@@ -2506,7 +2491,7 @@ function QueryProvider({ children }: { children: ReactNode }) {
|
|
|
2506
2491
|
const FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5 = "__KITCN_FUNCTIONS_DIR__";
|
|
2507
2492
|
const PLUGIN_CONFIG_IMPORT_PLACEHOLDER$1 = "__KITCN_PLUGIN_CONFIG_IMPORT__";
|
|
2508
2493
|
const PLUGIN_SCHEMA_IMPORT_PLACEHOLDER$1 = "__KITCN_PLUGIN_SCHEMA_IMPORT__";
|
|
2509
|
-
const PROJECT_CRPC_IMPORT_PLACEHOLDER$
|
|
2494
|
+
const PROJECT_CRPC_IMPORT_PLACEHOLDER$4 = "__KITCN_PROJECT_CRPC_IMPORT__";
|
|
2510
2495
|
const PROJECT_SHARED_API_IMPORT_PLACEHOLDER = "__KITCN_PROJECT_SHARED_API_IMPORT__";
|
|
2511
2496
|
const PROJECT_GET_ENV_IMPORT_PLACEHOLDER$2 = "__KITCN_PROJECT_GET_ENV_IMPORT__";
|
|
2512
2497
|
|
|
@@ -2536,7 +2521,7 @@ const INIT_HTTP_IMPORT_MARKER = "// __KITCN_HTTP_IMPORTS__";
|
|
|
2536
2521
|
const INIT_HTTP_ROUTE_MARKER = " // __KITCN_HTTP_ROUTES__";
|
|
2537
2522
|
const INIT_HTTP_TEMPLATE = `import { createHttpRouter } from 'kitcn/server';
|
|
2538
2523
|
import { Hono } from 'hono';
|
|
2539
|
-
import { router } from '${PROJECT_CRPC_IMPORT_PLACEHOLDER$
|
|
2524
|
+
import { router } from '${PROJECT_CRPC_IMPORT_PLACEHOLDER$4}';
|
|
2540
2525
|
${INIT_HTTP_IMPORT_MARKER}
|
|
2541
2526
|
|
|
2542
2527
|
const app = new Hono();
|
|
@@ -2849,7 +2834,7 @@ function resolveRelativeImportPath(filePath, targetFilePath) {
|
|
|
2849
2834
|
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
2850
2835
|
}
|
|
2851
2836
|
function renderInitTemplateContent(params) {
|
|
2852
|
-
return params.template.replaceAll(FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5, resolveRelativeImportPath(params.filePath, params.functionsDir)).replaceAll(PROJECT_CRPC_IMPORT_PLACEHOLDER$
|
|
2837
|
+
return params.template.replaceAll(FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5, resolveRelativeImportPath(params.filePath, params.functionsDir)).replaceAll(PROJECT_CRPC_IMPORT_PLACEHOLDER$4, resolveRelativeImportPath(params.filePath, params.crpcFilePath));
|
|
2853
2838
|
}
|
|
2854
2839
|
|
|
2855
2840
|
//#endregion
|
|
@@ -3424,7 +3409,7 @@ const resolvePluginScaffoldFiles = (templates, roots, functionsDir, existingTemp
|
|
|
3424
3409
|
const lockfilePath = normalizeLockfileScaffoldPath(mappedLockfilePath) ?? normalizePath$2(relative(process.cwd(), join(rootDir, template.path)));
|
|
3425
3410
|
const filePath = resolve(process.cwd(), lockfilePath);
|
|
3426
3411
|
const getEnvImportPath = resolveProjectGetEnvImportPrefix(filePath, roots.envFilePath);
|
|
3427
|
-
const content = template.content.replaceAll(FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5, resolveFunctionsDirImportPrefix(filePath, functionsDir)).replaceAll(PROJECT_CRPC_IMPORT_PLACEHOLDER$
|
|
3412
|
+
const content = template.content.replaceAll(FUNCTIONS_DIR_IMPORT_PLACEHOLDER$5, resolveFunctionsDirImportPrefix(filePath, functionsDir)).replaceAll(PROJECT_CRPC_IMPORT_PLACEHOLDER$4, resolveProjectCrpcImportPrefix(filePath, roots.crpcFilePath)).replaceAll(PROJECT_SHARED_API_IMPORT_PLACEHOLDER, resolveProjectSharedApiImportPrefix(filePath, roots.sharedApiFilePath)).replaceAll(PROJECT_GET_ENV_IMPORT_PLACEHOLDER$2, `import { getEnv } from '${getEnvImportPath}';`).replaceAll(PLUGIN_CONFIG_IMPORT_PLACEHOLDER$1, resolvePluginConfigImportPrefix(filePath, roots.libRootDir)).replaceAll(PLUGIN_SCHEMA_IMPORT_PLACEHOLDER$1, resolvePluginSchemaImportPrefix(filePath, roots.libRootDir));
|
|
3428
3413
|
if (!(descriptor.key === "auth" && preset === "convex") && (template.target === "functions" || template.target === "lib") && content.includes("process.env")) throw new Error(`Scaffold template "${descriptor.key}/${template.id}" contains process.env. Use getEnv() instead.`);
|
|
3429
3414
|
return {
|
|
3430
3415
|
templateId: template.id,
|
|
@@ -6776,10 +6761,46 @@ const authRegistryItem = defineInternalRegistryItem({
|
|
|
6776
6761
|
}
|
|
6777
6762
|
});
|
|
6778
6763
|
|
|
6764
|
+
//#endregion
|
|
6765
|
+
//#region src/cli/registry/items/ratelimit/ratelimit-functions.template.ts
|
|
6766
|
+
const PROJECT_CRPC_IMPORT_PLACEHOLDER$3 = "__KITCN_PROJECT_CRPC_IMPORT__";
|
|
6767
|
+
const RATELIMIT_FUNCTIONS_TEMPLATE = `import { cleanupRatelimitState } from "kitcn/ratelimit";
|
|
6768
|
+
import { z } from "zod";
|
|
6769
|
+
import { privateMutation } from "${PROJECT_CRPC_IMPORT_PLACEHOLDER$3}";
|
|
6770
|
+
|
|
6771
|
+
const DEFAULT_BATCH_SIZE = 500;
|
|
6772
|
+
|
|
6773
|
+
export const cleanup = privateMutation
|
|
6774
|
+
.input(
|
|
6775
|
+
z.object({
|
|
6776
|
+
olderThanMs: z.number().positive(),
|
|
6777
|
+
limit: z.number().int().min(1).max(1000).optional(),
|
|
6778
|
+
})
|
|
6779
|
+
)
|
|
6780
|
+
.output(
|
|
6781
|
+
z.object({
|
|
6782
|
+
deleted: z.number(),
|
|
6783
|
+
hasMore: z.boolean(),
|
|
6784
|
+
})
|
|
6785
|
+
)
|
|
6786
|
+
.mutation(async ({ ctx, input }) =>
|
|
6787
|
+
cleanupRatelimitState(ctx.db, {
|
|
6788
|
+
before: Date.now() - input.olderThanMs,
|
|
6789
|
+
limit: input.limit ?? DEFAULT_BATCH_SIZE,
|
|
6790
|
+
})
|
|
6791
|
+
);
|
|
6792
|
+
`;
|
|
6793
|
+
|
|
6779
6794
|
//#endregion
|
|
6780
6795
|
//#region src/cli/registry/items/ratelimit/ratelimit-plugin.template.ts
|
|
6781
6796
|
const FUNCTIONS_DIR_IMPORT_PLACEHOLDER$4 = "__KITCN_FUNCTIONS_DIR__";
|
|
6782
|
-
const RATELIMIT_PLUGIN_TEMPLATE = `import {
|
|
6797
|
+
const RATELIMIT_PLUGIN_TEMPLATE = `import {
|
|
6798
|
+
type LimitRequest,
|
|
6799
|
+
MINUTE,
|
|
6800
|
+
Ratelimit,
|
|
6801
|
+
RatelimitPlugin,
|
|
6802
|
+
SECOND,
|
|
6803
|
+
} from "kitcn/ratelimit";
|
|
6783
6804
|
import type { MutationCtx } from "${FUNCTIONS_DIR_IMPORT_PLACEHOLDER$4}/generated/server";
|
|
6784
6805
|
|
|
6785
6806
|
const fixed = (rate: number) => Ratelimit.fixedWindow(rate, MINUTE);
|
|
@@ -6790,6 +6811,11 @@ export const ratelimitBuckets = {
|
|
|
6790
6811
|
free: fixed(60),
|
|
6791
6812
|
premium: fixed(200),
|
|
6792
6813
|
},
|
|
6814
|
+
interactive: {
|
|
6815
|
+
public: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
6816
|
+
free: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
6817
|
+
premium: Ratelimit.fixedWindow(3, 30 * SECOND),
|
|
6818
|
+
},
|
|
6793
6819
|
} as const;
|
|
6794
6820
|
|
|
6795
6821
|
type RatelimitTier = keyof (typeof ratelimitBuckets)["default"];
|
|
@@ -6829,14 +6855,38 @@ async function getRequestSignals(ctx: RatelimitCtx) {
|
|
|
6829
6855
|
};
|
|
6830
6856
|
}
|
|
6831
6857
|
|
|
6858
|
+
/**
|
|
6859
|
+
* The identifier is the rate-limit partition key: everything that resolves to
|
|
6860
|
+
* the same string shares one budget and one \`ratelimitState\` document.
|
|
6861
|
+
*
|
|
6862
|
+
* Unauthenticated traffic is keyed by request IP so one visitor cannot spend
|
|
6863
|
+
* every other visitor's budget. Calls without request metadata all fall back
|
|
6864
|
+
* to one key.
|
|
6865
|
+
*/
|
|
6866
|
+
function getRequestIdentifier(
|
|
6867
|
+
user: RatelimitUser | null,
|
|
6868
|
+
signals: LimitRequest | undefined
|
|
6869
|
+
) {
|
|
6870
|
+
if (user) {
|
|
6871
|
+
return user.id;
|
|
6872
|
+
}
|
|
6873
|
+
|
|
6874
|
+
return signals?.ip ? \`ip:\${signals.ip}\` : "ip:unknown";
|
|
6875
|
+
}
|
|
6876
|
+
|
|
6832
6877
|
export const ratelimit = RatelimitPlugin.configure({
|
|
6833
6878
|
buckets: ratelimitBuckets,
|
|
6834
6879
|
getBucket: ({ meta }: { meta: RatelimitMeta }) => meta.ratelimit ?? "default",
|
|
6835
6880
|
getUser: ({ ctx }: { ctx: RatelimitCtx }) => ctx.user ?? null,
|
|
6836
|
-
getIdentifier: ({ user }: { user: RatelimitUser | null }) =>
|
|
6837
|
-
user?.id ?? "anonymous",
|
|
6838
6881
|
getTier: getUserTier,
|
|
6839
6882
|
getSignals: ({ ctx }: { ctx: RatelimitCtx }) => getRequestSignals(ctx),
|
|
6883
|
+
getIdentifier: ({
|
|
6884
|
+
user,
|
|
6885
|
+
signals,
|
|
6886
|
+
}: {
|
|
6887
|
+
user: RatelimitUser | null;
|
|
6888
|
+
signals: LimitRequest | undefined;
|
|
6889
|
+
}) => getRequestIdentifier(user, signals),
|
|
6840
6890
|
prefix: ({ bucket, tier }) => \`ratelimit:\${bucket}:\${tier}\`,
|
|
6841
6891
|
failureMode: "closed",
|
|
6842
6892
|
enableProtection: true,
|
|
@@ -6869,6 +6919,7 @@ export const ratelimitStateTable = convexTable(
|
|
|
6869
6919
|
(t) => [
|
|
6870
6920
|
index("by_name_key_shard").on(t.name, t.key, t.shard),
|
|
6871
6921
|
index("by_name_key").on(t.name, t.key),
|
|
6922
|
+
index("by_ts").on(t.ts),
|
|
6872
6923
|
],
|
|
6873
6924
|
);
|
|
6874
6925
|
|
|
@@ -6909,18 +6960,28 @@ export function ratelimitExtension() {
|
|
|
6909
6960
|
|
|
6910
6961
|
//#endregion
|
|
6911
6962
|
//#region src/cli/registry/items/ratelimit/ratelimit-item.ts
|
|
6912
|
-
const RATELIMIT_FILES = [
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6963
|
+
const RATELIMIT_FILES = [
|
|
6964
|
+
createRegistryFile({
|
|
6965
|
+
id: "ratelimit-schema",
|
|
6966
|
+
path: "schema.ts",
|
|
6967
|
+
target: "lib",
|
|
6968
|
+
content: RATELIMIT_SCHEMA_TEMPLATE
|
|
6969
|
+
}),
|
|
6970
|
+
createRegistryFile({
|
|
6971
|
+
id: "ratelimit-functions",
|
|
6972
|
+
path: "ratelimit.ts",
|
|
6973
|
+
target: "functions",
|
|
6974
|
+
content: RATELIMIT_FUNCTIONS_TEMPLATE,
|
|
6975
|
+
requires: ["ratelimit-schema"]
|
|
6976
|
+
}),
|
|
6977
|
+
createRegistryFile({
|
|
6978
|
+
id: "ratelimit-plugin",
|
|
6979
|
+
path: "plugin.ts",
|
|
6980
|
+
target: "lib",
|
|
6981
|
+
content: RATELIMIT_PLUGIN_TEMPLATE,
|
|
6982
|
+
requires: ["ratelimit-schema"]
|
|
6983
|
+
})
|
|
6984
|
+
];
|
|
6924
6985
|
function buildRatelimitCrpcRegistrationPlanFile(params) {
|
|
6925
6986
|
const crpcPath = getCrpcFilePath(params.config);
|
|
6926
6987
|
const baselineCrpcSource = renderInitTemplateContent({
|
|
@@ -6966,7 +7027,7 @@ const ratelimitRegistryItem = defineInternalRegistryItem({
|
|
|
6966
7027
|
presets: [{
|
|
6967
7028
|
name: "server-first",
|
|
6968
7029
|
description: "Scaffold a reusable ratelimit plugin and auto-register schema extension.",
|
|
6969
|
-
registryDependencies:
|
|
7030
|
+
registryDependencies: RATELIMIT_FILES.map((file) => file.meta.id)
|
|
6970
7031
|
}, {
|
|
6971
7032
|
name: "schema-only",
|
|
6972
7033
|
description: "Only register ratelimit extension in schema.",
|
|
@@ -11971,6 +12032,10 @@ function normalizeStringList(values) {
|
|
|
11971
12032
|
if (!Array.isArray(values)) return [];
|
|
11972
12033
|
return [...new Set(values.filter((value) => typeof value === "string"))].sort();
|
|
11973
12034
|
}
|
|
12035
|
+
function normalizeOrderedStringList(values) {
|
|
12036
|
+
if (!Array.isArray(values)) return [];
|
|
12037
|
+
return [...new Set(values.filter((value) => typeof value === "string"))];
|
|
12038
|
+
}
|
|
11974
12039
|
function readOptionalCliFlagValue(args, flag) {
|
|
11975
12040
|
for (let i = 0; i < args.length; i += 1) {
|
|
11976
12041
|
const arg = args[i];
|
|
@@ -11986,24 +12051,6 @@ function readOptionalCliFlagValue(args, flag) {
|
|
|
11986
12051
|
}
|
|
11987
12052
|
}
|
|
11988
12053
|
}
|
|
11989
|
-
function resolveSchemaDefaultExport(schemaModule) {
|
|
11990
|
-
const schemaValue = schemaModule.default !== void 0 ? schemaModule.default : schemaModule;
|
|
11991
|
-
if (!schemaValue || typeof schemaValue !== "object") return null;
|
|
11992
|
-
return schemaValue;
|
|
11993
|
-
}
|
|
11994
|
-
function collectSchemaTables(schemaValue) {
|
|
11995
|
-
const allTables = /* @__PURE__ */ new Set();
|
|
11996
|
-
const relations = getSchemaRelations(schemaValue);
|
|
11997
|
-
if (relations && typeof relations === "object" && !Array.isArray(relations)) for (const relationConfig of Object.values(relations)) {
|
|
11998
|
-
const table = relationConfig?.table;
|
|
11999
|
-
if (table) allTables.add(table);
|
|
12000
|
-
}
|
|
12001
|
-
const tables = schemaValue.tables;
|
|
12002
|
-
if (tables && typeof tables === "object" && !Array.isArray(tables)) {
|
|
12003
|
-
for (const table of Object.values(tables)) if (table) allTables.add(table);
|
|
12004
|
-
}
|
|
12005
|
-
return [...allTables];
|
|
12006
|
-
}
|
|
12007
12054
|
function buildAggregateFingerprintPayload(tables) {
|
|
12008
12055
|
return tables.map((table) => getTableConfig(table)).map((tableConfig) => ({
|
|
12009
12056
|
tableName: tableConfig.name,
|
|
@@ -12015,18 +12062,60 @@ function buildAggregateFingerprintPayload(tables) {
|
|
|
12015
12062
|
avgFields: normalizeStringList(index.avgFields),
|
|
12016
12063
|
minFields: normalizeStringList(index.minFields),
|
|
12017
12064
|
maxFields: normalizeStringList(index.maxFields)
|
|
12065
|
+
})).sort((a, b) => a.name.localeCompare(b.name)),
|
|
12066
|
+
rankIndexes: tableConfig.rankIndexes.map((index) => ({
|
|
12067
|
+
name: index.name,
|
|
12068
|
+
partitionFields: normalizeOrderedStringList(index.partitionFields),
|
|
12069
|
+
orderFields: (index.orderFields ?? []).map((field) => `${field.field}:${field.direction}`),
|
|
12070
|
+
sumField: index.sumField ?? ""
|
|
12018
12071
|
})).sort((a, b) => a.name.localeCompare(b.name))
|
|
12019
12072
|
})).sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
12020
12073
|
}
|
|
12021
|
-
async function
|
|
12074
|
+
async function loadBackendSchemaValue(functionsDir) {
|
|
12022
12075
|
const schemaPath = join(functionsDir, "schema.ts");
|
|
12023
12076
|
if (!fs.existsSync(schemaPath)) return null;
|
|
12024
12077
|
const schemaModule = await createProjectJiti().import(schemaPath);
|
|
12025
12078
|
if (!schemaModule || typeof schemaModule !== "object") return null;
|
|
12026
|
-
|
|
12079
|
+
return resolveSchemaDefaultExport(schemaModule);
|
|
12080
|
+
}
|
|
12081
|
+
/**
|
|
12082
|
+
* Whether the deployed ORM can serve the aggregate procedures at all.
|
|
12083
|
+
*
|
|
12084
|
+
* `kitcn codegen` registers `aggregateCapability()` only for schemas that
|
|
12085
|
+
* declare an `aggregateIndex`/`rankIndex`, so calling `aggregateBackfill`
|
|
12086
|
+
* against a schema without one is a guaranteed error rather than a no-op.
|
|
12087
|
+
*
|
|
12088
|
+
* No schema file means no ORM and no aggregate procedures to call. A schema
|
|
12089
|
+
* that exists but fails to load is treated as using them, so a real backfill
|
|
12090
|
+
* is never skipped over a transient load error.
|
|
12091
|
+
*/
|
|
12092
|
+
async function backendUsesAggregateIndexes() {
|
|
12093
|
+
try {
|
|
12094
|
+
const { functionsDir } = getConvexConfig();
|
|
12095
|
+
const schemaValue = await loadBackendSchemaValue(functionsDir);
|
|
12096
|
+
if (!schemaValue) return false;
|
|
12097
|
+
return schemaDeclaresAggregateIndexes(schemaValue);
|
|
12098
|
+
} catch {
|
|
12099
|
+
return true;
|
|
12100
|
+
}
|
|
12101
|
+
}
|
|
12102
|
+
/**
|
|
12103
|
+
* Whether the deployed ORM registered `migrationCapability()`. Codegen keys
|
|
12104
|
+
* that off the generated manifest, so the flows key off the same file.
|
|
12105
|
+
*/
|
|
12106
|
+
function backendUsesMigrations() {
|
|
12107
|
+
try {
|
|
12108
|
+
const { functionsDir } = getConvexConfig();
|
|
12109
|
+
return fs.existsSync(join(functionsDir, "migrations", "manifest.ts"));
|
|
12110
|
+
} catch {
|
|
12111
|
+
return true;
|
|
12112
|
+
}
|
|
12113
|
+
}
|
|
12114
|
+
async function computeAggregateIndexFingerprint(functionsDir) {
|
|
12115
|
+
const schemaValue = await loadBackendSchemaValue(functionsDir);
|
|
12027
12116
|
if (!schemaValue) return null;
|
|
12028
12117
|
const tables = collectSchemaTables(schemaValue);
|
|
12029
|
-
if (tables.length === 0) return null;
|
|
12118
|
+
if (tables.length === 0 || !schemaDeclaresAggregateIndexes(schemaValue)) return null;
|
|
12030
12119
|
const payload = buildAggregateFingerprintPayload(tables);
|
|
12031
12120
|
return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
12032
12121
|
}
|
|
@@ -12924,7 +13013,8 @@ async function runAggregateBackfillFlow(params) {
|
|
|
12924
13013
|
const { execaFn, backendAdapter, backfillConfig, mode, targetArgs, env, signal, context } = params;
|
|
12925
13014
|
if (signal?.aborted) return 0;
|
|
12926
13015
|
if (backfillConfig.enabled === "off") return 0;
|
|
12927
|
-
|
|
13016
|
+
if (!await backendUsesAggregateIndexes()) return 0;
|
|
13017
|
+
const kickoff = await runBackendFunction(execaFn, backendAdapter, "generated/aggregate:aggregateBackfill", {
|
|
12928
13018
|
mode,
|
|
12929
13019
|
batchSize: backfillConfig.batchSize
|
|
12930
13020
|
}, targetArgs, {
|
|
@@ -12957,7 +13047,7 @@ async function runAggregateBackfillFlow(params) {
|
|
|
12957
13047
|
const deadline = Date.now() + backfillConfig.timeoutMs;
|
|
12958
13048
|
let lastProgress = "";
|
|
12959
13049
|
while (!signal?.aborted) {
|
|
12960
|
-
const statusResult = await runBackendFunction(execaFn, backendAdapter, "generated/
|
|
13050
|
+
const statusResult = await runBackendFunction(execaFn, backendAdapter, "generated/aggregate:aggregateBackfillStatus", {}, targetArgs, {
|
|
12961
13051
|
echoOutput: false,
|
|
12962
13052
|
env
|
|
12963
13053
|
});
|
|
@@ -12991,7 +13081,7 @@ async function runAggregateBackfillFlow(params) {
|
|
|
12991
13081
|
}
|
|
12992
13082
|
async function runAggregatePruneFlow(params) {
|
|
12993
13083
|
const { execaFn, backendAdapter, targetArgs, env } = params;
|
|
12994
|
-
const result = await runBackendFunction(execaFn, backendAdapter, "generated/
|
|
13084
|
+
const result = await runBackendFunction(execaFn, backendAdapter, "generated/aggregate:aggregateBackfill", { mode: "prune" }, targetArgs, {
|
|
12995
13085
|
echoOutput: false,
|
|
12996
13086
|
env
|
|
12997
13087
|
});
|
|
@@ -13102,6 +13192,7 @@ export const migration = defineMigration({
|
|
|
13102
13192
|
async function runMigrationFlow(params) {
|
|
13103
13193
|
const { execaFn, backendAdapter, migrationConfig, targetArgs, env, signal, context, direction, steps, to } = params;
|
|
13104
13194
|
if (signal?.aborted || migrationConfig.enabled === "off") return 0;
|
|
13195
|
+
if (!backendUsesMigrations()) return 0;
|
|
13105
13196
|
const kickoff = await runBackendFunction(execaFn, backendAdapter, "generated/server:migrationRun", {
|
|
13106
13197
|
direction,
|
|
13107
13198
|
batchSize: migrationConfig.batchSize,
|
|
@@ -15318,6 +15409,10 @@ const handleMigrateCommand = async (argv, deps = {}) => {
|
|
|
15318
15409
|
to
|
|
15319
15410
|
});
|
|
15320
15411
|
}
|
|
15412
|
+
if (!backendUsesMigrations()) {
|
|
15413
|
+
logger.info("No migrations manifest. Run `kitcn migrate create <name>` to add one.");
|
|
15414
|
+
return 0;
|
|
15415
|
+
}
|
|
15321
15416
|
if (migrateArgs.subcommand === "status") return (await runBackendFunction(execaFn, backendAdapter, "generated/server:migrationStatus", {}, targetArgs, { env: commandEnv })).exitCode;
|
|
15322
15417
|
let runId;
|
|
15323
15418
|
const cancelArgs = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as omit } from "./upstream-
|
|
1
|
+
import { r as omit } from "./upstream-BCgGZX6q.js";
|
|
2
2
|
import { createAuthEndpoint, createAuthMiddleware, sessionMiddleware } from "better-auth/api";
|
|
3
3
|
import { bearer } from "better-auth/plugins/bearer";
|
|
4
4
|
import { jwt } from "better-auth/plugins/jwt";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { augmentBetterAuthTables, indexFields } from "./create-schema-
|
|
1
|
+
import { augmentBetterAuthTables, indexFields } from "./create-schema-BRFjdgRr.js";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/create-schema-orm.ts
|
|
4
4
|
const specialFields = (tables) => Object.fromEntries(Object.entries(tables).map(([key, table]) => {
|
package/dist/crpc/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as HttpInputArgs, C as ReservedMutationOptions, D as VanillaMutation, E as VanillaAction, F as replaceUrlParam, M as RESERVED_KEYS, N as buildSearchParams, O as HttpClientOptions, P as executeHttpRequest, S as ReservedInfiniteQueryOptions, T as StaticQueryOptsParam, _ as IsPaginated, a as BaseInfiniteQueryOptsParam, b as PaginatedFnMeta, c as ConvexMutationKey, d as ConvexQueryMeta, f as EmptyObject, g as InfiniteQueryInput, h as FnMeta, i as BaseConvexQueryOptions, j as HttpProxyBaseOptions, k as HttpFormValue, l as ConvexQueryHookOptions, m as FUNC_REF_SYMBOL, n as BaseConvexActionOptions, o as ConvexActionKey, p as ExtractPaginatedItem, r as BaseConvexInfiniteQueryOptions, s as ConvexInfiniteQueryMeta, t as AuthType, u as ConvexQueryKey, v as Meta, w as ReservedQueryOptions, x as PaginationOpts, y as MutationVariables } from "../types-
|
|
1
|
+
import { B as dateWireCodec, F as DATE_CODEC_TAG, G as identityTransformer, H as defaultCRPCTransformer, I as DataTransformer, L as DataTransformerOptions, P as CombinedDataTransformer, R as WireCodec, U as encodeWire, V as decodeWire, W as getTransformer, a as HttpErrorCode, c as HttpQueryKey, d as HttpRouteMap, f as InferHttpInput, g as isHttpClientError, h as buildHttpQueryPrefixKey, i as HttpClientFromRouter, l as HttpQueryPrefixKey, m as buildHttpQueryKey, n as HttpClient, o as HttpMutationKey, p as InferHttpOutput, r as HttpClientError, s as HttpProcedureCall, t as HTTP_DEFAULT_STALE_TIME, u as HttpRouteInfo, z as createTaggedTransformer } from "../http-types-90mapMP9.js";
|
|
2
|
+
import { A as HttpInputArgs, C as ReservedMutationOptions, D as VanillaMutation, E as VanillaAction, F as replaceUrlParam, M as RESERVED_KEYS, N as buildSearchParams, O as HttpClientOptions, P as executeHttpRequest, S as ReservedInfiniteQueryOptions, T as StaticQueryOptsParam, _ as IsPaginated, a as BaseInfiniteQueryOptsParam, b as PaginatedFnMeta, c as ConvexMutationKey, d as ConvexQueryMeta, f as EmptyObject, g as InfiniteQueryInput, h as FnMeta, i as BaseConvexQueryOptions, j as HttpProxyBaseOptions, k as HttpFormValue, l as ConvexQueryHookOptions, m as FUNC_REF_SYMBOL, n as BaseConvexActionOptions, o as ConvexActionKey, p as ExtractPaginatedItem, r as BaseConvexInfiniteQueryOptions, s as ConvexInfiniteQueryMeta, t as AuthType, u as ConvexQueryKey, v as Meta, w as ReservedQueryOptions, x as PaginationOpts, y as MutationVariables } from "../types-In-ZZOsd.js";
|
|
3
3
|
import { FunctionArgs, FunctionReference } from "convex/server";
|
|
4
4
|
|
|
5
5
|
//#region src/crpc/auth-error.d.ts
|
|
@@ -102,4 +102,4 @@ declare function convexAction<T extends FunctionReference<'action'>>(funcRef: T,
|
|
|
102
102
|
*/
|
|
103
103
|
declare function convexInfiniteQueryOptions<T extends FunctionReference<'query'>>(funcRef: T, args: Record<string, unknown> | 'skip', opts?: BaseInfiniteQueryOptsParam<T> & Record<string, unknown>, meta?: Meta): BaseConvexInfiniteQueryOptions<T> & Record<string, unknown>;
|
|
104
104
|
//#endregion
|
|
105
|
-
export { AuthMutationError, AuthType, BaseConvexActionOptions, BaseConvexInfiniteQueryOptions, BaseConvexQueryOptions, BaseInfiniteQueryOptsParam, CRPCClientError, CombinedDataTransformer, ConvexActionKey, ConvexInfiniteQueryMeta, ConvexMutationKey, ConvexQueryHookOptions, ConvexQueryKey, ConvexQueryMeta, DATE_CODEC_TAG, DataTransformer, DataTransformerOptions, EmptyObject, ExtractPaginatedItem, FUNC_REF_SYMBOL, FnMeta, HttpClient, HttpClientError, HttpClientFromRouter, HttpClientOptions, HttpErrorCode, HttpFormValue, HttpInputArgs, HttpProcedureCall, HttpProxyBaseOptions, HttpRouteInfo, HttpRouteMap, InferHttpInput, InferHttpOutput, InfiniteQueryInput, IsPaginated, Meta, MutationVariables, PaginatedFnMeta, PaginationOpts, RESERVED_KEYS, ReservedInfiniteQueryOptions, ReservedMutationOptions, ReservedQueryOptions, StaticQueryOptsParam, VanillaAction, VanillaMutation, WireCodec, buildSearchParams, convexAction, convexInfiniteQueryOptions, convexQuery, createTaggedTransformer, dateWireCodec, decodeWire, defaultCRPCTransformer, defaultIsUnauthorized, encodeWire, executeHttpRequest, getTransformer, identityTransformer, isAuthMutationError, isCRPCClientError, isCRPCError, isCRPCErrorCode, isHttpClientError, replaceUrlParam };
|
|
105
|
+
export { AuthMutationError, AuthType, BaseConvexActionOptions, BaseConvexInfiniteQueryOptions, BaseConvexQueryOptions, BaseInfiniteQueryOptsParam, CRPCClientError, CombinedDataTransformer, ConvexActionKey, ConvexInfiniteQueryMeta, ConvexMutationKey, ConvexQueryHookOptions, ConvexQueryKey, ConvexQueryMeta, DATE_CODEC_TAG, DataTransformer, DataTransformerOptions, EmptyObject, ExtractPaginatedItem, FUNC_REF_SYMBOL, FnMeta, HTTP_DEFAULT_STALE_TIME, HttpClient, HttpClientError, HttpClientFromRouter, HttpClientOptions, HttpErrorCode, HttpFormValue, HttpInputArgs, HttpMutationKey, HttpProcedureCall, HttpProxyBaseOptions, HttpQueryKey, HttpQueryPrefixKey, HttpRouteInfo, HttpRouteMap, InferHttpInput, InferHttpOutput, InfiniteQueryInput, IsPaginated, Meta, MutationVariables, PaginatedFnMeta, PaginationOpts, RESERVED_KEYS, ReservedInfiniteQueryOptions, ReservedMutationOptions, ReservedQueryOptions, StaticQueryOptsParam, VanillaAction, VanillaMutation, WireCodec, buildHttpQueryKey, buildHttpQueryPrefixKey, buildSearchParams, convexAction, convexInfiniteQueryOptions, convexQuery, createTaggedTransformer, dateWireCodec, decodeWire, defaultCRPCTransformer, defaultIsUnauthorized, encodeWire, executeHttpRequest, getTransformer, identityTransformer, isAuthMutationError, isCRPCClientError, isCRPCError, isCRPCErrorCode, isHttpClientError, replaceUrlParam };
|
package/dist/crpc/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as isCRPCErrorCode, i as isCRPCError, n as defaultIsUnauthorized, r as isCRPCClientError, t as CRPCClientError } from "../error-
|
|
2
|
-
import { a as defaultCRPCTransformer, c as identityTransformer, i as decodeWire, n as createTaggedTransformer, o as encodeWire, r as dateWireCodec, s as getTransformer, t as DATE_CODEC_TAG } from "../transformer-
|
|
3
|
-
import { a as buildSearchParams, c as
|
|
1
|
+
import { a as isCRPCErrorCode, i as isCRPCError, n as defaultIsUnauthorized, r as isCRPCClientError, t as CRPCClientError } from "../error-CMLeCadS.js";
|
|
2
|
+
import { a as defaultCRPCTransformer, c as identityTransformer, i as decodeWire, n as createTaggedTransformer, o as encodeWire, r as dateWireCodec, s as getTransformer, t as DATE_CODEC_TAG } from "../transformer-D8wO-kEj.js";
|
|
3
|
+
import { a as buildSearchParams, c as HTTP_DEFAULT_STALE_TIME, d as buildHttpQueryPrefixKey, f as isHttpClientError, i as RESERVED_KEYS, l as HttpClientError, n as convexInfiniteQueryOptions, o as executeHttpRequest, r as convexQuery, s as replaceUrlParam, t as convexAction, u as buildHttpQueryKey } from "../query-options-DUiOj01a.js";
|
|
4
4
|
|
|
5
5
|
//#region src/crpc/auth-error.ts
|
|
6
6
|
/**
|
|
@@ -40,4 +40,4 @@ function isAuthMutationError(error) {
|
|
|
40
40
|
const FUNC_REF_SYMBOL = Symbol.for("convex.funcRef");
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
43
|
-
export { AuthMutationError, CRPCClientError, DATE_CODEC_TAG, FUNC_REF_SYMBOL, HttpClientError, RESERVED_KEYS, buildSearchParams, convexAction, convexInfiniteQueryOptions, convexQuery, createTaggedTransformer, dateWireCodec, decodeWire, defaultCRPCTransformer, defaultIsUnauthorized, encodeWire, executeHttpRequest, getTransformer, identityTransformer, isAuthMutationError, isCRPCClientError, isCRPCError, isCRPCErrorCode, isHttpClientError, replaceUrlParam };
|
|
43
|
+
export { AuthMutationError, CRPCClientError, DATE_CODEC_TAG, FUNC_REF_SYMBOL, HTTP_DEFAULT_STALE_TIME, HttpClientError, RESERVED_KEYS, buildHttpQueryKey, buildHttpQueryPrefixKey, buildSearchParams, convexAction, convexInfiniteQueryOptions, convexQuery, createTaggedTransformer, dateWireCodec, decodeWire, defaultCRPCTransformer, defaultIsUnauthorized, encodeWire, executeHttpRequest, getTransformer, identityTransformer, isAuthMutationError, isCRPCClientError, isCRPCError, isCRPCErrorCode, isHttpClientError, replaceUrlParam };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as pick, r as omit } from "./upstream-
|
|
2
|
-
import { t as addFieldsToValidator } from "./validators-
|
|
1
|
+
import { i as pick, r as omit } from "./upstream-BCgGZX6q.js";
|
|
2
|
+
import { t as addFieldsToValidator } from "./validators-Dmbhsd3U.js";
|
|
3
3
|
|
|
4
4
|
//#region src/internal/upstream/server/customFunctions.ts
|
|
5
5
|
/**
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { A as integer, C as index, I as ConvexColumnBuilder, L as entityKind, M as custom, O as text, t as convexTable } from "./table-rmJm5Qio.js";
|
|
2
|
+
import { t as defineSchemaExtension } from "./extensions-DzjCJXYl.js";
|
|
3
|
+
import { v } from "convex/values";
|
|
4
|
+
|
|
5
|
+
//#region src/orm/builders/boolean.ts
|
|
6
|
+
/**
|
|
7
|
+
* Boolean column builder class
|
|
8
|
+
* Compiles to v.boolean() or v.optional(v.boolean())
|
|
9
|
+
*/
|
|
10
|
+
var ConvexBooleanBuilder = class extends ConvexColumnBuilder {
|
|
11
|
+
static [entityKind] = "ConvexBooleanBuilder";
|
|
12
|
+
constructor(name) {
|
|
13
|
+
super(name, "boolean", "ConvexBoolean");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Expose Convex validator for schema integration
|
|
17
|
+
*/
|
|
18
|
+
get convexValidator() {
|
|
19
|
+
if (this.config.notNull) return v.boolean();
|
|
20
|
+
return v.optional(v.union(v.null(), v.boolean()));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compile to Convex validator
|
|
24
|
+
* .notNull() → v.boolean()
|
|
25
|
+
* nullable → v.optional(v.boolean())
|
|
26
|
+
*/
|
|
27
|
+
build() {
|
|
28
|
+
return this.convexValidator;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function boolean(name) {
|
|
32
|
+
return new ConvexBooleanBuilder(name ?? "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/orm/migrations/schema.ts
|
|
37
|
+
const MIGRATION_STATE_TABLE = "migration_state";
|
|
38
|
+
const MIGRATION_RUN_TABLE = "migration_run";
|
|
39
|
+
const migrationStateTable = convexTable(MIGRATION_STATE_TABLE, {
|
|
40
|
+
migrationId: text().notNull(),
|
|
41
|
+
checksum: text().notNull(),
|
|
42
|
+
applied: boolean().notNull(),
|
|
43
|
+
status: text().notNull(),
|
|
44
|
+
direction: text(),
|
|
45
|
+
runId: text(),
|
|
46
|
+
cursor: text(),
|
|
47
|
+
processed: integer().notNull(),
|
|
48
|
+
startedAt: integer(),
|
|
49
|
+
updatedAt: integer().notNull(),
|
|
50
|
+
completedAt: integer(),
|
|
51
|
+
lastError: text(),
|
|
52
|
+
writeMode: text().notNull()
|
|
53
|
+
}, (t) => [index("by_migration_id").on(t.migrationId), index("by_status").on(t.status)]);
|
|
54
|
+
const migrationRunTable = convexTable(MIGRATION_RUN_TABLE, {
|
|
55
|
+
runId: text().notNull(),
|
|
56
|
+
direction: text().notNull(),
|
|
57
|
+
status: text().notNull(),
|
|
58
|
+
dryRun: boolean().notNull(),
|
|
59
|
+
allowDrift: boolean().notNull(),
|
|
60
|
+
migrationIds: custom(v.array(v.string())).notNull(),
|
|
61
|
+
currentIndex: integer().notNull(),
|
|
62
|
+
startedAt: integer().notNull(),
|
|
63
|
+
updatedAt: integer().notNull(),
|
|
64
|
+
completedAt: integer(),
|
|
65
|
+
cancelRequested: boolean().notNull(),
|
|
66
|
+
lastError: text()
|
|
67
|
+
}, (t) => [index("by_run_id").on(t.runId), index("by_status").on(t.status)]);
|
|
68
|
+
const migrationStorageTables = {
|
|
69
|
+
[MIGRATION_STATE_TABLE]: migrationStateTable,
|
|
70
|
+
[MIGRATION_RUN_TABLE]: migrationRunTable
|
|
71
|
+
};
|
|
72
|
+
const MIGRATION_STORAGE_TABLE_NAMES = new Set([MIGRATION_STATE_TABLE, MIGRATION_RUN_TABLE]);
|
|
73
|
+
function migrationExtension() {
|
|
74
|
+
return defineSchemaExtension("migration", migrationStorageTables);
|
|
75
|
+
}
|
|
76
|
+
function injectMigrationStorageTables(schema) {
|
|
77
|
+
const merged = { ...schema };
|
|
78
|
+
for (const [tableName, tableDef] of Object.entries(migrationStorageTables)) {
|
|
79
|
+
if (tableName in schema && schema[tableName] !== tableDef) throw new Error(`defineSchema cannot inject internal table '${tableName}' because the name is already in use.`);
|
|
80
|
+
merged[tableName] = tableDef;
|
|
81
|
+
}
|
|
82
|
+
return merged;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/orm/migrations/definitions.ts
|
|
87
|
+
const MIGRATION_ID_RE = /^[a-zA-Z0-9_:-]+$/;
|
|
88
|
+
const FUNCTION_SOURCE_WHITESPACE_RE = /\s+/g;
|
|
89
|
+
const FUNCTION_SOURCE_PUNCTUATION_SPACE_RE = /\s*([{}();,:])\s*/g;
|
|
90
|
+
function defineMigration(migration) {
|
|
91
|
+
validateMigrationId(migration.id);
|
|
92
|
+
validateMigrationStep("up", migration.up);
|
|
93
|
+
if (migration.down) validateMigrationStep("down", migration.down);
|
|
94
|
+
return migration;
|
|
95
|
+
}
|
|
96
|
+
function defineMigrationSet(migrations) {
|
|
97
|
+
const normalized = [...migrations].map((migration) => {
|
|
98
|
+
const defined = defineMigration(migration);
|
|
99
|
+
return {
|
|
100
|
+
...defined,
|
|
101
|
+
checksum: defined.checksum ?? computeMigrationChecksum(defined)
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
normalized.sort((a, b) => a.id.localeCompare(b.id));
|
|
105
|
+
const byId = {};
|
|
106
|
+
for (const migration of normalized) {
|
|
107
|
+
if (byId[migration.id]) throw new Error(`defineMigrationSet received duplicate migration id '${migration.id}'.`);
|
|
108
|
+
byId[migration.id] = migration;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
migrations: normalized,
|
|
112
|
+
ids: normalized.map((migration) => migration.id),
|
|
113
|
+
byId
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function detectMigrationDrift(params) {
|
|
117
|
+
const { migrationSet, appliedState } = params;
|
|
118
|
+
const issues = [];
|
|
119
|
+
for (const [migrationId, state] of Object.entries(appliedState)) {
|
|
120
|
+
if (!state.applied) continue;
|
|
121
|
+
const migration = migrationSet.byId[migrationId];
|
|
122
|
+
if (!migration) {
|
|
123
|
+
issues.push({
|
|
124
|
+
kind: "missing_from_manifest",
|
|
125
|
+
migrationId,
|
|
126
|
+
message: `Applied migration '${migrationId}' is missing from the current migration manifest.`
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (state.checksum && state.checksum !== migration.checksum) issues.push({
|
|
131
|
+
kind: "checksum_mismatch",
|
|
132
|
+
migrationId,
|
|
133
|
+
expectedChecksum: migration.checksum,
|
|
134
|
+
actualChecksum: state.checksum,
|
|
135
|
+
message: `Applied migration '${migrationId}' checksum drift detected.`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return issues;
|
|
139
|
+
}
|
|
140
|
+
function buildMigrationPlan(params) {
|
|
141
|
+
const { direction, migrationSet, appliedState, steps, to } = params;
|
|
142
|
+
if (direction === "up") return {
|
|
143
|
+
direction,
|
|
144
|
+
migrations: migrationSet.migrations.filter((migration) => !appliedState[migration.id]?.applied)
|
|
145
|
+
};
|
|
146
|
+
if (steps !== void 0 && to !== void 0) throw new Error("Use either down steps or down to, not both.");
|
|
147
|
+
const appliedInOrder = migrationSet.migrations.filter((migration) => appliedState[migration.id]?.applied);
|
|
148
|
+
const ensureDownSteps = (selected) => {
|
|
149
|
+
const missingDown = selected.find((migration) => !migration.down);
|
|
150
|
+
if (missingDown) throw new Error(`Cannot execute down migration for '${missingDown.id}': missing down migration handler.`);
|
|
151
|
+
return selected;
|
|
152
|
+
};
|
|
153
|
+
if (to) {
|
|
154
|
+
const targetIndex = migrationSet.ids.indexOf(to);
|
|
155
|
+
if (targetIndex === -1) throw new Error(`Unknown migration id '${to}' for down --to.`);
|
|
156
|
+
return {
|
|
157
|
+
direction,
|
|
158
|
+
migrations: ensureDownSteps(appliedInOrder.filter((migration) => {
|
|
159
|
+
return migrationSet.ids.indexOf(migration.id) > targetIndex;
|
|
160
|
+
})).reverse()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const resolvedSteps = steps ?? 1;
|
|
164
|
+
if (!Number.isInteger(resolvedSteps) || resolvedSteps < 1) throw new Error("Down steps must be a positive integer.");
|
|
165
|
+
return {
|
|
166
|
+
direction,
|
|
167
|
+
migrations: ensureDownSteps(appliedInOrder.slice(-resolvedSteps).reverse())
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function validateMigrationId(id) {
|
|
171
|
+
if (!id || typeof id !== "string") throw new Error("Migration id must be a non-empty string.");
|
|
172
|
+
if (!MIGRATION_ID_RE.test(id)) throw new Error(`Migration id '${id}' is invalid. Use alphanumeric characters, '_' ':' or '-'.`);
|
|
173
|
+
}
|
|
174
|
+
function validateMigrationStep(direction, step) {
|
|
175
|
+
if (!step || typeof step !== "object") throw new Error(`Migration ${direction} step must be an object.`);
|
|
176
|
+
if (!step.table || typeof step.table !== "string") throw new Error(`Migration ${direction} step.table must be a string.`);
|
|
177
|
+
if (typeof step.migrateOne !== "function") throw new Error(`Migration ${direction} step.migrateOne must be a function.`);
|
|
178
|
+
if (step.batchSize !== void 0 && (!Number.isInteger(step.batchSize) || step.batchSize < 1)) throw new Error(`Migration ${direction} step.batchSize must be a positive integer.`);
|
|
179
|
+
if (step.writeMode !== void 0 && step.writeMode !== "safe_bypass" && step.writeMode !== "normal") throw new Error(`Migration ${direction} step.writeMode must be 'safe_bypass' or 'normal'.`);
|
|
180
|
+
}
|
|
181
|
+
function computeMigrationChecksum(migration) {
|
|
182
|
+
return simpleStableHash(JSON.stringify({
|
|
183
|
+
id: migration.id,
|
|
184
|
+
name: migration.name ?? null,
|
|
185
|
+
description: migration.description ?? null,
|
|
186
|
+
up: serializeStep(migration.up),
|
|
187
|
+
down: migration.down ? serializeStep(migration.down) : null
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
function serializeStep(step) {
|
|
191
|
+
return {
|
|
192
|
+
table: step.table,
|
|
193
|
+
batchSize: step.batchSize ?? null,
|
|
194
|
+
writeMode: step.writeMode ?? "safe_bypass",
|
|
195
|
+
source: normalizeFunctionSource(step.migrateOne)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function normalizeFunctionSource(fn) {
|
|
199
|
+
return fn.toString().replace(FUNCTION_SOURCE_WHITESPACE_RE, " ").replace(FUNCTION_SOURCE_PUNCTUATION_SPACE_RE, "$1").trim();
|
|
200
|
+
}
|
|
201
|
+
function simpleStableHash(value) {
|
|
202
|
+
let hashA = 2166136261;
|
|
203
|
+
let hashB = 16777619;
|
|
204
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
205
|
+
const code = value.charCodeAt(i);
|
|
206
|
+
hashA ^= code;
|
|
207
|
+
hashA = Math.imul(hashA, 16777619);
|
|
208
|
+
hashB ^= code + i;
|
|
209
|
+
hashB = Math.imul(hashB, 2246822507);
|
|
210
|
+
}
|
|
211
|
+
return `m_${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
export { MIGRATION_RUN_TABLE as a, injectMigrationStorageTables as c, boolean as d, detectMigrationDrift as i, migrationExtension as l, defineMigration as n, MIGRATION_STATE_TABLE as o, defineMigrationSet as r, MIGRATION_STORAGE_TABLE_NAMES as s, buildMigrationPlan as t, migrationStorageTables as u };
|