@tailor-platform/sdk 2.29.0 → 2.29.1
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 +6 -0
- package/README.md +2 -1
- package/agent-skills/tailor/SKILL.md +2 -1
- package/dist/cli/main.mjs +1 -1
- package/dist/cli/main.mjs.map +1 -1
- package/dist/completion/zsh-worker.zsh +1 -1
- package/dist/configure/index.d.mts +1 -1
- package/dist/types/helpers.d.mts +2 -5
- package/dist/vitest/mocks/file.d.mts +1 -1
- package/docs/cli/application.md +2 -2
- package/docs/cli/data.md +2 -0
- package/docs/cli/tailordb.md +2 -2
- package/docs/configuration.md +2 -2
- package/docs/data-clone.md +71 -0
- package/docs/migration/v2.md +1 -1
- package/docs/quickstart.md +1 -1
- package/docs/services/{tailordb.md → tailordb/index.md} +4 -4
- package/docs/services/{tailordb-migration.md → tailordb/migration.md} +12 -10
- package/docs/testing.md +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @tailor-platform/sdk
|
|
2
2
|
|
|
3
|
+
## 2.29.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2653](https://github.com/tailor-platform/sdk/pull/2653) [`98d8d01`](https://github.com/tailor-platform/sdk/commit/98d8d01b8f4ab80a73ec90df6ec4ca29c8ac55d3) Thanks [@toiroakr](https://github.com/toiroakr)! - Move the TailorDB guides under `docs/services/tailordb/`: the service guide is now `docs/services/tailordb/index.md` and the migration guide `docs/services/tailordb/migration.md`. Add a `docs/data-clone.md` guide for `tailor data clone`, and note that `tailor tailordb migration test --data clone` copies TailorDB records only, without the `--copy-files` and `--copy-idp-users` options of `data clone`.
|
|
8
|
+
|
|
3
9
|
## 2.29.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ into the current project under `.agents/skills` and `.claude/skills`, so rerun
|
|
|
66
66
|
|
|
67
67
|
| Service | Description |
|
|
68
68
|
| -------------------------------------------------- | -------------------------------------------- |
|
|
69
|
-
| [TailorDB](./docs/services/tailordb.md)
|
|
69
|
+
| [TailorDB](./docs/services/tailordb/index.md) | Type-safe database schema definition |
|
|
70
70
|
| [Resolver](./docs/services/resolver.md) | Custom GraphQL resolvers with business logic |
|
|
71
71
|
| [Executor](./docs/services/executor.md) | Event-driven handlers for automation |
|
|
72
72
|
| [Workflow](./docs/services/workflow.md) | Job orchestration for complex operations |
|
|
@@ -78,6 +78,7 @@ into the current project under `.agents/skills` and `.claude/skills`, so rerun
|
|
|
78
78
|
### Guides
|
|
79
79
|
|
|
80
80
|
- [Multi-Environment Configuration](./docs/multi-environment.md) - Deploying one config to multiple workspaces
|
|
81
|
+
- [Cloning Application Data](./docs/data-clone.md) - Copying an application's data from one workspace to another
|
|
81
82
|
- [Runtime API](./docs/runtime.md) - Typed wrappers for `tailor.iconv`, `tailor.secretmanager`, `tailor.idp`, `tailor.workflow`, `tailor.context`, `tailor.authconnection`, and `tailordb.file`
|
|
82
83
|
- [Testing Guide](./docs/testing.md) - Unit and E2E testing patterns
|
|
83
84
|
- [CLI Reference](./docs/cli-reference.md) - Command-line interface documentation
|
|
@@ -16,6 +16,7 @@ Use these files as the single source of truth:
|
|
|
16
16
|
- `node_modules/@tailor-platform/sdk/README.md`
|
|
17
17
|
- `node_modules/@tailor-platform/sdk/docs/configuration.md`
|
|
18
18
|
- `node_modules/@tailor-platform/sdk/docs/services/*.md`
|
|
19
|
+
- `node_modules/@tailor-platform/sdk/docs/services/tailordb/*.md`
|
|
19
20
|
- `node_modules/@tailor-platform/sdk/docs/cli-reference.md`
|
|
20
21
|
- `node_modules/@tailor-platform/sdk/docs/cli/*.md`
|
|
21
22
|
- `node_modules/@tailor-platform/sdk/docs/testing.md`
|
|
@@ -36,7 +37,7 @@ Use these files as the single source of truth:
|
|
|
36
37
|
|
|
37
38
|
- Setup and first deploy: `docs/quickstart.md`
|
|
38
39
|
- Core config shape: `docs/configuration.md`
|
|
39
|
-
- Service details: `docs/services/*.md`
|
|
40
|
+
- Service details: `docs/services/*.md`; TailorDB and its migrations: `docs/services/tailordb/*.md`
|
|
40
41
|
- CLI commands: `docs/cli-reference.md` and `docs/cli/*.md`
|
|
41
42
|
- Testing patterns: `docs/testing.md`
|
|
42
43
|
- Runtime API wrappers (`tailor.iconv`, `tailor.secretmanager`, `tailor.idp`, `tailor.workflow`, `tailor.context`, `tailor.authconnection`, `tailordb.file`): `docs/runtime.md`
|
package/dist/cli/main.mjs
CHANGED
|
@@ -67,7 +67,7 @@ Logs:`));for(let e of n.logEntries.slice(a))x.log(` ${or(Cr(e))}`);a=n.logEntri
|
|
|
67
67
|
- A named-exported "main" function`})}function detectWorkflowJob(e,t){let n=[];for(let[t,r]of Object.entries(e)){if(t==="default")continue;let e=ge.safeParse(r);e.success&&n.push({name:e.data.name,exportName:t})}if(n.length===0)return null;if(t){let e=n.find(e=>e.name===t);if(!e){let e=n.map(e=>` - "${e.name}" (export: ${e.exportName})`).join(`
|
|
68
68
|
`);throw U({code:`WORKFLOW_JOB_NOT_FOUND`,message:`Workflow job "${t}" not found.`,details:`Available jobs:\n${e}`})}return{type:`workflow-job`,name:e.name,exportName:e.exportName}}if(n.length===1){let[e]=n,t=s(e,`workflow job missing`);return{type:`workflow-job`,name:t.name,exportName:t.exportName}}let r=n.map(e=>` - "${e.name}" (export: ${e.exportName})`).join(`
|
|
69
69
|
`);throw U({code:`WORKFLOW_JOB_AMBIGUOUS`,message:`Multiple workflow jobs found.`,details:`Available jobs:\n${r}`,suggestion:`Specify one with --name.`})}function deriveNameFromPath(e){return K.basename(e,K.extname(e))}const ya=`db.ts`,ba=`db.snapshot.json`,xa=[`/**`,` * Auto-generated Kysely types for one-off scripts in this directory.`," * Generated by `tailor function script` from the selected TailorDB schema.",` *`," * DO NOT EDIT - Re-run `tailor function script` to refresh.",` */`].join(`
|
|
70
|
-
`);function generateScriptDbTypes(e,t=!1){let n=Object.values(e.tables).map(e=>di(e.name,e.fields,t)),r=fi([{namespace:e.namespace,types:n}],t);return`${xa}\n\n${r}`}function isGeneratedScriptDbTypes(e){return e.includes("Generated by `tailor function script`")}function assertGeneratedTypeScript(e,t){let{errors:n}=Ni(K.basename(e),t);if(n.length>0)throw Qr(`Generated ${e} does not parse as TypeScript: ${n.map(e=>e.message).join(`; `)}`)}function generateScriptSkeleton(e){let{getDBImportPath:t,namespace:n}=e;return[`/**`,` * One-off script. Run it on the platform without deploying:`,` *`,` * tailor function run <this file>`,` *`,` * For large tables, batch updates by primary-key range instead of one`,` * statement over the whole table; see the "Performance and Large Tables"`,` * section in @tailor-platform/sdk's docs/services/tailordb
|
|
70
|
+
`);function generateScriptDbTypes(e,t=!1){let n=Object.values(e.tables).map(e=>di(e.name,e.fields,t)),r=fi([{namespace:e.namespace,types:n}],t);return`${xa}\n\n${r}`}function isGeneratedScriptDbTypes(e){return e.includes("Generated by `tailor function script`")}function assertGeneratedTypeScript(e,t){let{errors:n}=Ni(K.basename(e),t);if(n.length>0)throw Qr(`Generated ${e} does not parse as TypeScript: ${n.map(e=>e.message).join(`; `)}`)}function generateScriptSkeleton(e){let{getDBImportPath:t,namespace:n}=e;return[`/**`,` * One-off script. Run it on the platform without deploying:`,` *`,` * tailor function run <this file>`,` *`,` * For large tables, batch updates by primary-key range instead of one`,` * statement over the whole table; see the "Performance and Large Tables"`,` * section in @tailor-platform/sdk's docs/services/tailordb/migration.md.`,` */`,``,`import { getDB } from ${JSON.stringify(t)};`,``,`export default async function main() {`,` const db = getDB(${JSON.stringify(n)});`,``,` // A transaction rolls every change back when any statement fails.`,` await db.transaction().execute(async (trx) => {`,` // e.g. await trx.updateTable("MyTable").set({ ... }).where("id", "=", "...").execute();`,` });`,`}`,``].join(`
|
|
71
71
|
`)}function loadScriptSchemaSnapshot(e){let t=K.join(K.dirname(e),ba);if(!Y.existsSync(t))return null;let n;try{n=JSON.parse(Y.readFileSync(t,`utf-8`))}catch(e){throw U({code:`SCRIPT_SNAPSHOT_INVALID`,message:`Failed to parse schema snapshot ${t}: ${e instanceof Error?e.message:e}.`,suggestion:"Re-run `tailor function script` to regenerate it.",cause:e})}let r=n;if(typeof r!=`object`||!r||typeof r.namespace!=`string`||typeof r.tables!=`object`||r.tables===null)throw unexpectedSnapshotShapeError(t);let i=r.source??`remote`;if(i!==`local`&&i!==`remote`)throw unexpectedSnapshotShapeError(t);let a={...r};delete a.source;try{return{snapshotPath:t,source:i,snapshot:Tn(a)}}catch{throw unexpectedSnapshotShapeError(t)}}function unexpectedSnapshotShapeError(e){return U({code:`SCRIPT_SNAPSHOT_INVALID`,message:`Schema snapshot ${e} has an unexpected shape.`,suggestion:"Re-run `tailor function script` to regenerate it."})}async function verifyScriptSchemaSnapshot(e){let{client:t,workspaceId:n,config:r,configArgValue:i,sidecar:a}=e,{snapshot:o}=a,s=o.namespace;if(Fr(r).includes(s)){let{namespaces:t}=await qe({configPath:i,namespaces:[s]}),n=t[0];if(n){let t=Object.fromEntries(Object.values(n.tables).map(e=>[e.name,Re(e)])),r=Ct(generatedContractSnapshot(o),generatedContractSnapshot({...o,tables:t}));if(cn(r))throw schemaDriftError(`the local table definitions`,r,e)}}let c=await Tt(t,n,s),l=Ct(a.source===`local`?generatedContractSnapshot(o):Tn(pickTableFields(o)),a.source===`local`?generatedContractSnapshot(c):Tn(pickTableFields(c)));if(cn(l))throw schemaDriftError(`the deployed schema`,l,e)}function fieldsOnlySnapshot(e){return Tn(pickTableFields(Rr(e)))}function generatedContractSnapshot(e){let t=fieldsOnlySnapshot(e),n=Object.fromEntries(Object.entries(t.tables).map(([t,n])=>{let r=e.tables[t],i=Object.fromEntries(Object.entries(n.fields).map(([e,t])=>[e,preserveGeneratedContract(t,r?.fields[e])]));return[t,{...n,fields:i}]}));return Tn({...t,tables:n})}function preserveGeneratedContract(e,t){let n=e.fields?Object.fromEntries(Object.entries(e.fields).map(([e,n])=>[e,preserveGeneratedContract(n,t?.fields?.[e])])):void 0,r=t?.hooks?.create!==void 0||t?.default!==void 0||t?.optionalOnCreate===!0;return{...e,...n===void 0?{}:{fields:n},...r?{optionalOnCreate:!0}:{}}}function pickTableFields(e){let t=Object.fromEntries(Object.entries(e.tables).map(([e,t])=>[e,{name:t.name,pluralForm:t.pluralForm,fields:t.fields}]));return{...e,tables:t}}function schemaDriftError(e,t,n){let r=[...n.configArgValue===`tailor.config.ts`?[]:[`--config=${n.configArgValue}`],`--workspace-id=${n.workspaceId}`,...n.profileArgValue===void 0?[]:[`--profile=${n.profileArgValue}`]],i=H({command:`tailor`,args:[`function`,`script`,n.scriptArgValue,...n.sidecar.source===`remote`?[`--remote`]:[],...r]}),a=H({command:`tailor`,args:[`function`,`run`,n.scriptArgValue,`--allow-schema-drift`,...r]});return U({code:`SCRIPT_SCHEMA_DRIFT`,message:`Schema drift detected: ${ba} next to the script no longer matches ${e}.`,details:Vn(t),suggestion:`Refresh the generated types with ${i}, or run anyway with ${a}.`})}const Sa=C({name:`run`,aliases:[`test-run`],description:`Run a function on the Tailor Platform server without deploying.`,args:q.object({...M,file:W(q.string(),{positional:!0,description:`Path to the function file`}),name:W(q.string().optional(),{alias:`n`,description:"Workflow job name to run (matches the `name` field of createWorkflowJob)"}),arg:W(q.string().optional(),{alias:`a`,description:`JSON argument to pass to the function`}),"machine-user":W(q.string().optional(),{alias:`m`,description:`Machine user name for authentication. Falls back to the active profile's default machine user.`,env:`TAILOR_PLATFORM_MACHINE_USER_NAME`}),config:W(q.string().default(`tailor.config.ts`),{alias:`c`,description:`Path to SDK config file`}),"allow-schema-drift":W(q.boolean().default(!1),{description:`Run a scaffolded script even when its schema snapshot no longer matches`})}),notes:"You can pass either a source file (`.ts`) or a pre-bundled file (`.js`).\nWhen a `.js` file is provided, detection and bundling are skipped and the file is executed as-is.\n\nA script scaffolded by `function script` with a generated `db.ts` is checked against its `db.snapshot.json` before execution and refused on schema drift; pass `--allow-schema-drift` to run it anyway. The check compares table and field structure; hook and validator code changes are not detected.\n\n`test-run` is a deprecated alias of this command and will be removed in v3.\n\n> [!WARNING]\n> Workflow job `.start()` calls do not work in this mode.\n> Started jobs are not executed; only the target job's `body` function runs in isolation.",examples:[{cmd:`resolvers/add.ts --arg '{"a":1,"b":2}'`,desc:`Run a resolver with input arguments`},{cmd:`workflows/sample.ts --name validate-order`,desc:`Run a specific workflow job by name`},{cmd:`build/resolvers/add.js --arg '{"a":1,"b":2}'`,desc:`Run a pre-bundled .js file directly`}],run:async e=>{invokedViaTestRunAlias(process.argv)&&x.warn("`tailor function test-run` is deprecated and will be removed in v3. Use `tailor function run` instead.");let n=x.jsonMode,r=K.resolve(e.file);if(!Y.existsSync(r))throw U({code:`FILE_NOT_FOUND`,message:`File not found: ${r}`});let{config:i}=await z(e.config),a=resolveAuthNamespace(i.auth),o=await resolveMachineUserName({cliMachineUser:e[`machine-user`],cliMachineUserSource:xr(e[`machine-user`]),profile:e.profile,authConfig:i.auth}),{client:s,workspaceId:c}=await A({profile:e.profile,workspaceId:e[`workspace-id`]}),l=await resolveMachineUser({client:s,workspaceId:c,authNamespace:a,machineUserName:o,authConfig:i.auth}),u=K.relative(process.cwd(),r),d=r.endsWith(`.js`);if(!d){if(e[`allow-schema-drift`])Y.existsSync(K.join(K.dirname(r),`db.snapshot.json`))&&x.warn(`Skipping the schema snapshot check (--allow-schema-drift).`);else{let t=loadScriptSchemaSnapshot(r);t&&(x.info(`Checking the script's schema snapshot for drift...`),await verifyScriptSchemaSnapshot({client:s,workspaceId:c,config:i,configArgValue:e.config,scriptArgValue:e.file,profileArgValue:e.profile,sidecar:t}))}}let f,p,m,h;if(d)p=K.basename(r),f=Y.readFileSync(r,`utf-8`),x.info(`Using pre-bundled script ${S.bold(p)}`);else{x.info(`Detecting function type from ${S.path(u)}`);let t=await detectFunctionType({filePath:r,jobName:e.name});m=t.type,h=t.name,x.info(`Detected: ${S.bold(t.type)} ${S.info(`"${t.name}"`)}`),t.type===`resolver`&&e.arg&&!t.hasInput&&(x.warn(`--arg is ignored because this resolver has no input schema. Define "input" in your resolver to use --arg.`),e.arg=void 0),x.info(`Bundling...`);let n=de(i),a=K.dirname(i.path);({bundledCode:f,scriptName:p}=await bundleForRun({detected:t,sourceFile:r,baseDir:a,defaultPermission:t.type===`resolver`?ue({config:i.resolver,filePath:r,baseDir:a}):void 0,env:i.env??{},inlineSourcemap:n.inlineSourcemap,inlineSourcemapDependencySources:n.inlineSourcemapDependencySources,logLevel:n.logLevel,treeshake:n.treeshake,dateDefault:ce(i),machineUser:l,workspaceId:c})),x.info(`Bundled as ${S.bold(p)}`)}let g=Si(t,{namespace:a,machineUserName:l.name});x.info(`Executing on workspace ${S.dim(c)}...`);let _;if(e.arg!==void 0)try{_=JSON.parse(e.arg)}catch(e){throw U({code:`FUNCTION_ARG_INVALID`,message:`Invalid --arg JSON: ${e instanceof Error?e.message:e}`,command:`function run`,cause:e})}let v=await nt({client:s,workspaceId:c,name:p,code:f,arg:_,invoker:g});if(n)x.out({success:v.success,scriptName:p,functionType:m,functionName:h,logs:v.logs,result:v.result,error:v.error});else{if(v.success?x.success(`Execution succeeded`):x.error(`Execution failed`),v.logs.trim()){x.log(S.bold(`
|
|
72
72
|
Logs:`));for(let e of v.logs.split(`
|
|
73
73
|
`))x.log(` ${e}`)}if(v.result&&v.success){x.log(S.bold(`
|