@tailor-platform/sdk 1.48.0 → 1.50.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 +44 -0
- package/README.md +1 -1
- package/dist/{application-DUENhx4Y.mjs → application-CZMzt9jL.mjs} +3 -2
- package/dist/application-CZMzt9jL.mjs.map +1 -0
- package/dist/application-v_E2W-Fz.mjs +4 -0
- package/dist/brand-D-d15jx3.mjs.map +1 -1
- package/dist/cli/index.mjs +137 -25
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +1409 -1373
- package/dist/cli/lib.mjs +4 -4
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/skills.mjs.map +1 -1
- package/dist/client-_kHh0Pip.mjs.map +1 -1
- package/dist/configure/index.mjs.map +1 -1
- package/dist/crashreport-CvmdFs4i.mjs.map +1 -1
- package/dist/enum-constants-C3KSpsYj.mjs.map +1 -1
- package/dist/errors-pMPXghkO.mjs.map +1 -1
- package/dist/field-DLSIuMTu.mjs.map +1 -1
- package/dist/file-utils-DjNi_3U_.mjs.map +1 -1
- package/dist/interceptor-DTNS0EtF.mjs.map +1 -1
- package/dist/job-M3Avv_SV.mjs.map +1 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/kysely-type-B8aRz_oC.mjs.map +1 -1
- package/dist/logger-DTNAMYGy.mjs.map +1 -1
- package/dist/mock-BfL09ULZ.mjs.map +1 -1
- package/dist/multiline-e3IpANmS.mjs.map +1 -1
- package/dist/package-json-6Px8bDpG.mjs.map +1 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/repl-editor-jZ493eQI.mjs.map +1 -1
- package/dist/{runtime-CNg0w27y.mjs → runtime-B2K6JW7V.mjs} +543 -164
- package/dist/runtime-B2K6JW7V.mjs.map +1 -0
- package/dist/schema-C5QjYEc-.mjs.map +1 -1
- package/dist/secret-file-BHpxGyNf.mjs.map +1 -1
- package/dist/seed/index.mjs.map +1 -1
- package/dist/seed-DjfAn0BC.mjs.map +1 -1
- package/dist/service-DCgJxdg1.mjs.map +1 -1
- package/dist/telemetry-C1Y56L5E.mjs.map +1 -1
- package/dist/types-sir9UPht.mjs.map +1 -1
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/setup.mjs.map +1 -1
- package/docs/cli/tailordb.md +50 -0
- package/docs/cli/workspace.md +20 -17
- package/docs/cli-reference.md +1 -0
- package/docs/services/tailordb-migration.md +49 -31
- package/package.json +8 -8
- package/dist/application-BNkNt47b.mjs +0 -4
- package/dist/application-DUENhx4Y.mjs.map +0 -1
- package/dist/runtime-CNg0w27y.mjs.map +0 -1
- /package/{skills → agent-skills}/tailor-sdk/SKILL.md +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/utils/test/mock.ts","../../../src/utils/test/index.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { TailorInvoker } from \"@/types/user\";\n\ntype MainFunction = (args: Record<string, unknown>) => unknown | Promise<unknown>;\ntype QueryResolver = (query: string, params: unknown[]) => unknown[];\ntype JobHandler = (jobName: string, args: unknown) => unknown;\ntype WaitHandler = (key: string, payload: unknown) => unknown;\ntype ResolveHandler = (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n) => Promise<void> | void;\n\ninterface TailordbGlobal {\n tailordb?: {\n Client: new (config: { namespace?: string }) => {\n connect(): Promise<void> | void;\n end(): Promise<void> | void;\n queryObject(\n query: string,\n params?: unknown[],\n ): Promise<{ rows: unknown[] }> | { rows: unknown[] };\n };\n };\n tailor?: {\n workflow: {\n triggerJobFunction: (jobName: string, args: unknown) => unknown;\n wait?: (key: string, payload?: unknown) => unknown;\n resolve?: (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n ) => Promise<void>;\n };\n context: {\n getInvoker: () => tailor.context.Invoker | null;\n };\n };\n}\n\ninterface TailorErrorItem {\n message: string;\n path: (string | number)[];\n}\n\ninterface TailorErrorsGlobal {\n TailorErrors?: new (errors: TailorErrorItem[]) => Error;\n}\n\nconst GlobalThis = globalThis as TailordbGlobal & TailorErrorsGlobal;\n\n/**\n * Sets up a mock for `globalThis.tailordb.Client` used in bundled resolver/executor tests.\n * @deprecated Use `tailordbMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * @param resolver - Optional function to resolve query results. Defaults to returning empty arrays.\n * @returns Object containing arrays of executed queries and created clients for assertions.\n */\nexport function setupTailordbMock(resolver: QueryResolver = () => []): {\n executedQueries: { query: string; params: unknown[] }[];\n createdClients: { namespace?: string; ended: boolean }[];\n} {\n const executedQueries: { query: string; params: unknown[] }[] = [];\n const createdClients: { namespace?: string; ended: boolean }[] = [];\n\n class MockTailordbClient {\n private record: { namespace?: string; ended: boolean };\n\n constructor({ namespace }: { namespace?: string }) {\n this.record = { namespace, ended: false };\n createdClients.push(this.record);\n }\n\n async connect(): Promise<void> {\n /* noop */\n }\n\n async end(): Promise<void> {\n this.record.ended = true;\n }\n\n async queryObject(query: string, params: unknown[] = []): Promise<{ rows: unknown[] }> {\n executedQueries.push({ query, params });\n return { rows: resolver(query, params) ?? [] };\n }\n }\n\n GlobalThis.tailordb = {\n Client: MockTailordbClient,\n } as typeof GlobalThis.tailordb;\n\n return { executedQueries, createdClients };\n}\n\n/**\n * Sets up a mock for `globalThis.tailor.workflow.triggerJobFunction` used in bundled workflow tests.\n * `wait`/`resolve` are stubbed to throw a helpful error directing to `workflowMock`,\n * so mistakenly calling wait without wait-point mocks produces a clear message instead of a TypeError.\n * @deprecated Use `workflowMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * @param handler - Function that handles triggered job calls and returns results.\n * @returns Object containing an array of triggered jobs for assertions.\n */\nexport function setupWorkflowMock(handler: JobHandler): {\n triggeredJobs: { jobName: string; args: unknown }[];\n} {\n const triggeredJobs: { jobName: string; args: unknown }[] = [];\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n workflow: {\n wait: () => {\n throw new Error(\n \"tailor.workflow.wait is not mocked. Use workflowMock from @tailor-platform/sdk/vitest in tests.\",\n );\n },\n resolve: async () => {\n throw new Error(\n \"tailor.workflow.resolve is not mocked. Use workflowMock from @tailor-platform/sdk/vitest in tests.\",\n );\n },\n ...GlobalThis.tailor?.workflow,\n triggerJobFunction: (jobName: string, args: unknown) => {\n triggeredJobs.push({ jobName, args });\n return handler(jobName, args);\n },\n },\n } as typeof GlobalThis.tailor;\n\n return { triggeredJobs };\n}\n\n/**\n * Sets up a mock for `globalThis.tailor.context.getInvoker` used in bundled\n * resolver/executor/workflow tests.\n * @deprecated With the `tailor-runtime` environment from `@tailor-platform/sdk/vitest`, drive the invoker via `vi.spyOn(globalThis.tailor.context, \"getInvoker\").mockReturnValue(...)` for bundled tests, or pass `invoker` directly to `.body()` when unit-testing resolvers/executors/workflow jobs against the TypeScript source.\n * @param invoker - The `TailorInvoker` value to return, or `null` for anonymous.\n */\nexport function setupInvokerMock(invoker: TailorInvoker): void {\n const raw: tailor.context.Invoker | null = invoker\n ? {\n id: invoker.id,\n type: invoker.type,\n workspaceId: invoker.workspaceId,\n attributes: invoker.attributeList as string[],\n attributeMap: invoker.attributes as Record<string, unknown>,\n }\n : null;\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n context: {\n getInvoker: () => raw,\n },\n } as typeof GlobalThis.tailor;\n}\n\n/**\n * Sets up a mock for `globalThis.TailorErrors` used in bundled resolver tests.\n * Mimics the PF runtime's TailorErrors class that serializes errors with the `TailorErrors: ` prefix.\n * @deprecated Use the `tailor-runtime` environment from `@tailor-platform/sdk/vitest` which auto-injects TailorErrors.\n */\nexport function setupTailorErrorsMock(): void {\n GlobalThis.TailorErrors = class TailorErrors extends Error {\n errors: TailorErrorItem[];\n\n constructor(errors: TailorErrorItem[]) {\n super(`TailorErrors: ${JSON.stringify({ errors })}`);\n this.name = \"TailorErrors\";\n this.errors = errors;\n }\n };\n}\n\n/**\n * Sets up mocks for `globalThis.tailor.workflow.wait` and `.resolve` used in bundled workflow tests.\n * `triggerJobFunction` is stubbed to throw a helpful error directing to `setupWorkflowMock()`,\n * so mistakenly triggering a job without job mocks produces a clear message instead of silently returning undefined.\n * @deprecated Use `workflowMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * `setWaitHandler` / `setResolveHandler` cover wait/resolve, and `waitCalls` / `resolveCalls` give the same assertion shape.\n * @param config - Optional handlers for wait and resolve calls.\n * @param config.onWait - Handler called when wait is invoked.\n * @param config.onResolve - Handler called when resolve is invoked.\n * @returns Object containing arrays of wait and resolve calls for assertions.\n */\nexport function setupWaitPointMock(config?: { onWait?: WaitHandler; onResolve?: ResolveHandler }): {\n waitCalls: { key: string; payload: unknown }[];\n resolveCalls: { executionId: string; key: string }[];\n} {\n const waitCalls: { key: string; payload: unknown }[] = [];\n const resolveCalls: { executionId: string; key: string }[] = [];\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n workflow: {\n triggerJobFunction: () => {\n throw new Error(\n \"tailor.workflow.triggerJobFunction is not mocked. Use setupWorkflowMock() in tests.\",\n );\n },\n ...GlobalThis.tailor?.workflow,\n wait: (key: string, payload?: unknown) => {\n waitCalls.push({ key, payload });\n return config?.onWait?.(key, payload);\n },\n resolve: async (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n ) => {\n resolveCalls.push({ executionId, key });\n await config?.onResolve?.(executionId, key, callback);\n },\n },\n } as typeof GlobalThis.tailor;\n\n return { waitCalls, resolveCalls };\n}\n\n/**\n * Creates a function that imports a bundled JS file and returns its `main` export.\n * Used to test bundled output from `apply --buildOnly`.\n * @param baseDir - Base directory where bundled files are located.\n * @returns An async function that takes a relative path and returns the `main` function.\n * @deprecated This is an SDK-internal testing helper. Bundling integrity is the SDK's responsibility,\n * not the application's — verify your code through unit tests against the TypeScript source and\n * E2E tests against a deployed application instead. This export will be removed in a future release.\n */\nexport function createImportMain(baseDir: string): (relativePath: string) => Promise<MainFunction> {\n return async (relativePath: string): Promise<MainFunction> => {\n const fileUrl = pathToFileURL(path.join(baseDir, relativePath));\n fileUrl.searchParams.set(\"v\", `${Date.now()}-${Math.random()}`);\n const module = await import(fileUrl.href);\n const main = module.main;\n if (typeof main !== \"function\") {\n throw new Error(`Expected \"main\" to be a function in ${relativePath}, got ${typeof main}`);\n }\n return main;\n };\n}\n","import type { output, TailorUser } from \"@/configure\";\nimport type { TailorDBType } from \"@/configure/services/tailordb/schema\";\nimport type { TailorField } from \"@/configure/types/type\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport { WORKFLOW_TEST_ENV_KEY } from \"@/configure/services/workflow/job\";\nexport {\n setupTailordbMock,\n setupTailorErrorsMock,\n setupWorkflowMock,\n setupInvokerMock,\n setupWaitPointMock,\n createImportMain,\n} from \"./mock\";\n\n/** Represents an unauthenticated user in the Tailor platform. */\nexport const unauthenticatedTailorUser = {\n id: \"00000000-0000-0000-0000-000000000000\",\n type: \"\",\n workspaceId: \"00000000-0000-0000-0000-000000000000\",\n attributes: null,\n attributeList: [],\n} as const satisfies TailorUser;\n\n/**\n * Creates a hook function that processes TailorDB type fields\n * - Uses existing id from data if provided, otherwise generates UUID for id fields\n * - Recursively processes nested types\n * - Executes hooks.create for fields with create hooks\n * @template T - The output type of the hook function\n * @param type - TailorDB type definition\n * @returns A function that transforms input data according to field hooks\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function createTailorDBHook<T extends TailorDBType<any, any>>(type: T) {\n return (data: unknown) => {\n return Object.entries(type.fields).reduce(\n (hooked, [key, value]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const field = value as TailorField<any, any, any>;\n if (key === \"id\") {\n // Use existing id from data if provided, otherwise generate new UUID\n const existingId =\n data && typeof data === \"object\" ? (data as Record<string, unknown>)[key] : undefined;\n hooked[key] = existingId ?? crypto.randomUUID();\n } else if (field.type === \"nested\") {\n const nestedValue =\n data && typeof data === \"object\" ? (data as Record<string, unknown>)[key] : undefined;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nestedHook = createTailorDBHook({ fields: field.fields } as any);\n if (field.metadata.array) {\n // For nested array fields, recurse per element and pass through non-array values\n // (e.g. null/undefined for optional fields) so validation sees the original value.\n hooked[key] = Array.isArray(nestedValue)\n ? nestedValue.map((item) => nestedHook(item))\n : nestedValue;\n } else {\n hooked[key] = nestedHook(nestedValue);\n }\n } else if (field.metadata.hooks?.create) {\n hooked[key] = field.metadata.hooks.create({\n value: (data as Record<string, unknown>)[key],\n data: data,\n user: unauthenticatedTailorUser,\n });\n if (hooked[key] instanceof Date) {\n hooked[key] = hooked[key].toISOString();\n }\n } else if (data && typeof data === \"object\") {\n hooked[key] = (data as Record<string, unknown>)[key];\n }\n return hooked;\n },\n {} as Record<string, unknown>,\n ) as Partial<output<T>>;\n };\n}\n\n/**\n * Creates the standard schema definition for lines-db\n * This returns the first argument for defineSchema with the ~standard section\n * @template T - The output type after validation\n * @param schemaType - TailorDB field schema for validation\n * @param hook - Hook function to transform data before validation\n * @returns Schema object with ~standard section for defineSchema\n */\nexport function createStandardSchema<T = Record<string, unknown>>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schemaType: TailorField<any, T>,\n hook: (data: unknown) => Partial<T>,\n) {\n return {\n \"~standard\": {\n version: 1,\n vendor: \"@tailor-platform/sdk\",\n validate: (value: unknown) => {\n const hooked = hook(value);\n const result = schemaType.parse({\n value: hooked,\n data: hooked,\n user: unauthenticatedTailorUser,\n });\n if (result.issues) {\n return result;\n }\n return { value: hooked as T };\n },\n },\n } as const satisfies StandardSchemaV1<T>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/utils/test/mock.ts","../../../src/utils/test/index.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { TailorInvoker } from \"@/types/user\";\n\ntype MainFunction = (args: Record<string, unknown>) => unknown | Promise<unknown>;\ntype QueryResolver = (query: string, params: unknown[]) => unknown[];\ntype JobHandler = (jobName: string, args: unknown) => unknown;\ntype WaitHandler = (key: string, payload: unknown) => unknown;\ntype ResolveHandler = (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n) => Promise<void> | void;\n\ninterface TailordbGlobal {\n tailordb?: {\n Client: new (config: { namespace?: string }) => {\n connect(): Promise<void> | void;\n end(): Promise<void> | void;\n queryObject(\n query: string,\n params?: unknown[],\n ): Promise<{ rows: unknown[] }> | { rows: unknown[] };\n };\n };\n tailor?: {\n workflow: {\n triggerJobFunction: (jobName: string, args: unknown) => unknown;\n wait?: (key: string, payload?: unknown) => unknown;\n resolve?: (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n ) => Promise<void>;\n };\n context: {\n getInvoker: () => tailor.context.Invoker | null;\n };\n };\n}\n\ninterface TailorErrorItem {\n message: string;\n path: (string | number)[];\n}\n\ninterface TailorErrorsGlobal {\n TailorErrors?: new (errors: TailorErrorItem[]) => Error;\n}\n\nconst GlobalThis = globalThis as TailordbGlobal & TailorErrorsGlobal;\n\n/**\n * Sets up a mock for `globalThis.tailordb.Client` used in bundled resolver/executor tests.\n * @deprecated Use `tailordbMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * @param resolver - Optional function to resolve query results. Defaults to returning empty arrays.\n * @returns Object containing arrays of executed queries and created clients for assertions.\n */\nexport function setupTailordbMock(resolver: QueryResolver = () => []): {\n executedQueries: { query: string; params: unknown[] }[];\n createdClients: { namespace?: string; ended: boolean }[];\n} {\n const executedQueries: { query: string; params: unknown[] }[] = [];\n const createdClients: { namespace?: string; ended: boolean }[] = [];\n\n class MockTailordbClient {\n private record: { namespace?: string; ended: boolean };\n\n constructor({ namespace }: { namespace?: string }) {\n this.record = { namespace, ended: false };\n createdClients.push(this.record);\n }\n\n async connect(): Promise<void> {\n /* noop */\n }\n\n async end(): Promise<void> {\n this.record.ended = true;\n }\n\n async queryObject(query: string, params: unknown[] = []): Promise<{ rows: unknown[] }> {\n executedQueries.push({ query, params });\n return { rows: resolver(query, params) ?? [] };\n }\n }\n\n GlobalThis.tailordb = {\n Client: MockTailordbClient,\n } as typeof GlobalThis.tailordb;\n\n return { executedQueries, createdClients };\n}\n\n/**\n * Sets up a mock for `globalThis.tailor.workflow.triggerJobFunction` used in bundled workflow tests.\n * `wait`/`resolve` are stubbed to throw a helpful error directing to `workflowMock`,\n * so mistakenly calling wait without wait-point mocks produces a clear message instead of a TypeError.\n * @deprecated Use `workflowMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * @param handler - Function that handles triggered job calls and returns results.\n * @returns Object containing an array of triggered jobs for assertions.\n */\nexport function setupWorkflowMock(handler: JobHandler): {\n triggeredJobs: { jobName: string; args: unknown }[];\n} {\n const triggeredJobs: { jobName: string; args: unknown }[] = [];\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n workflow: {\n wait: () => {\n throw new Error(\n \"tailor.workflow.wait is not mocked. Use workflowMock from @tailor-platform/sdk/vitest in tests.\",\n );\n },\n resolve: async () => {\n throw new Error(\n \"tailor.workflow.resolve is not mocked. Use workflowMock from @tailor-platform/sdk/vitest in tests.\",\n );\n },\n ...GlobalThis.tailor?.workflow,\n triggerJobFunction: (jobName: string, args: unknown) => {\n triggeredJobs.push({ jobName, args });\n return handler(jobName, args);\n },\n },\n } as typeof GlobalThis.tailor;\n\n return { triggeredJobs };\n}\n\n/**\n * Sets up a mock for `globalThis.tailor.context.getInvoker` used in bundled\n * resolver/executor/workflow tests.\n * @deprecated With the `tailor-runtime` environment from `@tailor-platform/sdk/vitest`, drive the invoker via `vi.spyOn(globalThis.tailor.context, \"getInvoker\").mockReturnValue(...)` for bundled tests, or pass `invoker` directly to `.body()` when unit-testing resolvers/executors/workflow jobs against the TypeScript source.\n * @param invoker - The `TailorInvoker` value to return, or `null` for anonymous.\n */\nexport function setupInvokerMock(invoker: TailorInvoker): void {\n const raw: tailor.context.Invoker | null = invoker\n ? {\n id: invoker.id,\n type: invoker.type,\n workspaceId: invoker.workspaceId,\n attributes: invoker.attributeList as string[],\n attributeMap: invoker.attributes as Record<string, unknown>,\n }\n : null;\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n context: {\n getInvoker: () => raw,\n },\n } as typeof GlobalThis.tailor;\n}\n\n/**\n * Sets up a mock for `globalThis.TailorErrors` used in bundled resolver tests.\n * Mimics the PF runtime's TailorErrors class that serializes errors with the `TailorErrors: ` prefix.\n * @deprecated Use the `tailor-runtime` environment from `@tailor-platform/sdk/vitest` which auto-injects TailorErrors.\n */\nexport function setupTailorErrorsMock(): void {\n GlobalThis.TailorErrors = class TailorErrors extends Error {\n errors: TailorErrorItem[];\n\n constructor(errors: TailorErrorItem[]) {\n super(`TailorErrors: ${JSON.stringify({ errors })}`);\n this.name = \"TailorErrors\";\n this.errors = errors;\n }\n };\n}\n\n/**\n * Sets up mocks for `globalThis.tailor.workflow.wait` and `.resolve` used in bundled workflow tests.\n * `triggerJobFunction` is stubbed to throw a helpful error directing to `setupWorkflowMock()`,\n * so mistakenly triggering a job without job mocks produces a clear message instead of silently returning undefined.\n * @deprecated Use `workflowMock` from `@tailor-platform/sdk/vitest` with the `tailor-runtime` environment instead.\n * `setWaitHandler` / `setResolveHandler` cover wait/resolve, and `waitCalls` / `resolveCalls` give the same assertion shape.\n * @param config - Optional handlers for wait and resolve calls.\n * @param config.onWait - Handler called when wait is invoked.\n * @param config.onResolve - Handler called when resolve is invoked.\n * @returns Object containing arrays of wait and resolve calls for assertions.\n */\nexport function setupWaitPointMock(config?: { onWait?: WaitHandler; onResolve?: ResolveHandler }): {\n waitCalls: { key: string; payload: unknown }[];\n resolveCalls: { executionId: string; key: string }[];\n} {\n const waitCalls: { key: string; payload: unknown }[] = [];\n const resolveCalls: { executionId: string; key: string }[] = [];\n\n GlobalThis.tailor = {\n ...GlobalThis.tailor,\n workflow: {\n triggerJobFunction: () => {\n throw new Error(\n \"tailor.workflow.triggerJobFunction is not mocked. Use setupWorkflowMock() in tests.\",\n );\n },\n ...GlobalThis.tailor?.workflow,\n wait: (key: string, payload?: unknown) => {\n waitCalls.push({ key, payload });\n return config?.onWait?.(key, payload);\n },\n resolve: async (\n executionId: string,\n key: string,\n callback: (payload: unknown) => unknown,\n ) => {\n resolveCalls.push({ executionId, key });\n await config?.onResolve?.(executionId, key, callback);\n },\n },\n } as typeof GlobalThis.tailor;\n\n return { waitCalls, resolveCalls };\n}\n\n/**\n * Creates a function that imports a bundled JS file and returns its `main` export.\n * Used to test bundled output from `apply --buildOnly`.\n * @param baseDir - Base directory where bundled files are located.\n * @returns An async function that takes a relative path and returns the `main` function.\n * @deprecated This is an SDK-internal testing helper. Bundling integrity is the SDK's responsibility,\n * not the application's — verify your code through unit tests against the TypeScript source and\n * E2E tests against a deployed application instead. This export will be removed in a future release.\n */\nexport function createImportMain(baseDir: string): (relativePath: string) => Promise<MainFunction> {\n return async (relativePath: string): Promise<MainFunction> => {\n const fileUrl = pathToFileURL(path.join(baseDir, relativePath));\n fileUrl.searchParams.set(\"v\", `${Date.now()}-${Math.random()}`);\n const module = await import(fileUrl.href);\n const main = module.main;\n if (typeof main !== \"function\") {\n throw new Error(`Expected \"main\" to be a function in ${relativePath}, got ${typeof main}`);\n }\n return main;\n };\n}\n","import type { output, TailorUser } from \"@/configure\";\nimport type { TailorDBType } from \"@/configure/services/tailordb/schema\";\nimport type { TailorField } from \"@/configure/types/type\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport { WORKFLOW_TEST_ENV_KEY } from \"@/configure/services/workflow/job\";\nexport {\n setupTailordbMock,\n setupTailorErrorsMock,\n setupWorkflowMock,\n setupInvokerMock,\n setupWaitPointMock,\n createImportMain,\n} from \"./mock\";\n\n/** Represents an unauthenticated user in the Tailor platform. */\nexport const unauthenticatedTailorUser = {\n id: \"00000000-0000-0000-0000-000000000000\",\n type: \"\",\n workspaceId: \"00000000-0000-0000-0000-000000000000\",\n attributes: null,\n attributeList: [],\n} as const satisfies TailorUser;\n\n/**\n * Creates a hook function that processes TailorDB type fields\n * - Uses existing id from data if provided, otherwise generates UUID for id fields\n * - Recursively processes nested types\n * - Executes hooks.create for fields with create hooks\n * @template T - The output type of the hook function\n * @param type - TailorDB type definition\n * @returns A function that transforms input data according to field hooks\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function createTailorDBHook<T extends TailorDBType<any, any>>(type: T) {\n return (data: unknown) => {\n return Object.entries(type.fields).reduce(\n (hooked, [key, value]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const field = value as TailorField<any, any, any>;\n if (key === \"id\") {\n // Use existing id from data if provided, otherwise generate new UUID\n const existingId =\n data && typeof data === \"object\" ? (data as Record<string, unknown>)[key] : undefined;\n hooked[key] = existingId ?? crypto.randomUUID();\n } else if (field.type === \"nested\") {\n const nestedValue =\n data && typeof data === \"object\" ? (data as Record<string, unknown>)[key] : undefined;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nestedHook = createTailorDBHook({ fields: field.fields } as any);\n if (field.metadata.array) {\n // For nested array fields, recurse per element and pass through non-array values\n // (e.g. null/undefined for optional fields) so validation sees the original value.\n hooked[key] = Array.isArray(nestedValue)\n ? nestedValue.map((item) => nestedHook(item))\n : nestedValue;\n } else {\n hooked[key] = nestedHook(nestedValue);\n }\n } else if (field.metadata.hooks?.create) {\n hooked[key] = field.metadata.hooks.create({\n value: (data as Record<string, unknown>)[key],\n data: data,\n user: unauthenticatedTailorUser,\n });\n if (hooked[key] instanceof Date) {\n hooked[key] = hooked[key].toISOString();\n }\n } else if (data && typeof data === \"object\") {\n hooked[key] = (data as Record<string, unknown>)[key];\n }\n return hooked;\n },\n {} as Record<string, unknown>,\n ) as Partial<output<T>>;\n };\n}\n\n/**\n * Creates the standard schema definition for lines-db\n * This returns the first argument for defineSchema with the ~standard section\n * @template T - The output type after validation\n * @param schemaType - TailorDB field schema for validation\n * @param hook - Hook function to transform data before validation\n * @returns Schema object with ~standard section for defineSchema\n */\nexport function createStandardSchema<T = Record<string, unknown>>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schemaType: TailorField<any, T>,\n hook: (data: unknown) => Partial<T>,\n) {\n return {\n \"~standard\": {\n version: 1,\n vendor: \"@tailor-platform/sdk\",\n validate: (value: unknown) => {\n const hooked = hook(value);\n const result = schemaType.parse({\n value: hooked,\n data: hooked,\n user: unauthenticatedTailorUser,\n });\n if (result.issues) {\n return result;\n }\n return { value: hooked as T };\n },\n },\n } as const satisfies StandardSchemaV1<T>;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.mjs","names":[],"sources":["../../src/vitest/environment.ts"],"sourcesContent":["import * as globals from \"globals\";\nimport { STATE_KEY, RUNTIME_FLAG_KEY, injectMocks, cleanupMocks } from \"./mock\";\n\n// Normalize the `globals` module shape across CJS/ESM interop so the\n// whitelist build doesn't crash if the default export is unavailable or\n// the keyed sets are missing. Mirrors src/cli/services/tailordb/es-builtins.ts.\ntype GlobalsShape = {\n builtin?: Record<string, boolean>;\n \"shared-node-browser\"?: Record<string, boolean>;\n};\nconst globalsMap: GlobalsShape =\n (globals as unknown as { default?: GlobalsShape }).default ??\n (globals as unknown as GlobalsShape);\n\n// Globals allowed in the Tailor Platform runtime.\n// Mirrors ES_BUILTINS in src/cli/services/tailordb/es-builtins.ts so the\n// emulated runtime exposes exactly the same identifiers as the production\n// platform's free-variable allowlist (ECMAScript builtins + shared\n// Node/browser runtime globals like console, fetch, setTimeout).\n// Platform API mocks (tailor, tailordb, etc.) are not listed here — they are\n// injected by injectMocks() after the whitelist cleanup, so they are never removed.\nconst ALLOWED_GLOBALS = new Set([\n ...Object.keys(globalsMap.builtin ?? {}),\n ...Object.keys(globalsMap[\"shared-node-browser\"] ?? {}),\n\n // Mock state key (lazily populated by mock helpers) and the\n // environment-active flag (set in injectMocks, used by setup.ts to detect\n // the tailor-runtime environment).\n STATE_KEY,\n RUNTIME_FLAG_KEY,\n\n // Used by Vitest internally — not in the platform runtime, but removing breaks the test runner\n \"process\",\n \"require\",\n \"module\",\n \"exports\",\n \"__vitest_worker__\",\n \"__vitest_mocker__\",\n \"VITEST_POOL_ID\",\n]);\n\nexport default {\n name: \"tailor-runtime\",\n viteEnvironment: \"ssr\",\n\n async setup(global: typeof globalThis) {\n const g = global as Record<string, unknown>;\n\n // Save and remove all non-whitelisted globals\n const saved: Record<string, PropertyDescriptor> = {};\n for (const key of Object.getOwnPropertyNames(g)) {\n if (!ALLOWED_GLOBALS.has(key)) {\n const descriptor = Object.getOwnPropertyDescriptor(g, key);\n if (descriptor?.configurable) {\n saved[key] = descriptor;\n delete g[key];\n }\n }\n }\n\n // Inject platform API mocks after whitelist cleanup\n injectMocks(global);\n\n return {\n teardown(global: typeof globalThis) {\n cleanupMocks(global);\n\n // Restore removed globals\n const g = global as Record<string, unknown>;\n for (const [key, descriptor] of Object.entries(saved)) {\n Object.defineProperty(g, key, descriptor);\n }\n },\n };\n },\n};\n"],"mappings":";;;;;AAUA,MAAM,aACH,QAAkD,WAClD;AASH,MAAM,kBAAkB,IAAI,IAAI;CAC9B,GAAG,OAAO,KAAK,WAAW,WAAW,
|
|
1
|
+
{"version":3,"file":"environment.mjs","names":[],"sources":["../../src/vitest/environment.ts"],"sourcesContent":["import * as globals from \"globals\";\nimport { STATE_KEY, RUNTIME_FLAG_KEY, injectMocks, cleanupMocks } from \"./mock\";\n\n// Normalize the `globals` module shape across CJS/ESM interop so the\n// whitelist build doesn't crash if the default export is unavailable or\n// the keyed sets are missing. Mirrors src/cli/services/tailordb/es-builtins.ts.\ntype GlobalsShape = {\n builtin?: Record<string, boolean>;\n \"shared-node-browser\"?: Record<string, boolean>;\n};\nconst globalsMap: GlobalsShape =\n (globals as unknown as { default?: GlobalsShape }).default ??\n (globals as unknown as GlobalsShape);\n\n// Globals allowed in the Tailor Platform runtime.\n// Mirrors ES_BUILTINS in src/cli/services/tailordb/es-builtins.ts so the\n// emulated runtime exposes exactly the same identifiers as the production\n// platform's free-variable allowlist (ECMAScript builtins + shared\n// Node/browser runtime globals like console, fetch, setTimeout).\n// Platform API mocks (tailor, tailordb, etc.) are not listed here — they are\n// injected by injectMocks() after the whitelist cleanup, so they are never removed.\nconst ALLOWED_GLOBALS = new Set([\n ...Object.keys(globalsMap.builtin ?? {}),\n ...Object.keys(globalsMap[\"shared-node-browser\"] ?? {}),\n\n // Mock state key (lazily populated by mock helpers) and the\n // environment-active flag (set in injectMocks, used by setup.ts to detect\n // the tailor-runtime environment).\n STATE_KEY,\n RUNTIME_FLAG_KEY,\n\n // Used by Vitest internally — not in the platform runtime, but removing breaks the test runner\n \"process\",\n \"require\",\n \"module\",\n \"exports\",\n \"__vitest_worker__\",\n \"__vitest_mocker__\",\n \"VITEST_POOL_ID\",\n]);\n\nexport default {\n name: \"tailor-runtime\",\n viteEnvironment: \"ssr\",\n\n async setup(global: typeof globalThis) {\n const g = global as Record<string, unknown>;\n\n // Save and remove all non-whitelisted globals\n const saved: Record<string, PropertyDescriptor> = {};\n for (const key of Object.getOwnPropertyNames(g)) {\n if (!ALLOWED_GLOBALS.has(key)) {\n const descriptor = Object.getOwnPropertyDescriptor(g, key);\n if (descriptor?.configurable) {\n saved[key] = descriptor;\n delete g[key];\n }\n }\n }\n\n // Inject platform API mocks after whitelist cleanup\n injectMocks(global);\n\n return {\n teardown(global: typeof globalThis) {\n cleanupMocks(global);\n\n // Restore removed globals\n const g = global as Record<string, unknown>;\n for (const [key, descriptor] of Object.entries(saved)) {\n Object.defineProperty(g, key, descriptor);\n }\n },\n };\n },\n};\n"],"mappings":";;;;;AAUA,MAAM,aACH,QAAkD,WAClD;AASH,MAAM,kBAAkB,IAAI,IAAI;CAC9B,GAAG,OAAO,KAAK,WAAW,WAAW,CAAC,CAAC;CACvC,GAAG,OAAO,KAAK,WAAW,0BAA0B,CAAC,CAAC;CAKtD;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,0BAAe;CACb,MAAM;CACN,iBAAiB;CAEjB,MAAM,MAAM,QAA2B;EACrC,MAAM,IAAI;EAGV,MAAM,QAA4C,CAAC;EACnD,KAAK,MAAM,OAAO,OAAO,oBAAoB,CAAC,GAC5C,IAAI,CAAC,gBAAgB,IAAI,GAAG,GAAG;GAC7B,MAAM,aAAa,OAAO,yBAAyB,GAAG,GAAG;GACzD,IAAI,YAAY,cAAc;IAC5B,MAAM,OAAO;IACb,OAAO,EAAE;GACX;EACF;EAIF,YAAY,MAAM;EAElB,OAAO,EACL,SAAS,QAA2B;GAClC,aAAa,MAAM;GAGnB,MAAM,IAAI;GACV,KAAK,MAAM,CAAC,KAAK,eAAe,OAAO,QAAQ,KAAK,GAClD,OAAO,eAAe,GAAG,KAAK,UAAU;EAE5C,EACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest/blocked-modules.ts","../../src/vitest/plugin.ts","../../src/vitest/index.ts"],"sourcesContent":["/**\n * Blocked Node.js built-in modules and their Web Standard API alternatives.\n *\n * The Tailor Platform runtime only provides Web Standard APIs.\n * These Node.js modules are not available and should be replaced with\n * the suggested alternatives.\n */\nimport { builtinModules } from \"node:module\";\n\n// Suggestions keyed by bare specifier. Lookup also checks with \"node:\" prefix stripped.\nconst SUGGESTIONS: Record<string, string> = {\n crypto: \"Use the Web Crypto API (globalThis.crypto) instead.\",\n buffer: \"Use Uint8Array or ArrayBuffer instead.\",\n fs: \"File system access is not available in the Tailor Platform runtime.\",\n \"fs/promises\": \"File system access is not available in the Tailor Platform runtime.\",\n path: \"Use URL or URLPattern for path manipulation.\",\n http: \"Use the Fetch API (globalThis.fetch) for HTTP requests instead.\",\n https: \"Use the Fetch API (globalThis.fetch) for HTTPS requests instead.\",\n url: \"Use the URL and URLSearchParams Web APIs instead.\",\n util: \"Use Web Standard APIs instead.\",\n stream: \"Use Web Streams API (ReadableStream, WritableStream, TransformStream) instead.\",\n \"stream/web\": \"Use Web Streams API (ReadableStream, WritableStream, TransformStream) instead.\",\n events: \"Use EventTarget instead.\",\n zlib: \"Use CompressionStream and DecompressionStream Web APIs instead.\",\n querystring: \"Use URLSearchParams instead.\",\n string_decoder: \"Use TextDecoder instead.\",\n};\n\nconst BLOCKED_MODULES = new Set<string>();\nfor (const mod of builtinModules) {\n BLOCKED_MODULES.add(mod);\n BLOCKED_MODULES.add(`node:${mod}`);\n}\n\n/**\n * Check if a module specifier is a blocked Node.js built-in.\n * @param specifier - Module specifier to check (e.g. \"node:crypto\", \"fs\")\n * @returns Whether the specifier is blocked\n */\nexport function isBlockedModule(specifier: string): boolean {\n return BLOCKED_MODULES.has(specifier);\n}\n\n/**\n * Get the error message for a blocked module import.\n * @param specifier - Module specifier that was blocked\n * @returns Error message with optional suggestion for the Web Standard API alternative\n */\nexport function getBlockedMessage(specifier: string): string {\n const bare = specifier.startsWith(\"node:\") ? specifier.slice(5) : specifier;\n const suggestion = SUGGESTIONS[bare];\n const base = `\"${specifier}\" is not available in the Tailor Platform runtime.`;\n return suggestion ? `${base} ${suggestion}` : base;\n}\n","import { dirname, isAbsolute, matchesGlob, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isBlockedModule, getBlockedMessage } from \"./blocked-modules\";\nimport type { Plugin } from \"vitest/config\";\n\nconst DEFAULT_TEST_INCLUDE = [\"**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}\"];\n\ninterface ExportSpecifierNode {\n type?: string;\n exported?: { name?: unknown } | null;\n}\n\ninterface ImportLikeNode {\n type: string;\n start: number;\n end: number;\n source?: { value?: unknown } | null;\n specifiers?: ExportSpecifierNode[] | null;\n exported?: { name?: unknown } | null;\n}\n\nconst IMPORT_LIKE_TYPES = new Set([\n \"ImportDeclaration\",\n \"ExportNamedDeclaration\",\n \"ExportAllDeclaration\",\n]);\n\n// Re-export specifiers (`export { x as Y } from \"...\"`) accept any\n// `IdentifierName` for `Y` — including reserved words like `delete`. But\n// `export const Y = ...` requires a `BindingIdentifier`, which forbids\n// reserved words and the strict-mode-banned `arguments` / `eval`. Synthesizing\n// `export const delete = ...` would yield a syntax error, so we fall back to\n// plain `throw` for unsafe names.\nconst UNSAFE_BINDING_NAMES = new Set([\n // ReservedWord (ES2022+)\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"enum\",\n \"export\",\n \"extends\",\n \"false\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"null\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"true\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"yield\",\n // Strict-mode reserved (ESM is always strict)\n \"let\",\n \"static\",\n \"implements\",\n \"interface\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n // Module-specific reserved\n \"await\",\n // Banned as binding names in strict mode\n \"arguments\",\n \"eval\",\n]);\n\nconst ID_START = /^[A-Za-z_$]/;\nconst ID_CONT = /^[A-Za-z0-9_$]*$/;\n\nfunction isSafeBindingName(name: string): boolean {\n if (UNSAFE_BINDING_NAMES.has(name)) return false;\n if (name.length === 0) return false;\n // Restrict to ASCII identifiers — Unicode bindings are valid JS but rare\n // for re-exports of node:* modules, and a regex over the full\n // ID_Start/ID_Continue sets adds substantial weight for marginal gain.\n return ID_START.test(name[0] ?? \"\") && ID_CONT.test(name.slice(1));\n}\n\nfunction buildBlockedReplacement(node: ImportLikeNode, message: string): string {\n // JSON.stringify yields a fully-escaped string literal (including the\n // surrounding quotes), so we don't need to manually handle backslashes,\n // newlines, or other control characters that may appear in the message.\n const literal = JSON.stringify(message);\n const throwStmt = `throw new Error(${literal});`;\n const throwExpr = `(() => { throw new Error(${literal}); })()`;\n\n if (node.type === \"ExportNamedDeclaration\") {\n const specs = node.specifiers ?? [];\n const stubs: string[] = [];\n for (const spec of specs) {\n const exportedName = spec.exported?.name;\n if (typeof exportedName !== \"string\") continue;\n if (exportedName === \"default\") {\n stubs.push(`export default ${throwExpr};`);\n continue;\n }\n // Reserved words can be re-export names but not binding names.\n // Bail to a plain throw rather than emit invalid syntax.\n if (!isSafeBindingName(exportedName)) return throwStmt;\n stubs.push(`export const ${exportedName} = ${throwExpr};`);\n }\n return stubs.length > 0 ? stubs.join(\" \") : throwStmt;\n }\n\n if (node.type === \"ExportAllDeclaration\") {\n const exportedName = node.exported?.name;\n if (typeof exportedName === \"string\" && isSafeBindingName(exportedName)) {\n return `export const ${exportedName} = ${throwExpr};`;\n }\n return throwStmt;\n }\n\n return throwStmt;\n}\n\n/**\n * Vite plugin that blocks Node.js built-in module imports from production code.\n *\n * Uses the `transform` hook to walk the Rollup-provided AST of non-test source\n * files for static `node:*` imports and re-exports.\n * `ImportDeclaration` and bare `export * from \"...\"` are replaced with a\n * `throw new Error(...)` statement so the failure surfaces at evaluation time.\n * `ExportNamedDeclaration` (`export { x, y as z } from \"...\"`) and namespaced\n * `export * as ns from \"...\"` are rewritten to per-binding stub exports\n * (`export const x = (() => { throw new Error(...) })();`). The IIFE throws\n * eagerly during module evaluation (same timing as a top-level `throw`), but\n * preserving the declared export bindings ensures the surfaced error is the\n * actual \"node:* not available\" message rather than an opaque\n * \"missing export\" raised by the loader.\n * Vitest treats `node:*` as external SSR modules (skipping `resolveId`), so\n * source-level transformation is the only reliable interception point.\n * Runs in the default phase (no `enforce: \"pre\"`) so esbuild's TypeScript\n * transform strips `import type` first; only runtime imports reach this hook.\n * Node.js globals not in the platform runtime are removed by the environment (whitelist-based).\n * Test file patterns are read from the resolved Vitest config (`test.include`).\n * Vitest setup files (`test.setupFiles`) and global-setup files\n * (`test.globalSetup`) are also exempted: they run in the test runner host,\n * not in the emulated platform runtime, so they may freely use `node:*`\n * modules (e.g. `node:url` for `pathToFileURL`).\n * @returns Vite plugin\n */\nexport function createBlockPlugin(): Plugin {\n let isTestFile: (id: string) => boolean = () => false;\n let isUserSourceFile: (id: string) => boolean = () => false;\n\n return {\n name: \"tailor-runtime-block-node\",\n\n configResolved(config) {\n type HostFileTestConfig = {\n include?: string[];\n setupFiles?: string | string[];\n globalSetup?: string | string[];\n root?: string;\n };\n const testConfig = (\n config as typeof config & {\n test?: HostFileTestConfig & {\n projects?: { test?: HostFileTestConfig }[];\n };\n }\n ).test;\n const root = testConfig?.root ?? config.root;\n // Setup files and global-setup files run in the Vitest host (not the\n // emulated runtime), so they may freely import node:* modules. Collect\n // them from the top-level config AND from each `test.projects[i]` —\n // per-project setup files run in the host too and would otherwise be\n // transformed as production code, breaking node:* imports inside them.\n const toAbsolutePaths = (value: string | string[] | undefined, baseRoot: string) =>\n (Array.isArray(value) ? value : value ? [value] : []).map((f) => resolve(baseRoot, f));\n const exemptHostFiles = new Set<string>([\n ...toAbsolutePaths(testConfig?.setupFiles, root),\n ...toAbsolutePaths(testConfig?.globalSetup, root),\n ]);\n // Vitest projects can each define their own `test.include` (and root).\n // A project that uses non-default patterns (e.g. `tests/**/*.spec.ts`)\n // must also be considered when classifying test files — otherwise its\n // tests would be treated as production code and have node:* imports\n // rewritten. Build a list of (root, patterns) pairs covering top-level\n // + every project, and accept a file if any pair matches.\n const includePairs: { root: string; patterns: string[] }[] = [\n { root, patterns: testConfig?.include ?? DEFAULT_TEST_INCLUDE },\n ];\n for (const project of testConfig?.projects ?? []) {\n const projectTest = project?.test;\n if (!projectTest) continue;\n const projectRoot = projectTest.root ?? root;\n for (const f of toAbsolutePaths(projectTest.setupFiles, projectRoot)) {\n exemptHostFiles.add(f);\n }\n for (const f of toAbsolutePaths(projectTest.globalSetup, projectRoot)) {\n exemptHostFiles.add(f);\n }\n includePairs.push({\n root: projectRoot,\n patterns: projectTest.include ?? DEFAULT_TEST_INCLUDE,\n });\n }\n isTestFile = (id: string) => {\n if (exemptHostFiles.has(id)) return true;\n return includePairs.some(({ root: r, patterns }) => {\n const candidate = isAbsolute(id) ? relative(r, id) : id;\n return patterns.some((pattern) => matchesGlob(candidate, pattern));\n });\n };\n // Only transform files inside the project root. With pnpm workspaces,\n // dependencies are symlinked and Vite resolves them to absolute paths\n // outside `node_modules`, so the substring check alone is insufficient.\n // Non-absolute ids are Vite-internal: virtual modules (`\\0...`,\n // `virtual:...`), bare specifiers, etc. Those are never user source\n // files and must not be parsed/transformed.\n isUserSourceFile = (id: string) => {\n if (!isAbsolute(id)) return false;\n const rel = relative(root, id);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n };\n },\n\n transform(code, id) {\n // Vite can pass ids with query/hash suffixes (e.g. `file.ts?import`,\n // `file.ts?v=hash`). Strip them so exact-path lookups (Set membership,\n // glob matching, absolute-path checks) match what callers configured.\n const queryIdx = id.search(/[?#]/);\n const cleanId = queryIdx === -1 ? id : id.slice(0, queryIdx);\n\n if (isTestFile(cleanId)) return undefined;\n if (cleanId.includes(\"node_modules\")) return undefined;\n if (!isUserSourceFile(cleanId)) return undefined;\n\n let ast: { body: ImportLikeNode[] };\n try {\n ast = this.parse(code) as unknown as { body: ImportLikeNode[] };\n } catch {\n // Not parseable as ESM (e.g. JSON, asset). Let other plugins handle it.\n return undefined;\n }\n\n const replacements: { start: number; end: number; replacement: string }[] = [];\n for (const node of ast.body) {\n if (!IMPORT_LIKE_TYPES.has(node.type)) continue;\n const specifier = node.source?.value;\n if (typeof specifier !== \"string\") continue;\n if (isBlockedModule(specifier)) {\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: buildBlockedReplacement(node, getBlockedMessage(specifier)),\n });\n }\n }\n\n if (replacements.length === 0) return undefined;\n\n let transformed = code;\n for (const r of replacements.sort((a, b) => b.start - a.start)) {\n transformed = transformed.slice(0, r.start) + r.replacement + transformed.slice(r.end);\n }\n\n return { code: transformed, map: null };\n },\n };\n}\n\nconst ENVIRONMENT_NAME = \"tailor-runtime\";\n\n/**\n * Vite plugin that resolves the tailor-runtime environment and injects setup files.\n *\n * Vitest resolves environments starting with \".\" or \"/\" as file paths.\n * This plugin rewrites `environment: \"tailor-runtime\"` to the absolute path\n * of the bundled environment module, both at the top-level and per-project.\n * It also injects the setup file that removes Vitest-dependent globals\n * (like `performance`) per-test via beforeEach/afterEach hooks.\n * @param options - Optional configuration\n * @param options.config - Path to tailor.config.ts to load SecretManager values into mock\n * @returns Vite plugin\n */\nexport function createEnvironmentPlugin(options?: { config?: string }): Plugin {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const environmentPath = resolve(currentDir, \"environment.mjs\");\n const setupPath = resolve(currentDir, \"setup.mjs\");\n\n return {\n name: \"tailor-runtime-environment\",\n\n config(config) {\n const testConfig = config.test as\n | (Record<string, unknown> & {\n projects?: Record<string, unknown>[];\n setupFiles?: string | string[];\n })\n | undefined;\n\n // Rewrite environment name to absolute path at top-level\n let usesTailorRuntime = false;\n if (testConfig?.environment === ENVIRONMENT_NAME) {\n testConfig.environment = environmentPath;\n usesTailorRuntime = true;\n }\n\n // Rewrite in each project config\n if (testConfig?.projects) {\n for (const project of testConfig.projects) {\n const projectTest = project.test as Record<string, unknown> | undefined;\n if (projectTest?.environment === ENVIRONMENT_NAME) {\n projectTest.environment = environmentPath;\n usesTailorRuntime = true;\n }\n }\n }\n\n // Pass config path to setup.ts via env var (cross-process compatible).\n // Always clear first, then set only when tailor-runtime is actually\n // selected. This makes the env var deterministic across Vite config\n // reloads (watch mode, programmatic re-init): a stale value from a\n // prior iteration cannot make setup.ts load secrets from an old config.\n // The leading `__` marks this as plugin-private, so deleting any\n // pre-existing value is safe.\n delete process.env.__TAILOR_RUNTIME_CONFIG;\n if (options?.config && usesTailorRuntime) {\n // Resolve against the user-provided Vite root when present (falling\n // back to cwd). Vitest projects with a non-cwd `root` would otherwise\n // resolve a relative options.config against the wrong directory.\n const configRoot = (config.root as string | undefined) ?? process.cwd();\n const configAbsPath = resolve(configRoot, options.config);\n process.env.__TAILOR_RUNTIME_CONFIG = configAbsPath;\n }\n\n // Normalize a user-provided string `setupFiles` into an array so Vite's\n // array-concat merge sees both sides as arrays (the string form would\n // otherwise be replaced rather than concatenated by some merge paths).\n // Vite then concatenates the user's array with our [setupPath].\n if (testConfig && typeof testConfig.setupFiles === \"string\") {\n testConfig.setupFiles = [testConfig.setupFiles];\n }\n\n return {\n test: {\n setupFiles: [setupPath],\n },\n };\n },\n };\n}\n","import { createBlockPlugin, createEnvironmentPlugin } from \"./plugin\";\nimport type { Plugin } from \"vitest/config\";\n\n/**\n * Creates Vitest plugins that emulate the Tailor Platform function runtime environment.\n *\n * **Beta:** This API may change in future releases.\n *\n * ## What it does\n *\n * 1. **Node.js module blocking** (transform hook) — Imports of `node:*` modules\n * (and bare builtins like `crypto`, `fs`) in non-test source files are replaced\n * with code that throws an error with a suggestion for the Web Standard API alternative.\n * Test files are exempt and can use `node:*` freely. Test file patterns are read\n * from the resolved Vitest config (`test.include`).\n *\n * 2. **Node.js globals removal** (environment + setup) — Only globals available in the\n * Tailor Platform runtime are kept (whitelist: ECMAScript standard, Web Standard APIs\n * from bootstrap.js, platform mocks). All others (`Buffer`, `global`, `setImmediate`,\n * `__dirname`, `__filename`, etc.) are removed. `performance` is removed per-test\n * via beforeEach/afterEach since Vitest needs it during initialization.\n *\n * 3. **Platform API mocks** (environment) — All platform APIs are auto-injected with\n * control objects: `tailordbMock`, `workflowMock`, `secretmanagerMock`,\n * `authconnectionMock`, `idpMock`, `fileMock`, `iconvMock`. Each provides response\n * configuration, call recording, and reset.\n *\n * 4. **Environment resolution** — Rewrites `environment: \"tailor-runtime\"` to the\n * absolute path of the bundled environment module via the config hook.\n *\n * ## Known limitations\n *\n * - **`process`** and **`require`** are NOT removed or blocked. Vitest's internal\n * runner depends on them. On the real Tailor Platform runtime, they do not exist.\n * - **Dynamic `import()`** of bundled files (via `createImportMain()`) bypasses\n * the transform hook since those files are loaded through Node.js native loader.\n * ## Options\n *\n * - **`config`** — Path to `tailor.config.ts`. Loads `defineSecretManager()` values\n * into `secretmanagerMock` so `getSecret()` returns the configured values.\n * @example\n * ```typescript\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tailorRuntime } from \"@tailor-platform/sdk/vitest\";\n *\n * export default defineConfig({\n * plugins: [tailorRuntime({ config: \"./tailor.config.ts\" })],\n * test: {\n * environment: \"tailor-runtime\",\n * },\n * });\n * ```\n * @param options - Optional configuration\n * @param options.config - Path to tailor.config.ts to load SecretManager values into mock\n * @returns Array of Vite plugins\n */\nexport function tailorRuntime(options?: { config?: string }): Plugin[] {\n return [createBlockPlugin(), createEnvironmentPlugin(options)];\n}\n\nexport {\n tailordbMock,\n workflowMock,\n secretmanagerMock,\n authconnectionMock,\n idpMock,\n fileMock,\n iconvMock,\n} from \"./mock\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest/blocked-modules.ts","../../src/vitest/plugin.ts","../../src/vitest/index.ts"],"sourcesContent":["/**\n * Blocked Node.js built-in modules and their Web Standard API alternatives.\n *\n * The Tailor Platform runtime only provides Web Standard APIs.\n * These Node.js modules are not available and should be replaced with\n * the suggested alternatives.\n */\nimport { builtinModules } from \"node:module\";\n\n// Suggestions keyed by bare specifier. Lookup also checks with \"node:\" prefix stripped.\nconst SUGGESTIONS: Record<string, string> = {\n crypto: \"Use the Web Crypto API (globalThis.crypto) instead.\",\n buffer: \"Use Uint8Array or ArrayBuffer instead.\",\n fs: \"File system access is not available in the Tailor Platform runtime.\",\n \"fs/promises\": \"File system access is not available in the Tailor Platform runtime.\",\n path: \"Use URL or URLPattern for path manipulation.\",\n http: \"Use the Fetch API (globalThis.fetch) for HTTP requests instead.\",\n https: \"Use the Fetch API (globalThis.fetch) for HTTPS requests instead.\",\n url: \"Use the URL and URLSearchParams Web APIs instead.\",\n util: \"Use Web Standard APIs instead.\",\n stream: \"Use Web Streams API (ReadableStream, WritableStream, TransformStream) instead.\",\n \"stream/web\": \"Use Web Streams API (ReadableStream, WritableStream, TransformStream) instead.\",\n events: \"Use EventTarget instead.\",\n zlib: \"Use CompressionStream and DecompressionStream Web APIs instead.\",\n querystring: \"Use URLSearchParams instead.\",\n string_decoder: \"Use TextDecoder instead.\",\n};\n\nconst BLOCKED_MODULES = new Set<string>();\nfor (const mod of builtinModules) {\n BLOCKED_MODULES.add(mod);\n BLOCKED_MODULES.add(`node:${mod}`);\n}\n\n/**\n * Check if a module specifier is a blocked Node.js built-in.\n * @param specifier - Module specifier to check (e.g. \"node:crypto\", \"fs\")\n * @returns Whether the specifier is blocked\n */\nexport function isBlockedModule(specifier: string): boolean {\n return BLOCKED_MODULES.has(specifier);\n}\n\n/**\n * Get the error message for a blocked module import.\n * @param specifier - Module specifier that was blocked\n * @returns Error message with optional suggestion for the Web Standard API alternative\n */\nexport function getBlockedMessage(specifier: string): string {\n const bare = specifier.startsWith(\"node:\") ? specifier.slice(5) : specifier;\n const suggestion = SUGGESTIONS[bare];\n const base = `\"${specifier}\" is not available in the Tailor Platform runtime.`;\n return suggestion ? `${base} ${suggestion}` : base;\n}\n","import { dirname, isAbsolute, matchesGlob, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isBlockedModule, getBlockedMessage } from \"./blocked-modules\";\nimport type { Plugin } from \"vitest/config\";\n\nconst DEFAULT_TEST_INCLUDE = [\"**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}\"];\n\ninterface ExportSpecifierNode {\n type?: string;\n exported?: { name?: unknown } | null;\n}\n\ninterface ImportLikeNode {\n type: string;\n start: number;\n end: number;\n source?: { value?: unknown } | null;\n specifiers?: ExportSpecifierNode[] | null;\n exported?: { name?: unknown } | null;\n}\n\nconst IMPORT_LIKE_TYPES = new Set([\n \"ImportDeclaration\",\n \"ExportNamedDeclaration\",\n \"ExportAllDeclaration\",\n]);\n\n// Re-export specifiers (`export { x as Y } from \"...\"`) accept any\n// `IdentifierName` for `Y` — including reserved words like `delete`. But\n// `export const Y = ...` requires a `BindingIdentifier`, which forbids\n// reserved words and the strict-mode-banned `arguments` / `eval`. Synthesizing\n// `export const delete = ...` would yield a syntax error, so we fall back to\n// plain `throw` for unsafe names.\nconst UNSAFE_BINDING_NAMES = new Set([\n // ReservedWord (ES2022+)\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"enum\",\n \"export\",\n \"extends\",\n \"false\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"null\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"true\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"yield\",\n // Strict-mode reserved (ESM is always strict)\n \"let\",\n \"static\",\n \"implements\",\n \"interface\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n // Module-specific reserved\n \"await\",\n // Banned as binding names in strict mode\n \"arguments\",\n \"eval\",\n]);\n\nconst ID_START = /^[A-Za-z_$]/;\nconst ID_CONT = /^[A-Za-z0-9_$]*$/;\n\nfunction isSafeBindingName(name: string): boolean {\n if (UNSAFE_BINDING_NAMES.has(name)) return false;\n if (name.length === 0) return false;\n // Restrict to ASCII identifiers — Unicode bindings are valid JS but rare\n // for re-exports of node:* modules, and a regex over the full\n // ID_Start/ID_Continue sets adds substantial weight for marginal gain.\n return ID_START.test(name[0] ?? \"\") && ID_CONT.test(name.slice(1));\n}\n\nfunction buildBlockedReplacement(node: ImportLikeNode, message: string): string {\n // JSON.stringify yields a fully-escaped string literal (including the\n // surrounding quotes), so we don't need to manually handle backslashes,\n // newlines, or other control characters that may appear in the message.\n const literal = JSON.stringify(message);\n const throwStmt = `throw new Error(${literal});`;\n const throwExpr = `(() => { throw new Error(${literal}); })()`;\n\n if (node.type === \"ExportNamedDeclaration\") {\n const specs = node.specifiers ?? [];\n const stubs: string[] = [];\n for (const spec of specs) {\n const exportedName = spec.exported?.name;\n if (typeof exportedName !== \"string\") continue;\n if (exportedName === \"default\") {\n stubs.push(`export default ${throwExpr};`);\n continue;\n }\n // Reserved words can be re-export names but not binding names.\n // Bail to a plain throw rather than emit invalid syntax.\n if (!isSafeBindingName(exportedName)) return throwStmt;\n stubs.push(`export const ${exportedName} = ${throwExpr};`);\n }\n return stubs.length > 0 ? stubs.join(\" \") : throwStmt;\n }\n\n if (node.type === \"ExportAllDeclaration\") {\n const exportedName = node.exported?.name;\n if (typeof exportedName === \"string\" && isSafeBindingName(exportedName)) {\n return `export const ${exportedName} = ${throwExpr};`;\n }\n return throwStmt;\n }\n\n return throwStmt;\n}\n\n/**\n * Vite plugin that blocks Node.js built-in module imports from production code.\n *\n * Uses the `transform` hook to walk the Rollup-provided AST of non-test source\n * files for static `node:*` imports and re-exports.\n * `ImportDeclaration` and bare `export * from \"...\"` are replaced with a\n * `throw new Error(...)` statement so the failure surfaces at evaluation time.\n * `ExportNamedDeclaration` (`export { x, y as z } from \"...\"`) and namespaced\n * `export * as ns from \"...\"` are rewritten to per-binding stub exports\n * (`export const x = (() => { throw new Error(...) })();`). The IIFE throws\n * eagerly during module evaluation (same timing as a top-level `throw`), but\n * preserving the declared export bindings ensures the surfaced error is the\n * actual \"node:* not available\" message rather than an opaque\n * \"missing export\" raised by the loader.\n * Vitest treats `node:*` as external SSR modules (skipping `resolveId`), so\n * source-level transformation is the only reliable interception point.\n * Runs in the default phase (no `enforce: \"pre\"`) so esbuild's TypeScript\n * transform strips `import type` first; only runtime imports reach this hook.\n * Node.js globals not in the platform runtime are removed by the environment (whitelist-based).\n * Test file patterns are read from the resolved Vitest config (`test.include`).\n * Vitest setup files (`test.setupFiles`) and global-setup files\n * (`test.globalSetup`) are also exempted: they run in the test runner host,\n * not in the emulated platform runtime, so they may freely use `node:*`\n * modules (e.g. `node:url` for `pathToFileURL`).\n * @returns Vite plugin\n */\nexport function createBlockPlugin(): Plugin {\n let isTestFile: (id: string) => boolean = () => false;\n let isUserSourceFile: (id: string) => boolean = () => false;\n\n return {\n name: \"tailor-runtime-block-node\",\n\n configResolved(config) {\n type HostFileTestConfig = {\n include?: string[];\n setupFiles?: string | string[];\n globalSetup?: string | string[];\n root?: string;\n };\n const testConfig = (\n config as typeof config & {\n test?: HostFileTestConfig & {\n projects?: { test?: HostFileTestConfig }[];\n };\n }\n ).test;\n const root = testConfig?.root ?? config.root;\n // Setup files and global-setup files run in the Vitest host (not the\n // emulated runtime), so they may freely import node:* modules. Collect\n // them from the top-level config AND from each `test.projects[i]` —\n // per-project setup files run in the host too and would otherwise be\n // transformed as production code, breaking node:* imports inside them.\n const toAbsolutePaths = (value: string | string[] | undefined, baseRoot: string) =>\n (Array.isArray(value) ? value : value ? [value] : []).map((f) => resolve(baseRoot, f));\n const exemptHostFiles = new Set<string>([\n ...toAbsolutePaths(testConfig?.setupFiles, root),\n ...toAbsolutePaths(testConfig?.globalSetup, root),\n ]);\n // Vitest projects can each define their own `test.include` (and root).\n // A project that uses non-default patterns (e.g. `tests/**/*.spec.ts`)\n // must also be considered when classifying test files — otherwise its\n // tests would be treated as production code and have node:* imports\n // rewritten. Build a list of (root, patterns) pairs covering top-level\n // + every project, and accept a file if any pair matches.\n const includePairs: { root: string; patterns: string[] }[] = [\n { root, patterns: testConfig?.include ?? DEFAULT_TEST_INCLUDE },\n ];\n for (const project of testConfig?.projects ?? []) {\n const projectTest = project?.test;\n if (!projectTest) continue;\n const projectRoot = projectTest.root ?? root;\n for (const f of toAbsolutePaths(projectTest.setupFiles, projectRoot)) {\n exemptHostFiles.add(f);\n }\n for (const f of toAbsolutePaths(projectTest.globalSetup, projectRoot)) {\n exemptHostFiles.add(f);\n }\n includePairs.push({\n root: projectRoot,\n patterns: projectTest.include ?? DEFAULT_TEST_INCLUDE,\n });\n }\n isTestFile = (id: string) => {\n if (exemptHostFiles.has(id)) return true;\n return includePairs.some(({ root: r, patterns }) => {\n const candidate = isAbsolute(id) ? relative(r, id) : id;\n return patterns.some((pattern) => matchesGlob(candidate, pattern));\n });\n };\n // Only transform files inside the project root. With pnpm workspaces,\n // dependencies are symlinked and Vite resolves them to absolute paths\n // outside `node_modules`, so the substring check alone is insufficient.\n // Non-absolute ids are Vite-internal: virtual modules (`\\0...`,\n // `virtual:...`), bare specifiers, etc. Those are never user source\n // files and must not be parsed/transformed.\n isUserSourceFile = (id: string) => {\n if (!isAbsolute(id)) return false;\n const rel = relative(root, id);\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n };\n },\n\n transform(code, id) {\n // Vite can pass ids with query/hash suffixes (e.g. `file.ts?import`,\n // `file.ts?v=hash`). Strip them so exact-path lookups (Set membership,\n // glob matching, absolute-path checks) match what callers configured.\n const queryIdx = id.search(/[?#]/);\n const cleanId = queryIdx === -1 ? id : id.slice(0, queryIdx);\n\n if (isTestFile(cleanId)) return undefined;\n if (cleanId.includes(\"node_modules\")) return undefined;\n if (!isUserSourceFile(cleanId)) return undefined;\n\n let ast: { body: ImportLikeNode[] };\n try {\n ast = this.parse(code) as unknown as { body: ImportLikeNode[] };\n } catch {\n // Not parseable as ESM (e.g. JSON, asset). Let other plugins handle it.\n return undefined;\n }\n\n const replacements: { start: number; end: number; replacement: string }[] = [];\n for (const node of ast.body) {\n if (!IMPORT_LIKE_TYPES.has(node.type)) continue;\n const specifier = node.source?.value;\n if (typeof specifier !== \"string\") continue;\n if (isBlockedModule(specifier)) {\n replacements.push({\n start: node.start,\n end: node.end,\n replacement: buildBlockedReplacement(node, getBlockedMessage(specifier)),\n });\n }\n }\n\n if (replacements.length === 0) return undefined;\n\n let transformed = code;\n for (const r of replacements.sort((a, b) => b.start - a.start)) {\n transformed = transformed.slice(0, r.start) + r.replacement + transformed.slice(r.end);\n }\n\n return { code: transformed, map: null };\n },\n };\n}\n\nconst ENVIRONMENT_NAME = \"tailor-runtime\";\n\n/**\n * Vite plugin that resolves the tailor-runtime environment and injects setup files.\n *\n * Vitest resolves environments starting with \".\" or \"/\" as file paths.\n * This plugin rewrites `environment: \"tailor-runtime\"` to the absolute path\n * of the bundled environment module, both at the top-level and per-project.\n * It also injects the setup file that removes Vitest-dependent globals\n * (like `performance`) per-test via beforeEach/afterEach hooks.\n * @param options - Optional configuration\n * @param options.config - Path to tailor.config.ts to load SecretManager values into mock\n * @returns Vite plugin\n */\nexport function createEnvironmentPlugin(options?: { config?: string }): Plugin {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const environmentPath = resolve(currentDir, \"environment.mjs\");\n const setupPath = resolve(currentDir, \"setup.mjs\");\n\n return {\n name: \"tailor-runtime-environment\",\n\n config(config) {\n const testConfig = config.test as\n | (Record<string, unknown> & {\n projects?: Record<string, unknown>[];\n setupFiles?: string | string[];\n })\n | undefined;\n\n // Rewrite environment name to absolute path at top-level\n let usesTailorRuntime = false;\n if (testConfig?.environment === ENVIRONMENT_NAME) {\n testConfig.environment = environmentPath;\n usesTailorRuntime = true;\n }\n\n // Rewrite in each project config\n if (testConfig?.projects) {\n for (const project of testConfig.projects) {\n const projectTest = project.test as Record<string, unknown> | undefined;\n if (projectTest?.environment === ENVIRONMENT_NAME) {\n projectTest.environment = environmentPath;\n usesTailorRuntime = true;\n }\n }\n }\n\n // Pass config path to setup.ts via env var (cross-process compatible).\n // Always clear first, then set only when tailor-runtime is actually\n // selected. This makes the env var deterministic across Vite config\n // reloads (watch mode, programmatic re-init): a stale value from a\n // prior iteration cannot make setup.ts load secrets from an old config.\n // The leading `__` marks this as plugin-private, so deleting any\n // pre-existing value is safe.\n delete process.env.__TAILOR_RUNTIME_CONFIG;\n if (options?.config && usesTailorRuntime) {\n // Resolve against the user-provided Vite root when present (falling\n // back to cwd). Vitest projects with a non-cwd `root` would otherwise\n // resolve a relative options.config against the wrong directory.\n const configRoot = (config.root as string | undefined) ?? process.cwd();\n const configAbsPath = resolve(configRoot, options.config);\n process.env.__TAILOR_RUNTIME_CONFIG = configAbsPath;\n }\n\n // Normalize a user-provided string `setupFiles` into an array so Vite's\n // array-concat merge sees both sides as arrays (the string form would\n // otherwise be replaced rather than concatenated by some merge paths).\n // Vite then concatenates the user's array with our [setupPath].\n if (testConfig && typeof testConfig.setupFiles === \"string\") {\n testConfig.setupFiles = [testConfig.setupFiles];\n }\n\n return {\n test: {\n setupFiles: [setupPath],\n },\n };\n },\n };\n}\n","import { createBlockPlugin, createEnvironmentPlugin } from \"./plugin\";\nimport type { Plugin } from \"vitest/config\";\n\n/**\n * Creates Vitest plugins that emulate the Tailor Platform function runtime environment.\n *\n * **Beta:** This API may change in future releases.\n *\n * ## What it does\n *\n * 1. **Node.js module blocking** (transform hook) — Imports of `node:*` modules\n * (and bare builtins like `crypto`, `fs`) in non-test source files are replaced\n * with code that throws an error with a suggestion for the Web Standard API alternative.\n * Test files are exempt and can use `node:*` freely. Test file patterns are read\n * from the resolved Vitest config (`test.include`).\n *\n * 2. **Node.js globals removal** (environment + setup) — Only globals available in the\n * Tailor Platform runtime are kept (whitelist: ECMAScript standard, Web Standard APIs\n * from bootstrap.js, platform mocks). All others (`Buffer`, `global`, `setImmediate`,\n * `__dirname`, `__filename`, etc.) are removed. `performance` is removed per-test\n * via beforeEach/afterEach since Vitest needs it during initialization.\n *\n * 3. **Platform API mocks** (environment) — All platform APIs are auto-injected with\n * control objects: `tailordbMock`, `workflowMock`, `secretmanagerMock`,\n * `authconnectionMock`, `idpMock`, `fileMock`, `iconvMock`. Each provides response\n * configuration, call recording, and reset.\n *\n * 4. **Environment resolution** — Rewrites `environment: \"tailor-runtime\"` to the\n * absolute path of the bundled environment module via the config hook.\n *\n * ## Known limitations\n *\n * - **`process`** and **`require`** are NOT removed or blocked. Vitest's internal\n * runner depends on them. On the real Tailor Platform runtime, they do not exist.\n * - **Dynamic `import()`** of bundled files (via `createImportMain()`) bypasses\n * the transform hook since those files are loaded through Node.js native loader.\n * ## Options\n *\n * - **`config`** — Path to `tailor.config.ts`. Loads `defineSecretManager()` values\n * into `secretmanagerMock` so `getSecret()` returns the configured values.\n * @example\n * ```typescript\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tailorRuntime } from \"@tailor-platform/sdk/vitest\";\n *\n * export default defineConfig({\n * plugins: [tailorRuntime({ config: \"./tailor.config.ts\" })],\n * test: {\n * environment: \"tailor-runtime\",\n * },\n * });\n * ```\n * @param options - Optional configuration\n * @param options.config - Path to tailor.config.ts to load SecretManager values into mock\n * @returns Array of Vite plugins\n */\nexport function tailorRuntime(options?: { config?: string }): Plugin[] {\n return [createBlockPlugin(), createEnvironmentPlugin(options)];\n}\n\nexport {\n tailordbMock,\n workflowMock,\n secretmanagerMock,\n authconnectionMock,\n idpMock,\n fileMock,\n iconvMock,\n} from \"./mock\";\n"],"mappings":";;;;;;;;;;;;;;AAUA,MAAM,cAAsC;CAC1C,QAAQ;CACR,QAAQ;CACR,IAAI;CACJ,eAAe;CACf,MAAM;CACN,MAAM;CACN,OAAO;CACP,KAAK;CACL,MAAM;CACN,QAAQ;CACR,cAAc;CACd,QAAQ;CACR,MAAM;CACN,aAAa;CACb,gBAAgB;AAClB;AAEA,MAAM,kCAAkB,IAAI,IAAY;AACxC,KAAK,MAAM,OAAO,gBAAgB;CAChC,gBAAgB,IAAI,GAAG;CACvB,gBAAgB,IAAI,QAAQ,KAAK;AACnC;;;;;;AAOA,SAAgB,gBAAgB,WAA4B;CAC1D,OAAO,gBAAgB,IAAI,SAAS;AACtC;;;;;;AAOA,SAAgB,kBAAkB,WAA2B;CAE3D,MAAM,aAAa,YADN,UAAU,WAAW,OAAO,IAAI,UAAU,MAAM,CAAC,IAAI;CAElE,MAAM,OAAO,IAAI,UAAU;CAC3B,OAAO,aAAa,GAAG,KAAK,GAAG,eAAe;AAChD;;;;AChDA,MAAM,uBAAuB,CAAC,kDAAkD;AAgBhF,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;AACF,CAAC;AAQD,MAAM,uBAAuB,IAAI,IAAI;CAEnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CAEA;CACA;AACF,CAAC;AAED,MAAM,WAAW;AACjB,MAAM,UAAU;AAEhB,SAAS,kBAAkB,MAAuB;CAChD,IAAI,qBAAqB,IAAI,IAAI,GAAG,OAAO;CAC3C,IAAI,KAAK,WAAW,GAAG,OAAO;CAI9B,OAAO,SAAS,KAAK,KAAK,MAAM,EAAE,KAAK,QAAQ,KAAK,KAAK,MAAM,CAAC,CAAC;AACnE;AAEA,SAAS,wBAAwB,MAAsB,SAAyB;CAI9E,MAAM,UAAU,KAAK,UAAU,OAAO;CACtC,MAAM,YAAY,mBAAmB,QAAQ;CAC7C,MAAM,YAAY,4BAA4B,QAAQ;CAEtD,IAAI,KAAK,SAAS,0BAA0B;EAC1C,MAAM,QAAQ,KAAK,cAAc,CAAC;EAClC,MAAM,QAAkB,CAAC;EACzB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,eAAe,KAAK,UAAU;GACpC,IAAI,OAAO,iBAAiB,UAAU;GACtC,IAAI,iBAAiB,WAAW;IAC9B,MAAM,KAAK,kBAAkB,UAAU,EAAE;IACzC;GACF;GAGA,IAAI,CAAC,kBAAkB,YAAY,GAAG,OAAO;GAC7C,MAAM,KAAK,gBAAgB,aAAa,KAAK,UAAU,EAAE;EAC3D;EACA,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;CAC9C;CAEA,IAAI,KAAK,SAAS,wBAAwB;EACxC,MAAM,eAAe,KAAK,UAAU;EACpC,IAAI,OAAO,iBAAiB,YAAY,kBAAkB,YAAY,GACpE,OAAO,gBAAgB,aAAa,KAAK,UAAU;EAErD,OAAO;CACT;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,oBAA4B;CAC1C,IAAI,mBAA4C;CAChD,IAAI,yBAAkD;CAEtD,OAAO;EACL,MAAM;EAEN,eAAe,QAAQ;GAOrB,MAAM,aACJ,OAKA;GACF,MAAM,OAAO,YAAY,QAAQ,OAAO;GAMxC,MAAM,mBAAmB,OAAsC,cAC5D,MAAM,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,KAAK,IAAI,CAAC,GAAG,KAAK,MAAM,QAAQ,UAAU,CAAC,CAAC;GACvF,MAAM,kBAAkB,IAAI,IAAY,CACtC,GAAG,gBAAgB,YAAY,YAAY,IAAI,GAC/C,GAAG,gBAAgB,YAAY,aAAa,IAAI,CAClD,CAAC;GAOD,MAAM,eAAuD,CAC3D;IAAE;IAAM,UAAU,YAAY,WAAW;GAAqB,CAChE;GACA,KAAK,MAAM,WAAW,YAAY,YAAY,CAAC,GAAG;IAChD,MAAM,cAAc,SAAS;IAC7B,IAAI,CAAC,aAAa;IAClB,MAAM,cAAc,YAAY,QAAQ;IACxC,KAAK,MAAM,KAAK,gBAAgB,YAAY,YAAY,WAAW,GACjE,gBAAgB,IAAI,CAAC;IAEvB,KAAK,MAAM,KAAK,gBAAgB,YAAY,aAAa,WAAW,GAClE,gBAAgB,IAAI,CAAC;IAEvB,aAAa,KAAK;KAChB,MAAM;KACN,UAAU,YAAY,WAAW;IACnC,CAAC;GACH;GACA,cAAc,OAAe;IAC3B,IAAI,gBAAgB,IAAI,EAAE,GAAG,OAAO;IACpC,OAAO,aAAa,MAAM,EAAE,MAAM,GAAG,eAAe;KAClD,MAAM,YAAY,WAAW,EAAE,IAAI,SAAS,GAAG,EAAE,IAAI;KACrD,OAAO,SAAS,MAAM,YAAY,YAAY,WAAW,OAAO,CAAC;IACnE,CAAC;GACH;GAOA,oBAAoB,OAAe;IACjC,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO;IAC5B,MAAM,MAAM,SAAS,MAAM,EAAE;IAC7B,OAAO,QAAQ,MAAM,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG;GAC/D;EACF;EAEA,UAAU,MAAM,IAAI;GAIlB,MAAM,WAAW,GAAG,OAAO,MAAM;GACjC,MAAM,UAAU,aAAa,KAAK,KAAK,GAAG,MAAM,GAAG,QAAQ;GAE3D,IAAI,WAAW,OAAO,GAAG,OAAO;GAChC,IAAI,QAAQ,SAAS,cAAc,GAAG,OAAO;GAC7C,IAAI,CAAC,iBAAiB,OAAO,GAAG,OAAO;GAEvC,IAAI;GACJ,IAAI;IACF,MAAM,KAAK,MAAM,IAAI;GACvB,QAAQ;IAEN;GACF;GAEA,MAAM,eAAsE,CAAC;GAC7E,KAAK,MAAM,QAAQ,IAAI,MAAM;IAC3B,IAAI,CAAC,kBAAkB,IAAI,KAAK,IAAI,GAAG;IACvC,MAAM,YAAY,KAAK,QAAQ;IAC/B,IAAI,OAAO,cAAc,UAAU;IACnC,IAAI,gBAAgB,SAAS,GAC3B,aAAa,KAAK;KAChB,OAAO,KAAK;KACZ,KAAK,KAAK;KACV,aAAa,wBAAwB,MAAM,kBAAkB,SAAS,CAAC;IACzE,CAAC;GAEL;GAEA,IAAI,aAAa,WAAW,GAAG,OAAO;GAEtC,IAAI,cAAc;GAClB,KAAK,MAAM,KAAK,aAAa,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,GAC3D,cAAc,YAAY,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,cAAc,YAAY,MAAM,EAAE,GAAG;GAGvF,OAAO;IAAE,MAAM;IAAa,KAAK;GAAK;EACxC;CACF;AACF;AAEA,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,SAAgB,wBAAwB,SAAuC;CAC7E,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;CACzD,MAAM,kBAAkB,QAAQ,YAAY,iBAAiB;CAC7D,MAAM,YAAY,QAAQ,YAAY,WAAW;CAEjD,OAAO;EACL,MAAM;EAEN,OAAO,QAAQ;GACb,MAAM,aAAa,OAAO;GAQ1B,IAAI,oBAAoB;GACxB,IAAI,YAAY,gBAAgB,kBAAkB;IAChD,WAAW,cAAc;IACzB,oBAAoB;GACtB;GAGA,IAAI,YAAY,UACd,KAAK,MAAM,WAAW,WAAW,UAAU;IACzC,MAAM,cAAc,QAAQ;IAC5B,IAAI,aAAa,gBAAgB,kBAAkB;KACjD,YAAY,cAAc;KAC1B,oBAAoB;IACtB;GACF;GAUF,OAAO,QAAQ,IAAI;GACnB,IAAI,SAAS,UAAU,mBAAmB;IAKxC,MAAM,gBAAgB,QADF,OAAO,QAA+B,QAAQ,IAAI,GAC5B,QAAQ,MAAM;IACxD,QAAQ,IAAI,0BAA0B;GACxC;GAMA,IAAI,cAAc,OAAO,WAAW,eAAe,UACjD,WAAW,aAAa,CAAC,WAAW,UAAU;GAGhD,OAAO,EACL,MAAM,EACJ,YAAY,CAAC,SAAS,EACxB,EACF;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpTA,SAAgB,cAAc,SAAyC;CACrE,OAAO,CAAC,kBAAkB,GAAG,wBAAwB,OAAO,CAAC;AAC/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/vitest/setup.ts"],"sourcesContent":["/**\n * Vitest setup file that removes Node.js globals which Vitest depends on\n * but are not available in the Tailor Platform runtime.\n *\n * These globals cannot be removed in the environment's setup() because\n * Vitest's runner needs them during initialization. By using beforeEach/afterEach,\n * they are only removed during user test code execution.\n *\n * This file is auto-injected by tailorRuntime() but only activates when\n * the tailor-runtime environment is active (detected via __tailorRuntimeActive,\n * a flag set by injectMocks() during environment setup).\n */\nimport { pathToFileURL } from \"node:url\";\nimport { afterEach, beforeAll, beforeEach } from \"vitest\";\nimport { RUNTIME_FLAG_KEY, secretmanagerMock } from \"./mock\";\n\n// Globals that Vitest internals depend on but don't exist in the platform runtime.\n// Removed before each test, restored after.\nconst BLOCKED_GLOBALS = [\"performance\"] as const;\n\ntype SavedGlobals = Record<string, PropertyDescriptor | undefined>;\n\n/**\n * Reference-counted lifecycle for blocked globals.\n *\n * Concurrent tests in the same Vitest worker (`test.concurrent`) interleave\n * their beforeEach/afterEach hooks: a naive `let saved` shared across hooks\n * would let one test's `afterEach` restore `performance` mid-execution of\n * another test. Reference counting keeps the globals removed for the union\n * of all overlapping test scopes — the property is removed on first entry\n * and restored only when the last test exits.\n * @returns Lifecycle hooks\n */\nexport function createBlockedGlobalsLifecycle(): {\n enter: (globalObj: Record<string, unknown>, keys: readonly string[]) => void;\n exit: (globalObj: Record<string, unknown>) => void;\n readonly active: number;\n} {\n let active = 0;\n let saved: SavedGlobals = {};\n return {\n get active() {\n return active;\n },\n enter(globalObj, keys) {\n if (active === 0) saved = removeBlockedGlobals(globalObj, keys);\n active++;\n },\n exit(globalObj) {\n // Defensive guard — in practice every `enter` is paired with `exit`,\n // but a hook crash could desync the counter.\n if (active === 0) return;\n active--;\n if (active === 0) {\n restoreBlockedGlobals(globalObj, saved);\n saved = {};\n }\n },\n };\n}\n\nconst lifecycle = createBlockedGlobalsLifecycle();\n\nfunction isTailorRuntime(): boolean {\n return RUNTIME_FLAG_KEY in globalThis;\n}\n\n/**\n * Extract a vault store from a secrets-shaped value.\n *\n * `defineSecretManager()` returns `{ vaults, options, get, getAll }` (get/getAll\n * are non-enumerable). When that shape is present, the actual vaults live\n * under `.vaults`. Otherwise fall back to treating the object itself as the\n * vault map (for plain object configs).\n * @param secrets - Value from `appConfig.secrets` or `config.secrets`\n * @returns Vault store, or null if the value is unusable\n */\nexport function extractVaultStore(secrets: unknown): Record<string, Record<string, string>> | null {\n if (!secrets || typeof secrets !== \"object\") return null;\n\n const source =\n \"vaults\" in secrets &&\n typeof (secrets as { vaults?: unknown }).vaults === \"object\" &&\n (secrets as { vaults?: unknown }).vaults !== null\n ? ((secrets as { vaults: Record<string, unknown> }).vaults as Record<string, unknown>)\n : (secrets as Record<string, unknown>);\n\n const store: Record<string, Record<string, string>> = {};\n for (const [vaultName, vaultData] of Object.entries(source)) {\n if (typeof vaultData === \"object\" && vaultData !== null) {\n store[vaultName] = { ...(vaultData as Record<string, string>) };\n }\n }\n return Object.keys(store).length > 0 ? store : null;\n}\n\n/**\n * Load and parse secrets from a tailor.config.ts file.\n *\n * Returns a vault store on success, or `null` on any failure (missing config,\n * import failure, missing/invalid secrets shape). Errors are swallowed so a\n * misconfigured project still boots — the user can set secrets manually via\n * `secretmanagerMock.setSecrets()`.\n * @param configPath - Absolute path to tailor.config.ts\n * @returns Vault store keyed by vault name, or null if unavailable\n */\nexport async function loadSecretsFromConfig(\n configPath: string,\n): Promise<Record<string, Record<string, string>> | null> {\n try {\n // Convert to file URL so absolute Windows paths (e.g. \"C:\\...\") parse as\n // valid ESM specifiers.\n const config = await import(pathToFileURL(configPath).href);\n const appConfig = config.default;\n const secrets = appConfig?.secrets ?? config.secrets;\n return extractVaultStore(secrets);\n } catch {\n return null;\n }\n}\n\n// Load secrets from tailor.config.ts if config path is provided via env var\nbeforeAll(async () => {\n if (!isTailorRuntime()) return;\n const configPath = process.env.__TAILOR_RUNTIME_CONFIG;\n if (!configPath) return;\n\n const store = await loadSecretsFromConfig(configPath);\n if (store) {\n secretmanagerMock.setSecrets(store);\n }\n});\n\n/**\n * Remove the given globals from `globalObj`, returning the descriptors that\n * were actually deleted so `restoreBlockedGlobals` can put them back.\n *\n * Mirrors environment.ts: non-configurable properties are skipped instead of\n * deleted, so `delete` never throws in strict-mode runtimes that lock them\n * down. Only properties whose deletion actually happened are returned, so the\n * caller restores nothing for the skipped case.\n * @param globalObj - Target object (typically `globalThis`)\n * @param keys - Property names to remove\n * @returns Map of removed descriptors keyed by property name\n */\nexport function removeBlockedGlobals(\n globalObj: Record<string, unknown>,\n keys: readonly string[],\n): SavedGlobals {\n const removed: SavedGlobals = {};\n for (const key of keys) {\n const descriptor = Object.getOwnPropertyDescriptor(globalObj, key);\n if (descriptor?.configurable) {\n removed[key] = descriptor;\n delete globalObj[key];\n }\n }\n return removed;\n}\n\n/**\n * Restore previously-removed globals onto `globalObj` from a `SavedGlobals`\n * map produced by `removeBlockedGlobals`.\n * @param globalObj - Target object (typically `globalThis`)\n * @param saved - Descriptors to re-define\n */\nexport function restoreBlockedGlobals(\n globalObj: Record<string, unknown>,\n saved: SavedGlobals,\n): void {\n for (const [key, descriptor] of Object.entries(saved)) {\n if (descriptor) {\n Object.defineProperty(globalObj, key, descriptor);\n }\n }\n}\n\nbeforeEach(() => {\n if (!isTailorRuntime()) return;\n lifecycle.enter(globalThis as Record<string, unknown>, BLOCKED_GLOBALS);\n});\n\nafterEach(() => {\n if (!isTailorRuntime()) return;\n lifecycle.exit(globalThis as Record<string, unknown>);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAM,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/vitest/setup.ts"],"sourcesContent":["/**\n * Vitest setup file that removes Node.js globals which Vitest depends on\n * but are not available in the Tailor Platform runtime.\n *\n * These globals cannot be removed in the environment's setup() because\n * Vitest's runner needs them during initialization. By using beforeEach/afterEach,\n * they are only removed during user test code execution.\n *\n * This file is auto-injected by tailorRuntime() but only activates when\n * the tailor-runtime environment is active (detected via __tailorRuntimeActive,\n * a flag set by injectMocks() during environment setup).\n */\nimport { pathToFileURL } from \"node:url\";\nimport { afterEach, beforeAll, beforeEach } from \"vitest\";\nimport { RUNTIME_FLAG_KEY, secretmanagerMock } from \"./mock\";\n\n// Globals that Vitest internals depend on but don't exist in the platform runtime.\n// Removed before each test, restored after.\nconst BLOCKED_GLOBALS = [\"performance\"] as const;\n\ntype SavedGlobals = Record<string, PropertyDescriptor | undefined>;\n\n/**\n * Reference-counted lifecycle for blocked globals.\n *\n * Concurrent tests in the same Vitest worker (`test.concurrent`) interleave\n * their beforeEach/afterEach hooks: a naive `let saved` shared across hooks\n * would let one test's `afterEach` restore `performance` mid-execution of\n * another test. Reference counting keeps the globals removed for the union\n * of all overlapping test scopes — the property is removed on first entry\n * and restored only when the last test exits.\n * @returns Lifecycle hooks\n */\nexport function createBlockedGlobalsLifecycle(): {\n enter: (globalObj: Record<string, unknown>, keys: readonly string[]) => void;\n exit: (globalObj: Record<string, unknown>) => void;\n readonly active: number;\n} {\n let active = 0;\n let saved: SavedGlobals = {};\n return {\n get active() {\n return active;\n },\n enter(globalObj, keys) {\n if (active === 0) saved = removeBlockedGlobals(globalObj, keys);\n active++;\n },\n exit(globalObj) {\n // Defensive guard — in practice every `enter` is paired with `exit`,\n // but a hook crash could desync the counter.\n if (active === 0) return;\n active--;\n if (active === 0) {\n restoreBlockedGlobals(globalObj, saved);\n saved = {};\n }\n },\n };\n}\n\nconst lifecycle = createBlockedGlobalsLifecycle();\n\nfunction isTailorRuntime(): boolean {\n return RUNTIME_FLAG_KEY in globalThis;\n}\n\n/**\n * Extract a vault store from a secrets-shaped value.\n *\n * `defineSecretManager()` returns `{ vaults, options, get, getAll }` (get/getAll\n * are non-enumerable). When that shape is present, the actual vaults live\n * under `.vaults`. Otherwise fall back to treating the object itself as the\n * vault map (for plain object configs).\n * @param secrets - Value from `appConfig.secrets` or `config.secrets`\n * @returns Vault store, or null if the value is unusable\n */\nexport function extractVaultStore(secrets: unknown): Record<string, Record<string, string>> | null {\n if (!secrets || typeof secrets !== \"object\") return null;\n\n const source =\n \"vaults\" in secrets &&\n typeof (secrets as { vaults?: unknown }).vaults === \"object\" &&\n (secrets as { vaults?: unknown }).vaults !== null\n ? ((secrets as { vaults: Record<string, unknown> }).vaults as Record<string, unknown>)\n : (secrets as Record<string, unknown>);\n\n const store: Record<string, Record<string, string>> = {};\n for (const [vaultName, vaultData] of Object.entries(source)) {\n if (typeof vaultData === \"object\" && vaultData !== null) {\n store[vaultName] = { ...(vaultData as Record<string, string>) };\n }\n }\n return Object.keys(store).length > 0 ? store : null;\n}\n\n/**\n * Load and parse secrets from a tailor.config.ts file.\n *\n * Returns a vault store on success, or `null` on any failure (missing config,\n * import failure, missing/invalid secrets shape). Errors are swallowed so a\n * misconfigured project still boots — the user can set secrets manually via\n * `secretmanagerMock.setSecrets()`.\n * @param configPath - Absolute path to tailor.config.ts\n * @returns Vault store keyed by vault name, or null if unavailable\n */\nexport async function loadSecretsFromConfig(\n configPath: string,\n): Promise<Record<string, Record<string, string>> | null> {\n try {\n // Convert to file URL so absolute Windows paths (e.g. \"C:\\...\") parse as\n // valid ESM specifiers.\n const config = await import(pathToFileURL(configPath).href);\n const appConfig = config.default;\n const secrets = appConfig?.secrets ?? config.secrets;\n return extractVaultStore(secrets);\n } catch {\n return null;\n }\n}\n\n// Load secrets from tailor.config.ts if config path is provided via env var\nbeforeAll(async () => {\n if (!isTailorRuntime()) return;\n const configPath = process.env.__TAILOR_RUNTIME_CONFIG;\n if (!configPath) return;\n\n const store = await loadSecretsFromConfig(configPath);\n if (store) {\n secretmanagerMock.setSecrets(store);\n }\n});\n\n/**\n * Remove the given globals from `globalObj`, returning the descriptors that\n * were actually deleted so `restoreBlockedGlobals` can put them back.\n *\n * Mirrors environment.ts: non-configurable properties are skipped instead of\n * deleted, so `delete` never throws in strict-mode runtimes that lock them\n * down. Only properties whose deletion actually happened are returned, so the\n * caller restores nothing for the skipped case.\n * @param globalObj - Target object (typically `globalThis`)\n * @param keys - Property names to remove\n * @returns Map of removed descriptors keyed by property name\n */\nexport function removeBlockedGlobals(\n globalObj: Record<string, unknown>,\n keys: readonly string[],\n): SavedGlobals {\n const removed: SavedGlobals = {};\n for (const key of keys) {\n const descriptor = Object.getOwnPropertyDescriptor(globalObj, key);\n if (descriptor?.configurable) {\n removed[key] = descriptor;\n delete globalObj[key];\n }\n }\n return removed;\n}\n\n/**\n * Restore previously-removed globals onto `globalObj` from a `SavedGlobals`\n * map produced by `removeBlockedGlobals`.\n * @param globalObj - Target object (typically `globalThis`)\n * @param saved - Descriptors to re-define\n */\nexport function restoreBlockedGlobals(\n globalObj: Record<string, unknown>,\n saved: SavedGlobals,\n): void {\n for (const [key, descriptor] of Object.entries(saved)) {\n if (descriptor) {\n Object.defineProperty(globalObj, key, descriptor);\n }\n }\n}\n\nbeforeEach(() => {\n if (!isTailorRuntime()) return;\n lifecycle.enter(globalThis as Record<string, unknown>, BLOCKED_GLOBALS);\n});\n\nafterEach(() => {\n if (!isTailorRuntime()) return;\n lifecycle.exit(globalThis as Record<string, unknown>);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAM,kBAAkB,CAAC,aAAa;;;;;;;;;;;;AAetC,SAAgB,gCAId;CACA,IAAI,SAAS;CACb,IAAI,QAAsB,CAAC;CAC3B,OAAO;EACL,IAAI,SAAS;GACX,OAAO;EACT;EACA,MAAM,WAAW,MAAM;GACrB,IAAI,WAAW,GAAG,QAAQ,qBAAqB,WAAW,IAAI;GAC9D;EACF;EACA,KAAK,WAAW;GAGd,IAAI,WAAW,GAAG;GAClB;GACA,IAAI,WAAW,GAAG;IAChB,sBAAsB,WAAW,KAAK;IACtC,QAAQ,CAAC;GACX;EACF;CACF;AACF;AAEA,MAAM,YAAY,8BAA8B;AAEhD,SAAS,kBAA2B;CAClC,OAAO,oBAAoB;AAC7B;;;;;;;;;;;AAYA,SAAgB,kBAAkB,SAAiE;CACjG,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,OAAO;CAEpD,MAAM,SACJ,YAAY,WACZ,OAAQ,QAAiC,WAAW,YACnD,QAAiC,WAAW,OACvC,QAAgD,SACjD;CAEP,MAAM,QAAgD,CAAC;CACvD,KAAK,MAAM,CAAC,WAAW,cAAc,OAAO,QAAQ,MAAM,GACxD,IAAI,OAAO,cAAc,YAAY,cAAc,MACjD,MAAM,aAAa,EAAE,GAAI,UAAqC;CAGlE,OAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;;;;;;;;;;;AAYA,eAAsB,sBACpB,YACwD;CACxD,IAAI;EAGF,MAAM,SAAS,MAAM,OAAO,cAAc,UAAU,EAAE;EAGtD,OAAO,kBAFW,OAAO,SACE,WAAW,OAAO,OACb;CAClC,QAAQ;EACN,OAAO;CACT;AACF;AAGA,UAAU,YAAY;CACpB,IAAI,CAAC,gBAAgB,GAAG;CACxB,MAAM,aAAa,QAAQ,IAAI;CAC/B,IAAI,CAAC,YAAY;CAEjB,MAAM,QAAQ,MAAM,sBAAsB,UAAU;CACpD,IAAI,OACF,kBAAkB,WAAW,KAAK;AAEtC,CAAC;;;;;;;;;;;;;AAcD,SAAgB,qBACd,WACA,MACc;CACd,MAAM,UAAwB,CAAC;CAC/B,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,aAAa,OAAO,yBAAyB,WAAW,GAAG;EACjE,IAAI,YAAY,cAAc;GAC5B,QAAQ,OAAO;GACf,OAAO,UAAU;EACnB;CACF;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,sBACd,WACA,OACM;CACN,KAAK,MAAM,CAAC,KAAK,eAAe,OAAO,QAAQ,KAAK,GAClD,IAAI,YACF,OAAO,eAAe,WAAW,KAAK,UAAU;AAGtD;AAEA,iBAAiB;CACf,IAAI,CAAC,gBAAgB,GAAG;CACxB,UAAU,MAAM,YAAuC,eAAe;AACxE,CAAC;AAED,gBAAgB;CACd,IAAI,CAAC,gBAAgB,GAAG;CACxB,UAAU,KAAK,UAAqC;AACtD,CAAC"}
|
package/docs/cli/tailordb.md
CHANGED
|
@@ -154,6 +154,7 @@ tailor-sdk tailordb migration [command]
|
|
|
154
154
|
| Command | Description |
|
|
155
155
|
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
156
156
|
| [`tailordb migration generate`](#tailordb-migration-generate) | Generate migration files by detecting schema differences between current local types and the previous migration snapshot. |
|
|
157
|
+
| [`tailordb migration script`](#tailordb-migration-script) | Add a migration script (migrate.ts) template to an existing migration directory. |
|
|
157
158
|
| [`tailordb migration set`](#tailordb-migration-set) | Set migration checkpoint to a specific number. |
|
|
158
159
|
| [`tailordb migration status`](#tailordb-migration-status) | Show the current migration status for TailorDB namespaces, including applied and pending migrations. |
|
|
159
160
|
|
|
@@ -204,6 +205,55 @@ tailor-sdk tailordb migration generate [options]
|
|
|
204
205
|
See [Global Options](../cli-reference.md#global-options) for options available to all commands.
|
|
205
206
|
|
|
206
207
|
<!-- politty:command:tailordb migration generate:global-options-link:end -->
|
|
208
|
+
<!-- politty:command:tailordb migration script:heading:start -->
|
|
209
|
+
|
|
210
|
+
#### tailordb migration script
|
|
211
|
+
|
|
212
|
+
<!-- politty:command:tailordb migration script:heading:end -->
|
|
213
|
+
|
|
214
|
+
<!-- politty:command:tailordb migration script:description:start -->
|
|
215
|
+
|
|
216
|
+
Add a migration script (migrate.ts) template to an existing migration directory.
|
|
217
|
+
|
|
218
|
+
<!-- politty:command:tailordb migration script:description:end -->
|
|
219
|
+
|
|
220
|
+
<!-- politty:command:tailordb migration script:usage:start -->
|
|
221
|
+
|
|
222
|
+
**Usage**
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
tailor-sdk tailordb migration script [options] <number>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
<!-- politty:command:tailordb migration script:usage:end -->
|
|
229
|
+
|
|
230
|
+
<!-- politty:command:tailordb migration script:arguments:start -->
|
|
231
|
+
|
|
232
|
+
**Arguments**
|
|
233
|
+
|
|
234
|
+
| Argument | Description | Required |
|
|
235
|
+
| -------- | ----------------------------------------------------- | -------- |
|
|
236
|
+
| `number` | Migration number to add a script to (e.g., 0001 or 1) | Yes |
|
|
237
|
+
|
|
238
|
+
<!-- politty:command:tailordb migration script:arguments:end -->
|
|
239
|
+
|
|
240
|
+
<!-- politty:command:tailordb migration script:options:start -->
|
|
241
|
+
|
|
242
|
+
**Options**
|
|
243
|
+
|
|
244
|
+
| Option | Alias | Description | Required | Default | Env |
|
|
245
|
+
| ------------------------- | ----- | ----------------------------------------------------------------- | -------- | -------------------- | --------------------------------- |
|
|
246
|
+
| `--config <CONFIG>` | `-c` | Path to SDK config file | No | `"tailor.config.ts"` | `TAILOR_PLATFORM_SDK_CONFIG_PATH` |
|
|
247
|
+
| `--namespace <NAMESPACE>` | `-n` | Target TailorDB namespace (required if multiple namespaces exist) | No | - | - |
|
|
248
|
+
|
|
249
|
+
<!-- politty:command:tailordb migration script:options:end -->
|
|
250
|
+
|
|
251
|
+
<!-- politty:command:tailordb migration script:global-options-link:start -->
|
|
252
|
+
|
|
253
|
+
See [Global Options](../cli-reference.md#global-options) for options available to all commands.
|
|
254
|
+
|
|
255
|
+
<!-- politty:command:tailordb migration script:global-options-link:end -->
|
|
256
|
+
|
|
207
257
|
<!-- politty:command:tailordb migration set:heading:start -->
|
|
208
258
|
|
|
209
259
|
#### tailordb migration set
|
package/docs/cli/workspace.md
CHANGED
|
@@ -71,15 +71,16 @@ tailor-sdk workspace create [options]
|
|
|
71
71
|
|
|
72
72
|
**Options**
|
|
73
73
|
|
|
74
|
-
| Option | Alias | Description
|
|
75
|
-
| ------------------------------------- | ----- |
|
|
76
|
-
| `--name <NAME>` | `-n` | Workspace name
|
|
77
|
-
| `--region <REGION>` | `-r` | Workspace region (us-west, asia-northeast)
|
|
78
|
-
| `--delete-protection` | `-d` | Enable delete protection
|
|
79
|
-
| `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID to workspace associate with
|
|
80
|
-
| `--folder-id <FOLDER_ID>` | `-f` | Folder ID to workspace associate with
|
|
81
|
-
| `--profile-name <PROFILE_NAME>` | `-p` | Profile name to create
|
|
82
|
-
| `--profile-user <PROFILE_USER>` | - | User email for the profile (defaults to current user)
|
|
74
|
+
| Option | Alias | Description | Required | Default | Env |
|
|
75
|
+
| ------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------- | -------- | --------- | --------------------------------- |
|
|
76
|
+
| `--name <NAME>` | `-n` | Workspace name | Yes | - | - |
|
|
77
|
+
| `--region <REGION>` | `-r` | Workspace region (us-west, asia-northeast) | Yes | - | - |
|
|
78
|
+
| `--delete-protection` | `-d` | Enable delete protection | No | `false` | - |
|
|
79
|
+
| `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID to workspace associate with | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
|
|
80
|
+
| `--folder-id <FOLDER_ID>` | `-f` | Folder ID to workspace associate with | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
|
|
81
|
+
| `--profile-name <PROFILE_NAME>` | `-p` | Profile name to create | No | - | - |
|
|
82
|
+
| `--profile-user <PROFILE_USER>` | - | User email for the profile (defaults to current user) | No | - | - |
|
|
83
|
+
| `--permission <PERMISSION>` | - | Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active. | No | `"write"` | - |
|
|
83
84
|
|
|
84
85
|
<!-- politty:command:workspace create:options:end -->
|
|
85
86
|
|
|
@@ -240,10 +241,11 @@ tailor-sdk profile create [options] <name>
|
|
|
240
241
|
|
|
241
242
|
**Options**
|
|
242
243
|
|
|
243
|
-
| Option | Alias | Description
|
|
244
|
-
| ------------------------------- | ----- |
|
|
245
|
-
| `--user <USER>` | `-u` | User email
|
|
246
|
-
| `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID
|
|
244
|
+
| Option | Alias | Description | Required | Default |
|
|
245
|
+
| ------------------------------- | ----- | --------------------------------------------------------------------------------- | -------- | --------- |
|
|
246
|
+
| `--user <USER>` | `-u` | User email | Yes | - |
|
|
247
|
+
| `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | Yes | - |
|
|
248
|
+
| `--permission <PERMISSION>` | - | Profile permission. 'read' blocks all write commands while the profile is active. | No | `"write"` |
|
|
247
249
|
|
|
248
250
|
<!-- politty:command:profile create:options:end -->
|
|
249
251
|
|
|
@@ -318,10 +320,11 @@ tailor-sdk profile update [options] <name>
|
|
|
318
320
|
|
|
319
321
|
**Options**
|
|
320
322
|
|
|
321
|
-
| Option | Alias | Description
|
|
322
|
-
| ------------------------------- | ----- |
|
|
323
|
-
| `--user <USER>` | `-u` | New user email
|
|
324
|
-
| `--workspace-id <WORKSPACE_ID>` | `-w` | New workspace ID
|
|
323
|
+
| Option | Alias | Description | Required | Default |
|
|
324
|
+
| ------------------------------- | ----- | ------------------------------------------------------------------------------------ | -------- | ------- |
|
|
325
|
+
| `--user <USER>` | `-u` | New user email | No | - |
|
|
326
|
+
| `--workspace-id <WORKSPACE_ID>` | `-w` | New workspace ID | No | - |
|
|
327
|
+
| `--permission <PERMISSION>` | - | Profile permission. 'read' blocks all write commands; 'write' lifts the restriction. | No | - |
|
|
325
328
|
|
|
326
329
|
<!-- politty:command:profile update:options:end -->
|
|
327
330
|
|
package/docs/cli-reference.md
CHANGED
|
@@ -114,6 +114,7 @@ Commands for managing TailorDB tables, data, and schema migrations.
|
|
|
114
114
|
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
115
115
|
| [tailordb truncate](./cli/tailordb.md#tailordb-truncate) | Truncate (delete all records from) TailorDB tables. |
|
|
116
116
|
| [tailordb migration generate](./cli/tailordb.md#tailordb-migration-generate) | Generate migration files by detecting schema differences between current local types and the previous migration snapshot. |
|
|
117
|
+
| [tailordb migration script](./cli/tailordb.md#tailordb-migration-script) | Add a migration script (migrate.ts) template to an existing migration directory. |
|
|
117
118
|
| [tailordb migration set](./cli/tailordb.md#tailordb-migration-set) | Set migration checkpoint to a specific number. |
|
|
118
119
|
| [tailordb migration status](./cli/tailordb.md#tailordb-migration-status) | Show the current migration status for TailorDB namespaces, including applied and pending migrations. |
|
|
119
120
|
| [tailordb erd export](./cli/tailordb.md#tailordb-erd-export) | Export Liam ERD dist from applied TailorDB schema. |
|
|
@@ -23,14 +23,14 @@ migrations/
|
|
|
23
23
|
│ └── schema.json
|
|
24
24
|
├── 0001/ # First change
|
|
25
25
|
│ ├── diff.json # Field-level diff from 0000
|
|
26
|
-
│ ├── migrate.ts # Data migration script (
|
|
26
|
+
│ ├── migrate.ts # Data migration script (auto-generated for breaking changes; can be added manually via `migration script`)
|
|
27
27
|
│ └── db.ts # Kysely types for the script (pre-migration shape)
|
|
28
28
|
├── 0002/
|
|
29
29
|
│ └── diff.json # No script — non-breaking changes only
|
|
30
30
|
└── ...
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`0000` always contains a full snapshot. `0001` and onward contain a diff plus, for breaking changes,
|
|
33
|
+
`0000` always contains a full snapshot. `0001` and onward contain a diff plus, optionally, a script and its types (auto-generated for breaking changes, or added manually via `tailordb migration script` for warning-tier changes). **Commit the entire `migrations/` directory to version control.**
|
|
34
34
|
|
|
35
35
|
## Initial Setup
|
|
36
36
|
|
|
@@ -113,6 +113,24 @@ A typical change cycle:
|
|
|
113
113
|
```
|
|
114
114
|
The pre-migration phase relaxes the new field to optional, the script runs and populates values, then the post-migration phase enforces `required: true`.
|
|
115
115
|
|
|
116
|
+
### Warnings and optional migration scripts
|
|
117
|
+
|
|
118
|
+
Some non-breaking changes can still cause data loss — most notably removing a field (`field_removed`) or removing a type (`type_removed`). `migration generate` reports these as **warnings**:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Warning: data loss possible:
|
|
122
|
+
|
|
123
|
+
- User.legacyParentId: Field removed (existing data will be dropped in the post-migration phase)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
No `migrate.ts` is generated automatically because the schema change itself is non-breaking, but the existing data is dropped during the post-migration phase. If you need to preserve or transform that data first (for example, copy a column into another table before it disappears), add a script with:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
tailor-sdk tailordb migration script 0002
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This writes `migrations/0002/migrate.ts` and `migrations/0002/db.ts` next to the existing `diff.json`. The removed field stays readable inside `migrate.ts` because the pre-migration phase keeps it on the type until the script finishes (see [Per-migration phases](#per-migration-phases)). The next `tailor-sdk deploy` runs the script automatically — `migrate.ts` is executed whenever the file exists on disk, regardless of whether the diff itself required it.
|
|
133
|
+
|
|
116
134
|
## Configuration
|
|
117
135
|
|
|
118
136
|
```typescript
|
|
@@ -139,12 +157,12 @@ export default defineConfig({
|
|
|
139
157
|
|
|
140
158
|
## Generated Files
|
|
141
159
|
|
|
142
|
-
| File | When generated
|
|
143
|
-
| ------------------ |
|
|
144
|
-
| `0000/schema.json` | First `migration generate`
|
|
145
|
-
| `XXXX/diff.json` | Every subsequent migration
|
|
146
|
-
| `XXXX/migrate.ts` |
|
|
147
|
-
| `XXXX/db.ts` | Generated
|
|
160
|
+
| File | When generated | Description |
|
|
161
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
162
|
+
| `0000/schema.json` | First `migration generate` | Full snapshot of all types in the namespace. |
|
|
163
|
+
| `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot. |
|
|
164
|
+
| `XXXX/migrate.ts` | Auto-generated for breaking changes; added manually via `tailordb migration script` for warning-tier changes | Data transformation script. The `main` export receives a Kysely `Transaction`. |
|
|
165
|
+
| `XXXX/db.ts` | Generated once when `migrate.ts` is created | Kysely types reflecting the schema **before** this migration. |
|
|
148
166
|
|
|
149
167
|
`db.ts` reflects the pre-migration schema because the script runs after the pre-migration phase has temporarily relaxed breaking constraints (e.g., a new `required` field is added as `optional` first), so the data being read still matches the previous shape.
|
|
150
168
|
|
|
@@ -178,25 +196,25 @@ export async function main(trx: Transaction): Promise<void> {
|
|
|
178
196
|
|
|
179
197
|
## Supported Schema Changes
|
|
180
198
|
|
|
181
|
-
| Change Type | Breaking? | Migration Script? | Notes
|
|
182
|
-
| ------------------------------ | --------- | ----------------- |
|
|
183
|
-
| Add optional field | No | No | Schema change only
|
|
184
|
-
| Add required field | Yes | Yes | Script populates default values
|
|
185
|
-
| Remove field | No |
|
|
186
|
-
| Change optional → required | Yes | Yes | Script sets defaults for null values
|
|
187
|
-
| Change required → optional | No | No | Schema change only
|
|
188
|
-
| Add index | No | No | Schema change only
|
|
189
|
-
| Remove index | No | No | Schema change only
|
|
190
|
-
| Add unique constraint | Yes | Yes | Script must resolve duplicate values
|
|
191
|
-
| Remove unique constraint | No | No | Schema change only
|
|
192
|
-
| Add enum value | No | No | Schema change only
|
|
193
|
-
| Remove enum value | Yes | Yes | Script migrates records with removed values
|
|
194
|
-
| Add type | No | No | Schema change only
|
|
195
|
-
| Remove type | No |
|
|
196
|
-
| Change foreign key target type | Yes | Yes | Script updates references to the new target
|
|
197
|
-
| Change field type | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes)
|
|
198
|
-
| Change array → single value | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes)
|
|
199
|
-
| Change single value → array | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes)
|
|
199
|
+
| Change Type | Breaking? | Migration Script? | Notes |
|
|
200
|
+
| ------------------------------ | --------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
201
|
+
| Add optional field | No | No | Schema change only |
|
|
202
|
+
| Add required field | Yes | Yes | Script populates default values |
|
|
203
|
+
| Remove field | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve data before the field is dropped. The field stays readable from `migrate.ts` during Pre-migration and is dropped in Post-migration. |
|
|
204
|
+
| Change optional → required | Yes | Yes | Script sets defaults for null values |
|
|
205
|
+
| Change required → optional | No | No | Schema change only |
|
|
206
|
+
| Add index | No | No | Schema change only |
|
|
207
|
+
| Remove index | No | No | Schema change only |
|
|
208
|
+
| Add unique constraint | Yes | Yes | Script must resolve duplicate values |
|
|
209
|
+
| Remove unique constraint | No | No | Schema change only |
|
|
210
|
+
| Add enum value | No | No | Schema change only |
|
|
211
|
+
| Remove enum value | Yes | Yes | Script migrates records with removed values |
|
|
212
|
+
| Add type | No | No | Schema change only |
|
|
213
|
+
| Remove type | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve data before the type is dropped. The type stays readable from `migrate.ts` during Pre-migration and is dropped in Post-migration. |
|
|
214
|
+
| Change foreign key target type | Yes | Yes | Script updates references to the new target |
|
|
215
|
+
| Change field type | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
216
|
+
| Change array → single value | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
217
|
+
| Change single value → array | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
200
218
|
|
|
201
219
|
### 3-step migration for unsupported changes
|
|
202
220
|
|
|
@@ -216,11 +234,11 @@ When you run `tailor-sdk deploy`, the SDK detects pending migrations (anything p
|
|
|
216
234
|
|
|
217
235
|
For each pending migration:
|
|
218
236
|
|
|
219
|
-
1. **Pre-migration**: Type changes that would be breaking are applied in a relaxed form first. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Non-breaking changes that are part of the same migration are also applied here.
|
|
220
|
-
2. **Script execution**: If `
|
|
221
|
-
3. **Post-migration**: Required constraints are enforced; field
|
|
237
|
+
1. **Pre-migration**: Type changes that would be breaking are applied in a relaxed form first. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the type so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped). Non-breaking changes that are part of the same migration are also applied here.
|
|
238
|
+
2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and sent to the platform via the script execution API and runs as the configured machine user inside a transaction. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) but is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
|
|
239
|
+
3. **Post-migration**: Required constraints are enforced; field and type deletions are applied (the columns/tables are physically dropped here); the `sdk-migration` label is bumped to this migration's number.
|
|
222
240
|
|
|
223
|
-
This split is what allows existing rows to be backfilled before the database starts rejecting nulls.
|
|
241
|
+
This split is what allows existing rows to be backfilled before the database starts rejecting nulls, and what lets `migrate.ts` traverse foreign-key fields that the same migration removes.
|
|
224
242
|
|
|
225
243
|
### Schema verification
|
|
226
244
|
|