@tailor-platform/sdk 1.53.0 → 1.54.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/{actor-DwAh0Dij.d.mts → actor-Cjae_LGD.d.mts} +2 -2
  3. package/dist/{application-DF74unzA.mjs → application-BfGje3iZ.mjs} +12 -5
  4. package/dist/application-BfGje3iZ.mjs.map +1 -0
  5. package/dist/application-BsipSxp3.mjs +4 -0
  6. package/dist/cli/index.mjs +2 -2
  7. package/dist/cli/lib.d.mts +8 -7
  8. package/dist/cli/lib.mjs +2 -2
  9. package/dist/configure/index.d.mts +3 -3
  10. package/dist/idp-Ch95ag8h.mjs.map +1 -1
  11. package/dist/{idp-CbxR6A_0.d.mts → idp-Di9N4FSJ.d.mts} +1 -2
  12. package/dist/{index-UySZfxON.d.mts → index-BTLgs0DP.d.mts} +4 -4
  13. package/dist/{index-BL5LQnBX.d.mts → index-Cf1Lo_XT.d.mts} +2 -2
  14. package/dist/{index-BIbKrme4.d.mts → index-CyyoHrPK.d.mts} +2 -2
  15. package/dist/{index-ECerapTN.d.mts → index-DTSQthwF.d.mts} +2 -2
  16. package/dist/{index-BCJtNXKo.d.mts → index-DrYHpTja.d.mts} +2 -2
  17. package/dist/{index-DW6gAGmC.d.mts → index-qQYMbkT-.d.mts} +2 -2
  18. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  19. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  20. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  21. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  22. package/dist/plugin/index.d.mts +2 -2
  23. package/dist/runtime/globals.d.mts +2 -2
  24. package/dist/runtime/idp.d.mts +1 -1
  25. package/dist/runtime/index.d.mts +2 -2
  26. package/dist/{runtime-D-3AE1x-.mjs → runtime-DLFzjgEo.mjs} +35 -23
  27. package/dist/runtime-DLFzjgEo.mjs.map +1 -0
  28. package/dist/{tailor-db-field-Y_zEoGu1.d.mts → tailor-db-field-D0qg8s4U.d.mts} +4 -2
  29. package/dist/utils/test/index.d.mts +3 -3
  30. package/dist/vitest/index.d.mts +41 -5
  31. package/dist/vitest/index.mjs +124 -1
  32. package/dist/vitest/index.mjs.map +1 -1
  33. package/dist/{workflow.generated-BIY41La-.d.mts → workflow.generated-dBixCwUo.d.mts} +2 -2
  34. package/docs/configuration.md +10 -0
  35. package/docs/services/auth.md +3 -2
  36. package/docs/services/idp.md +6 -0
  37. package/docs/services/tailordb-migration.md +18 -1
  38. package/docs/testing.md +53 -0
  39. package/package.json +5 -5
  40. package/dist/application-Cmz1Y7X_.mjs +0 -4
  41. package/dist/application-DF74unzA.mjs.map +0 -1
  42. package/dist/runtime-D-3AE1x-.mjs.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { n as TailorEnv } from "./env-B-g-qgE4.mjs";
1
2
  import { IsAny, JsonObject, NonEmptyObject } from "type-fest";
2
3
 
3
4
  //#region src/types/helpers.d.ts
@@ -694,7 +695,8 @@ type MachineUser<User extends TailorDBInstance, AttributeMap extends UserAttribu
694
695
  });
695
696
  type BeforeLoginHookArgs = {
696
697
  claims: JsonObject;
697
- idpConfigName: string;
698
+ idpConfigName: string; /** Environment variables defined in `defineConfig({ env })`. */
699
+ env: TailorEnv;
698
700
  };
699
701
  type BeforeLoginHook<MachineUserNames extends string> = {
700
702
  handler(args: BeforeLoginHookArgs): Promise<void>;
@@ -1634,4 +1636,4 @@ type TailorAnyDBType = TailorDBType<any, any>;
1634
1636
  type TailorDBInstance<Fields extends Record<string, TailorAnyDBField> = any, User extends object = InferredAttributeMap> = TailorDBType<Fields, User>;
1635
1637
  //#endregion
1636
1638
  export { ResolverInput as $, RelationType as A, FieldMetadata as At, BeforeLoginHookArgs as B, InferredAttributeMap as Bt, ResolverReadyContext as C, SCIMAuthorization as Ct, DefinedDBFieldMetadata as D, ArrayFieldOutput as Dt, DBFieldMetadata as E, TenantProvider as Et, AuthConfig as F, FieldValidateInput as Ft, UserAttributeListKey as G, JsonCompatible as Gt, OAuth2ClientGrantType as H, TailorUser as Ht, AuthConnectionTokenResult as I, Validators as It, ValueOperand as J, output as Jt, UserAttributeMap as K, JsonValue as Kt, AuthExternalConfig as L, AttributeList as Lt, TailorDBServiceInput as M, FieldOutput$1 as Mt, TailorDBType$1 as N, TailorFieldType as Nt, GqlOperationsConfig as O, DefinedFieldMetadata as Ot, TypeSourceInfoEntry as P, TailorToTs as Pt, Resolver as Q, AuthOwnConfig as R, AttributeMap as Rt, ResolverNamespaceData as S, SCIMAttributeMapping as St, TailorDBReadyContext as T, SCIMResource as Tt, SCIMAttributeType as U, unauthenticatedTailorUser as Ut, DefinedAuth as V, TailorInvoker as Vt, UserAttributeKey as W, InferFieldsOutput as Wt, AuthConnectionConfig as X, TailorField as Y, AuthConnectionOAuth2Config as Z, PluginProcessContext as _, IdProvider as _t, NamespacePluginOutput as a, FunctionOperation as at, ExecutorReadyContext as b, SAML as bt, PluginConfigs as c, IncomingWebhookTrigger as ct, PluginGeneratedExecutor as d, TailorDBTrigger as dt, GeneratorConfig as et, PluginGeneratedExecutorWithFile as f, WebhookOperation as ft, PluginOutput as g, IDToken as gt, PluginNamespaceProcessContext as h, BuiltinIdP as ht, TailorDBType as i, ExecutorInput as it, SerialConfig as j, FieldOptions as jt, IndexDef as k, EnumValue as kt, PluginExecutorContext as l, ResolverExecutedTrigger as lt, PluginGeneratedType as m, AuthInvoker as mt, TailorAnyDBType as n, AuthAccessTokenTrigger as nt, Plugin as o, GqlOperation as ot, PluginGeneratedResolver as p, WorkflowOperation as pt, UsernameFieldKey as q, Prettify as qt, TailorDBField as r, Executor as rt, PluginAttachment as s, IdpUserTrigger as st, TailorAnyDBField as t, BaseGeneratorConfig as tt, PluginExecutorContextBase as u, ScheduleTriggerInput as ut, TailorDBTypeForPlugin as v, OAuth2ClientInput as vt, TailorDBNamespaceData as w, SCIMConfig as wt, GeneratorResult as x, SCIMAttribute as xt, TypePluginOutput as y, OIDC as yt, AuthServiceInput as z, InferredAttributeList as zt };
1637
- //# sourceMappingURL=tailor-db-field-Y_zEoGu1.d.mts.map
1639
+ //# sourceMappingURL=tailor-db-field-D0qg8s4U.d.mts.map
@@ -1,6 +1,6 @@
1
- import { Vt as TailorInvoker } from "../../tailor-db-field-Y_zEoGu1.mjs";
2
- import { O as TailorDBType } from "../../workflow.generated-BIY41La-.mjs";
3
- import { dt as WORKFLOW_TEST_ENV_KEY, n as output, xt as TailorField } from "../../index-UySZfxON.mjs";
1
+ import { Vt as TailorInvoker } from "../../tailor-db-field-D0qg8s4U.mjs";
2
+ import { O as TailorDBType } from "../../workflow.generated-dBixCwUo.mjs";
3
+ import { dt as WORKFLOW_TEST_ENV_KEY, n as output, xt as TailorField } from "../../index-BTLgs0DP.mjs";
4
4
  import { StandardSchemaV1 } from "@standard-schema/spec";
5
5
 
6
6
  //#region src/utils/test/mock.d.ts
@@ -1,8 +1,9 @@
1
1
  import { n as TailorEnv } from "../env-B-g-qgE4.mjs";
2
+ import { Kysely, OperationNodeKind } from "kysely";
2
3
  import { Plugin } from "vitest/config";
3
4
 
4
5
  //#region src/vitest/mock.d.ts
5
- type QueryResolver = (query: string, params: unknown[]) => unknown[];
6
+ type QueryResolver$1 = (query: string, params: unknown[]) => unknown[];
6
7
  type JobHandler = (jobName: string, args: unknown) => unknown;
7
8
  type IdpResolver = (method: string, args: unknown[], namespace: string) => unknown;
8
9
  type FileResolver = (method: string, call: FileCall) => unknown;
@@ -20,7 +21,7 @@ type SetWaitHandler = {
20
21
  (handler: WaitHandlerFn): void;
21
22
  (handler: unknown): void;
22
23
  };
23
- interface ExecutedQuery {
24
+ interface ExecutedQuery$1 {
24
25
  query: string;
25
26
  params: unknown[];
26
27
  }
@@ -93,7 +94,7 @@ declare const tailordbMock: {
93
94
  * Set a fallback query resolver. Called when the result queue is empty.
94
95
  * @param resolver - Function that returns rows for a given query and params
95
96
  */
96
- setQueryResolver(resolver: QueryResolver): void;
97
+ setQueryResolver(resolver: QueryResolver$1): void;
97
98
  /**
98
99
  * Enqueue rows for the next `queryObject` call. Arguments are the row objects returned
99
100
  * by that single query. Call with no arguments for an empty result. Consumed in FIFO
@@ -113,7 +114,7 @@ declare const tailordbMock: {
113
114
  * All queries executed via `queryObject`, in order.
114
115
  * @returns Executed queries array
115
116
  */
116
- readonly executedQueries: ExecutedQuery[];
117
+ readonly executedQueries: ExecutedQuery$1[];
117
118
  /**
118
119
  * All TailorDB clients created, with their namespace and end state.
119
120
  * @returns Created clients array
@@ -281,6 +282,41 @@ declare const iconvMock: {
281
282
  reset(): void;
282
283
  };
283
284
  //#endregion
285
+ //#region src/vitest/mock-kysely.d.ts
286
+ /** A single statement executed against the mock, captured in order. */
287
+ interface ExecutedQuery {
288
+ kind: OperationNodeKind;
289
+ sql: string;
290
+ parameters: readonly unknown[];
291
+ }
292
+ type MockRow = Record<string, unknown>;
293
+ type MockResult = MockRow[] | {
294
+ rows?: MockRow[];
295
+ numAffectedRows?: number | bigint;
296
+ };
297
+ type QueryResolver = (query: ExecutedQuery) => MockResult | undefined;
298
+ /** Controls and assertions for a {@link createKyselyMock} instance. */
299
+ interface KyselyMock<DB> {
300
+ db: Kysely<DB>;
301
+ executedQueries: ExecutedQuery[];
302
+ selects: ExecutedQuery[];
303
+ inserts: ExecutedQuery[];
304
+ updates: ExecutedQuery[];
305
+ deletes: ExecutedQuery[];
306
+ enqueueResult: (result: MockResult) => void;
307
+ enqueueResults: (...results: MockResult[]) => void;
308
+ setQueryResolver: (resolver: QueryResolver) => void;
309
+ reset: () => void;
310
+ [Symbol.dispose]: () => void;
311
+ }
312
+ /**
313
+ * Create a mock Kysely instance for unit-testing code that runs Kysely queries.
314
+ * Pass the namespace schema as the type argument, e.g.
315
+ * `createKyselyMock<Namespace["main-db"]>()`.
316
+ * @returns A {@link KyselyMock} with the mock `db`, recorded queries, and result staging.
317
+ */
318
+ declare function createKyselyMock<DB = Record<string, never>>(): KyselyMock<DB>;
319
+ //#endregion
284
320
  //#region src/vitest/index.d.ts
285
321
  /**
286
322
  * Creates Vitest plugins that emulate the Tailor Platform function runtime environment.
@@ -340,5 +376,5 @@ declare function tailorRuntime(options?: {
340
376
  config?: string;
341
377
  }): Plugin[];
342
378
  //#endregion
343
- export { authconnectionMock, fileMock, iconvMock, idpMock, secretmanagerMock, tailorRuntime, tailordbMock, workflowMock };
379
+ export { type ExecutedQuery, type KyselyMock, authconnectionMock, createKyselyMock, fileMock, iconvMock, idpMock, secretmanagerMock, tailorRuntime, tailordbMock, workflowMock };
344
380
  //# sourceMappingURL=index.d.mts.map
@@ -3,6 +3,7 @@ import { a as fileMock, d as workflowMock, l as secretmanagerMock, o as iconvMoc
3
3
  import { builtinModules } from "node:module";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
6
+ import { Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler } from "kysely";
6
7
 
7
8
  //#region src/vitest/blocked-modules.ts
8
9
  /**
@@ -285,6 +286,128 @@ function createEnvironmentPlugin(options) {
285
286
  };
286
287
  }
287
288
 
289
+ //#endregion
290
+ //#region src/vitest/mock-kysely.ts
291
+ /**
292
+ * Kysely-layer mock for unit testing.
293
+ *
294
+ * Builds a real Kysely instance backed by a mock driver: queries compile and
295
+ * type-check normally, but execution returns staged rows and records every
296
+ * query for assertions.
297
+ */
298
+ function toStagedResult(result) {
299
+ if (Array.isArray(result)) return {
300
+ rows: result,
301
+ numAffectedRows: void 0
302
+ };
303
+ return {
304
+ rows: result.rows ?? [],
305
+ numAffectedRows: result.numAffectedRows === void 0 ? void 0 : BigInt(result.numAffectedRows)
306
+ };
307
+ }
308
+ var MockState = class {
309
+ executed = [];
310
+ queue = [];
311
+ resolver;
312
+ enqueue(...results) {
313
+ this.queue.push(...results);
314
+ }
315
+ setResolver(resolver) {
316
+ this.resolver = resolver;
317
+ }
318
+ next(query) {
319
+ const resolved = this.resolver?.(query);
320
+ if (resolved !== void 0) return toStagedResult(resolved);
321
+ const queued = this.queue.shift();
322
+ return queued === void 0 ? {
323
+ rows: [],
324
+ numAffectedRows: void 0
325
+ } : toStagedResult(queued);
326
+ }
327
+ reset() {
328
+ this.executed.length = 0;
329
+ this.queue.length = 0;
330
+ this.resolver = void 0;
331
+ }
332
+ };
333
+ var MockConnection = class {
334
+ state;
335
+ constructor(state) {
336
+ this.state = state;
337
+ }
338
+ async executeQuery(compiledQuery) {
339
+ const query = {
340
+ kind: compiledQuery.query.kind,
341
+ sql: compiledQuery.sql,
342
+ parameters: compiledQuery.parameters
343
+ };
344
+ this.state.executed.push(query);
345
+ const { rows, numAffectedRows } = this.state.next(query);
346
+ return {
347
+ rows,
348
+ numAffectedRows: numAffectedRows ?? BigInt(rows.length)
349
+ };
350
+ }
351
+ streamQuery() {
352
+ throw new Error("createKyselyMock: streaming is not supported");
353
+ }
354
+ };
355
+ var MockDriver = class {
356
+ state;
357
+ constructor(state) {
358
+ this.state = state;
359
+ }
360
+ async init() {}
361
+ async acquireConnection() {
362
+ return new MockConnection(this.state);
363
+ }
364
+ async beginTransaction() {}
365
+ async commitTransaction() {}
366
+ async rollbackTransaction() {}
367
+ async releaseConnection() {}
368
+ async destroy() {}
369
+ };
370
+ function byKind(state, kind) {
371
+ return state.executed.filter((query) => query.kind === kind);
372
+ }
373
+ /**
374
+ * Create a mock Kysely instance for unit-testing code that runs Kysely queries.
375
+ * Pass the namespace schema as the type argument, e.g.
376
+ * `createKyselyMock<Namespace["main-db"]>()`.
377
+ * @returns A {@link KyselyMock} with the mock `db`, recorded queries, and result staging.
378
+ */
379
+ function createKyselyMock() {
380
+ const state = new MockState();
381
+ return {
382
+ db: new Kysely({ dialect: {
383
+ createDriver: () => new MockDriver(state),
384
+ createQueryCompiler: () => new PostgresQueryCompiler(),
385
+ createAdapter: () => new PostgresAdapter(),
386
+ createIntrospector: (db) => new PostgresIntrospector(db)
387
+ } }),
388
+ get executedQueries() {
389
+ return state.executed;
390
+ },
391
+ get selects() {
392
+ return byKind(state, "SelectQueryNode");
393
+ },
394
+ get inserts() {
395
+ return byKind(state, "InsertQueryNode");
396
+ },
397
+ get updates() {
398
+ return byKind(state, "UpdateQueryNode");
399
+ },
400
+ get deletes() {
401
+ return byKind(state, "DeleteQueryNode");
402
+ },
403
+ enqueueResult: (result) => state.enqueue(result),
404
+ enqueueResults: (...results) => state.enqueue(...results),
405
+ setQueryResolver: (resolver) => state.setResolver(resolver),
406
+ reset: () => state.reset(),
407
+ [Symbol.dispose]: () => state.reset()
408
+ };
409
+ }
410
+
288
411
  //#endregion
289
412
  //#region src/vitest/index.ts
290
413
  /**
@@ -346,5 +469,5 @@ function tailorRuntime(options) {
346
469
  }
347
470
 
348
471
  //#endregion
349
- export { authconnectionMock, fileMock, iconvMock, idpMock, secretmanagerMock, tailorRuntime, tailordbMock, workflowMock };
472
+ export { authconnectionMock, createKyselyMock, fileMock, iconvMock, idpMock, secretmanagerMock, tailorRuntime, tailordbMock, workflowMock };
350
473
  //# sourceMappingURL=index.mjs.map
@@ -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\n * provides response 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/mock-kysely.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","/**\n * Kysely-layer mock for unit testing.\n *\n * Builds a real Kysely instance backed by a mock driver: queries compile and\n * type-check normally, but execution returns staged rows and records every\n * query for assertions.\n */\n\nimport {\n type CompiledQuery,\n type DatabaseConnection,\n type Dialect,\n type Driver,\n Kysely,\n type OperationNodeKind,\n PostgresAdapter,\n PostgresIntrospector,\n PostgresQueryCompiler,\n type QueryResult,\n} from \"kysely\";\n\n/** A single statement executed against the mock, captured in order. */\nexport interface ExecutedQuery {\n kind: OperationNodeKind;\n sql: string;\n parameters: readonly unknown[];\n}\n\ntype MockRow = Record<string, unknown>;\ntype MockResult = MockRow[] | { rows?: MockRow[]; numAffectedRows?: number | bigint };\ntype QueryResolver = (query: ExecutedQuery) => MockResult | undefined;\n\ninterface StagedResult {\n rows: MockRow[];\n numAffectedRows: bigint | undefined;\n}\n\nfunction toStagedResult(result: MockResult): StagedResult {\n if (Array.isArray(result)) return { rows: result, numAffectedRows: undefined };\n return {\n rows: result.rows ?? [],\n numAffectedRows:\n result.numAffectedRows === undefined ? undefined : BigInt(result.numAffectedRows),\n };\n}\n\n/** Controls and assertions for a {@link createKyselyMock} instance. */\nexport interface KyselyMock<DB> {\n db: Kysely<DB>;\n executedQueries: ExecutedQuery[];\n selects: ExecutedQuery[];\n inserts: ExecutedQuery[];\n updates: ExecutedQuery[];\n deletes: ExecutedQuery[];\n enqueueResult: (result: MockResult) => void;\n enqueueResults: (...results: MockResult[]) => void;\n setQueryResolver: (resolver: QueryResolver) => void;\n reset: () => void;\n [Symbol.dispose]: () => void;\n}\n\nclass MockState {\n readonly executed: ExecutedQuery[] = [];\n private readonly queue: MockResult[] = [];\n private resolver: QueryResolver | undefined;\n\n enqueue(...results: MockResult[]): void {\n this.queue.push(...results);\n }\n\n setResolver(resolver: QueryResolver): void {\n this.resolver = resolver;\n }\n\n next(query: ExecutedQuery): StagedResult {\n const resolved = this.resolver?.(query);\n if (resolved !== undefined) return toStagedResult(resolved);\n const queued = this.queue.shift();\n return queued === undefined ? { rows: [], numAffectedRows: undefined } : toStagedResult(queued);\n }\n\n reset(): void {\n this.executed.length = 0;\n this.queue.length = 0;\n this.resolver = undefined;\n }\n}\n\nclass MockConnection implements DatabaseConnection {\n constructor(private readonly state: MockState) {}\n\n async executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>> {\n const query: ExecutedQuery = {\n kind: compiledQuery.query.kind,\n sql: compiledQuery.sql,\n parameters: compiledQuery.parameters,\n };\n this.state.executed.push(query);\n const { rows, numAffectedRows } = this.state.next(query);\n return {\n rows: rows as R[],\n numAffectedRows: numAffectedRows ?? BigInt(rows.length),\n };\n }\n\n streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {\n throw new Error(\"createKyselyMock: streaming is not supported\");\n }\n}\n\nclass MockDriver implements Driver {\n constructor(private readonly state: MockState) {}\n\n async init(): Promise<void> {}\n\n async acquireConnection(): Promise<DatabaseConnection> {\n return new MockConnection(this.state);\n }\n\n // No-ops so begin/commit/rollback never enter `executed` and pollute counts.\n async beginTransaction(): Promise<void> {}\n async commitTransaction(): Promise<void> {}\n async rollbackTransaction(): Promise<void> {}\n\n async releaseConnection(): Promise<void> {}\n async destroy(): Promise<void> {}\n}\n\nfunction byKind(state: MockState, kind: OperationNodeKind): ExecutedQuery[] {\n return state.executed.filter((query) => query.kind === kind);\n}\n\n/**\n * Create a mock Kysely instance for unit-testing code that runs Kysely queries.\n * Pass the namespace schema as the type argument, e.g.\n * `createKyselyMock<Namespace[\"main-db\"]>()`.\n * @returns A {@link KyselyMock} with the mock `db`, recorded queries, and result staging.\n */\nexport function createKyselyMock<DB = Record<string, never>>(): KyselyMock<DB> {\n const state = new MockState();\n const dialect: Dialect = {\n createDriver: () => new MockDriver(state),\n createQueryCompiler: () => new PostgresQueryCompiler(),\n createAdapter: () => new PostgresAdapter(),\n createIntrospector: (db) => new PostgresIntrospector(db),\n };\n const kysely = new Kysely<DB>({ dialect });\n\n return {\n db: kysely,\n get executedQueries() {\n return state.executed;\n },\n get selects() {\n return byKind(state, \"SelectQueryNode\");\n },\n get inserts() {\n return byKind(state, \"InsertQueryNode\");\n },\n get updates() {\n return byKind(state, \"UpdateQueryNode\");\n },\n get deletes() {\n return byKind(state, \"DeleteQueryNode\");\n },\n enqueueResult: (result) => state.enqueue(result),\n enqueueResults: (...results) => state.enqueue(...results),\n setQueryResolver: (resolver) => state.setResolver(resolver),\n reset: () => state.reset(),\n [Symbol.dispose]: () => state.reset(),\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\n * provides response 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\nexport { createKyselyMock, type KyselyMock, type ExecutedQuery } from \"./mock-kysely\";\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as RelationType, Bt as InferredAttributeMap, D as DefinedDBFieldMetadata, Dt as ArrayFieldOutput, E as DBFieldMetadata, F as AuthConfig, Ft as FieldValidateInput, Ht as TailorUser, It as Validators, Jt as output, M as TailorDBServiceInput, Mt as FieldOutput, O as GqlOperationsConfig, Pt as TailorToTs, Wt as InferFieldsOutput, Y as TailorField, c as PluginConfigs, ht as BuiltinIdP, i as TailorDBType$1, j as SerialConfig, jt as FieldOptions, k as IndexDef, kt as EnumValue, qt as Prettify, r as TailorDBField$1, t as TailorAnyDBField$1 } from "./tailor-db-field-Y_zEoGu1.mjs";
1
+ import { A as RelationType, Bt as InferredAttributeMap, D as DefinedDBFieldMetadata, Dt as ArrayFieldOutput, E as DBFieldMetadata, F as AuthConfig, Ft as FieldValidateInput, Ht as TailorUser, It as Validators, Jt as output, M as TailorDBServiceInput, Mt as FieldOutput, O as GqlOperationsConfig, Pt as TailorToTs, Wt as InferFieldsOutput, Y as TailorField, c as PluginConfigs, ht as BuiltinIdP, i as TailorDBType$1, j as SerialConfig, jt as FieldOptions, k as IndexDef, kt as EnumValue, qt as Prettify, r as TailorDBField$1, t as TailorAnyDBField$1 } from "./tailor-db-field-D0qg8s4U.mjs";
2
2
  import { NonEmptyObject } from "type-fest";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
 
@@ -1216,4 +1216,4 @@ type ConcurrencyPolicy = {
1216
1216
  };
1217
1217
  //#endregion
1218
1218
  export { PermissionCondition as A, IdPInput as C, TailorDBInstance as D, TailorDBField as E, AllowedValues as F, AllowedValuesOutput as I, TailorTypePermission as M, unsafeAllowAllGqlPermission as N, TailorDBType as O, unsafeAllowAllTypePermission as P, IdPGqlOperationsInput as S, TailorAnyDBType as T, IdPExternalConfig as _, ExecutorServiceInput as a, IdPEmailConfig as b, ResolverServiceInput as c, StaticWebsiteConfig as d, StaticWebsiteDefinitionBrand as f, IdPConfig as g, SecretsDefinitionBrand as h, ExecutorServiceConfig as i, TailorTypeGqlPermission as j, db as k, WorkflowServiceConfig as l, SecretsConfig as m, RetryPolicy as n, ResolverExternalConfig as o, StaticWebsiteInput as p, AppConfig as r, ResolverServiceConfig as s, ConcurrencyPolicy as t, WorkflowServiceInput as u, IdPUserField as v, TailorAnyDBField as w, IdPGqlOperations as x, IdpDefinitionBrand as y };
1219
- //# sourceMappingURL=workflow.generated-BIY41La-.d.mts.map
1219
+ //# sourceMappingURL=workflow.generated-dBixCwUo.d.mts.map
@@ -204,6 +204,16 @@ body: (input, { env }) => {
204
204
  console.log(`Environment: ${env.bar}`);
205
205
  return { value: env.foo };
206
206
  };
207
+
208
+ // In auth before-login hooks
209
+ hooks: {
210
+ beforeLogin: {
211
+ handler: async ({ claims, idpConfigName, env }) => {
212
+ console.log(`Environment: ${env.bar}`);
213
+ },
214
+ invoker: "hook-invoker",
215
+ },
216
+ };
207
217
  ```
208
218
 
209
219
  ### Workflow Service
@@ -472,8 +472,9 @@ export const auth = defineAuth("my-auth", {
472
472
  },
473
473
  hooks: {
474
474
  beforeLogin: {
475
- handler: async ({ claims, idpConfigName }) => {
475
+ handler: async ({ claims, idpConfigName, env }) => {
476
476
  // Provision or update user based on IdP claims
477
+ // `env` exposes the variables defined in `defineConfig({ env })`
477
478
  },
478
479
  invoker: "hook-invoker",
479
480
  },
@@ -481,7 +482,7 @@ export const auth = defineAuth("my-auth", {
481
482
  });
482
483
  ```
483
484
 
484
- **handler**: An async function that receives `{ claims, idpConfigName }` and is called before each login. `claims` contains the token claims from the identity provider, and `idpConfigName` is the name of the IdP configuration used for authentication.
485
+ **handler**: An async function that receives `{ claims, idpConfigName, env }` and is called before each login. `claims` contains the token claims from the identity provider, `idpConfigName` is the name of the IdP configuration used for authentication, and `env` exposes the environment variables defined in `defineConfig({ env })` (the same values available via `context.env` in resolvers).
485
486
 
486
487
  **invoker**: The machine user whose permissions are used to execute the hook. Must reference a machine user defined in the same auth configuration.
487
488
 
@@ -88,6 +88,12 @@ defineIdp("my-idp", {
88
88
  - `delete` - Controls who can delete IdP users
89
89
  - `sendPasswordResetEmail` - Controls who can send password reset emails. The examples above disable this operation; to enable it, use a permission such as `[{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }]`.
90
90
 
91
+ **Policy fields:** each entry in an operation's policy array supports:
92
+
93
+ - `conditions` - Array of conditions evaluated for the policy (see Operands/Operators below)
94
+ - `permit` - Whether to allow (`true`) or deny (`false`) when the conditions match
95
+ - `description` - (Optional) Human-readable description of the policy
96
+
91
97
  **Operands:**
92
98
 
93
99
  - `{ user: "field" }` - Authenticated user's attribute. Built-in fields: `"id"` (user ID), `"_loggedIn"` (boolean, whether the user is authenticated). User-defined attributes (e.g., `"role"`) are also available when configured via `userProfile.attributes` or `machineUserAttributes` in `defineAuth()`
@@ -186,6 +186,23 @@ export async function main(trx: Transaction): Promise<void> {
186
186
  }
187
187
  ```
188
188
 
189
+ **Accessing environment variables**
190
+
191
+ The migration `main` receives an optional second argument exposing the variables defined in `defineConfig({ env })` — the same values available via `context.env` in resolvers. The `MigrationContext` type is exported from the generated `./db`:
192
+
193
+ ```typescript
194
+ import type { Transaction, MigrationContext } from "./db";
195
+
196
+ export async function main(trx: Transaction, { env }: MigrationContext): Promise<void> {
197
+ // Branch on environment-specific config resolved at deploy time
198
+ if (env.SKIP_BACKFILL) return;
199
+
200
+ await trx.updateTable("User").set({ stage: env.ENVIRONMENT }).execute();
201
+ }
202
+ ```
203
+
204
+ The `env` values are injected at bundle time (the same mechanism as resolvers/executors/workflow jobs); `process.env` and other Node-side environment access remain unavailable at runtime. The second argument is optional — existing `main(trx)` scripts continue to work unchanged.
205
+
189
206
  **Rules**
190
207
 
191
208
  - Always use the `trx` argument for database access. Anything that bypasses `trx` is not part of the transaction and will not roll back on failure.
@@ -394,7 +411,7 @@ The platform-side execution path is hard to fully replicate locally, but you can
394
411
 
395
412
  ## Environment-Specific Strategies
396
413
 
397
- A migration script is a function — branching on environment (e.g., to skip a backfill in dev) is just normal TypeScript. The migration runtime injects nothing environment-specific into the script itself; if you need environment awareness, query a sentinel record or rely on data shape. Avoid env-vars inside `migrate.ts` because the script is bundled and shipped to the platform; the platform-side environment is what counts.
414
+ A migration script is a function — branching on environment (e.g., to skip a backfill in dev) is just normal TypeScript. For environment awareness, use the `env` values defined in `defineConfig({ env })`, exposed via the optional second argument `{ env }: MigrationContext` (see [Migration Script Anatomy](#migration-script-anatomy)). These are resolved at deploy time and inlined into the bundle. Do not read `process.env` inside `migrate.ts` Node-side environment access is unavailable at runtime; only the injected `env` (and the data itself) reflect the target environment.
398
415
 
399
416
  For genuinely different schemas across environments, prefer separate workspaces with the same migration history rather than divergent `migrations/` directories.
400
417
 
package/docs/testing.md CHANGED
@@ -468,6 +468,59 @@ describe("decrementUserAge", () => {
468
468
 
469
469
  **Use when:** multi-step business logic. The tests survive query rewrites because they assert high-level intent, not SQL shape.
470
470
 
471
+ #### Kysely-layer mock (`createKyselyMock`)
472
+
473
+ `createKyselyMock` returns a real Kysely instance whose execution is mocked. Stage the rows each query returns, run your code, then assert what it did — each query's SQL and parameters, how many `selects`/`inserts`/`updates`/`deletes` ran, and the value your code returned. Queries stay fully typed and compile to the same SQL as production.
474
+
475
+ Pass `mock.db` to functions that take a Kysely instance. When a resolver or executor calls `getDB()` internally there is no such seam, so spy the generated `getDB` and point it at the mock:
476
+
477
+ ```typescript
478
+ import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
479
+ import { createKyselyMock } from "@tailor-platform/sdk/vitest";
480
+ import { describe, expect, test, vi } from "vitest";
481
+ import { getDB, type Namespace } from "../generated/db";
482
+ import resolver from "./upsertUsers";
483
+
484
+ vi.mock("../generated/db", { spy: true });
485
+
486
+ describe("upsertUsers resolver", () => {
487
+ test("inserts new users and updates existing ones", async () => {
488
+ const mock = createKyselyMock<Namespace["main-db"]>();
489
+ vi.mocked(getDB).mockReturnValue(mock.db);
490
+
491
+ mock.setQueryResolver((query) => {
492
+ switch (query.kind) {
493
+ case "SelectQueryNode":
494
+ return query.parameters.includes("exists@example.com") ? [{ id: "user-1" }] : [];
495
+ case "InsertQueryNode":
496
+ case "UpdateQueryNode":
497
+ return { numAffectedRows: 1 };
498
+ default:
499
+ return [];
500
+ }
501
+ });
502
+
503
+ const result = await resolver.body({
504
+ input: {
505
+ users: [
506
+ { name: "Newcomer", email: "new@example.com", age: 22 },
507
+ { name: "Existing", email: "exists@example.com", age: 41 },
508
+ ],
509
+ },
510
+ user: unauthenticatedTailorUser,
511
+ env: { appName: "Resolver Template", version: 1 },
512
+ });
513
+
514
+ expect(result).toEqual({ created: 1, updated: 1 });
515
+ expect(mock.selects).toHaveLength(2);
516
+ expect(mock.inserts).toHaveLength(1);
517
+ expect(mock.updates).toHaveLength(1);
518
+ });
519
+ });
520
+ ```
521
+
522
+ Reach for [`tailordbMock`](#mocking-the-tailordb-client) instead when you want to drive the raw query sequence at the `tailordb.Client` level rather than at the Kysely layer.
523
+
471
524
  #### Resolvers that resume a workflow
472
525
 
473
526
  Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `workflowMock.setResolveHandler` to drive the user-supplied callback and inspect `workflowMock.resolveCalls`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "1.53.0",
3
+ "version": "1.54.2",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -140,8 +140,8 @@
140
140
  "@bufbuild/protobuf": "2.12.0",
141
141
  "@connectrpc/connect": "2.1.1",
142
142
  "@connectrpc/connect-node": "2.1.1",
143
- "@inquirer/core": "11.2.0",
144
- "@inquirer/prompts": "8.5.0",
143
+ "@inquirer/core": "11.2.1",
144
+ "@inquirer/prompts": "8.5.1",
145
145
  "@jridgewell/trace-mapping": "0.3.31",
146
146
  "@liam-hq/cli": "0.7.24",
147
147
  "@napi-rs/keyring": "1.3.0",
@@ -159,7 +159,7 @@
159
159
  "chalk": "5.6.2",
160
160
  "chokidar": "5.0.0",
161
161
  "confbox": "0.2.4",
162
- "date-fns": "4.3.0",
162
+ "date-fns": "4.4.0",
163
163
  "es-toolkit": "1.47.0",
164
164
  "find-up-simple": "1.0.1",
165
165
  "globals": "17.6.0",
@@ -174,7 +174,7 @@
174
174
  "pathe": "2.0.3",
175
175
  "pgsql-ast-parser": "12.0.2",
176
176
  "pkg-types": "2.3.1",
177
- "politty": "0.5.0",
177
+ "politty": "0.5.1",
178
178
  "rolldown": "1.0.3",
179
179
  "semver": "7.8.1",
180
180
  "serve": "14.2.6",
@@ -1,4 +0,0 @@
1
-
2
- import { n as generatePluginFilesIfNeeded, r as loadApplication, t as defineApplication } from "./application-DF74unzA.mjs";
3
-
4
- export { defineApplication };