@tailor-platform/sdk 2.3.0 → 2.4.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/CHANGELOG.md +68 -0
- package/README.md +1 -1
- package/dist/{application-F-nl107y.mjs → application-C8a0UJ-z.mjs} +201 -149
- package/dist/application-C8a0UJ-z.mjs.map +1 -0
- package/dist/application-D4zOiLLa.mjs +3 -0
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +4 -4
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +3 -3
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +5 -5
- package/dist/cli/commands/tailordb/truncate.d.mts +2 -2
- package/dist/cli/lib.mjs +2 -2
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +174 -106
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/ts-hook.mjs +3 -1
- package/dist/completion/zsh-worker.zsh +19 -11
- package/dist/configure/config/index.d.mts +1 -1
- package/dist/configure/index.d.mts +2 -1
- package/dist/configure/index.mjs +2 -2
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/aigateway/index.d.mts +5 -4
- package/dist/configure/services/aigateway/types.d.mts +6 -1
- package/dist/configure/services/auth/types.d.mts +2 -2
- package/dist/configure/services/tailordb/permission.d.mts +1 -1
- package/dist/configure/services/tailordb/schema.d.mts +1 -1
- package/dist/configure/services/tailordb/types.d.mts +7 -4
- package/dist/configure/types/auth-namespace-name.d.mts +18 -0
- package/dist/{crashreport-CINZ5dVN.mjs → crashreport-Cd4TqfZq.mjs} +1 -1
- package/dist/{crashreport-CIIfLnoO.mjs → crashreport-Q0pDAlrj.mjs} +8 -4
- package/dist/{crashreport-CIIfLnoO.mjs.map → crashreport-Q0pDAlrj.mjs.map} +1 -1
- package/dist/{errors-BVb6vYGy.mjs → errors-Bxut5_Sy.mjs} +2 -2
- package/dist/{errors-BVb6vYGy.mjs.map → errors-Bxut5_Sy.mjs.map} +1 -1
- package/dist/file-BUcmVIH2.mjs.map +1 -1
- package/dist/kysely/index.mjs +2 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/{logger-DSdExfor.mjs → logger-D0bEznPv.mjs} +9 -5
- package/dist/{logger-DSdExfor.mjs.map → logger-D0bEznPv.mjs.map} +1 -1
- package/dist/package-json-8b0O9TlX.mjs.map +1 -1
- package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
- package/dist/plugin/builtin/enum-constants/index.mjs +5 -5
- package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.mjs +14 -11
- package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
- package/dist/plugin/builtin/kysely-type/index.mjs +9 -9
- package/dist/plugin/builtin/kysely-type/index.mjs.map +1 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -4
- package/dist/plugin/get-generated-type.d.mts +4 -4
- package/dist/plugin/index.mjs +13 -13
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/types.d.mts +2 -2
- package/dist/plugin/with-context.d.mts +1 -1
- package/dist/{register-ts-hook-ClI226n2.mjs → register-ts-hook-Vqjza10T.mjs} +552 -416
- package/dist/register-ts-hook-Vqjza10T.mjs.map +1 -0
- package/dist/{repl-editor-BCozyiNq.mjs → repl-editor-F9LotMNx.mjs} +3 -2
- package/dist/{repl-editor-BCozyiNq.mjs.map → repl-editor-F9LotMNx.mjs.map} +1 -1
- package/dist/runtime/file.d.mts +14 -14
- package/dist/{schema--xYWRGfe.mjs → schema-D4TC4BC3.mjs} +4 -3
- package/dist/schema-D4TC4BC3.mjs.map +1 -0
- package/dist/seed/index.d.mts +4 -4
- package/dist/seed/index.mjs +3 -3
- package/dist/seed/index.mjs.map +1 -1
- package/dist/{seed-xqM9WIHP.mjs → seed-Cibjmtry.mjs} +46 -40
- package/dist/seed-Cibjmtry.mjs.map +1 -0
- package/dist/{service-C_WpbKHu.mjs → service-2grcAOCJ.mjs} +24 -24
- package/dist/service-2grcAOCJ.mjs.map +1 -0
- package/dist/service-CaBPNDIZ.mjs +3 -0
- package/dist/{service-hZskxZmg.mjs → service-LTfp03ZZ.mjs} +7 -5
- package/dist/{service-hZskxZmg.mjs.map → service-LTfp03ZZ.mjs.map} +1 -1
- package/dist/types/aigateway.generated.d.mts +2 -2
- package/dist/types/auth.generated.d.mts +3 -3
- package/dist/types/executor.generated.d.mts +1 -1
- package/dist/types/tailordb.generated.d.mts +12 -12
- package/dist/utils/test/index.d.mts +4 -4
- package/dist/utils/test/index.mjs +4 -4
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/index.mjs +13 -7
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/file.d.mts +1 -1
- package/docs/cli/application.md +3 -1
- package/docs/cli/function.md +17 -13
- package/docs/cli/tailordb.md +30 -30
- package/docs/cli/workspace.md +2 -2
- package/docs/cli-reference.md +21 -21
- package/docs/configuration.md +2 -2
- package/docs/migration/v2.md +4 -4
- package/docs/migration/v3.md +41 -0
- package/docs/quickstart.md +1 -1
- package/docs/services/aigateway.md +33 -9
- package/docs/services/executor.md +5 -1
- package/docs/services/tailordb-migration.md +47 -47
- package/docs/services/tailordb.md +7 -7
- package/docs/testing.md +1 -1
- package/package.json +6 -6
- package/dist/application-BZfk4HKm.mjs +0 -3
- package/dist/application-F-nl107y.mjs.map +0 -1
- package/dist/register-ts-hook-ClI226n2.mjs.map +0 -1
- package/dist/schema--xYWRGfe.mjs.map +0 -1
- package/dist/seed-xqM9WIHP.mjs.map +0 -1
- package/dist/service-C_WpbKHu.mjs.map +0 -1
- package/dist/service-D1RCdzIL.mjs +0 -3
|
@@ -77,7 +77,8 @@ const GQL_BUILTINS = /* @__PURE__ */ new Set([
|
|
|
77
77
|
function highlightGraphqlLine(line) {
|
|
78
78
|
if (line.trimStart().startsWith("#")) return `${DIM}${line}${RESET}`;
|
|
79
79
|
try {
|
|
80
|
-
const
|
|
80
|
+
const source = new Source(line);
|
|
81
|
+
const lexer = new Lexer(source);
|
|
81
82
|
let result = "";
|
|
82
83
|
let pos = 0;
|
|
83
84
|
let parenDepth = 0;
|
|
@@ -248,4 +249,4 @@ function replTransform(state, event) {
|
|
|
248
249
|
|
|
249
250
|
//#endregion
|
|
250
251
|
export { highlightGraphqlLine, highlightSqlLine, replTransform };
|
|
251
|
-
//# sourceMappingURL=repl-editor-
|
|
252
|
+
//# sourceMappingURL=repl-editor-F9LotMNx.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl-editor-BCozyiNq.mjs","names":[],"sources":["../src/cli/query/repl-editor.ts"],"sourcesContent":["import { GraphQLError, Lexer, Source, TokenKind } from \"graphql\";\nimport { getSegments } from \"sql-highlight\";\nimport { assertDefined } from \"#/utils/assert\";\nimport type { TransformEvent, TransformState } from \"@toiroakr/read-multiline\";\n\n// ANSI colour sequences. Kept inline (rather than going through `node:util`\n// styleText) so that tests running outside a TTY still produce deterministic\n// escape sequences regardless of detected colour support.\nconst RESET = \"\\x1b[0m\";\nconst BLUE = \"\\x1b[34m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BRIGHT_GREEN = \"\\x1b[92m\";\nconst CYAN = \"\\x1b[36m\";\nconst BRIGHT_BLUE = \"\\x1b[94m\";\nconst BOLD_CYAN = \"\\x1b[1;36m\";\nconst BOLD_MAGENTA = \"\\x1b[1;35m\";\nconst ITALIC_YELLOW = \"\\x1b[3;33m\";\nconst GREEN = \"\\x1b[32m\";\nconst DIM = \"\\x1b[90m\";\nconst DIM_YELLOW = \"\\x1b[2;33m\";\n\nconst SQL_STYLE_MAP: Record<string, string> = {\n keyword: BLUE,\n function: MAGENTA,\n identifier: YELLOW,\n string: BRIGHT_GREEN,\n number: CYAN,\n bracket: DIM_YELLOW,\n special: DIM,\n};\n\n/**\n * Highlight a single SQL line using the `sql-highlight` tokenizer.\n * @param line - SQL text for a single editor line\n * @returns ANSI-decorated line safe for terminal output\n */\nexport function highlightSqlLine(line: string): string {\n const segments = getSegments(line);\n let result = \"\";\n for (const seg of segments) {\n const style = SQL_STYLE_MAP[seg.name];\n result += style ? style + seg.content + RESET : seg.content;\n }\n return result;\n}\n\nconst GQL_KEYWORDS = new Set([\n \"query\",\n \"mutation\",\n \"subscription\",\n \"fragment\",\n \"on\",\n \"type\",\n \"input\",\n \"enum\",\n \"interface\",\n \"union\",\n \"scalar\",\n \"extend\",\n \"schema\",\n \"directive\",\n \"implements\",\n]);\n\n// Keywords that introduce a definition name (next NAME token is the def).\nconst GQL_DEF_KEYWORDS = new Set([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\nconst GQL_BUILTINS = new Set([\"true\", \"false\", \"null\"]);\n\n/**\n * Highlight a single GraphQL line using the official `graphql` Lexer. Tracks\n * paren depth and the previous token to provide semantic-level colouring\n * (field names vs argument names vs types).\n * @param line - GraphQL text for a single editor line\n * @returns ANSI-decorated line, or the input unchanged when the lexer rejects it\n */\nexport function highlightGraphqlLine(line: string): string {\n if (line.trimStart().startsWith(\"#\")) {\n return `${DIM}${line}${RESET}`;\n }\n\n try {\n const source = new Source(line);\n const lexer = new Lexer(source);\n let result = \"\";\n let pos = 0;\n\n let parenDepth = 0;\n let prevKind: string = \"\";\n let prevText = \"\";\n let afterColon = false;\n\n let token = lexer.advance();\n while (token.kind !== TokenKind.EOF) {\n if (token.start > pos) {\n result += line.slice(pos, token.start);\n }\n\n const text = line.slice(token.start, token.end);\n switch (token.kind) {\n case TokenKind.NAME: {\n if (prevKind === TokenKind.DOLLAR) {\n result += `${MAGENTA}${text}${RESET}`;\n } else if (prevKind === TokenKind.AT) {\n result += `${BOLD_MAGENTA}${text}${RESET}`;\n } else if (GQL_BUILTINS.has(text) || GQL_KEYWORDS.has(text)) {\n result += `${BLUE}${text}${RESET}`;\n } else if (GQL_DEF_KEYWORDS.has(prevText)) {\n result += `${BOLD_CYAN}${text}${RESET}`;\n } else if (afterColon) {\n result += `${CYAN}${text}${RESET}`;\n } else if (parenDepth > 0) {\n result += `${ITALIC_YELLOW}${text}${RESET}`;\n } else {\n result += `${BRIGHT_BLUE}${text}${RESET}`;\n }\n afterColon = false;\n break;\n }\n case TokenKind.INT:\n case TokenKind.FLOAT:\n result += `${BLUE}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.STRING:\n case TokenKind.BLOCK_STRING:\n result += `${GREEN}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.DOLLAR:\n result += `${MAGENTA}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.AT:\n result += `${BOLD_MAGENTA}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.BRACE_L:\n case TokenKind.BRACE_R:\n result += `${YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.PAREN_L:\n parenDepth += 1;\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.PAREN_R:\n parenDepth = Math.max(0, parenDepth - 1);\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.BRACKET_L:\n case TokenKind.BRACKET_R:\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.COLON:\n result += `${DIM}${text}${RESET}`;\n afterColon = true;\n break;\n case TokenKind.BANG:\n case TokenKind.EQUALS:\n case TokenKind.PIPE:\n case TokenKind.AMP:\n case TokenKind.SPREAD:\n result += `${DIM}${text}${RESET}`;\n afterColon = false;\n break;\n default:\n result += text;\n afterColon = false;\n }\n\n prevKind = token.kind;\n prevText = text;\n pos = token.end;\n token = lexer.advance();\n }\n\n if (pos < line.length) {\n result += line.slice(pos);\n }\n return result;\n } catch (error) {\n // The lexer throws GraphQLError on partial or invalid input (e.g. an\n // unterminated string while the user is still typing). Fall back to the\n // raw line so the editor keeps rendering without colour until the input\n // is valid. Any other error is a real bug and should surface.\n if (error instanceof GraphQLError) {\n return line;\n }\n throw error;\n }\n}\n\nconst BRACKET_PAIRS: Record<string, string> = { \"(\": \")\", \"[\": \"]\", \"{\": \"}\" };\nconst CLOSE_BRACKETS = new Set(Object.values(BRACKET_PAIRS));\n\n/**\n * Apply auto-close brackets and auto-indent on newline. Works for both SQL\n * and GraphQL because both languages share the `()`, `[]`, `{}` bracket set.\n * @param state - Editor state after the last edit\n * @param event - Event describing the edit that just occurred\n * @returns A new editor state to apply, or `undefined` to leave the state unchanged\n */\nexport function replTransform(\n state: TransformState,\n event: TransformEvent,\n): TransformState | undefined {\n const { lines, row, col } = state;\n\n if (event.type === \"insert\" && event.char in BRACKET_PAIRS) {\n const close = BRACKET_PAIRS[event.char];\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n const newLine = line.slice(0, col) + close + line.slice(col);\n return { lines: lines.with(row, newLine), row, col };\n }\n\n if (event.type === \"insert\" && CLOSE_BRACKETS.has(event.char)) {\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n if (line[col] === event.char) {\n const newLine = line.slice(0, col) + line.slice(col + 1);\n return { lines: lines.with(row, newLine), row, col };\n }\n }\n\n if (event.type === \"backspace\") {\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n const beforeCursor = line.slice(0, col);\n if (beforeCursor.length >= 1 && /^ +$/.test(beforeCursor)) {\n const newIndent = beforeCursor.slice(0, -1);\n const newLine = newIndent + line.slice(col);\n return { lines: lines.with(row, newLine), row, col: newIndent.length };\n }\n }\n\n if (event.type === \"newline\" && row > 0) {\n const prevLine = assertDefined(lines[row - 1], `line at row ${row - 1} missing`);\n const baseIndent = prevLine.match(/^(\\s*)/)?.[1] ?? \"\";\n const endsWithOpen = /[{([]$/.test(prevLine.trimEnd());\n const currentLine = assertDefined(lines[row], `line at row ${row} missing`);\n const startsWithClose = /^[}\\])]/.test(currentLine.trimStart());\n\n if (endsWithOpen && startsWithClose) {\n // Bracket expansion: the cursor sits between a matching open/close\n // pair (e.g. `{|}`). Expand into a three-line block with the cursor\n // on an indented middle line.\n const innerIndent = baseIndent + \" \";\n const newLines = lines\n .with(row, innerIndent)\n .toSpliced(row + 1, 0, baseIndent + currentLine.trimStart());\n return { lines: newLines, row, col: innerIndent.length };\n }\n if (endsWithOpen) {\n // A lone open bracket on the previous line: drop an extra indent for\n // the new line and auto-insert the matching closing bracket below.\n const openChar = prevLine.trimEnd().slice(-1);\n const closeChar = BRACKET_PAIRS[openChar] ?? \"}\";\n const indent = baseIndent + \" \";\n const newLines = lines\n .with(row, indent + currentLine)\n .toSpliced(row + 1, 0, baseIndent + closeChar);\n return { lines: newLines, row, col: indent.length };\n }\n if (baseIndent && col === 0) {\n return { lines: lines.with(row, baseIndent + currentLine), row, col: baseIndent.length };\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"repl-editor-F9LotMNx.mjs","names":[],"sources":["../src/cli/query/repl-editor.ts"],"sourcesContent":["import { GraphQLError, Lexer, Source, TokenKind } from \"graphql\";\nimport { getSegments } from \"sql-highlight\";\nimport { assertDefined } from \"#/utils/assert\";\nimport type { TransformEvent, TransformState } from \"@toiroakr/read-multiline\";\n\n// ANSI colour sequences. Kept inline (rather than going through `node:util`\n// styleText) so that tests running outside a TTY still produce deterministic\n// escape sequences regardless of detected colour support.\nconst RESET = \"\\x1b[0m\";\nconst BLUE = \"\\x1b[34m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BRIGHT_GREEN = \"\\x1b[92m\";\nconst CYAN = \"\\x1b[36m\";\nconst BRIGHT_BLUE = \"\\x1b[94m\";\nconst BOLD_CYAN = \"\\x1b[1;36m\";\nconst BOLD_MAGENTA = \"\\x1b[1;35m\";\nconst ITALIC_YELLOW = \"\\x1b[3;33m\";\nconst GREEN = \"\\x1b[32m\";\nconst DIM = \"\\x1b[90m\";\nconst DIM_YELLOW = \"\\x1b[2;33m\";\n\nconst SQL_STYLE_MAP: Record<string, string> = {\n keyword: BLUE,\n function: MAGENTA,\n identifier: YELLOW,\n string: BRIGHT_GREEN,\n number: CYAN,\n bracket: DIM_YELLOW,\n special: DIM,\n};\n\n/**\n * Highlight a single SQL line using the `sql-highlight` tokenizer.\n * @param line - SQL text for a single editor line\n * @returns ANSI-decorated line safe for terminal output\n */\nexport function highlightSqlLine(line: string): string {\n const segments = getSegments(line);\n let result = \"\";\n for (const seg of segments) {\n const style = SQL_STYLE_MAP[seg.name];\n result += style ? style + seg.content + RESET : seg.content;\n }\n return result;\n}\n\nconst GQL_KEYWORDS = new Set([\n \"query\",\n \"mutation\",\n \"subscription\",\n \"fragment\",\n \"on\",\n \"type\",\n \"input\",\n \"enum\",\n \"interface\",\n \"union\",\n \"scalar\",\n \"extend\",\n \"schema\",\n \"directive\",\n \"implements\",\n]);\n\n// Keywords that introduce a definition name (next NAME token is the def).\nconst GQL_DEF_KEYWORDS = new Set([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\nconst GQL_BUILTINS = new Set([\"true\", \"false\", \"null\"]);\n\n/**\n * Highlight a single GraphQL line using the official `graphql` Lexer. Tracks\n * paren depth and the previous token to provide semantic-level colouring\n * (field names vs argument names vs types).\n * @param line - GraphQL text for a single editor line\n * @returns ANSI-decorated line, or the input unchanged when the lexer rejects it\n */\nexport function highlightGraphqlLine(line: string): string {\n if (line.trimStart().startsWith(\"#\")) {\n return `${DIM}${line}${RESET}`;\n }\n\n try {\n const source = new Source(line);\n const lexer = new Lexer(source);\n let result = \"\";\n let pos = 0;\n\n let parenDepth = 0;\n let prevKind: string = \"\";\n let prevText = \"\";\n let afterColon = false;\n\n let token = lexer.advance();\n while (token.kind !== TokenKind.EOF) {\n if (token.start > pos) {\n result += line.slice(pos, token.start);\n }\n\n const text = line.slice(token.start, token.end);\n switch (token.kind) {\n case TokenKind.NAME: {\n if (prevKind === TokenKind.DOLLAR) {\n result += `${MAGENTA}${text}${RESET}`;\n } else if (prevKind === TokenKind.AT) {\n result += `${BOLD_MAGENTA}${text}${RESET}`;\n } else if (GQL_BUILTINS.has(text) || GQL_KEYWORDS.has(text)) {\n result += `${BLUE}${text}${RESET}`;\n } else if (GQL_DEF_KEYWORDS.has(prevText)) {\n result += `${BOLD_CYAN}${text}${RESET}`;\n } else if (afterColon) {\n result += `${CYAN}${text}${RESET}`;\n } else if (parenDepth > 0) {\n result += `${ITALIC_YELLOW}${text}${RESET}`;\n } else {\n result += `${BRIGHT_BLUE}${text}${RESET}`;\n }\n afterColon = false;\n break;\n }\n case TokenKind.INT:\n case TokenKind.FLOAT:\n result += `${BLUE}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.STRING:\n case TokenKind.BLOCK_STRING:\n result += `${GREEN}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.DOLLAR:\n result += `${MAGENTA}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.AT:\n result += `${BOLD_MAGENTA}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.BRACE_L:\n case TokenKind.BRACE_R:\n result += `${YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.PAREN_L:\n parenDepth += 1;\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.PAREN_R:\n parenDepth = Math.max(0, parenDepth - 1);\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.BRACKET_L:\n case TokenKind.BRACKET_R:\n result += `${DIM_YELLOW}${text}${RESET}`;\n afterColon = false;\n break;\n case TokenKind.COLON:\n result += `${DIM}${text}${RESET}`;\n afterColon = true;\n break;\n case TokenKind.BANG:\n case TokenKind.EQUALS:\n case TokenKind.PIPE:\n case TokenKind.AMP:\n case TokenKind.SPREAD:\n result += `${DIM}${text}${RESET}`;\n afterColon = false;\n break;\n default:\n result += text;\n afterColon = false;\n }\n\n prevKind = token.kind;\n prevText = text;\n pos = token.end;\n token = lexer.advance();\n }\n\n if (pos < line.length) {\n result += line.slice(pos);\n }\n return result;\n } catch (error) {\n // The lexer throws GraphQLError on partial or invalid input (e.g. an\n // unterminated string while the user is still typing). Fall back to the\n // raw line so the editor keeps rendering without colour until the input\n // is valid. Any other error is a real bug and should surface.\n if (error instanceof GraphQLError) {\n return line;\n }\n throw error;\n }\n}\n\nconst BRACKET_PAIRS: Record<string, string> = { \"(\": \")\", \"[\": \"]\", \"{\": \"}\" };\nconst CLOSE_BRACKETS = new Set(Object.values(BRACKET_PAIRS));\n\n/**\n * Apply auto-close brackets and auto-indent on newline. Works for both SQL\n * and GraphQL because both languages share the `()`, `[]`, `{}` bracket set.\n * @param state - Editor state after the last edit\n * @param event - Event describing the edit that just occurred\n * @returns A new editor state to apply, or `undefined` to leave the state unchanged\n */\nexport function replTransform(\n state: TransformState,\n event: TransformEvent,\n): TransformState | undefined {\n const { lines, row, col } = state;\n\n if (event.type === \"insert\" && event.char in BRACKET_PAIRS) {\n const close = BRACKET_PAIRS[event.char];\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n const newLine = line.slice(0, col) + close + line.slice(col);\n return { lines: lines.with(row, newLine), row, col };\n }\n\n if (event.type === \"insert\" && CLOSE_BRACKETS.has(event.char)) {\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n if (line[col] === event.char) {\n const newLine = line.slice(0, col) + line.slice(col + 1);\n return { lines: lines.with(row, newLine), row, col };\n }\n }\n\n if (event.type === \"backspace\") {\n const line = assertDefined(lines[row], `line at row ${row} missing`);\n const beforeCursor = line.slice(0, col);\n if (beforeCursor.length >= 1 && /^ +$/.test(beforeCursor)) {\n const newIndent = beforeCursor.slice(0, -1);\n const newLine = newIndent + line.slice(col);\n return { lines: lines.with(row, newLine), row, col: newIndent.length };\n }\n }\n\n if (event.type === \"newline\" && row > 0) {\n const prevLine = assertDefined(lines[row - 1], `line at row ${row - 1} missing`);\n const baseIndent = prevLine.match(/^(\\s*)/)?.[1] ?? \"\";\n const endsWithOpen = /[{([]$/.test(prevLine.trimEnd());\n const currentLine = assertDefined(lines[row], `line at row ${row} missing`);\n const startsWithClose = /^[}\\])]/.test(currentLine.trimStart());\n\n if (endsWithOpen && startsWithClose) {\n // Bracket expansion: the cursor sits between a matching open/close\n // pair (e.g. `{|}`). Expand into a three-line block with the cursor\n // on an indented middle line.\n const innerIndent = baseIndent + \" \";\n const newLines = lines\n .with(row, innerIndent)\n .toSpliced(row + 1, 0, baseIndent + currentLine.trimStart());\n return { lines: newLines, row, col: innerIndent.length };\n }\n if (endsWithOpen) {\n // A lone open bracket on the previous line: drop an extra indent for\n // the new line and auto-insert the matching closing bracket below.\n const openChar = prevLine.trimEnd().slice(-1);\n const closeChar = BRACKET_PAIRS[openChar] ?? \"}\";\n const indent = baseIndent + \" \";\n const newLines = lines\n .with(row, indent + currentLine)\n .toSpliced(row + 1, 0, baseIndent + closeChar);\n return { lines: newLines, row, col: indent.length };\n }\n if (baseIndent && col === 0) {\n return { lines: lines.with(row, baseIndent + currentLine), row, col: baseIndent.length };\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
|
package/dist/runtime/file.d.mts
CHANGED
|
@@ -85,8 +85,8 @@ interface TailorDBFileAPI {
|
|
|
85
85
|
/**
|
|
86
86
|
* Upload a file to TailorDB.
|
|
87
87
|
* @param namespace - TailorDB namespace
|
|
88
|
-
* @param typeName - TailorDB
|
|
89
|
-
* @param fieldName - File field name on the
|
|
88
|
+
* @param typeName - TailorDB table name
|
|
89
|
+
* @param fieldName - File field name on the table
|
|
90
90
|
* @param recordId - Record ID owning the field
|
|
91
91
|
* @param data - File contents
|
|
92
92
|
* @param options - Upload options (e.g. `contentType`)
|
|
@@ -99,8 +99,8 @@ interface TailorDBFileAPI {
|
|
|
99
99
|
* Throws `TailorDBFileError` with code `FILE_TOO_LARGE` when the file
|
|
100
100
|
* exceeds 10MB — use {@link downloadStream} for large files.
|
|
101
101
|
* @param namespace - TailorDB namespace
|
|
102
|
-
* @param typeName - TailorDB
|
|
103
|
-
* @param fieldName - File field name on the
|
|
102
|
+
* @param typeName - TailorDB table name
|
|
103
|
+
* @param fieldName - File field name on the table
|
|
104
104
|
* @param recordId - Record ID owning the field
|
|
105
105
|
* @returns Bytes and metadata for the file
|
|
106
106
|
*/
|
|
@@ -111,8 +111,8 @@ interface TailorDBFileAPI {
|
|
|
111
111
|
* Throws `TailorDBFileError` with code `FILE_TOO_LARGE` when the file
|
|
112
112
|
* exceeds 10MB — use {@link downloadStream} for large files.
|
|
113
113
|
* @param namespace - TailorDB namespace
|
|
114
|
-
* @param typeName - TailorDB
|
|
115
|
-
* @param fieldName - File field name on the
|
|
114
|
+
* @param typeName - TailorDB table name
|
|
115
|
+
* @param fieldName - File field name on the table
|
|
116
116
|
* @param recordId - Record ID owning the field
|
|
117
117
|
* @returns Base64-encoded contents and metadata for the file
|
|
118
118
|
*/
|
|
@@ -120,8 +120,8 @@ interface TailorDBFileAPI {
|
|
|
120
120
|
/**
|
|
121
121
|
* Delete a file from TailorDB.
|
|
122
122
|
* @param namespace - TailorDB namespace
|
|
123
|
-
* @param typeName - TailorDB
|
|
124
|
-
* @param fieldName - File field name on the
|
|
123
|
+
* @param typeName - TailorDB table name
|
|
124
|
+
* @param fieldName - File field name on the table
|
|
125
125
|
* @param recordId - Record ID owning the field
|
|
126
126
|
* @returns Resolves once the file has been deleted
|
|
127
127
|
*/
|
|
@@ -129,8 +129,8 @@ interface TailorDBFileAPI {
|
|
|
129
129
|
/**
|
|
130
130
|
* Get file metadata from TailorDB.
|
|
131
131
|
* @param namespace - TailorDB namespace
|
|
132
|
-
* @param typeName - TailorDB
|
|
133
|
-
* @param fieldName - File field name on the
|
|
132
|
+
* @param typeName - TailorDB table name
|
|
133
|
+
* @param fieldName - File field name on the table
|
|
134
134
|
* @param recordId - Record ID owning the field
|
|
135
135
|
* @returns Metadata for the stored file
|
|
136
136
|
*/
|
|
@@ -138,8 +138,8 @@ interface TailorDBFileAPI {
|
|
|
138
138
|
/**
|
|
139
139
|
* Download a file as a ReadableStream.
|
|
140
140
|
* @param namespace - TailorDB namespace
|
|
141
|
-
* @param typeName - TailorDB
|
|
142
|
-
* @param fieldName - File field name on the
|
|
141
|
+
* @param typeName - TailorDB table name
|
|
142
|
+
* @param fieldName - File field name on the table
|
|
143
143
|
* @param recordId - Record ID owning the field
|
|
144
144
|
* @returns ReadableStream body and metadata for the file
|
|
145
145
|
*/
|
|
@@ -147,8 +147,8 @@ interface TailorDBFileAPI {
|
|
|
147
147
|
/**
|
|
148
148
|
* Upload a file using a ReadableStream.
|
|
149
149
|
* @param namespace - TailorDB namespace
|
|
150
|
-
* @param typeName - TailorDB
|
|
151
|
-
* @param fieldName - File field name on the
|
|
150
|
+
* @param typeName - TailorDB table name
|
|
151
|
+
* @param fieldName - File field name on the table
|
|
152
152
|
* @param recordId - Record ID owning the field
|
|
153
153
|
* @param readableStream - ReadableStream providing the file data
|
|
154
154
|
* @param options - Upload stream options (e.g. `contentType`, `fileSize`)
|
|
@@ -477,7 +477,7 @@ function createTailorDBType(name, fields, options) {
|
|
|
477
477
|
if (name === options.pluralForm) throw new Error(`The name and the plural form must be different. name=${name}`);
|
|
478
478
|
_settings.pluralForm = options.pluralForm;
|
|
479
479
|
}
|
|
480
|
-
|
|
480
|
+
const dbType = {
|
|
481
481
|
name,
|
|
482
482
|
fields: { ...fields },
|
|
483
483
|
_output: null,
|
|
@@ -582,7 +582,8 @@ function createTailorDBType(name, fields, options) {
|
|
|
582
582
|
});
|
|
583
583
|
return this;
|
|
584
584
|
}
|
|
585
|
-
}
|
|
585
|
+
};
|
|
586
|
+
return brandValue(dbType, "tailordb-type");
|
|
586
587
|
}
|
|
587
588
|
const idField = uuid();
|
|
588
589
|
function dbTable(name, fieldsOrDescription, fields) {
|
|
@@ -636,4 +637,4 @@ timestamps: () => ({
|
|
|
636
637
|
|
|
637
638
|
//#endregion
|
|
638
639
|
export { parseInternal as i, mapAllowedValues as n, parseInputFields as r, db as t };
|
|
639
|
-
//# sourceMappingURL=schema
|
|
640
|
+
//# sourceMappingURL=schema-D4TC4BC3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-D4TC4BC3.mjs","names":["parseInternal","parseFieldInternal"],"sources":["../src/runtime/field-parse.ts","../src/configure/types/field.ts","../src/configure/services/tailordb/schema.ts"],"sourcesContent":["import type { FieldMetadata, TailorFieldType } from \"#/configure/types/field.types\";\nimport type { TailorPrincipal } from \"#/runtime/types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nconst regex = {\n uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,\n date: /^(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})$/,\n time: /^(?<hour>[01]\\d|2[0-3]):(?<minute>[0-5]\\d)$/,\n datetime:\n /^(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})[Tt](?<hour>[01]\\d|2[0-3]):(?<minute>[0-5]\\d):(?<second>[0-5]\\d|60)(\\.(?<fraction>\\d+))?(?<offset>[Zz]|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$/,\n decimal: /^-?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/,\n} as const;\n\nexport type FieldParseArgs = {\n value: unknown;\n data: unknown;\n invoker: TailorPrincipal | null;\n};\n\nexport type FieldParseInternalArgs = {\n value: unknown;\n data: unknown;\n invoker: TailorPrincipal | null;\n pathArray: string[];\n};\n\nexport type FieldRuntime<T extends TailorFieldType = TailorFieldType> = {\n readonly type: T;\n readonly fields: Record<string, FieldRuntime>;\n _metadata: FieldMetadata;\n};\n\ntype FieldParseRuntimeArgs<T extends TailorFieldType> = FieldParseInternalArgs & {\n field: FieldRuntime<T>;\n};\n\ntype FieldValidationArgs<T extends TailorFieldType> = FieldParseRuntimeArgs<T> & {\n issues: StandardSchemaV1.Issue[];\n};\n\nfunction validateBaseValue<T extends TailorFieldType>(args: FieldValidationArgs<T>): boolean {\n const { field, value, pathArray, issues } = args;\n const path = pathArray.length > 0 ? pathArray : undefined;\n const initialIssueCount = issues.length;\n\n switch (field.type) {\n case \"string\":\n if (typeof value !== \"string\") {\n issues.push({\n message: `Expected a string: received ${String(value)}`,\n path,\n });\n }\n break;\n\n case \"integer\":\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n issues.push({\n message: `Expected an integer: received ${String(value)}`,\n path,\n });\n }\n break;\n\n case \"float\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n issues.push({\n message: `Expected a number: received ${String(value)}`,\n path,\n });\n }\n break;\n\n case \"boolean\":\n if (typeof value !== \"boolean\") {\n issues.push({\n message: `Expected a boolean: received ${String(value)}`,\n path,\n });\n }\n break;\n\n case \"uuid\":\n if (typeof value !== \"string\" || !regex.uuid.test(value)) {\n issues.push({\n message: `Expected a valid UUID: received ${String(value)}`,\n path,\n });\n }\n break;\n case \"date\":\n if (typeof value !== \"string\" || !regex.date.test(value)) {\n issues.push({\n message: `Expected to match \"yyyy-MM-dd\" format: received ${String(value)}`,\n path,\n });\n }\n break;\n case \"datetime\":\n if (typeof value !== \"string\" || !regex.datetime.test(value)) {\n issues.push({\n message: `Expected to match ISO format: received ${String(value)}`,\n path,\n });\n }\n break;\n case \"time\":\n if (typeof value !== \"string\" || !regex.time.test(value)) {\n issues.push({\n message: `Expected to match \"HH:mm\" format: received ${String(value)}`,\n path,\n });\n }\n break;\n case \"decimal\":\n if (typeof value !== \"string\" || !regex.decimal.test(value)) {\n issues.push({\n message: `Expected a decimal string: received ${String(value)}`,\n path,\n });\n }\n break;\n\n case \"enum\":\n if (field._metadata.allowedValues) {\n const allowedValues = field._metadata.allowedValues.map((v) => v.value);\n if (typeof value !== \"string\" || !allowedValues.includes(value)) {\n issues.push({\n message: `Must be one of [${allowedValues.join(\", \")}]: received ${String(value)}`,\n path,\n });\n }\n }\n break;\n\n case \"nested\": {\n // Runtime input may not match the declared field type.\n // oxlint-disable typescript/no-unnecessary-condition\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value) ||\n value instanceof Date\n ) {\n // oxlint-enable typescript/no-unnecessary-condition\n issues.push({\n message: `Expected an object: received ${String(value)}`,\n path,\n });\n return false;\n }\n\n let nestedBaseValid = true;\n for (const [fieldName, nestedField] of Object.entries(field.fields)) {\n const fieldValue = (value as Record<string, unknown>)[fieldName];\n if (\n !validateBaseField({\n ...args,\n field: nestedField,\n value: fieldValue,\n pathArray: pathArray.concat(fieldName),\n })\n ) {\n nestedBaseValid = false;\n }\n }\n return nestedBaseValid;\n }\n }\n\n return issues.length === initialIssueCount;\n}\n\nfunction validateCustomValue<T extends TailorFieldType>(\n args: FieldValidationArgs<T>,\n validateFns: NonNullable<FieldMetadata[\"validate\"]>,\n): void {\n const { value, pathArray, issues } = args;\n const path = pathArray.length > 0 ? pathArray : undefined;\n\n for (const fn of validateFns) {\n const result = fn({ value });\n if (typeof result === \"string\") {\n issues.push({\n message: result,\n path,\n });\n }\n }\n}\n\nfunction validateBaseField<T extends TailorFieldType>(args: FieldValidationArgs<T>): boolean {\n const { field, value, pathArray, issues } = args;\n const path = pathArray.length > 0 ? pathArray : undefined;\n\n const isNullOrUndefined = value === null || value === undefined;\n if (field._metadata.required && isNullOrUndefined) {\n issues.push({\n message: \"Required field is missing\",\n path,\n });\n return false;\n }\n\n if (!field._metadata.required && isNullOrUndefined) {\n return true;\n }\n\n let baseValid = true;\n if (field._metadata.array) {\n if (!Array.isArray(value)) {\n issues.push({\n message: \"Expected an array\",\n path,\n });\n return false;\n }\n\n for (let i = 0; i < value.length; i++) {\n if (\n !validateBaseValue({\n ...args,\n value: value[i],\n pathArray: pathArray.concat(`[${i}]`),\n })\n ) {\n baseValid = false;\n }\n }\n } else {\n baseValid = validateBaseValue(args);\n }\n\n return baseValid;\n}\n\nfunction validateCustomField<T extends TailorFieldType>(args: FieldValidationArgs<T>): void {\n const { field, value, pathArray } = args;\n if (value === null || value === undefined) {\n return;\n }\n\n if (field.type === \"nested\") {\n const records = field._metadata.array\n ? (value as Record<string, unknown>[])\n : [value as Record<string, unknown>];\n\n for (let i = 0; i < records.length; i++) {\n const record = records[i] as Record<string, unknown>;\n const recordPath = field._metadata.array ? pathArray.concat(`[${i}]`) : pathArray;\n for (const [fieldName, nestedField] of Object.entries(field.fields)) {\n validateCustomField({\n ...args,\n field: nestedField,\n value: record[fieldName],\n pathArray: recordPath.concat(fieldName),\n });\n }\n }\n }\n\n const validateFns = field._metadata.validate;\n if (validateFns?.length) {\n validateCustomValue(args, validateFns);\n }\n}\n\nexport function parseInternal<T extends TailorFieldType, Output>(\n args: FieldParseRuntimeArgs<T>,\n): StandardSchemaV1.Result<Output> {\n const { value } = args;\n const issues: StandardSchemaV1.Issue[] = [];\n const validationArgs = { ...args, issues };\n const baseValid = validateBaseField(validationArgs);\n if (baseValid) {\n validateCustomField(validationArgs);\n }\n if (issues.length > 0) {\n return { issues };\n }\n\n return { value: (value ?? null) as Output };\n}\n\ntype ParseInputFieldsArgs = FieldParseArgs & {\n fields: Record<string, FieldRuntime>;\n};\n\n/**\n * Validate a value against a record of input fields, treating the record as a\n * single required object — the same code path deployed functions run via\n * `t.object(fields).parse(...)`, so local validation matches platform behavior.\n * @param args - Input field definitions plus the value, context data, and user\n * @returns Validation result\n */\nexport function parseInputFields(args: ParseInputFieldsArgs): StandardSchemaV1.Result<unknown> {\n const { fields, ...parseArgs } = args;\n return parseInternal({\n ...parseArgs,\n field: { type: \"nested\", fields, _metadata: { required: true } },\n pathArray: [],\n });\n}\n","import type { EnumValue } from \"#/configure/types/field.types\";\n\nexport type AllowedValues = readonly [string | EnumValue, ...(string | EnumValue)[]];\n\n/**\n * Normalize allowed values into EnumValue objects with descriptions.\n * @param values - Allowed values as strings or EnumValue objects\n * @returns Normalized allowed values\n */\nexport function mapAllowedValues(values: AllowedValues): EnumValue[] {\n return values.map((value) => {\n if (typeof value === \"string\") {\n return { value, description: \"\" };\n }\n return { ...value, description: value.description ?? \"\" };\n });\n}\n\nexport type AllowedValuesOutput<V extends AllowedValues> = V[number] extends infer T\n ? T extends string\n ? T\n : T extends { value: infer K }\n ? K\n : never\n : never;\n","import { cloneDeep } from \"es-toolkit\";\nimport {\n type AllowedValues,\n type AllowedValuesOutput,\n mapAllowedValues,\n} from \"#/configure/types/field\";\nimport {\n parseInternal as parseFieldInternal,\n type FieldParseArgs,\n type FieldParseInternalArgs,\n} from \"#/runtime/field-parse\";\nimport { brandValue } from \"#/utils/brand\";\nimport type {\n TailorDBField as TailorDBFieldBase,\n TailorDBType as TailorDBTypeBase,\n DBFieldMetadata,\n DefinedDBFieldMetadata,\n SerialConfig,\n IndexDef,\n TailorDBTypeMetadata,\n RawRelationConfig,\n RelationType,\n} from \"#/configure/services/tailordb/types\";\nimport type {\n FieldOptions,\n FieldOutput,\n TailorFieldType,\n TailorToTs,\n FieldValidateInput,\n} from \"#/configure/types/field.types\";\nimport type { PluginAttachment, PluginConfigs } from \"#/plugin/types\";\nimport type { InferredAttributes } from \"#/runtime/types\";\nimport type { output, InferFieldsOutput, TypeLevelError } from \"#/types/helpers\";\nimport type { RawPermissions } from \"#/types/tailordb.generated\";\nimport type { TailorTypeGqlPermission, TailorTypePermission } from \"./permission\";\nimport type {\n Hook,\n TypeHook,\n ExcludeNestedDBFields,\n ExcludeHookedDBFields,\n ExcludeDefaultedDBFields,\n TypeFeatures,\n TypeValidateFn,\n} from \"./types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n// Erased DB fields stay assignable across builder method-state changes.\n// oxlint-disable-next-line no-explicit-any\ntype AnyBuilderMethod = any;\n\nexport type TailorAnyDBField = Omit<\n TailorDBFieldBase<AnyBuilderMethod, AnyBuilderMethod>,\n \"fields\"\n> & {\n readonly fields: Record<string, AnyBuilderMethod>;\n _metadata: DBFieldMetadata;\n parse: AnyBuilderMethod;\n readonly typeName: TypeLevelError<string>;\n description: AnyBuilderMethod;\n relation: AnyBuilderMethod;\n index: AnyBuilderMethod;\n unique: AnyBuilderMethod;\n vector: AnyBuilderMethod;\n default: AnyBuilderMethod;\n hooks: AnyBuilderMethod;\n validate: AnyBuilderMethod;\n serial: AnyBuilderMethod;\n clone: AnyBuilderMethod;\n};\n\n// Helper alias\n// oxlint-disable-next-line no-explicit-any\nexport type TailorAnyDBType = TailorDBType<any, any, any>;\n\ntype IsAny<T> = 0 extends 1 & T ? true : false;\ntype DBFieldTypeNameMethod<Defined extends DefinedDBFieldMetadata> =\n IsAny<Defined> extends true\n ? TypeLevelError<string>\n : TypeLevelError<\"typeName cannot be used on TailorDB fields\">;\n\ntype WithDBFieldDescription<Defined> = Defined & { description: true };\ntype WithDBFieldRelation<Defined, S extends RelationType | RelationSelfConfig> = S extends\n | \"oneToOne\"\n | \"1-1\"\n ? Defined & { unique: true; index: true; relation: true }\n : S extends { type: \"oneToOne\" | \"1-1\" }\n ? Defined & { unique: true; index: true; relation: true }\n : Defined & { index: true; relation: true };\ntype WithDBFieldIndex<Defined> = Defined & { index: true };\ntype WithDBFieldUnique<Defined> = Defined & { unique: true; index: true };\ntype WithDBFieldVector<Defined> = Defined & { vector: true };\ntype WithDBFieldHooks<Defined, H> = Defined & {\n hooks: {\n create: H extends { create: unknown } ? true : false;\n update: H extends { update: unknown } ? true : false;\n };\n serial: false;\n};\ntype WithDBFieldDefault<Defined> = Defined & { default: true };\ntype WithDBFieldValidate<Defined> = Defined & { validate: true };\ntype WithDBFieldSerial<Defined> = Defined & {\n serial: true;\n hooks: { create: false; update: false };\n};\ntype WithDBFieldCloneOptions<Defined extends DefinedDBFieldMetadata, NewOpt extends FieldOptions> =\n IsAny<Defined> extends true\n ? Defined\n : Omit<Defined, \"array\"> & {\n array: NewOpt extends { array: true }\n ? true\n : NewOpt extends { array: false }\n ? false\n : Defined[\"array\"];\n };\ntype NonNullableDBFieldOutput<Output> = Exclude<Output, null>;\ntype DBFieldScalarOutput<Output> =\n NonNullableDBFieldOutput<Output> extends (infer Item)[] ? Item : NonNullableDBFieldOutput<Output>;\ntype DBFieldCloneArrayOutput<Output, NewOpt extends FieldOptions> = NewOpt extends {\n array: true;\n}\n ? DBFieldScalarOutput<Output>[]\n : NewOpt extends { array: false }\n ? DBFieldScalarOutput<Output>\n : NonNullableDBFieldOutput<Output>;\ntype DBFieldCloneOutput<Output, NewOpt extends FieldOptions> = NewOpt extends { optional: true }\n ? DBFieldCloneArrayOutput<Output, NewOpt> | null\n : NewOpt extends { optional: false }\n ? DBFieldCloneArrayOutput<Output, NewOpt>\n : null extends Output\n ? DBFieldCloneArrayOutput<Output, NewOpt> | null\n : DBFieldCloneArrayOutput<Output, NewOpt>;\ntype DBFieldCloneOptions<Defined extends DefinedDBFieldMetadata> = Omit<FieldOptions, \"array\"> & {\n array?: Defined extends { validate: unknown } ? Defined[\"array\"] : boolean;\n};\ntype InvalidValidatedArrayCloneKeys<\n Fields extends Record<string, TailorAnyDBField>,\n K extends keyof Fields,\n Opt extends FieldOptions,\n> = Opt extends { array: infer ArrayOption }\n ? {\n [P in K]: Fields[P] extends TailorDBField<infer Defined, infer _Output>\n ? Defined extends { validate: unknown }\n ? ArrayOption extends Defined[\"array\"]\n ? never\n : P\n : never\n : never;\n }[K]\n : never;\ntype DBFieldsCloneOptionsGuard<\n Fields extends Record<string, TailorAnyDBField>,\n K extends keyof Fields,\n Opt extends FieldOptions,\n> = [InvalidValidatedArrayCloneKeys<Fields, K, Opt>] extends [never]\n ? unknown\n : TypeLevelError<\"array cannot be changed on fields with custom validation\">;\ntype DefinedDBTypeMetadata = {\n hooks?: true;\n validate?: true;\n features?: true;\n indexes?: true;\n files?: true;\n permission?: true;\n gqlPermission?: true;\n description?: true;\n};\ntype WithDBTypeMetadata<\n Defined extends DefinedDBTypeMetadata,\n Key extends keyof DefinedDBTypeMetadata,\n> = Defined & Record<Key, true>;\ntype DBTypeDuplicateInputGuard<\n Defined extends DefinedDBTypeMetadata,\n Key extends keyof DefinedDBTypeMetadata,\n Input,\n Message extends string,\n> =\n IsAny<Defined> extends true\n ? Input\n : Defined extends Record<Key, unknown>\n ? TypeLevelError<Message>\n : Input;\ntype DBTypeDuplicateRestGuard<\n Defined extends DefinedDBTypeMetadata,\n Key extends keyof DefinedDBTypeMetadata,\n Input extends unknown[],\n Message extends string,\n> =\n IsAny<Defined> extends true\n ? Input\n : Defined extends Record<Key, unknown>\n ? [TypeLevelError<Message>, ...TypeLevelError<Message>[]]\n : Input;\ntype FileKeyConflictError<\n Fields extends Record<string, TailorAnyDBField>,\n User extends object,\n> = Partial<\n Record<\n keyof output<TailorDBType<Fields, User>> & string,\n TypeLevelError<\"file keys cannot use existing field names\">\n >\n>;\ntype DBFieldDescriptionFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = (description: string) => TailorDBField<WithDBFieldDescription<Defined>, Output, Nested>;\ntype DBFieldRelationFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = {\n <S extends RelationType, T extends TailorAnyDBType>(\n config: RelationConfig<S, T>,\n ): TailorDBField<WithDBFieldRelation<Defined, S>, Output, Nested>;\n <S extends RelationSelfConfig>(\n config: S,\n ): TailorDBField<WithDBFieldRelation<Defined, S>, Output, Nested>;\n};\ntype DBFieldIndexFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = () => TailorDBField<WithDBFieldIndex<Defined>, Output, Nested>;\ntype DBFieldUniqueFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = () => TailorDBField<WithDBFieldUnique<Defined>, Output, Nested>;\ntype DBFieldVectorFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = () => TailorDBField<WithDBFieldVector<Defined>, Output, Nested>;\ntype DBFieldHooksFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = <\n const H extends Hook<\n Output,\n Defined extends { default: true } ? Output | null | undefined : Output\n >,\n>(\n hooks: H,\n) => TailorDBField<WithDBFieldHooks<Defined, H>, Output, Nested>;\ntype DBFieldValidateFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = (\n ...validate: FieldValidateInput<Output>[]\n) => TailorDBField<WithDBFieldValidate<Defined>, Output, Nested>;\ntype DBFieldSerialFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = (\n config: SerialConfig<Defined[\"type\"] & (\"integer\" | \"string\")>,\n) => TailorDBField<WithDBFieldSerial<Defined>, Output, Nested>;\ntype DBFieldDescriptionMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldDescriptionFn<Defined, Output, Nested>\n : Defined extends { description: unknown }\n ? TypeLevelError<\".description() has already been set\">\n : DBFieldDescriptionFn<Defined, Output, Nested>;\ntype DBFieldRelationMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldRelationFn<Defined, Output, Nested>\n : Defined extends { relation: unknown }\n ? TypeLevelError<\".relation() has already been set\">\n : DBFieldRelationFn<Defined, Output, Nested>;\ntype DBFieldArrayCheck<A extends boolean, Ok, Msg extends string> = A extends true\n ? TypeLevelError<Msg>\n : Ok;\ntype DBFieldIndexMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldIndexFn<Defined, Output, Nested>\n : Defined extends { index: unknown }\n ? TypeLevelError<\".index() has already been set\">\n : DBFieldArrayCheck<\n Defined[\"array\"],\n DBFieldIndexFn<Defined, Output, Nested>,\n \"index cannot be set on array fields\"\n >;\ntype DBFieldUniqueMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldUniqueFn<Defined, Output, Nested>\n : Defined extends { unique: unknown }\n ? TypeLevelError<\".unique() has already been set\">\n : DBFieldArrayCheck<\n Defined[\"array\"],\n DBFieldUniqueFn<Defined, Output, Nested>,\n \"unique cannot be set on array fields\"\n >;\ntype DBFieldVectorMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldVectorFn<Defined, Output, Nested>\n : Defined extends { vector: unknown }\n ? TypeLevelError<\".vector() has already been set\">\n : Defined extends { type: \"string\"; array: false }\n ? DBFieldVectorFn<Defined, Output, Nested>\n : TypeLevelError<\"vector can only be set on non-array string fields\">;\ntype DBFieldHooksMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldHooksFn<Defined, Output, Nested>\n : Defined extends {\n serial: true;\n hooks: { create: false; update: false };\n }\n ? TypeLevelError<\"hooks cannot be set after serial\">\n : Defined extends {\n hooks: unknown;\n }\n ? TypeLevelError<\".hooks() has already been set\">\n : Defined extends { type: \"nested\" }\n ? TypeLevelError<\"hooks cannot be set on nested type fields\">\n : DBFieldHooksFn<Defined, Output, Nested>;\ntype DBFieldValidateMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldValidateFn<Defined, Output, Nested>\n : Defined extends { validate: unknown }\n ? TypeLevelError<\".validate() has already been set\">\n : DBFieldValidateFn<Defined, Output, Nested>;\ntype DBFieldSerialMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldSerialFn<Defined, Output, Nested>\n : Defined extends { serial: true }\n ? TypeLevelError<\".serial() has already been set\">\n : Defined extends { serial: false }\n ? TypeLevelError<\"serial cannot be set after hooks\">\n : IsAny<Output> extends true\n ? Defined extends { type: \"integer\" | \"string\"; array: false }\n ? DBFieldSerialFn<Defined, Output, Nested>\n : TypeLevelError<\"serial can only be set on non-array integer or string fields\">\n : null extends Output\n ? TypeLevelError<\"serial can only be set on non-array integer or string fields\">\n : Defined extends { type: \"integer\" | \"string\"; array: false }\n ? DBFieldSerialFn<Defined, Output, Nested>\n : TypeLevelError<\"serial can only be set on non-array integer or string fields\">;\ntype DBFieldDefaultFn<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> = (\n value: Output extends null ? NonNullable<Output> : Output,\n) => TailorDBField<WithDBFieldDefault<Defined>, Output, Nested>;\ntype DBFieldDefaultMethod<\n Defined extends DefinedDBFieldMetadata,\n Output,\n Nested extends Record<string, TailorAnyDBField>,\n> =\n IsAny<Defined> extends true\n ? DBFieldDefaultFn<Defined, Output, Nested>\n : Defined extends { default: unknown }\n ? TypeLevelError<\".default() has already been set\">\n : Defined extends { type: \"nested\" }\n ? TypeLevelError<\"default cannot be set on nested type fields\">\n : Defined extends { serial: true }\n ? TypeLevelError<\"default cannot be set on serial fields\">\n : null extends Output\n ? TypeLevelError<\"default cannot be set on optional fields\">\n : DBFieldDefaultFn<Defined, Output, Nested>;\n\n/**\n * Full TailorDBField interface with builder methods.\n * Extends the minimal structural interface from types/ with fluent API methods.\n */\nexport interface TailorDBField<\n Defined extends DefinedDBFieldMetadata = DefinedDBFieldMetadata,\n // oxlint-disable-next-line no-explicit-any\n Output = any,\n // Nested object fields, so a `db.object()` field keeps the shape it was declared with.\n // Every builder method passes it through; dropping it here would erase the shape as\n // soon as one is chained.\n Nested extends Record<string, TailorAnyDBField> = Record<string, TailorAnyDBField>,\n> extends Omit<TailorDBFieldBase<Defined, Output, Nested>, \"fields\"> {\n readonly fields: Nested;\n _metadata: DBFieldMetadata;\n\n /**\n * Parse and validate a value against this field's validation rules\n */\n parse(args: FieldParseArgs): StandardSchemaV1.Result<Output>;\n\n /**\n * typeName is not available on TailorDB fields.\n * Use typeName on pipeline fields (t.enum / t.object) instead.\n */\n typeName: DBFieldTypeNameMethod<Defined>;\n\n /**\n * Set a description for the field\n */\n description: DBFieldDescriptionMethod<Defined, Output, Nested>;\n\n /**\n * Define a relation to another table.\n */\n relation: DBFieldRelationMethod<Defined, Output, Nested>;\n\n /**\n * Add an index to the field\n */\n index: DBFieldIndexMethod<Defined, Output, Nested>;\n\n /**\n * Make the field unique (also adds an index)\n */\n unique: DBFieldUniqueMethod<Defined, Output, Nested>;\n\n /**\n * Enable vector search on the field (string type only)\n */\n vector: DBFieldVectorMethod<Defined, Output, Nested>;\n\n /**\n * Set a default value for the field on create. When the field is required,\n * this makes it optional in the Create input — the default fills in when\n * no value (or a nullish hook result) is provided.\n *\n * For datetime/date/time fields, pass `\"now\"` to use the operation timestamp.\n */\n default: DBFieldDefaultMethod<Defined, Output, Nested>;\n\n /**\n * Add hooks for create/update operations on this field.\n */\n hooks: DBFieldHooksMethod<Defined, Output, Nested>;\n\n /**\n * Add validation functions to the field.\n *\n * Validators receive `{ value, data, user }` and run after hooks and\n * built-in type validation; they are skipped when built-in validation\n * fails. For array fields, `value` is the complete array.\n */\n validate: DBFieldValidateMethod<Defined, Output, Nested>;\n\n /**\n * Configure serial/auto-increment behavior\n */\n serial: DBFieldSerialMethod<Defined, Output, Nested>;\n\n /**\n * Clone the field with optional overrides for field options.\n * The `array` option cannot change on fields with custom validation.\n */\n clone<const NewOpt extends DBFieldCloneOptions<Defined>>(\n options?: NewOpt,\n ): TailorDBField<\n WithDBFieldCloneOptions<Defined, NewOpt>,\n DBFieldCloneOutput<Output, NewOpt>,\n Nested\n >;\n}\n\n/**\n * Full TailorDBType interface with builder methods.\n * Extends the minimal structural interface from types/ with fluent API methods.\n */\nexport interface TailorDBType<\n // oxlint-disable-next-line no-explicit-any\n Fields extends Record<string, TailorAnyDBField> = any,\n User extends object = InferredAttributes,\n // oxlint-disable-next-line no-explicit-any\n Defined extends DefinedDBTypeMetadata = any,\n> extends TailorDBTypeBase<Fields, User> {\n _description?: string;\n\n hooks(\n hook: DBTypeDuplicateInputGuard<\n Defined,\n \"hooks\",\n TypeHook<Fields>,\n \".hooks() has already been set\"\n >,\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"hooks\">>;\n validate(\n fn: DBTypeDuplicateInputGuard<\n Defined,\n \"validate\",\n TypeValidateFn<Fields>,\n \".validate() has already been set\"\n >,\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"validate\">>;\n features(\n features: DBTypeDuplicateInputGuard<\n Defined,\n \"features\",\n Omit<TypeFeatures, \"pluralForm\">,\n \".features() has already been set\"\n >,\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"features\">>;\n indexes(\n ...indexes: DBTypeDuplicateRestGuard<\n Defined,\n \"indexes\",\n IndexDef<TailorDBType<Fields, User, Defined>>[],\n \".indexes() has already been set\"\n >\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"indexes\">>;\n files<const F extends string>(\n files: DBTypeDuplicateInputGuard<\n Defined,\n \"files\",\n Record<F, string> & FileKeyConflictError<Fields, User>,\n \".files() has already been set\"\n >,\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"files\">>;\n permission<\n U extends object = User,\n P extends TailorTypePermission<U, output<TailorDBType<Fields, User, Defined>>> =\n TailorTypePermission<U, output<TailorDBType<Fields, User, Defined>>>,\n >(\n permission: DBTypeDuplicateInputGuard<\n Defined,\n \"permission\",\n P,\n \".permission() has already been set\"\n >,\n ): TailorDBType<Fields, U, WithDBTypeMetadata<Defined, \"permission\">>;\n gqlPermission<\n U extends object = User,\n P extends TailorTypeGqlPermission<U> = TailorTypeGqlPermission<U>,\n >(\n permission: DBTypeDuplicateInputGuard<\n Defined,\n \"gqlPermission\",\n P,\n \".gqlPermission() has already been set\"\n >,\n ): TailorDBType<Fields, U, WithDBTypeMetadata<Defined, \"gqlPermission\">>;\n description(\n description: DBTypeDuplicateInputGuard<\n Defined,\n \"description\",\n string,\n \".description() has already been set\"\n >,\n ): TailorDBType<Fields, User, WithDBTypeMetadata<Defined, \"description\">>;\n pickFields<K extends keyof Fields>(keys: K[]): Pick<Fields, K>;\n pickFields<K extends keyof Fields, const Opt extends FieldOptions>(\n keys: K[],\n options: Opt & DBFieldsCloneOptionsGuard<Fields, K, Opt>,\n ): {\n [P in K]: Fields[P] extends TailorDBField<infer D, infer O>\n ? TailorDBField<WithDBFieldCloneOptions<D, Opt>, DBFieldCloneOutput<O, Opt>>\n : never;\n };\n omitFields<K extends keyof Fields>(keys: K[]): Omit<Fields, K>;\n plugin<P extends keyof PluginConfigs<keyof Fields & string>>(config: {\n [K in P]: PluginConfigs<keyof Fields & string>[K];\n }): TailorDBType<Fields, User, Defined>;\n}\n\nexport type TailorDBInstance<\n // oxlint-disable-next-line no-explicit-any\n Fields extends Record<string, TailorAnyDBField> = any,\n User extends object = InferredAttributes,\n // oxlint-disable-next-line no-explicit-any\n Defined extends DefinedDBTypeMetadata = any,\n> = TailorDBType<Fields, User, Defined>;\n\ninterface RelationConfig<S extends RelationType, T extends TailorDBType> {\n type: S;\n toward: {\n type: T;\n as?: string;\n key?: keyof T[\"fields\"] & string;\n };\n backward?: string;\n}\n\n// Special config variant for self-referencing relations\ntype RelationSelfConfig = {\n type: RelationType;\n toward: {\n type: \"self\";\n as?: string;\n key?: string;\n };\n backward?: string;\n};\n\nfunction isRelationSelfConfig(\n config: RelationConfig<RelationType, TailorDBType> | RelationSelfConfig,\n): config is RelationSelfConfig {\n return config.toward.type === \"self\";\n}\n\ntype DBFieldDefined<T extends TailorFieldType, Opt extends FieldOptions> = {\n type: T;\n array: Opt extends { array: true } ? true : false;\n};\ntype DBFieldOutput<\n T extends TailorFieldType,\n Opt extends FieldOptions,\n OutputBase = TailorToTs[T],\n> = FieldOutput<OutputBase, Opt>;\ntype TailorDBFieldInstance<\n T extends TailorFieldType,\n Opt extends FieldOptions,\n OutputBase = TailorToTs[T],\n> = TailorDBField<DBFieldDefined<T, Opt>, DBFieldOutput<T, Opt, OutputBase>>;\ntype TailorDBFieldRuntimeInstance<\n T extends TailorFieldType,\n Opt extends FieldOptions,\n OutputBase = TailorToTs[T],\n> = TailorDBFieldRuntime<DBFieldDefined<T, Opt>, DBFieldOutput<T, Opt, OutputBase>>;\ntype TailorDBFieldRuntime<Defined extends DefinedDBFieldMetadata, Output> = Omit<\n TailorDBFieldBase<Defined, Output>,\n \"fields\"\n> & {\n readonly fields: Record<string, TailorAnyDBField>;\n _metadata: DBFieldMetadata;\n description(description: string): object;\n typeName(typeName: string): object;\n validate(...validate: FieldValidateInput<Output>[]): object;\n relation(config: RelationConfig<RelationType, TailorDBType> | RelationSelfConfig): object;\n index(): object;\n unique(): object;\n vector(): object;\n default(value: unknown): object;\n hooks(hooks: Hook<Output>): object;\n serial(config: SerialConfig): object;\n clone(options?: FieldOptions): TailorDBFieldRuntime<DefinedDBFieldMetadata, AnyBuilderMethod>;\n parse(args: FieldParseArgs): StandardSchemaV1.Result<Output>;\n _setRawRelation(relation: RawRelationConfig): void;\n};\n\n/**\n * Creates a new TailorDBField instance.\n * @param type - Field type\n * @param options - Field options\n * @param fields - Nested fields for object-like types\n * @param values - Allowed values for enum-like fields\n * @returns A new TailorDBField\n */\nfunction createTailorDBField<\n const T extends TailorFieldType,\n const TOptions extends FieldOptions,\n const OutputBase = TailorToTs[T],\n>(\n type: T,\n options?: TOptions,\n fields?: Record<string, TailorAnyDBField>,\n values?: AllowedValues,\n): TailorDBFieldInstance<T, TOptions, OutputBase>;\nfunction createTailorDBField<const T extends TailorFieldType, const TOptions extends FieldOptions>(\n type: T,\n options?: TOptions,\n fields?: Record<string, TailorAnyDBField>,\n values?: AllowedValues,\n): object {\n return createTailorDBFieldRuntime(type, options, fields, values);\n}\n\nfunction createTailorDBFieldRuntime<\n const T extends TailorFieldType,\n const TOptions extends FieldOptions,\n const OutputBase = TailorToTs[T],\n>(\n type: T,\n options?: TOptions,\n fields?: Record<string, TailorAnyDBField>,\n values?: AllowedValues,\n): TailorDBFieldRuntimeInstance<T, TOptions, OutputBase> {\n type FieldValue = DBFieldOutput<T, TOptions, OutputBase>;\n type FieldType = TailorDBFieldRuntimeInstance<T, TOptions, OutputBase>;\n\n const _metadata: DBFieldMetadata = { required: true };\n let _rawRelation: RawRelationConfig | undefined;\n\n if (options) {\n if (options.optional === true) {\n _metadata.required = false;\n }\n if (options.array === true) {\n _metadata.array = true;\n }\n }\n if (values) {\n _metadata.allowedValues = mapAllowedValues(values);\n }\n\n function parseInternal(args: FieldParseInternalArgs): StandardSchemaV1.Result<FieldValue> {\n return parseFieldInternal<T, FieldValue>({\n ...args,\n field,\n });\n }\n\n function cloneWith(metadataUpdates: Partial<DBFieldMetadata>) {\n const cloned = field.clone();\n Object.assign(cloned._metadata, metadataUpdates);\n return cloned;\n }\n\n const field: FieldType = {\n type,\n fields: fields ?? {},\n _defined: undefined as unknown as DBFieldDefined<T, TOptions>,\n _output: undefined as FieldValue,\n _metadata,\n\n get metadata() {\n return { ...this._metadata };\n },\n\n get rawRelation(): Readonly<RawRelationConfig> | undefined {\n return _rawRelation ? { ..._rawRelation, toward: { ..._rawRelation.toward } } : undefined;\n },\n\n description(description: string) {\n return cloneWith({ description });\n },\n\n typeName(typeName: string) {\n return cloneWith({ typeName });\n },\n\n validate(...validateInputs: FieldValidateInput<FieldValue>[]) {\n return cloneWith({ validate: validateInputs });\n },\n\n parse(args: FieldParseArgs): StandardSchemaV1.Result<FieldValue> {\n return parseInternal({\n value: args.value,\n data: args.data,\n invoker: args.invoker,\n pathArray: [],\n });\n },\n\n // TailorDBField specific methods\n relation(config: RelationConfig<RelationType, TailorDBType> | RelationSelfConfig) {\n const cloned = field.clone();\n const targetType = isRelationSelfConfig(config) ? \"self\" : config.toward.type.name;\n cloned._setRawRelation({\n type: config.type,\n toward: {\n type: targetType,\n as: config.toward.as,\n key: config.toward.key,\n },\n backward: config.backward,\n });\n return cloned;\n },\n\n index() {\n return cloneWith({ index: true });\n },\n\n unique() {\n return cloneWith({ unique: true, index: true });\n },\n\n vector() {\n return cloneWith({ vector: true });\n },\n\n // oxlint-disable-next-line no-explicit-any\n default(value: any) {\n // oxlint-disable-next-line no-explicit-any\n return cloneWith({ default: value }) as any;\n },\n\n hooks(hooks: Hook<FieldValue>) {\n return cloneWith({ hooks });\n },\n\n serial(config: SerialConfig) {\n return cloneWith({ serial: config });\n },\n\n clone(cloneOptions?: FieldOptions) {\n if (\n this._metadata.validate?.length &&\n cloneOptions?.array !== undefined &&\n cloneOptions.array !== (this._metadata.array === true)\n ) {\n throw new Error(\"Cannot change the array option on a field with custom validation\");\n }\n\n // Deep clone nested object fields if present\n let clonedFields = fields;\n if (fields) {\n const cloned: Record<string, TailorAnyDBField> = {};\n for (const [key, field] of Object.entries(fields)) {\n cloned[key] = field.clone();\n }\n clonedFields = cloned;\n }\n\n // Create a new field with cloned configuration\n const clonedField = createTailorDBFieldRuntime(type, options, clonedFields, values);\n\n // Deep copy metadata using cloneDeep (preserves function references)\n Object.assign(clonedField._metadata, cloneDeep(this._metadata));\n\n // Apply new options if provided\n if (cloneOptions) {\n if (cloneOptions.optional !== undefined) {\n clonedField._metadata.required = !cloneOptions.optional;\n }\n if (cloneOptions.array !== undefined) {\n clonedField._metadata.array = cloneOptions.array;\n }\n }\n\n // Copy raw relation if exists\n if (_rawRelation) {\n clonedField._setRawRelation(cloneDeep(_rawRelation));\n }\n\n return clonedField;\n },\n\n _setRawRelation(relation: RawRelationConfig) {\n _rawRelation = relation;\n },\n };\n\n return field;\n}\n\nconst createField = createTailorDBField;\n\n/**\n * Create a UUID field.\n * @param options - Field configuration options\n * @returns A UUID field\n * @example db.uuid()\n * @example db.uuid({ optional: true })\n */\nfunction uuid<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"uuid\", options);\n}\n\n/**\n * Create a string field.\n * @param options - Field configuration options\n * @returns A string field\n * @example db.string()\n * @example db.string({ optional: true })\n */\nfunction string<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"string\", options);\n}\n\n/**\n * Create a boolean field.\n * Note: The method name is `bool` but creates a `boolean` type field.\n * @param options - Field configuration options\n * @returns A boolean field\n * @example db.bool()\n * @example db.bool({ optional: true })\n */\nfunction bool<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"boolean\", options);\n}\n\n/**\n * Create an integer field.\n * @param options - Field configuration options\n * @returns An integer field\n * @example db.int()\n * @example db.int({ optional: true })\n */\nfunction int<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"integer\", options);\n}\n\n/**\n * Create a float (decimal number) field.\n * @param options - Field configuration options\n * @returns A float field\n * @example db.float()\n * @example db.float({ optional: true })\n */\nfunction float<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"float\", options);\n}\n\ninterface DecimalFieldOptions extends FieldOptions {\n scale?: number;\n}\n\n/**\n * Create a decimal field (stored as string for precision).\n * @param options - Field configuration options including optional scale (0-12)\n * @returns A decimal field\n * @example db.decimal()\n * @example db.decimal({ scale: 2 })\n * @example db.decimal({ scale: 2, optional: true })\n */\nfunction decimal<const Opt extends DecimalFieldOptions>(options?: Opt) {\n if (options?.scale !== undefined) {\n if (!Number.isInteger(options.scale) || options.scale < 0 || options.scale > 12) {\n throw new Error(\"scale must be an integer between 0 and 12\");\n }\n }\n const field = createField(\"decimal\", options);\n if (options?.scale !== undefined) {\n field._metadata.scale = options.scale;\n }\n return field;\n}\n\n/**\n * Create a date field (date only, no time component).\n * Format: \"yyyy-MM-dd\"\n * @param options - Field configuration options\n * @returns A date field\n * @example db.date()\n */\nfunction date<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"date\", options);\n}\n\n/**\n * Create a datetime field (date and time).\n * Format: ISO 8601, such as \"yyyy-MM-ddTHH:mm:ssZ\" or \"yyyy-MM-ddTHH:mm:ss+09:00\"\n * @param options - Field configuration options\n * @returns A datetime field\n * @example db.datetime()\n */\nfunction datetime<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"datetime\", options);\n}\n\n/**\n * Create a time field (time only, no date component).\n * Format: \"HH:mm\"\n * @param options - Field configuration options\n * @returns A time field\n * @example db.time()\n */\nfunction time<const Opt extends FieldOptions>(options?: Opt) {\n return createField(\"time\", options);\n}\n\n/**\n * Create an enum field with a fixed set of allowed string values.\n * @param values - Array of allowed string values, or array of `{ value, description }` objects\n * @param options - Field configuration options\n * @returns An enum field\n * @example db.enum([\"active\", \"inactive\", \"suspended\"])\n * @example db.enum([\"small\", \"medium\", \"large\"], { optional: true })\n */\nfunction _enum<const V extends AllowedValues, const Opt extends FieldOptions>(\n values: V,\n options?: Opt,\n): TailorDBField<\n { type: \"enum\"; array: Opt extends { array: true } ? true : false },\n FieldOutput<AllowedValuesOutput<V>, Opt>\n> {\n return createField<\"enum\", Opt, AllowedValuesOutput<V>>(\"enum\", options, undefined, values);\n}\n\n/**\n * Create a nested object field with sub-fields.\n * @param fields - Record of nested field definitions\n * @param options - Field configuration options\n * @returns A nested object field\n * @example db.object({ street: db.string(), city: db.string(), zip: db.string() })\n * @example db.object({ name: db.string() }, { optional: true })\n */\nfunction object<\n const F extends Record<string, TailorAnyDBField> &\n ExcludeNestedDBFields<F> &\n ExcludeHookedDBFields<F> &\n ExcludeDefaultedDBFields<F>,\n const Opt extends FieldOptions,\n>(fields: F, options?: Opt) {\n return createField(\"nested\", options, fields) as unknown as TailorDBField<\n { type: \"nested\"; array: Opt extends { array: true } ? true : false },\n FieldOutput<InferFieldsOutput<F>, Opt>,\n F\n >;\n}\n\n/**\n * Creates a new TailorDBType instance.\n * @param name - Type name\n * @param fields - Field definitions\n * @param options - Type options\n * @param options.pluralForm - Optional plural form\n * @param options.description - Optional description\n * @returns A new TailorDBType\n */\nfunction createTailorDBType<\n // oxlint-disable-next-line no-explicit-any\n const Fields extends Record<string, TailorAnyDBField> = any,\n User extends object = InferredAttributes,\n>(\n name: string,\n fields: Fields,\n options: { pluralForm?: string; description?: string },\n): TailorDBType<Fields, User, DefinedDBTypeMetadata> {\n let _description = options.description;\n let _settings: TypeFeatures = {};\n let _indexes: IndexDef<TailorDBType<Fields, User, DefinedDBTypeMetadata>>[] = [];\n const _permissions: RawPermissions = {};\n let _files: Record<string, string> = {};\n const _plugins: PluginAttachment[] = [];\n // oxlint-disable-next-line typescript/no-unsafe-function-type\n let _typeHook: { create?: Function; update?: Function } | undefined;\n // oxlint-disable-next-line typescript/no-unsafe-function-type\n let _typeValidate: Function | undefined;\n const _definedMethods = new Set<keyof DefinedDBTypeMetadata>();\n if (options.description !== undefined) {\n _definedMethods.add(\"description\");\n }\n\n function runMethodOnce<T>(method: keyof DefinedDBTypeMetadata, action: () => T): T {\n if (_definedMethods.has(method)) {\n throw new Error(`.${method}() has already been set`);\n }\n const result = action();\n _definedMethods.add(method);\n return result;\n }\n type TypeAfter<Key extends keyof DefinedDBTypeMetadata> = TailorDBType<\n Fields,\n User,\n WithDBTypeMetadata<DefinedDBTypeMetadata, Key>\n >;\n type TypeAfterUser<\n NextUser extends object,\n Key extends keyof DefinedDBTypeMetadata,\n > = TailorDBType<Fields, NextUser, WithDBTypeMetadata<DefinedDBTypeMetadata, Key>>;\n\n if (options.pluralForm) {\n if (name === options.pluralForm) {\n throw new Error(`The name and the plural form must be different. name=${name}`);\n }\n _settings.pluralForm = options.pluralForm;\n }\n\n const dbType: TailorDBType<Fields, User, DefinedDBTypeMetadata> = {\n name,\n fields: { ...fields },\n _output: null as unknown as InferFieldsOutput<Fields>,\n _description,\n\n get metadata(): TailorDBTypeMetadata {\n // Convert indexes to the format expected by the manifest\n const indexes: Record<string, { fields: string[]; unique?: boolean }> = {};\n if (_indexes.length > 0) {\n _indexes.forEach((index) => {\n const fieldNames = index.fields.map((field) => String(field));\n const key = index.name || `idx_${fieldNames.join(\"_\")}`;\n indexes[key] = {\n fields: fieldNames,\n unique: index.unique,\n };\n });\n }\n\n return {\n name: this.name,\n description: _description,\n settings: _settings,\n permissions: _permissions,\n files: _files,\n ...(Object.keys(indexes).length > 0 && { indexes }),\n ...(_typeHook && { typeHook: _typeHook }),\n ...(_typeValidate && { typeValidate: _typeValidate }),\n };\n },\n\n hooks(hook: TypeHook<Fields>): TypeAfter<\"hooks\"> {\n return runMethodOnce(\"hooks\", () => {\n _typeHook = hook;\n return this as TypeAfter<\"hooks\">;\n });\n },\n\n validate(fn: TypeValidateFn<Fields>): TypeAfter<\"validate\"> {\n return runMethodOnce(\"validate\", () => {\n _typeValidate = fn;\n return this as TypeAfter<\"validate\">;\n });\n },\n\n features(features: Omit<TypeFeatures, \"pluralForm\">): TypeAfter<\"features\"> {\n return runMethodOnce(\"features\", () => {\n _settings = {\n ..._settings,\n ...features,\n };\n return this as TypeAfter<\"features\">;\n });\n },\n\n indexes(\n ...indexes: IndexDef<TailorDBType<Fields, User, DefinedDBTypeMetadata>>[]\n ): TypeAfter<\"indexes\"> {\n return runMethodOnce(\"indexes\", () => {\n _indexes = indexes;\n return this as TypeAfter<\"indexes\">;\n });\n },\n\n files<const F extends string>(\n files: Record<F, string> & FileKeyConflictError<Fields, User>,\n ): TypeAfter<\"files\"> {\n return runMethodOnce(\"files\", () => {\n _files = files;\n return this as TypeAfter<\"files\">;\n });\n },\n\n permission<\n U extends object = User,\n P extends TailorTypePermission<U, output<TailorDBType<Fields, User, DefinedDBTypeMetadata>>> =\n TailorTypePermission<U, output<TailorDBType<Fields, User, DefinedDBTypeMetadata>>>,\n >(permission: P): TypeAfterUser<U, \"permission\"> {\n return runMethodOnce(\"permission\", () => {\n const ret = this as unknown as TypeAfterUser<U, \"permission\">;\n _permissions.record = permission as RawPermissions[\"record\"];\n return ret;\n });\n },\n\n gqlPermission<\n U extends object = User,\n P extends TailorTypeGqlPermission<U> = TailorTypeGqlPermission<U>,\n >(permission: P): TypeAfterUser<U, \"gqlPermission\"> {\n return runMethodOnce(\"gqlPermission\", () => {\n const ret = this as unknown as TypeAfterUser<U, \"gqlPermission\">;\n _permissions.gql = permission as RawPermissions[\"gql\"];\n return ret;\n });\n },\n\n description(description: string): TypeAfter<\"description\"> {\n return runMethodOnce(\"description\", () => {\n _description = description;\n this._description = description;\n return this as TypeAfter<\"description\">;\n });\n },\n\n pickFields<K extends keyof Fields, const Opt extends FieldOptions>(keys: K[], options?: Opt) {\n const result = {} as Record<K, TailorAnyDBField>;\n for (const key of keys) {\n const field = this.fields[key] as TailorAnyDBField;\n if (options) {\n result[key] = field.clone(options);\n } else {\n result[key] = field;\n }\n }\n // oxlint-disable-next-line no-explicit-any\n return result as any;\n },\n\n omitFields<K extends keyof Fields>(keys: K[]): Omit<Fields, K> {\n const keysSet = new Set(keys);\n const result = {} as Record<string, TailorAnyDBField>;\n for (const key in this.fields) {\n if (Object.hasOwn(this.fields, key) && !keysSet.has(key as unknown as K)) {\n result[key] = this.fields[key] as TailorAnyDBField;\n }\n }\n return result as Omit<Fields, K>;\n },\n\n get plugins(): PluginAttachment[] {\n return _plugins;\n },\n\n plugin<P extends keyof PluginConfigs<keyof Fields & string>>(config: {\n [K in P]: PluginConfigs<keyof Fields & string>[K];\n }): TailorDBType<Fields, User, AnyBuilderMethod> {\n for (const [pluginId, pluginConfig] of Object.entries(config)) {\n _plugins.push({ pluginId, config: pluginConfig });\n }\n return this;\n },\n };\n\n return brandValue(dbType, \"tailordb-type\");\n}\n\nconst idField = uuid();\ntype idField = typeof idField;\ntype DBTable<\n F extends { id?: never } & Record<string, TailorAnyDBField>,\n Defined extends DefinedDBTypeMetadata = DefinedDBTypeMetadata,\n> = TailorDBInstance<{ id: idField } & F, InferredAttributes, Defined>;\n\n/**\n * Creates a new database table with the specified fields.\n * An `id` field (UUID) is automatically added to every table.\n * @param name - The name of the table, or a tuple of [name, pluralForm]\n * @param fields - The field definitions for the table\n * @returns A new TailorDB table instance\n * @example\n * export const user = db.table(\"User\", {\n * name: db.string(),\n * email: db.string(),\n * age: db.int({ optional: true }),\n * role: db.enum([\"admin\", \"member\"]),\n * ...db.fields.timestamps(),\n * });\n * // Always export both the value and type:\n * export type user = typeof user;\n */\nfunction dbTable<const F extends { id?: never } & Record<string, TailorAnyDBField>>(\n name: string | [string, string],\n fields: F,\n): DBTable<F>;\n/**\n * Creates a new database table with the specified fields and description.\n * An `id` field (UUID) is automatically added to every table.\n * @param name - The name of the table, or a tuple of [name, pluralForm]\n * @param description - A description of the table\n * @param fields - The field definitions for the table\n * @returns A new TailorDB table instance\n */\nfunction dbTable<const F extends { id?: never } & Record<string, TailorAnyDBField>>(\n name: string | [string, string],\n description: string,\n fields: F,\n): DBTable<F, WithDBTypeMetadata<DefinedDBTypeMetadata, \"description\">>;\nfunction dbTable<const F extends { id?: never } & Record<string, TailorAnyDBField>>(\n name: string | [string, string],\n fieldsOrDescription: string | F,\n fields?: F,\n): DBTable<F> | DBTable<F, WithDBTypeMetadata<DefinedDBTypeMetadata, \"description\">> {\n const typeName = Array.isArray(name) ? name[0] : name;\n const pluralForm = Array.isArray(name) ? name[1] : undefined;\n\n let description: string | undefined;\n let fieldDef: F;\n if (typeof fieldsOrDescription === \"string\") {\n description = fieldsOrDescription;\n fieldDef = fields as F;\n } else {\n fieldDef = fieldsOrDescription;\n }\n return createTailorDBType<{ id: idField } & F>(\n typeName,\n {\n id: idField,\n ...fieldDef,\n },\n { pluralForm, description },\n ) as DBTable<F>;\n}\n\n/** TailorDB schema builder utilities for defining tables and fields. */\nexport const db = {\n table: dbTable,\n uuid,\n string,\n bool,\n int,\n float,\n decimal,\n date,\n datetime,\n time,\n enum: _enum,\n object,\n fields: {\n /**\n * Creates standard timestamp fields (createdAt, updatedAt) with automatic defaults.\n * Both fields default to the current time on create. updatedAt is also refreshed on update.\n * User-specified values are respected when provided (e.g. seeding historical records).\n * @returns An object with createdAt and updatedAt fields\n * @example\n * const model = db.table(\"Model\", {\n * name: db.string(),\n * ...db.fields.timestamps(),\n * });\n */\n timestamps: () => ({\n createdAt: datetime().default(\"now\").description(\"Record creation timestamp\"),\n updatedAt: datetime()\n .default(\"now\")\n .hooks({ update: ({ input, now }) => input ?? now })\n .description(\"Record update timestamp\"),\n }),\n },\n};\n"],"mappings":"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"}
|
package/dist/seed/index.d.mts
CHANGED
|
@@ -18,7 +18,7 @@ type ValidateSeedResult = {
|
|
|
18
18
|
* A JSONL seed file that received values.
|
|
19
19
|
*/
|
|
20
20
|
type FilledSeedFile = {
|
|
21
|
-
/** Name of the seeded
|
|
21
|
+
/** Name of the seeded table, matching the JSONL file name */
|
|
22
22
|
table: string;
|
|
23
23
|
/** Absolute path to the updated JSONL file */
|
|
24
24
|
file: string;
|
|
@@ -50,7 +50,7 @@ declare function validateSeedData(options: ValidateSeedDataOptions): Promise<Val
|
|
|
50
50
|
* are missing them, so a row can be referenced by `id` or carry a timestamp
|
|
51
51
|
* before it is ever seeded.
|
|
52
52
|
*
|
|
53
|
-
* The values come from the
|
|
53
|
+
* The values come from the table's own create-time behavior — its `id`, its field
|
|
54
54
|
* defaults, and its create hooks — applied to each row on its own. Nothing is
|
|
55
55
|
* validated, so a row can be filled while the data around it is still
|
|
56
56
|
* incomplete: that is what lets you get the ids you need in order to write the
|
|
@@ -59,8 +59,8 @@ declare function validateSeedData(options: ValidateSeedDataOptions): Promise<Val
|
|
|
59
59
|
* Only the named fields are written, and only into a row that has no value for
|
|
60
60
|
* them, so a value already in the file is never replaced. A line that gains
|
|
61
61
|
* nothing is written back exactly as it was, byte for byte; a line that does get
|
|
62
|
-
* a value is re-serialized with its keys in the order the
|
|
63
|
-
* fields, so a filled-in `id` lands at the front. A field the
|
|
62
|
+
* a value is re-serialized with its keys in the order the table declares its
|
|
63
|
+
* fields, so a filled-in `id` lands at the front. A field the table gives no
|
|
64
64
|
* value to — one it does not declare, or one the platform assigns such as a
|
|
65
65
|
* serial field — is skipped, so one field list covers a whole data directory.
|
|
66
66
|
*
|
package/dist/seed/index.mjs
CHANGED
|
@@ -147,7 +147,7 @@ async function loadSeedHook(dataDir, table) {
|
|
|
147
147
|
* are missing them, so a row can be referenced by `id` or carry a timestamp
|
|
148
148
|
* before it is ever seeded.
|
|
149
149
|
*
|
|
150
|
-
* The values come from the
|
|
150
|
+
* The values come from the table's own create-time behavior — its `id`, its field
|
|
151
151
|
* defaults, and its create hooks — applied to each row on its own. Nothing is
|
|
152
152
|
* validated, so a row can be filled while the data around it is still
|
|
153
153
|
* incomplete: that is what lets you get the ids you need in order to write the
|
|
@@ -156,8 +156,8 @@ async function loadSeedHook(dataDir, table) {
|
|
|
156
156
|
* Only the named fields are written, and only into a row that has no value for
|
|
157
157
|
* them, so a value already in the file is never replaced. A line that gains
|
|
158
158
|
* nothing is written back exactly as it was, byte for byte; a line that does get
|
|
159
|
-
* a value is re-serialized with its keys in the order the
|
|
160
|
-
* fields, so a filled-in `id` lands at the front. A field the
|
|
159
|
+
* a value is re-serialized with its keys in the order the table declares its
|
|
160
|
+
* fields, so a filled-in `id` lands at the front. A field the table gives no
|
|
161
161
|
* value to — one it does not declare, or one the platform assigns such as a
|
|
162
162
|
* serial field — is skipped, so one field list covers a whole data directory.
|
|
163
163
|
*
|
package/dist/seed/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/seed/index.ts"],"sourcesContent":["/**\n * Seed integration module for generated seed code.\n *\n * Re-exports `@toiroakr/lines-db` through a single import path\n * to avoid phantom dependency issues with pnpm, and provides\n * seed-specific utility functions used by the code generator.\n */\n\nimport { readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { LinesDB, ErrorFormatter, findSchemaFile } from \"@toiroakr/lines-db\";\n// `pathe`, not `node:path`: the file paths reported back are printed and returned\n// to the caller, and these stay separator-stable across platforms.\nimport { basename, dirname, join } from \"pathe\";\nimport type { JsonObject, ValidationErrorDetail } from \"@toiroakr/lines-db\";\n\nexport { defineSchema } from \"@toiroakr/lines-db\";\nexport type { ForeignKeyDefinition, IndexDefinition } from \"@toiroakr/lines-db\";\n\n/** Fields `fillSeedData` writes when the caller names none. */\nconst DEFAULT_FILL_FIELDS = [\"id\"];\n\ntype SeedDataTarget = {\n dataDir: string;\n tableName?: string;\n};\n\ntype ValidateSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Show verbose error output */\n verbose?: boolean;\n};\n\ntype ValidateSeedResult =\n | { valid: true; output: string }\n | { valid: false; output: string; error: string };\n\n/**\n * A JSONL seed file that received values.\n */\ntype FilledSeedFile = {\n /** Name of the seeded type, matching the JSONL file name */\n table: string;\n /** Absolute path to the updated JSONL file */\n file: string;\n /** Fields that were written back to the file */\n fields: string[];\n /** How many rows were missing at least one of those fields */\n count: number;\n};\n\ntype FillSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Fields to fill. Defaults to `id`. */\n fields?: readonly string[];\n};\n\ntype FillSeedDataResult = {\n output: string;\n filled: FilledSeedFile[];\n};\n\nasync function resolveSeedDataTarget(resolvedPath: string): Promise<SeedDataTarget> {\n const stats = await stat(resolvedPath);\n if (stats.isDirectory()) {\n return { dataDir: resolvedPath };\n }\n if (stats.isFile() && resolvedPath.endsWith(\".jsonl\")) {\n return { dataDir: dirname(resolvedPath), tableName: basename(resolvedPath, \".jsonl\") };\n }\n throw new Error(`Invalid path: ${resolvedPath}. Must be a directory or .jsonl file.`);\n}\n\nasync function listSeedTables(dataDir: string): Promise<string[]> {\n const entries = await readdir(dataDir);\n return entries\n .filter((entry) => entry.endsWith(\".jsonl\"))\n .map((entry) => basename(entry, \".jsonl\"))\n .toSorted();\n}\n\nfunction formatWarnings(warnings: string[]): string[] {\n if (warnings.length === 0) {\n return [];\n }\n return [...warnings.map((warning) => `⚠ ${warning}`), \"\"];\n}\n\nfunction formatValidationErrors(errors: ValidationErrorDetail[], verbose: boolean): string {\n const formatter = new ErrorFormatter({ verbose });\n const errorLines: string[] = [];\n const errorsByFile = new Map<string, ValidationErrorDetail[]>();\n for (const error of errors) {\n const fileErrors = errorsByFile.get(error.file) || [];\n fileErrors.push(error);\n errorsByFile.set(error.file, fileErrors);\n }\n for (const [file, fileErrors] of errorsByFile) {\n errorLines.push(formatter.formatErrorHeader(fileErrors.length, file));\n errorLines.push(\"\");\n const validationErrors = fileErrors.filter(\n (e) => e.type !== \"foreignKey\" || !e.foreignKeyError,\n );\n const foreignKeyErrors = fileErrors.filter((e) => e.type === \"foreignKey\" && e.foreignKeyError);\n if (validationErrors.length > 0) {\n errorLines.push(\n formatter.formatValidationErrors(\n validationErrors.map((e) => ({\n file: e.file,\n rowIndex: e.rowIndex,\n issues: e.issues,\n })),\n ),\n );\n }\n for (const fkError of foreignKeyErrors) {\n if (fkError.foreignKeyError) {\n errorLines.push(\n formatter.formatForeignKeyError({\n file: fkError.file,\n rowIndex: fkError.rowIndex,\n column: fkError.foreignKeyError.column,\n value: fkError.foreignKeyError.value,\n referencedTable: fkError.foreignKeyError.referencedTable,\n referencedColumn: fkError.foreignKeyError.referencedColumn,\n }),\n );\n }\n }\n errorLines.push(\"\");\n }\n\n return errorLines.join(\"\\n\");\n}\n\n/**\n * Validate JSONL seed data against schema definitions.\n * Resolves the given path (directory or `.jsonl` file), validates the rows it\n * holds, and returns formatted output and error messages.\n * @param options - Validation options including path and verbose flag\n * @returns Validation result with output messages and optional error details\n */\nexport async function validateSeedData(\n options: ValidateSeedDataOptions,\n): Promise<ValidateSeedResult> {\n const { path: resolvedPath, verbose = false } = options;\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n\n const db = LinesDB.create({ dataDir });\n let result;\n try {\n result = await db.initialize({ tableName, detailedValidate: true });\n } finally {\n await db.close();\n }\n\n const outputLines = formatWarnings(result.warnings);\n\n if (result.valid) {\n outputLines.push(\"✓ All records are valid\");\n return { valid: true, output: outputLines.join(\"\\n\") };\n }\n\n return {\n valid: false,\n output: outputLines.join(\"\\n\"),\n error: formatValidationErrors(result.errors, verbose),\n };\n}\n\n// A row's own value for a field, or undefined when the row has none.\nfunction ownValue(row: Record<string, unknown>, field: string): unknown {\n return Object.hasOwn(row, field) ? row[field] : undefined;\n}\n\n// Not `row[field] = value`: a field named `__proto__` goes through the inherited\n// setter, which leaves no own property for the serializer to read back.\nfunction setField(row: JsonObject, field: string, value: JsonObject[string]): void {\n Object.defineProperty(row, field, {\n value,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n // A nested field the row never had comes back as an object whose keys carry no\n // value of their own, and writing that into the line fills nothing in.\n return Object.values(value).every(isBlank);\n}\n\n/** A JSONL line, kept as text so an untouched line is written back verbatim. */\ntype SeedLine = {\n text: string;\n /** Line separator the file used after this line, kept so CRLF survives. */\n eol: string;\n row: JsonObject | undefined;\n};\n\nfunction splitLines(content: string): SeedLine[] {\n if (content === \"\") {\n return [];\n }\n return content.split(\"\\n\").map((raw, index, all) => {\n const eol = index === all.length - 1 ? \"\" : \"\\n\";\n const text = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n const carriage = raw.endsWith(\"\\r\") ? \"\\r\" : \"\";\n let row: JsonObject | undefined;\n if (text.trim() !== \"\") {\n try {\n const parsed: unknown = JSON.parse(text);\n row =\n parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as JsonObject)\n : undefined;\n } catch {\n row = undefined;\n }\n }\n return { text, eol: `${carriage}${eol}`, row };\n });\n}\n\n// Keys go in the order the hook produced them, which is the order the type\n// declares its fields. Keys the type does not declare follow the declared ones.\nfunction serializeRow(row: JsonObject, fieldOrder: string[]): string {\n const rank = new Map(fieldOrder.map((field, index) => [field, index]));\n const rankOf = (key: string): number => rank.get(key) ?? fieldOrder.length;\n return JSON.stringify(\n Object.fromEntries(Object.entries(row).toSorted(([a], [b]) => rankOf(a) - rankOf(b))),\n );\n}\n\ntype SeedHook = (row: unknown) => Record<string, unknown>;\n\nasync function loadSeedHook(dataDir: string, table: string): Promise<SeedHook | undefined> {\n const schemaPath = await findSchemaFile(dataDir, table);\n if (!schemaPath) {\n return undefined;\n }\n const loaded: unknown = await import(pathToFileURL(schemaPath).href);\n const hook = (loaded as { hook?: unknown }).hook;\n if (typeof hook !== \"function\") {\n throw new Error(\n `${schemaPath} does not export \\`hook\\`. Run \\`tailor generate\\` to regenerate the seed schema files.`,\n );\n }\n return hook as SeedHook;\n}\n\n/**\n * Fill in the values a record gets on create for the JSONL seed data rows that\n * are missing them, so a row can be referenced by `id` or carry a timestamp\n * before it is ever seeded.\n *\n * The values come from the type's own create-time behavior — its `id`, its field\n * defaults, and its create hooks — applied to each row on its own. Nothing is\n * validated, so a row can be filled while the data around it is still\n * incomplete: that is what lets you get the ids you need in order to write the\n * rows that reference them. Run `validateSeedData` when the data is ready.\n *\n * Only the named fields are written, and only into a row that has no value for\n * them, so a value already in the file is never replaced. A line that gains\n * nothing is written back exactly as it was, byte for byte; a line that does get\n * a value is re-serialized with its keys in the order the type declares its\n * fields, so a filled-in `id` lands at the front. A field the type gives no\n * value to — one it does not declare, or one the platform assigns such as a\n * serial field — is skipped, so one field list covers a whole data directory.\n *\n * The values are read from the schema files generated next to the data, and all\n * of them are read before anything is written: a file that predates the current\n * generator stops the run with nothing filled in anywhere.\n * @param options - Fill options including path and fields\n * @returns Which files received which fields\n */\nexport async function fillSeedData(options: FillSeedDataOptions): Promise<FillSeedDataResult> {\n const { path: resolvedPath, fields = DEFAULT_FILL_FIELDS } = options;\n if (fields.length === 0) {\n throw new Error(\"No fields to fill. Name at least one field.\");\n }\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n const tables = tableName ? [tableName] : await listSeedTables(dataDir);\n\n const warnings: string[] = [];\n const filled: FilledSeedFile[] = [];\n const producedFields = new Set<string>();\n\n // Every hook loads before anything is written, so a schema file that predates\n // `tailor generate` stops the run instead of leaving half the files filled.\n const hooks: { table: string; hook: SeedHook }[] = [];\n for (const table of tables) {\n const hook = await loadSeedHook(dataDir, table);\n if (!hook) {\n warnings.push(`No schema file for ${table}, so nothing can be filled in there`);\n continue;\n }\n hooks.push({ table, hook });\n }\n\n // Every line is decided before any file is written, so a hook that throws on\n // one table cannot leave another one already rewritten.\n const writes: { file: string; content: string }[] = [];\n for (const { table, hook } of hooks) {\n const file = join(dataDir, `${table}.jsonl`);\n const lines = splitLines(await readFile(file, \"utf-8\"));\n\n const written = new Set<string>();\n const unreadable: number[] = [];\n let count = 0;\n let fieldOrder: string[] = [];\n for (const [index, line] of lines.entries()) {\n const row = line.row;\n if (!row) {\n if (line.text.trim() !== \"\") {\n unreadable.push(index + 1);\n }\n continue;\n }\n const hooked = hook(row);\n fieldOrder = Object.keys(hooked);\n const gained = fields.filter((field) => {\n const value = ownValue(hooked, field);\n if (isBlank(value)) {\n return false;\n }\n producedFields.add(field);\n return isBlank(ownValue(row, field));\n });\n if (gained.length === 0) {\n continue;\n }\n for (const field of gained) {\n setField(row, field, hooked[field] as JsonObject[string]);\n written.add(field);\n }\n line.text = serializeRow(row, fieldOrder);\n count += 1;\n }\n\n if (unreadable.length > 0) {\n warnings.push(\n `${file}: line(s) ${unreadable.join(\", \")} are not JSON objects, so nothing was filled in there`,\n );\n }\n\n if (count === 0) {\n continue;\n }\n writes.push({ file, content: lines.map((line) => `${line.text}${line.eol}`).join(\"\") });\n filled.push({ table, file, fields: [...written], count });\n }\n\n for (const { file, content } of writes) {\n await writeFile(file, content);\n }\n\n const unproducedFields = fields.filter((field) => !producedFields.has(field));\n if (tables.length > 0 && unproducedFields.length > 0) {\n warnings.push(`No seed data produces a value for: ${unproducedFields.join(\", \")}`);\n }\n\n const outputLines = formatWarnings(warnings);\n outputLines.push(\n filled.length === 0\n ? \"\\u2713 Nothing to fill\"\n : filled\n .map(\n ({ file, fields: tableFields, count }) =>\n `\\u2713 ${file}: filled ${tableFields.join(\", \")} in ${count} row(s)`,\n )\n .join(\"\\n\"),\n );\n\n return { output: outputLines.join(\"\\n\"), filled };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/seed/index.ts"],"sourcesContent":["/**\n * Seed integration module for generated seed code.\n *\n * Re-exports `@toiroakr/lines-db` through a single import path\n * to avoid phantom dependency issues with pnpm, and provides\n * seed-specific utility functions used by the code generator.\n */\n\nimport { readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { LinesDB, ErrorFormatter, findSchemaFile } from \"@toiroakr/lines-db\";\n// `pathe`, not `node:path`: the file paths reported back are printed and returned\n// to the caller, and these stay separator-stable across platforms.\nimport { basename, dirname, join } from \"pathe\";\nimport type { JsonObject, ValidationErrorDetail } from \"@toiroakr/lines-db\";\n\nexport { defineSchema } from \"@toiroakr/lines-db\";\nexport type { ForeignKeyDefinition, IndexDefinition } from \"@toiroakr/lines-db\";\n\n/** Fields `fillSeedData` writes when the caller names none. */\nconst DEFAULT_FILL_FIELDS = [\"id\"];\n\ntype SeedDataTarget = {\n dataDir: string;\n tableName?: string;\n};\n\ntype ValidateSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Show verbose error output */\n verbose?: boolean;\n};\n\ntype ValidateSeedResult =\n | { valid: true; output: string }\n | { valid: false; output: string; error: string };\n\n/**\n * A JSONL seed file that received values.\n */\ntype FilledSeedFile = {\n /** Name of the seeded table, matching the JSONL file name */\n table: string;\n /** Absolute path to the updated JSONL file */\n file: string;\n /** Fields that were written back to the file */\n fields: string[];\n /** How many rows were missing at least one of those fields */\n count: number;\n};\n\ntype FillSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Fields to fill. Defaults to `id`. */\n fields?: readonly string[];\n};\n\ntype FillSeedDataResult = {\n output: string;\n filled: FilledSeedFile[];\n};\n\nasync function resolveSeedDataTarget(resolvedPath: string): Promise<SeedDataTarget> {\n const stats = await stat(resolvedPath);\n if (stats.isDirectory()) {\n return { dataDir: resolvedPath };\n }\n if (stats.isFile() && resolvedPath.endsWith(\".jsonl\")) {\n return { dataDir: dirname(resolvedPath), tableName: basename(resolvedPath, \".jsonl\") };\n }\n throw new Error(`Invalid path: ${resolvedPath}. Must be a directory or .jsonl file.`);\n}\n\nasync function listSeedTables(dataDir: string): Promise<string[]> {\n const entries = await readdir(dataDir);\n return entries\n .filter((entry) => entry.endsWith(\".jsonl\"))\n .map((entry) => basename(entry, \".jsonl\"))\n .toSorted();\n}\n\nfunction formatWarnings(warnings: string[]): string[] {\n if (warnings.length === 0) {\n return [];\n }\n return [...warnings.map((warning) => `⚠ ${warning}`), \"\"];\n}\n\nfunction formatValidationErrors(errors: ValidationErrorDetail[], verbose: boolean): string {\n const formatter = new ErrorFormatter({ verbose });\n const errorLines: string[] = [];\n const errorsByFile = new Map<string, ValidationErrorDetail[]>();\n for (const error of errors) {\n const fileErrors = errorsByFile.get(error.file) || [];\n fileErrors.push(error);\n errorsByFile.set(error.file, fileErrors);\n }\n for (const [file, fileErrors] of errorsByFile) {\n errorLines.push(formatter.formatErrorHeader(fileErrors.length, file));\n errorLines.push(\"\");\n const validationErrors = fileErrors.filter(\n (e) => e.type !== \"foreignKey\" || !e.foreignKeyError,\n );\n const foreignKeyErrors = fileErrors.filter((e) => e.type === \"foreignKey\" && e.foreignKeyError);\n if (validationErrors.length > 0) {\n errorLines.push(\n formatter.formatValidationErrors(\n validationErrors.map((e) => ({\n file: e.file,\n rowIndex: e.rowIndex,\n issues: e.issues,\n })),\n ),\n );\n }\n for (const fkError of foreignKeyErrors) {\n if (fkError.foreignKeyError) {\n errorLines.push(\n formatter.formatForeignKeyError({\n file: fkError.file,\n rowIndex: fkError.rowIndex,\n column: fkError.foreignKeyError.column,\n value: fkError.foreignKeyError.value,\n referencedTable: fkError.foreignKeyError.referencedTable,\n referencedColumn: fkError.foreignKeyError.referencedColumn,\n }),\n );\n }\n }\n errorLines.push(\"\");\n }\n\n return errorLines.join(\"\\n\");\n}\n\n/**\n * Validate JSONL seed data against schema definitions.\n * Resolves the given path (directory or `.jsonl` file), validates the rows it\n * holds, and returns formatted output and error messages.\n * @param options - Validation options including path and verbose flag\n * @returns Validation result with output messages and optional error details\n */\nexport async function validateSeedData(\n options: ValidateSeedDataOptions,\n): Promise<ValidateSeedResult> {\n const { path: resolvedPath, verbose = false } = options;\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n\n const db = LinesDB.create({ dataDir });\n let result;\n try {\n result = await db.initialize({ tableName, detailedValidate: true });\n } finally {\n await db.close();\n }\n\n const outputLines = formatWarnings(result.warnings);\n\n if (result.valid) {\n outputLines.push(\"✓ All records are valid\");\n return { valid: true, output: outputLines.join(\"\\n\") };\n }\n\n return {\n valid: false,\n output: outputLines.join(\"\\n\"),\n error: formatValidationErrors(result.errors, verbose),\n };\n}\n\n// A row's own value for a field, or undefined when the row has none.\nfunction ownValue(row: Record<string, unknown>, field: string): unknown {\n return Object.hasOwn(row, field) ? row[field] : undefined;\n}\n\n// Not `row[field] = value`: a field named `__proto__` goes through the inherited\n// setter, which leaves no own property for the serializer to read back.\nfunction setField(row: JsonObject, field: string, value: JsonObject[string]): void {\n Object.defineProperty(row, field, {\n value,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n // A nested field the row never had comes back as an object whose keys carry no\n // value of their own, and writing that into the line fills nothing in.\n return Object.values(value).every(isBlank);\n}\n\n/** A JSONL line, kept as text so an untouched line is written back verbatim. */\ntype SeedLine = {\n text: string;\n /** Line separator the file used after this line, kept so CRLF survives. */\n eol: string;\n row: JsonObject | undefined;\n};\n\nfunction splitLines(content: string): SeedLine[] {\n if (content === \"\") {\n return [];\n }\n return content.split(\"\\n\").map((raw, index, all) => {\n const eol = index === all.length - 1 ? \"\" : \"\\n\";\n const text = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n const carriage = raw.endsWith(\"\\r\") ? \"\\r\" : \"\";\n let row: JsonObject | undefined;\n if (text.trim() !== \"\") {\n try {\n const parsed: unknown = JSON.parse(text);\n row =\n parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as JsonObject)\n : undefined;\n } catch {\n row = undefined;\n }\n }\n return { text, eol: `${carriage}${eol}`, row };\n });\n}\n\n// Keys go in the order the hook produced them, which is the order the type\n// declares its fields. Keys the type does not declare follow the declared ones.\nfunction serializeRow(row: JsonObject, fieldOrder: string[]): string {\n const rank = new Map(fieldOrder.map((field, index) => [field, index]));\n const rankOf = (key: string): number => rank.get(key) ?? fieldOrder.length;\n return JSON.stringify(\n Object.fromEntries(Object.entries(row).toSorted(([a], [b]) => rankOf(a) - rankOf(b))),\n );\n}\n\ntype SeedHook = (row: unknown) => Record<string, unknown>;\n\nasync function loadSeedHook(dataDir: string, table: string): Promise<SeedHook | undefined> {\n const schemaPath = await findSchemaFile(dataDir, table);\n if (!schemaPath) {\n return undefined;\n }\n const loaded: unknown = await import(pathToFileURL(schemaPath).href);\n const hook = (loaded as { hook?: unknown }).hook;\n if (typeof hook !== \"function\") {\n throw new Error(\n `${schemaPath} does not export \\`hook\\`. Run \\`tailor generate\\` to regenerate the seed schema files.`,\n );\n }\n return hook as SeedHook;\n}\n\n/**\n * Fill in the values a record gets on create for the JSONL seed data rows that\n * are missing them, so a row can be referenced by `id` or carry a timestamp\n * before it is ever seeded.\n *\n * The values come from the table's own create-time behavior — its `id`, its field\n * defaults, and its create hooks — applied to each row on its own. Nothing is\n * validated, so a row can be filled while the data around it is still\n * incomplete: that is what lets you get the ids you need in order to write the\n * rows that reference them. Run `validateSeedData` when the data is ready.\n *\n * Only the named fields are written, and only into a row that has no value for\n * them, so a value already in the file is never replaced. A line that gains\n * nothing is written back exactly as it was, byte for byte; a line that does get\n * a value is re-serialized with its keys in the order the table declares its\n * fields, so a filled-in `id` lands at the front. A field the table gives no\n * value to — one it does not declare, or one the platform assigns such as a\n * serial field — is skipped, so one field list covers a whole data directory.\n *\n * The values are read from the schema files generated next to the data, and all\n * of them are read before anything is written: a file that predates the current\n * generator stops the run with nothing filled in anywhere.\n * @param options - Fill options including path and fields\n * @returns Which files received which fields\n */\nexport async function fillSeedData(options: FillSeedDataOptions): Promise<FillSeedDataResult> {\n const { path: resolvedPath, fields = DEFAULT_FILL_FIELDS } = options;\n if (fields.length === 0) {\n throw new Error(\"No fields to fill. Name at least one field.\");\n }\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n const tables = tableName ? [tableName] : await listSeedTables(dataDir);\n\n const warnings: string[] = [];\n const filled: FilledSeedFile[] = [];\n const producedFields = new Set<string>();\n\n // Every hook loads before anything is written, so a schema file that predates\n // `tailor generate` stops the run instead of leaving half the files filled.\n const hooks: { table: string; hook: SeedHook }[] = [];\n for (const table of tables) {\n const hook = await loadSeedHook(dataDir, table);\n if (!hook) {\n warnings.push(`No schema file for ${table}, so nothing can be filled in there`);\n continue;\n }\n hooks.push({ table, hook });\n }\n\n // Every line is decided before any file is written, so a hook that throws on\n // one table cannot leave another one already rewritten.\n const writes: { file: string; content: string }[] = [];\n for (const { table, hook } of hooks) {\n const file = join(dataDir, `${table}.jsonl`);\n const lines = splitLines(await readFile(file, \"utf-8\"));\n\n const written = new Set<string>();\n const unreadable: number[] = [];\n let count = 0;\n let fieldOrder: string[] = [];\n for (const [index, line] of lines.entries()) {\n const row = line.row;\n if (!row) {\n if (line.text.trim() !== \"\") {\n unreadable.push(index + 1);\n }\n continue;\n }\n const hooked = hook(row);\n fieldOrder = Object.keys(hooked);\n const gained = fields.filter((field) => {\n const value = ownValue(hooked, field);\n if (isBlank(value)) {\n return false;\n }\n producedFields.add(field);\n return isBlank(ownValue(row, field));\n });\n if (gained.length === 0) {\n continue;\n }\n for (const field of gained) {\n setField(row, field, hooked[field] as JsonObject[string]);\n written.add(field);\n }\n line.text = serializeRow(row, fieldOrder);\n count += 1;\n }\n\n if (unreadable.length > 0) {\n warnings.push(\n `${file}: line(s) ${unreadable.join(\", \")} are not JSON objects, so nothing was filled in there`,\n );\n }\n\n if (count === 0) {\n continue;\n }\n writes.push({ file, content: lines.map((line) => `${line.text}${line.eol}`).join(\"\") });\n filled.push({ table, file, fields: [...written], count });\n }\n\n for (const { file, content } of writes) {\n await writeFile(file, content);\n }\n\n const unproducedFields = fields.filter((field) => !producedFields.has(field));\n if (tables.length > 0 && unproducedFields.length > 0) {\n warnings.push(`No seed data produces a value for: ${unproducedFields.join(\", \")}`);\n }\n\n const outputLines = formatWarnings(warnings);\n outputLines.push(\n filled.length === 0\n ? \"\\u2713 Nothing to fill\"\n : filled\n .map(\n ({ file, fields: tableFields, count }) =>\n `\\u2713 ${file}: filled ${tableFields.join(\", \")} in ${count} row(s)`,\n )\n .join(\"\\n\"),\n );\n\n return { output: outputLines.join(\"\\n\"), filled };\n}\n"],"mappings":"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"}
|