@tailor-platform/sdk 2.0.0-next.1 → 2.0.0-next.3

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 (429) hide show
  1. package/CHANGELOG.md +514 -2
  2. package/README.md +9 -9
  3. package/agent-skills/{tailor-sdk → tailor}/SKILL.md +3 -1
  4. package/dist/aigateway-B3oZZE6v.mjs +15 -0
  5. package/dist/aigateway-B3oZZE6v.mjs.map +1 -0
  6. package/dist/application-D2hSK1ZL.mjs +3 -0
  7. package/dist/{application-DqS1yBg3.mjs → application-eHp9jXld.mjs} +1397 -684
  8. package/dist/application-eHp9jXld.mjs.map +1 -0
  9. package/dist/{authconnection-D2MhtTN5.mjs → authconnection-BGQM8FZI.mjs} +2 -2
  10. package/dist/authconnection-BGQM8FZI.mjs.map +1 -0
  11. package/dist/cli/cache/bundle-cache.d.mts +1 -0
  12. package/dist/cli/cache/store.d.mts +1 -0
  13. package/dist/cli/cache/types.d.mts +1 -0
  14. package/dist/cli/commands/api/api-call.d.mts +19 -0
  15. package/dist/cli/commands/api/index.d.mts +3 -0
  16. package/dist/cli/commands/deploy/aigateway.d.mts +1 -0
  17. package/dist/cli/commands/deploy/application.d.mts +1 -0
  18. package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
  19. package/dist/cli/commands/deploy/auth.d.mts +1 -0
  20. package/dist/cli/commands/deploy/deploy.d.mts +24 -0
  21. package/dist/cli/commands/deploy/executor.d.mts +1 -0
  22. package/dist/cli/commands/deploy/function-registry-types.d.mts +12 -0
  23. package/dist/cli/commands/deploy/function-registry.d.mts +2 -0
  24. package/dist/cli/commands/deploy/idp.d.mts +1 -0
  25. package/dist/cli/commands/deploy/resolver.d.mts +1 -0
  26. package/dist/cli/commands/deploy/secret-manager.d.mts +1 -0
  27. package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
  28. package/dist/cli/commands/deploy/tailordb/index.d.mts +1 -0
  29. package/dist/cli/commands/deploy/types.d.mts +1 -0
  30. package/dist/cli/commands/deploy/workflow.d.mts +1 -0
  31. package/dist/cli/commands/executor/get.d.mts +29 -0
  32. package/dist/cli/commands/executor/jobs.d.mts +123 -0
  33. package/dist/cli/commands/executor/list.d.mts +19 -0
  34. package/dist/cli/commands/executor/transform.d.mts +41 -0
  35. package/dist/cli/commands/executor/trigger.d.mts +41 -0
  36. package/dist/cli/commands/executor/webhook.d.mts +22 -0
  37. package/dist/cli/commands/function/get.d.mts +18 -0
  38. package/dist/cli/commands/function/list.d.mts +22 -0
  39. package/dist/cli/commands/function/transform.d.mts +11 -0
  40. package/dist/cli/commands/generate/options.d.mts +7 -0
  41. package/dist/cli/commands/generate/seed/bundler.d.mts +26 -0
  42. package/dist/cli/commands/generate/service.d.mts +11 -0
  43. package/dist/cli/commands/machineuser/list.d.mts +27 -0
  44. package/dist/cli/commands/machineuser/token.d.mts +22 -0
  45. package/dist/cli/commands/oauth2client/get.d.mts +18 -0
  46. package/dist/cli/commands/oauth2client/list.d.mts +20 -0
  47. package/dist/cli/commands/oauth2client/transform.d.mts +20 -0
  48. package/dist/cli/commands/organization/folder/create.d.mts +18 -0
  49. package/dist/cli/commands/organization/folder/delete.d.mts +16 -0
  50. package/dist/cli/commands/organization/folder/get.d.mts +17 -0
  51. package/dist/cli/commands/organization/folder/list.d.mts +22 -0
  52. package/dist/cli/commands/organization/folder/update.d.mts +18 -0
  53. package/dist/cli/commands/organization/get.d.mts +16 -0
  54. package/dist/cli/commands/organization/list.d.mts +15 -0
  55. package/dist/cli/commands/organization/transform.d.mts +27 -0
  56. package/dist/cli/commands/organization/tree.d.mts +25 -0
  57. package/dist/cli/commands/organization/update.d.mts +17 -0
  58. package/dist/cli/commands/remove.d.mts +16 -0
  59. package/dist/cli/commands/show.d.mts +34 -0
  60. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +28 -0
  61. package/dist/cli/commands/tailordb/migrate/config.d.mts +19 -0
  62. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +223 -0
  63. package/dist/cli/commands/tailordb/migrate/generate.d.mts +17 -0
  64. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +202 -0
  65. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +98 -0
  66. package/dist/cli/commands/tailordb/migrate/types.d.mts +7 -0
  67. package/dist/cli/commands/tailordb/truncate.d.mts +19 -0
  68. package/dist/cli/commands/workflow/executions.d.mts +75 -0
  69. package/dist/cli/commands/workflow/get.d.mts +29 -0
  70. package/dist/cli/commands/workflow/list.d.mts +19 -0
  71. package/dist/cli/commands/workflow/resume.d.mts +23 -0
  72. package/dist/cli/commands/workflow/start.d.mts +61 -0
  73. package/dist/cli/commands/workflow/status.d.mts +5 -0
  74. package/dist/cli/commands/workflow/transform.d.mts +33 -0
  75. package/dist/cli/commands/workflow/wait.d.mts +16 -0
  76. package/dist/cli/commands/workflow/waiter.d.mts +23 -0
  77. package/dist/cli/commands/workspace/app/health.d.mts +18 -0
  78. package/dist/cli/commands/workspace/app/list.d.mts +22 -0
  79. package/dist/cli/commands/workspace/app/transform.d.mts +18 -0
  80. package/dist/cli/commands/workspace/create.d.mts +25 -0
  81. package/dist/cli/commands/workspace/delete.d.mts +15 -0
  82. package/dist/cli/commands/workspace/get.d.mts +17 -0
  83. package/dist/cli/commands/workspace/list.d.mts +17 -0
  84. package/dist/cli/commands/workspace/restore.d.mts +15 -0
  85. package/dist/cli/commands/workspace/transform.d.mts +16 -0
  86. package/dist/cli/commands/workspace/user/invite.d.mts +22 -0
  87. package/dist/cli/commands/workspace/user/list.d.mts +22 -0
  88. package/dist/cli/commands/workspace/user/remove.d.mts +17 -0
  89. package/dist/cli/commands/workspace/user/transform.d.mts +8 -0
  90. package/dist/cli/commands/workspace/user/update.d.mts +22 -0
  91. package/dist/cli/erd-viewer-assets/app.js +279 -36
  92. package/dist/cli/erd-viewer-assets/index.html +4 -0
  93. package/dist/cli/erd-viewer-assets/styles.css +252 -5
  94. package/dist/cli/index.d.mts +18 -3
  95. package/dist/cli/index.mjs +2193 -519
  96. package/dist/cli/index.mjs.map +1 -1
  97. package/dist/cli/lib.d.mts +81 -21048
  98. package/dist/cli/lib.mjs +8 -12
  99. package/dist/cli/lib.mjs.map +1 -1
  100. package/dist/cli/query/index.d.mts +35 -0
  101. package/dist/cli/services/application.d.mts +1 -0
  102. package/dist/cli/services/auth/service.d.mts +1 -0
  103. package/dist/cli/services/executor/service.d.mts +1 -0
  104. package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
  105. package/dist/cli/services/http-adapter/service.d.mts +1 -0
  106. package/dist/cli/services/resolver/service.d.mts +1 -0
  107. package/dist/cli/services/tailordb/service.d.mts +1 -0
  108. package/dist/cli/services/workflow/bundler.d.mts +1 -0
  109. package/dist/cli/services/workflow/service.d.mts +1 -0
  110. package/dist/cli/shared/args.d.mts +13 -0
  111. package/dist/cli/shared/client.d.mts +1273 -0
  112. package/dist/cli/shared/config-loader.d.mts +26 -0
  113. package/dist/cli/shared/context.d.mts +28 -0
  114. package/dist/cli/shared/script-executor.d.mts +75 -0
  115. package/dist/cli/shared/seed-chunker.d.mts +46 -0
  116. package/dist/cli/shared/trigger-context.d.mts +1 -0
  117. package/dist/cli/shared/type-generator.d.mts +20 -0
  118. package/dist/cli/ts-hook.d.mts +4 -0
  119. package/dist/cli/ts-hook.mjs +303 -0
  120. package/dist/completion/zsh-worker.zsh +1577 -1098
  121. package/dist/configure/config/index.d.mts +21 -0
  122. package/dist/configure/config/types.d.mts +103 -0
  123. package/dist/configure/index.d.mts +122 -8
  124. package/dist/configure/index.mjs +74 -34
  125. package/dist/configure/index.mjs.map +1 -1
  126. package/dist/configure/services/aigateway/index.d.mts +17 -0
  127. package/dist/configure/services/aigateway/types.d.mts +11 -0
  128. package/dist/configure/services/auth/index.d.mts +34 -0
  129. package/dist/configure/services/auth/types.d.mts +172 -0
  130. package/dist/configure/services/executor/executor.d.mts +81 -0
  131. package/dist/configure/services/executor/index.d.mts +8 -0
  132. package/dist/configure/services/executor/operation.d.mts +45 -0
  133. package/dist/configure/services/executor/trigger/event.d.mts +272 -0
  134. package/dist/configure/services/executor/trigger/index.d.mts +8 -0
  135. package/dist/configure/services/executor/trigger/schedule.d.mts +25 -0
  136. package/dist/configure/services/executor/trigger/webhook.d.mts +44 -0
  137. package/dist/configure/services/executor/types.d.mts +2 -0
  138. package/dist/configure/services/http-adapter/http-adapter.d.mts +125 -0
  139. package/dist/configure/services/http-adapter/index.d.mts +2 -0
  140. package/dist/configure/services/idp/index.d.mts +64 -0
  141. package/dist/configure/services/idp/permission.d.mts +80 -0
  142. package/dist/configure/services/idp/types.d.mts +20 -0
  143. package/dist/configure/services/index.d.mts +32 -0
  144. package/dist/configure/services/resolver/index.d.mts +5 -0
  145. package/dist/configure/services/resolver/resolver.d.mts +75 -0
  146. package/dist/configure/services/resolver/types.d.mts +6 -0
  147. package/dist/configure/services/secrets/index.d.mts +38 -0
  148. package/dist/configure/services/secrets/types.d.mts +11 -0
  149. package/dist/configure/services/staticwebsite/index.d.mts +19 -0
  150. package/dist/configure/services/staticwebsite/types.d.mts +11 -0
  151. package/dist/configure/services/tailordb/index.d.mts +4 -0
  152. package/dist/configure/services/tailordb/permission.d.mts +119 -0
  153. package/dist/configure/services/tailordb/schema.d.mts +456 -0
  154. package/dist/configure/services/tailordb/types.d.mts +149 -0
  155. package/dist/configure/services/workflow/index.d.mts +6 -0
  156. package/dist/configure/services/workflow/job.d.mts +79 -0
  157. package/dist/configure/services/workflow/types.d.mts +2 -0
  158. package/dist/configure/services/workflow/wait-point.d.mts +73 -0
  159. package/dist/configure/services/workflow/workflow.d.mts +54 -0
  160. package/dist/configure/types/aigateway-name.d.mts +12 -0
  161. package/dist/configure/types/connection-name.d.mts +12 -0
  162. package/dist/configure/types/field-runtime.d.mts +17 -0
  163. package/dist/configure/types/field.d.mts +9 -0
  164. package/dist/configure/types/field.types.d.mts +109 -0
  165. package/dist/configure/types/idp-name.d.mts +12 -0
  166. package/dist/configure/types/index.d.mts +4 -0
  167. package/dist/configure/types/machine-user.d.mts +2 -0
  168. package/dist/configure/types/type.d.mts +82 -0
  169. package/dist/context-Bd266-ru.mjs.map +1 -1
  170. package/dist/{crashreport-BsjAkFWw.mjs → crashreport-Cg-pqRJV.mjs} +9 -11
  171. package/dist/{crashreport-BsjAkFWw.mjs.map → crashreport-Cg-pqRJV.mjs.map} +1 -1
  172. package/dist/{crashreport-pr6Rhvza.mjs → crashreport-D3RjuSYb.mjs} +1 -1
  173. package/dist/enum-constants-j9QBF0cB.mjs.map +1 -1
  174. package/dist/{errors-Dtf2WPaW.mjs → errors-118wUDRr.mjs} +2 -2
  175. package/dist/errors-118wUDRr.mjs.map +1 -0
  176. package/dist/field-runtime-CAnh_oow.mjs +183 -0
  177. package/dist/field-runtime-CAnh_oow.mjs.map +1 -0
  178. package/dist/{file-_oUZo76X.mjs → file-BbdFGdMV.mjs} +2 -10
  179. package/dist/file-BbdFGdMV.mjs.map +1 -0
  180. package/dist/{file-utils-DcyIPFQh.mjs → file-utils-CYZnO1pX.mjs} +5 -5
  181. package/dist/file-utils-CYZnO1pX.mjs.map +1 -0
  182. package/dist/{globals-Crz8o65k.mjs → globals-DhwZj0RB.mjs} +5 -47
  183. package/dist/globals-DhwZj0RB.mjs.map +1 -0
  184. package/dist/{idp-BDbK5gjm.mjs → idp-Bx25ZBdN.mjs} +9 -1
  185. package/dist/idp-Bx25ZBdN.mjs.map +1 -0
  186. package/dist/{interceptor-D-q1rvRl.mjs → interceptor-e33JtsC-.mjs} +2 -2
  187. package/dist/interceptor-e33JtsC-.mjs.map +1 -0
  188. package/dist/kysely/index.d.mts +2 -3
  189. package/dist/{kysely-type-DR8uzZTA.mjs → kysely-type-5eAChaHO.mjs} +4 -2
  190. package/dist/kysely-type-5eAChaHO.mjs.map +1 -0
  191. package/dist/{logger-CxF-Ex5d.mjs → logger-BEiZZ3qT.mjs} +3 -3
  192. package/dist/{logger-CxF-Ex5d.mjs.map → logger-BEiZZ3qT.mjs.map} +1 -1
  193. package/dist/parser/service/auth/index.d.mts +1 -0
  194. package/dist/parser/service/auth/schema.d.mts +1 -0
  195. package/dist/parser/service/http-adapter/index.d.mts +1 -0
  196. package/dist/parser/service/http-adapter/schema.d.mts +1 -0
  197. package/dist/parser/service/idp/types.d.mts +4 -0
  198. package/dist/parser/service/tailordb/types.d.mts +126 -0
  199. package/dist/platform-serialize-RoRtBS0v.mjs +46 -0
  200. package/dist/platform-serialize-RoRtBS0v.mjs.map +1 -0
  201. package/dist/plugin/builtin/enum-constants/index.d.mts +16 -1
  202. package/dist/plugin/builtin/file-utils/index.d.mts +16 -1
  203. package/dist/plugin/builtin/file-utils/index.mjs +1 -1
  204. package/dist/plugin/builtin/kysely-type/index.d.mts +16 -1
  205. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  206. package/dist/plugin/builtin/seed/index.d.mts +45 -1
  207. package/dist/plugin/builtin/seed/index.mjs +1 -1
  208. package/dist/plugin/get-generated-type.d.mts +17 -0
  209. package/dist/plugin/index.d.mts +3 -117
  210. package/dist/plugin/index.mjs +1 -1
  211. package/dist/plugin/index.mjs.map +1 -1
  212. package/dist/plugin/manager.d.mts +1 -0
  213. package/dist/plugin/types.d.mts +243 -0
  214. package/dist/plugin/with-context.d.mts +102 -0
  215. package/dist/{runtime-n9NCkjee.mjs → register-ts-hook-ZpBacA07.mjs} +4374 -2446
  216. package/dist/register-ts-hook-ZpBacA07.mjs.map +1 -0
  217. package/dist/{registry-DdsYlL_P.mjs → registry-DH4m7eYo.mjs} +4 -2
  218. package/dist/registry-DH4m7eYo.mjs.map +1 -0
  219. package/dist/{repl-editor-DmGr9zMw.mjs → repl-editor-BCozyiNq.mjs} +6 -5
  220. package/dist/{repl-editor-DmGr9zMw.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
  221. package/dist/runtime/aigateway.d.mts +35 -0
  222. package/dist/runtime/aigateway.mjs +3 -0
  223. package/dist/runtime/authconnection.d.mts +31 -2
  224. package/dist/runtime/authconnection.mjs +1 -1
  225. package/dist/runtime/context.d.mts +46 -2
  226. package/dist/runtime/file.d.mts +210 -2
  227. package/dist/runtime/file.mjs +2 -2
  228. package/dist/runtime/globals.d.mts +31 -19
  229. package/dist/runtime/iconv.d.mts +121 -2
  230. package/dist/runtime/idp.d.mts +186 -2
  231. package/dist/runtime/idp.mjs +1 -1
  232. package/dist/runtime/index.d.mts +47 -9
  233. package/dist/runtime/index.mjs +6 -5
  234. package/dist/runtime/secretmanager.d.mts +54 -2
  235. package/dist/runtime/types.d.mts +20 -0
  236. package/dist/runtime/workflow.d.mts +112 -2
  237. package/dist/runtime/workflow.mjs +2 -2
  238. package/dist/{schema-BhkpP5Hw.mjs → schema-89dYxNC2.mjs} +25 -187
  239. package/dist/schema-89dYxNC2.mjs.map +1 -0
  240. package/dist/{secret-file-DBqZhjFQ.mjs → secret-file-aoNXJmKa.mjs} +28 -3
  241. package/dist/secret-file-aoNXJmKa.mjs.map +1 -0
  242. package/dist/secretmanager-Bd45j7an.mjs +98 -0
  243. package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
  244. package/dist/seed/index.d.mts +1 -2
  245. package/dist/{seed-jf3008-h.mjs → seed-CRSQJI_z.mjs} +16 -6
  246. package/dist/seed-CRSQJI_z.mjs.map +1 -0
  247. package/dist/service-CkIwEctX.mjs +3 -0
  248. package/dist/{service-DU1mVzri.mjs → service-DeZeYa06.mjs} +3 -3
  249. package/dist/{service-DU1mVzri.mjs.map → service-DeZeYa06.mjs.map} +1 -1
  250. package/dist/service-ufH7sS8o.mjs +797 -0
  251. package/dist/service-ufH7sS8o.mjs.map +1 -0
  252. package/dist/{client-z_oHGVNy.mjs → service_pb-CIrhGwHk.mjs} +38 -466
  253. package/dist/service_pb-CIrhGwHk.mjs.map +1 -0
  254. package/dist/service_pb-DjwIn4jO.mjs +3 -0
  255. package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +204 -0
  256. package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +58 -0
  257. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +316 -0
  258. package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +161 -0
  259. package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +2070 -0
  260. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +861 -0
  261. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +112 -0
  262. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +69 -0
  263. package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +439 -0
  264. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +564 -0
  265. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +145 -0
  266. package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +451 -0
  267. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +192 -0
  268. package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +50 -0
  269. package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +407 -0
  270. package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +430 -0
  271. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +73 -0
  272. package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +17 -0
  273. package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +309 -0
  274. package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +591 -0
  275. package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +330 -0
  276. package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +113 -0
  277. package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +346 -0
  278. package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +54 -0
  279. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +3576 -0
  280. package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +187 -0
  281. package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +29 -0
  282. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +429 -0
  283. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +90 -0
  284. package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +684 -0
  285. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +797 -0
  286. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +330 -0
  287. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +183 -0
  288. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +842 -0
  289. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +276 -0
  290. package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2015 -0
  291. package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +483 -0
  292. package/dist/{telemetry-CdqJEzkj.mjs → telemetry-BRVdwh14.mjs} +3 -3
  293. package/dist/telemetry-BRVdwh14.mjs.map +1 -0
  294. package/dist/telemetry-CkbkeJxl.mjs +3 -0
  295. package/dist/test-env-key-D7UkZp99.mjs +75 -0
  296. package/dist/test-env-key-D7UkZp99.mjs.map +1 -0
  297. package/dist/types/aigateway.generated.d.mts +9 -0
  298. package/dist/types/app-config.generated.d.mts +4 -0
  299. package/dist/types/auth-connection.generated.d.mts +20 -0
  300. package/dist/types/auth.generated.d.mts +145 -0
  301. package/dist/types/executor.generated.d.mts +177 -0
  302. package/dist/types/helpers.d.mts +39 -0
  303. package/dist/types/http-adapter.generated.d.mts +17 -0
  304. package/dist/{workflow.generated-DtQwEo-x.d.mts → types/idp.generated.d.mts} +98 -185
  305. package/dist/types/resolver.generated.d.mts +39 -0
  306. package/dist/types/secrets.generated.d.mts +14 -0
  307. package/dist/types/staticwebsite.generated.d.mts +10 -0
  308. package/dist/types/tailordb.generated.d.mts +428 -0
  309. package/dist/types/workflow.generated.d.mts +12 -0
  310. package/dist/types-C-lQ4se3.mjs +4 -0
  311. package/dist/types-CXF2OYdR.mjs +199 -0
  312. package/dist/types-CXF2OYdR.mjs.map +1 -0
  313. package/dist/utils/test/index.d.mts +5 -98
  314. package/dist/utils/test/index.mjs +4 -13
  315. package/dist/utils/test/index.mjs.map +1 -1
  316. package/dist/utils/test/mock.d.mts +87 -0
  317. package/dist/vitest/environment.d.mts +1 -2
  318. package/dist/vitest/environment.mjs +2 -2
  319. package/dist/vitest/environment.mjs.map +1 -1
  320. package/dist/vitest/index.d.mts +12 -428
  321. package/dist/vitest/index.mjs +741 -10
  322. package/dist/vitest/index.mjs.map +1 -1
  323. package/dist/vitest/mock-kysely.d.mts +62 -0
  324. package/dist/vitest/mock.d.mts +9 -0
  325. package/dist/vitest/mocks/aigateway.d.mts +30 -0
  326. package/dist/vitest/mocks/authconnection.d.mts +28 -0
  327. package/dist/vitest/mocks/file.d.mts +41 -0
  328. package/dist/vitest/mocks/iconv.d.mts +27 -0
  329. package/dist/vitest/mocks/idp.d.mts +44 -0
  330. package/dist/vitest/mocks/secretmanager.d.mts +33 -0
  331. package/dist/vitest/mocks/tailordb.d.mts +71 -0
  332. package/dist/vitest/mocks/workflow.d.mts +115 -0
  333. package/dist/vitest/setup.d.mts +1 -2
  334. package/dist/vitest/setup.mjs +2 -2
  335. package/dist/vitest/workflow-local.d.mts +40 -0
  336. package/dist/{workflow-DgemCAz3.mjs → workflow-Bo59B75H.mjs} +16 -4
  337. package/dist/workflow-Bo59B75H.mjs.map +1 -0
  338. package/docs/cli/application.md +98 -250
  339. package/docs/cli/auth.md +48 -292
  340. package/docs/cli/completion.md +1 -25
  341. package/docs/cli/crashreport.md +3 -61
  342. package/docs/cli/executor.md +72 -183
  343. package/docs/cli/function.md +14 -130
  344. package/docs/cli/organization.md +11 -221
  345. package/docs/cli/plugin.md +29 -0
  346. package/docs/cli/query.md +2 -22
  347. package/docs/cli/secret.md +71 -251
  348. package/docs/cli/setup.md +138 -40
  349. package/docs/cli/skills.md +50 -39
  350. package/docs/cli/staticwebsite.md +32 -180
  351. package/docs/cli/tailordb.md +97 -323
  352. package/docs/cli/upgrade.md +2 -22
  353. package/docs/cli/user.md +78 -247
  354. package/docs/cli/workflow.md +150 -172
  355. package/docs/cli/workspace.md +165 -538
  356. package/docs/cli-reference.md +184 -78
  357. package/docs/configuration.md +10 -4
  358. package/docs/github-actions.md +64 -24
  359. package/docs/migration/v2.md +735 -0
  360. package/docs/multi-environment.md +29 -7
  361. package/docs/plugin/index.md +6 -6
  362. package/docs/quickstart.md +8 -7
  363. package/docs/runtime.md +6 -2
  364. package/docs/services/aigateway.md +18 -2
  365. package/docs/services/auth.md +44 -52
  366. package/docs/services/executor.md +3 -3
  367. package/docs/services/http-adapter.md +16 -1
  368. package/docs/services/idp.md +55 -2
  369. package/docs/services/resolver.md +7 -7
  370. package/docs/services/secret.md +11 -11
  371. package/docs/services/staticwebsite.md +8 -2
  372. package/docs/services/tailordb-migration.md +24 -22
  373. package/docs/services/tailordb.md +39 -14
  374. package/docs/services/workflow.md +18 -18
  375. package/docs/testing.md +108 -50
  376. package/package.json +46 -35
  377. package/postinstall.mjs +4 -6
  378. package/dist/application-DB2r36Et.mjs +0 -3
  379. package/dist/application-DqS1yBg3.mjs.map +0 -1
  380. package/dist/authconnection-D2MhtTN5.mjs.map +0 -1
  381. package/dist/authconnection-DvUQAjQS.d.mts +0 -39
  382. package/dist/client-Dbohmtkv.mjs +0 -3
  383. package/dist/client-z_oHGVNy.mjs.map +0 -1
  384. package/dist/context-BuuIb8CC.d.mts +0 -68
  385. package/dist/errors-Dtf2WPaW.mjs.map +0 -1
  386. package/dist/field-DOsJCPFa.mjs +0 -22
  387. package/dist/field-DOsJCPFa.mjs.map +0 -1
  388. package/dist/file-BB8Vs9O_.d.mts +0 -250
  389. package/dist/file-_oUZo76X.mjs.map +0 -1
  390. package/dist/file-utils-DcyIPFQh.mjs.map +0 -1
  391. package/dist/globals-Crz8o65k.mjs.map +0 -1
  392. package/dist/http-adapter.generated-WgMnb7Sb.d.mts +0 -580
  393. package/dist/iconv-Co-TOPuH.d.mts +0 -122
  394. package/dist/idp-BDbK5gjm.mjs.map +0 -1
  395. package/dist/idp-DrhVrLmV.d.mts +0 -160
  396. package/dist/index-5vPyRu1y.d.mts +0 -18
  397. package/dist/index-B7AKc18V.d.mts +0 -47
  398. package/dist/index-BlpzXncY.d.mts +0 -1016
  399. package/dist/index-CK7u9isy.d.mts +0 -46
  400. package/dist/index-CNYe5lnW.d.mts +0 -18
  401. package/dist/index-DjUdWlzf.d.mts +0 -18
  402. package/dist/index-ZePLwxw7.d.mts +0 -208
  403. package/dist/interceptor-D-q1rvRl.mjs.map +0 -1
  404. package/dist/job-fuc3j1Ma.mjs +0 -53
  405. package/dist/job-fuc3j1Ma.mjs.map +0 -1
  406. package/dist/kysely-type-DR8uzZTA.mjs.map +0 -1
  407. package/dist/mock-BjFj5o1I.mjs +0 -804
  408. package/dist/mock-BjFj5o1I.mjs.map +0 -1
  409. package/dist/registry-DdsYlL_P.mjs.map +0 -1
  410. package/dist/runtime-n9NCkjee.mjs.map +0 -1
  411. package/dist/schema-BhkpP5Hw.mjs.map +0 -1
  412. package/dist/secret-file-DBqZhjFQ.mjs.map +0 -1
  413. package/dist/secretmanager-B3n4KHfm.d.mts +0 -55
  414. package/dist/seed-jf3008-h.mjs.map +0 -1
  415. package/dist/service-CCL8ruDf.mjs +0 -459
  416. package/dist/service-CCL8ruDf.mjs.map +0 -1
  417. package/dist/service-D6yonf2I.mjs +0 -3
  418. package/dist/telemetry-CdqJEzkj.mjs.map +0 -1
  419. package/dist/telemetry-ClwW5ohF.mjs +0 -3
  420. package/dist/test-env-key-D9kM6ETE.mjs +0 -49
  421. package/dist/test-env-key-D9kM6ETE.mjs.map +0 -1
  422. package/dist/types-B2RpYyA_.mjs +0 -371
  423. package/dist/types-B2RpYyA_.mjs.map +0 -1
  424. package/dist/types-ClhIrW_C.mjs +0 -4
  425. package/dist/types-DCUhgpyI.d.mts +0 -784
  426. package/dist/types-DhO_VEZd.d.mts +0 -574
  427. package/dist/types-DwDgacni.d.mts +0 -338
  428. package/dist/workflow-BbKvGLQg.d.mts +0 -96
  429. package/dist/workflow-DgemCAz3.mjs.map +0 -1
@@ -1,34 +1,37 @@
1
1
  import { n as isSdkBranded } from "./brand-Eo4pLXPJ.mjs";
2
- import { a as fetchAll, d as initOAuth2Client, l as fetchUserInfo } from "./client-z_oHGVNy.mjs";
3
2
  import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
4
- import { a as parseBoolean, n as logger, r as styles } from "./logger-CxF-Ex5d.mjs";
5
- import { a as TailorFieldSchema, i as AuthInvokerSchema, n as ExecutorSchema, o as loadFilesWithIgnores, r as AuthConfigSchema, s as functionSchema, t as createExecutorService } from "./service-CCL8ruDf.mjs";
3
+ import { a as parseBoolean, n as logger, r as styles } from "./logger-BEiZZ3qT.mjs";
4
+ import { a as AuthConfigSchema, c as stripTailorDBTypeBuilderHelpers, d as applyRelationMetadataToFieldConfig, f as buildRelationInfo, g as functionSchema, h as loadFilesWithIgnores, l as TailorDBServiceConfigSchema, m as validateRelationConfig, n as loadExecutor, o as AuthInvokerSchema, p as processRelationMetadata, s as TailorFieldSchema, t as createExecutorService, u as TailorDBTypeSchema } from "./service-ufH7sS8o.mjs";
6
5
  import { t as multiline } from "./multiline-sfHpTZZK.mjs";
7
6
  import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
8
- import { t as createCLIError } from "./errors-Dtf2WPaW.mjs";
9
- import { n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-DBqZhjFQ.mjs";
7
+ import { i as userAgent, n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-aoNXJmKa.mjs";
8
+ import { t as createCLIError } from "./errors-118wUDRr.mjs";
10
9
  import { t as isPluginGeneratedType } from "./type-source-DH_LH20p.mjs";
11
10
  import { builtinModules, createRequire } from "node:module";
11
+ import * as path from "pathe";
12
+ import { join, resolve } from "pathe";
13
+ import { resolveTSConfig } from "pkg-types";
12
14
  import { z } from "zod";
15
+ import { create } from "@bufbuild/protobuf";
13
16
  import * as fs$1 from "node:fs";
14
17
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
15
- import * as path from "pathe";
16
- import { join, resolve } from "pathe";
18
+ import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";
17
19
  import { pathToFileURL } from "node:url";
18
- import * as os from "node:os";
19
- import { parseTOML, parseYAML, stringifyYAML } from "confbox";
20
+ import { parseYAML, stringifyYAML } from "confbox";
20
21
  import { findUpSync } from "find-up-simple";
21
22
  import { lt } from "semver";
22
23
  import { xdgConfig } from "xdg-basedir";
23
- import { Code, ConnectError } from "@connectrpc/connect";
24
- import { resolveTSConfig } from "pkg-types";
24
+ import { OAuth2Client } from "@badgateway/oauth2-client";
25
+ import { Code, ConnectError, createClient } from "@connectrpc/connect";
26
+ import { getGlobalDispatcher } from "undici";
27
+ import pLimit from "p-limit";
25
28
  import * as crypto from "node:crypto";
26
29
  import * as rolldown from "rolldown";
27
30
  import * as fs from "node:fs/promises";
28
31
  import { parseSync } from "oxc-parser";
29
32
  import * as inflection from "inflection";
30
33
  import * as globals from "globals";
31
- import pLimit from "p-limit";
34
+ import * as os from "node:os";
32
35
 
33
36
  //#region src/parser/app-config/log-level.ts
34
37
  const LOG_LEVELS = [
@@ -62,7 +65,7 @@ const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(val
62
65
  * label-compatible prefix is added at the metadata boundary, so user-facing
63
66
  * configs only need to carry a UUID.
64
67
  */
65
- const AppConfigSchema = z.object({
68
+ const AppConfigSchema = z.strictObject({
66
69
  id: z.uuid({ message: "'id' must be a UUID." }).optional(),
67
70
  name: z.string().min(1, { message: "'name' must be a non-empty string." }),
68
71
  env: z.record(z.string(), envValueSchema).optional(),
@@ -85,7 +88,7 @@ const AppConfigSchema = z.object({
85
88
 
86
89
  //#endregion
87
90
  //#region src/parser/plugin-config/schema.ts
88
- const PluginConfigSchema = z.object({
91
+ const PluginConfigSchema = z.looseObject({
89
92
  id: z.string(),
90
93
  description: z.string(),
91
94
  importPath: z.string().optional(),
@@ -96,10 +99,639 @@ const PluginConfigSchema = z.object({
96
99
  onTailorDBReady: functionSchema.optional(),
97
100
  onResolverReady: functionSchema.optional(),
98
101
  onExecutorReady: functionSchema.optional()
99
- }).passthrough().refine((p) => {
102
+ }).refine((p) => {
100
103
  return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
101
104
  }, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
102
105
 
106
+ //#endregion
107
+ //#region src/cli/shared/apply-concurrency.ts
108
+ /**
109
+ * Default cap on concurrent operator RPCs when `TAILOR_APPLY_CONCURRENCY` is
110
+ * unset. Chosen to tame the create burst (a fresh workspace fires one
111
+ * `create*` per resource at once) that triggers the platform-side
112
+ * `already_exists` race on compound creates, while still deploying quickly.
113
+ */
114
+ const DEFAULT_APPLY_CONCURRENCY = 16;
115
+ /**
116
+ * Resolve the maximum number of operator RPCs to run in parallel.
117
+ *
118
+ * Resolution order:
119
+ * 1. `TAILOR_APPLY_CONCURRENCY` env var (positive integer)
120
+ * 2. `DEFAULT_APPLY_CONCURRENCY`
121
+ *
122
+ * A fresh-workspace apply creates every resource at once; firing all of them
123
+ * concurrently overloads the platform, whose responses then come back as
124
+ * `Unavailable`/`ResourceExhausted` and drive retries into the non-idempotent
125
+ * compound-create `already_exists` race. Capping bounds the worst case.
126
+ * @returns Concurrency cap (always >= 1)
127
+ */
128
+ function resolveApplyConcurrency() {
129
+ const envValue = process.env.TAILOR_APPLY_CONCURRENCY;
130
+ if (envValue !== void 0) {
131
+ const trimmed = envValue.trim();
132
+ if (trimmed !== "" && /^[1-9]\d*$/.test(trimmed)) return Number.parseInt(trimmed, 10);
133
+ }
134
+ return DEFAULT_APPLY_CONCURRENCY;
135
+ }
136
+ /**
137
+ * Create a limiter capped at the resolved apply concurrency. The returned
138
+ * function defers a task until a slot is free, then resolves with its result.
139
+ *
140
+ * Share a single instance across all RPCs that should contend for the same
141
+ * budget (e.g. one per operator client) so the cap bounds total in-flight
142
+ * calls, not per-call-site bursts.
143
+ * @returns A limiter that defers tasks beyond the concurrency cap
144
+ */
145
+ function createApplyLimiter() {
146
+ return pLimit(resolveApplyConcurrency());
147
+ }
148
+ /**
149
+ * Comparator that orders `name`-bearing items by `name`, for a stable,
150
+ * reproducible apply order within the concurrency cap.
151
+ *
152
+ * Uses a code-point comparison rather than `localeCompare` so the order does
153
+ * not depend on the runtime's default locale/collation.
154
+ * @param a - Left item
155
+ * @param b - Right item
156
+ * @returns Negative, zero, or positive per code-point ordering of the names
157
+ */
158
+ function byName(a, b) {
159
+ if (a.name < b.name) return -1;
160
+ if (a.name > b.name) return 1;
161
+ return 0;
162
+ }
163
+
164
+ //#endregion
165
+ //#region src/cli/shared/client.ts
166
+ const defaultPlatformBaseUrl = "https://api.tailor.tech";
167
+ const defaultConsoleBaseUrl = "https://console.tailor.tech";
168
+ const defaultOAuth2ClientId = "cpoc_0Iudir72fqSpqC6GQ58ri1cLAqcq5vJl";
169
+ const oauth2DiscoveryEndpoint = "/.well-known/oauth-authorization-server/oauth2/platform";
170
+ const tokenPlatformConfigs = /* @__PURE__ */ new Map();
171
+ function getEnvPlatformUrl() {
172
+ return process.env.TAILOR_PLATFORM_URL ?? process.env.PLATFORM_URL;
173
+ }
174
+ function getEnvOAuth2ClientId() {
175
+ return process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID ?? process.env.PLATFORM_OAUTH2_CLIENT_ID;
176
+ }
177
+ function normalizeBaseUrl(value) {
178
+ const url = new URL(value);
179
+ url.hash = "";
180
+ url.search = "";
181
+ return url.toString().replace(/\/$/, "");
182
+ }
183
+ function getEffectivePlatformConfig(config = {}) {
184
+ const platformUrl = config.platformUrl ?? getEnvPlatformUrl();
185
+ const oauth2ClientId = config.oauth2ClientId ?? getEnvOAuth2ClientId();
186
+ const consoleUrl = config.consoleUrl ?? process.env.TAILOR_PLATFORM_CONSOLE_URL;
187
+ const effective = {
188
+ ...platformUrl ? { platformUrl } : {},
189
+ ...oauth2ClientId ? { oauth2ClientId } : {},
190
+ ...consoleUrl ? { consoleUrl } : {}
191
+ };
192
+ return Object.keys(effective).length > 0 ? effective : void 0;
193
+ }
194
+ function rememberPlatformConfigForToken(accessToken, config) {
195
+ const effectiveConfig = getEffectivePlatformConfig(config);
196
+ if (effectiveConfig) tokenPlatformConfigs.set(accessToken, effectiveConfig);
197
+ else tokenPlatformConfigs.delete(accessToken);
198
+ }
199
+ function getPlatformConfigForToken(accessToken) {
200
+ return tokenPlatformConfigs.get(accessToken);
201
+ }
202
+ function getPlatformBaseUrl(config = {}) {
203
+ return normalizeBaseUrl(config.platformUrl ?? getEnvPlatformUrl() ?? "https://api.tailor.tech");
204
+ }
205
+ function isDefaultPlatform(config) {
206
+ return getPlatformBaseUrl(config) === normalizeBaseUrl(defaultPlatformBaseUrl);
207
+ }
208
+ function getOAuth2ClientId(config = {}) {
209
+ return config.oauth2ClientId ?? getEnvOAuth2ClientId() ?? defaultOAuth2ClientId;
210
+ }
211
+ function inferConsoleBaseUrl(platformBaseUrl) {
212
+ const platformUrl = new URL(platformBaseUrl);
213
+ if (platformUrl.hostname.startsWith("api.")) {
214
+ platformUrl.hostname = platformUrl.hostname.replace(/^api\./, "console.");
215
+ return normalizeBaseUrl(platformUrl.toString());
216
+ }
217
+ return defaultConsoleBaseUrl;
218
+ }
219
+ function getConsoleBaseUrl(config = {}) {
220
+ if (config.consoleUrl) return normalizeBaseUrl(config.consoleUrl);
221
+ if (config.platformUrl) {
222
+ const inferredUrl = inferConsoleBaseUrl(config.platformUrl);
223
+ if (inferredUrl !== "https://console.tailor.tech") return inferredUrl;
224
+ }
225
+ if (process.env.TAILOR_PLATFORM_CONSOLE_URL) return normalizeBaseUrl(process.env.TAILOR_PLATFORM_CONSOLE_URL);
226
+ return inferConsoleBaseUrl(getPlatformBaseUrl(config));
227
+ }
228
+ /**
229
+ * Initialize an OAuth2 client for Tailor Platform.
230
+ * @param config - Optional platform connection settings
231
+ * @returns Configured OAuth2 client
232
+ */
233
+ function initOAuth2Client(config) {
234
+ return new OAuth2Client({
235
+ clientId: getOAuth2ClientId(config),
236
+ server: getPlatformBaseUrl(config),
237
+ discoveryEndpoint: oauth2DiscoveryEndpoint
238
+ });
239
+ }
240
+ /**
241
+ * Initialize an Operator client with the given access token.
242
+ * @param accessToken - Access token for authentication
243
+ * @param config - Optional platform connection settings
244
+ * @returns Configured Operator client
245
+ */
246
+ async function initOperatorClient(accessToken, config) {
247
+ const platformConfig = config ?? getPlatformConfigForToken(accessToken);
248
+ const [{ createTracingInterceptor }, { OperatorService }] = await Promise.all([import("./interceptor-e33JtsC-.mjs"), import("./service_pb-DjwIn4jO.mjs")]);
249
+ const interceptors = [
250
+ await userAgentInterceptor(),
251
+ await bearerTokenInterceptor(accessToken),
252
+ retryInterceptor(),
253
+ errorHandlingInterceptor(),
254
+ createTracingInterceptor(),
255
+ concurrencyLimitInterceptor()
256
+ ];
257
+ return createClient(OperatorService, await createTransport(getPlatformBaseUrl(platformConfig), interceptors));
258
+ }
259
+ /**
260
+ * Create a Connect transport using connect-node (HTTP/2).
261
+ *
262
+ * connect-node works on both Node.js and Bun. connect-web is not used because
263
+ * it does not support client_streaming, which is required for function uploads.
264
+ * @param baseUrl - Base URL for the transport
265
+ * @param interceptors - Request interceptors
266
+ * @returns Configured transport
267
+ */
268
+ async function createTransport(baseUrl, interceptors) {
269
+ const { createConnectTransport } = await import("@connectrpc/connect-node");
270
+ return createConnectTransport({
271
+ httpVersion: "2",
272
+ baseUrl,
273
+ interceptors
274
+ });
275
+ }
276
+ /**
277
+ * Create an interceptor that sets a User-Agent header.
278
+ * @returns User-Agent interceptor
279
+ */
280
+ async function userAgentInterceptor() {
281
+ const ua = await userAgent();
282
+ return (next) => async (req) => {
283
+ req.header.set("User-Agent", ua);
284
+ return await next(req);
285
+ };
286
+ }
287
+ /**
288
+ * Create an interceptor that sets the Authorization bearer token.
289
+ * @param accessToken - Access token to use
290
+ * @returns Bearer token interceptor
291
+ */
292
+ async function bearerTokenInterceptor(accessToken) {
293
+ return (next) => async (req) => {
294
+ req.header.set("Authorization", `Bearer ${accessToken}`);
295
+ return await next(req);
296
+ };
297
+ }
298
+ /**
299
+ * Create an interceptor that retries failed unary requests with backoff.
300
+ *
301
+ * Retries any unary method on `Unavailable`/`ResourceExhausted`, and `Internal`
302
+ * only for methods declared idempotent, up to 3 attempts (despite the historical
303
+ * "idempotent" naming, the first two codes are retried regardless of idempotency).
304
+ * As a targeted exception for the deploy/apply flow, a post-retry `AlreadyExists`
305
+ * from an allowlisted Create (see `RETRY_SAFE_CREATE_METHODS`) is treated as
306
+ * success, since it means a prior attempt already committed the resource
307
+ * server-side. A first-attempt `AlreadyExists` from such a Create still
308
+ * surfaces, but is routed to crash/error reporting first (the top-level handler
309
+ * skips `ConnectError`), so the otherwise-silent compound-create race is
310
+ * trackable.
311
+ * @internal
312
+ * @returns Retry interceptor
313
+ */
314
+ function retryInterceptor() {
315
+ return (next) => async (req) => {
316
+ if (req.stream) return await next(req);
317
+ let lastError;
318
+ for (let i = 0; i < 3; i++) {
319
+ if (i > 0) await waitRetryBackoff(i);
320
+ try {
321
+ return await next(req);
322
+ } catch (error) {
323
+ if (isRetrySafeCreateAlreadyExists(error, req.method.name)) {
324
+ if (i > 0) {
325
+ logger.debug(`retry: ${req.method.name} returned AlreadyExists on attempt ${i + 1}; treating as success (prior attempt likely committed)`);
326
+ return synthesizeEmptyUnaryResponse(req);
327
+ }
328
+ const { reportCrash } = await import("./crashreport-D3RjuSYb.mjs");
329
+ await reportCrash(error, "handledError");
330
+ }
331
+ if (isRetirable(error, req.method.idempotency)) {
332
+ lastError = error;
333
+ logger.debug(`retry: ${req.method.name} attempt ${i + 1} failed with ${connectCodeName(error)}; retrying`);
334
+ continue;
335
+ }
336
+ throw error;
337
+ }
338
+ }
339
+ throw lastError;
340
+ };
341
+ }
342
+ /**
343
+ * Create an interceptor that caps the number of concurrent unary RPCs.
344
+ *
345
+ * A fresh-workspace apply fires one `create*` per resource at once; left
346
+ * unbounded, the platform sheds load as `Unavailable`/`ResourceExhausted`,
347
+ * which drives retries into the non-idempotent compound-create `already_exists`
348
+ * race (#1350). One shared limiter per client bounds total in-flight calls
349
+ * across every deploy resource, not just a single call site. Streaming RPCs
350
+ * (e.g. function uploads) are not gated.
351
+ * @internal
352
+ * @returns Concurrency-limiting interceptor
353
+ */
354
+ function concurrencyLimitInterceptor() {
355
+ const limit = createApplyLimiter();
356
+ return (next) => async (req) => {
357
+ if (req.stream) return await next(req);
358
+ return await limit(() => next(req));
359
+ };
360
+ }
361
+ /**
362
+ * Human-readable name for the Connect status code of an error, for diagnostics.
363
+ * @param error - Error thrown by a request (expected to be a ConnectError)
364
+ * @returns The Code name (e.g., "Unavailable"), or "unknown" for non-ConnectError
365
+ */
366
+ function connectCodeName(error) {
367
+ return error instanceof ConnectError ? Code[error.code] : "unknown";
368
+ }
369
+ /**
370
+ * Create RPCs for which a post-retry `AlreadyExists` may be treated as success.
371
+ *
372
+ * Membership is deliberately an allowlist, not `startsWith("Create")`: swallowing
373
+ * synthesizes an empty response (see `synthesizeEmptyUnaryResponse`), which is only
374
+ * safe when every caller ignores the response body. These are the deploy/apply
375
+ * resource creations that fire under heavy parallelism and discard their response.
376
+ *
377
+ * Intentionally excluded because their callers read the response body — swallowing
378
+ * would hand back an empty message and corrupt downstream state:
379
+ * - `CreateIdPClient` (uses `resp.client.clientSecret` to seed the secret vault)
380
+ * - `CreateWorkflowJobFunction` (uses `response.jobFunction.version`)
381
+ * - `CreateWorkspace` / `CreatePersonalAccessToken` / `CreateDeployment` /
382
+ * `CreateOrganizationFolder` (interactive commands that return created data)
383
+ *
384
+ * `CreateFunctionRegistry` is client-streaming and never reaches this path
385
+ * (streaming requests bypass the retry loop entirely).
386
+ *
387
+ * An allowlist miss is safe: the resource simply loses race protection and an
388
+ * `already_exists` surfaces loudly, as before — never a silent empty response.
389
+ *
390
+ * A drift guard (see client.test.ts) fails CI if any `client.create*` used in the
391
+ * deploy flow is neither listed here nor explicitly classified as response-consuming,
392
+ * so a newly added apply create cannot silently miss this list.
393
+ * @internal
394
+ */
395
+ const RETRY_SAFE_CREATE_METHODS = /* @__PURE__ */ new Set([
396
+ "CreateAIGateway",
397
+ "CreateApplication",
398
+ "CreateAuthConnection",
399
+ "CreateAuthHook",
400
+ "CreateAuthIDPConfig",
401
+ "CreateAuthMachineUser",
402
+ "CreateAuthOAuth2Client",
403
+ "CreateAuthSCIMConfig",
404
+ "CreateAuthSCIMResource",
405
+ "CreateAuthService",
406
+ "CreateExecutorExecutor",
407
+ "CreateIdPService",
408
+ "CreatePipelineResolver",
409
+ "CreatePipelineService",
410
+ "CreateSecretManagerSecret",
411
+ "CreateSecretManagerVault",
412
+ "CreateStaticWebsite",
413
+ "CreateTailorDBGQLPermission",
414
+ "CreateTailorDBService",
415
+ "CreateTailorDBType",
416
+ "CreateTenantConfig",
417
+ "CreateUserProfileConfig",
418
+ "CreateWorkflow"
419
+ ]);
420
+ /**
421
+ * Whether an error is an `AlreadyExists` from a retry-safe Create RPC.
422
+ *
423
+ * Only `AlreadyExists` stands in for "my prior write already landed"; for other
424
+ * verbs/codes it would be a real conflict that must surface.
425
+ * @param error - Error thrown by the request
426
+ * @param methodName - RPC method name (e.g., "CreateTailorDBType")
427
+ * @returns True if the error is an `AlreadyExists` from a retry-safe Create method
428
+ */
429
+ function isRetrySafeCreateAlreadyExists(error, methodName) {
430
+ return error instanceof ConnectError && error.code === Code.AlreadyExists && RETRY_SAFE_CREATE_METHODS.has(methodName);
431
+ }
432
+ /**
433
+ * Build a default (empty) unary response for the request's output message.
434
+ *
435
+ * Used when a retried Create is determined to have already succeeded on a prior
436
+ * attempt: callers in the deploy pipeline ignore Create response bodies, so an
437
+ * empty message faithfully represents the already-applied state.
438
+ * @param req - Unary request whose output schema is used
439
+ * @returns A synthesized unary response with an empty output message
440
+ */
441
+ function synthesizeEmptyUnaryResponse(req) {
442
+ return {
443
+ stream: false,
444
+ service: req.service,
445
+ method: req.method,
446
+ header: new Headers(),
447
+ message: create(req.method.output),
448
+ trailer: new Headers()
449
+ };
450
+ }
451
+ /**
452
+ * Base delay (ms) for the first retry. Subsequent attempts double it.
453
+ *
454
+ * Kept relatively large so a retry does not immediately race an original request
455
+ * that is still settling server-side under load (e.g. a compound create whose
456
+ * response was lost), which is what triggers the `already_exists` race.
457
+ */
458
+ const RETRY_BASE_DELAY_MS = 500;
459
+ /**
460
+ * Wait for an exponential backoff delay with jitter.
461
+ * @param attempt - Current retry attempt number (1-based)
462
+ * @returns Promise that resolves after the delay
463
+ */
464
+ function waitRetryBackoff(attempt) {
465
+ const backoff = RETRY_BASE_DELAY_MS * 2 ** (attempt - 1) * (1 + .1 * (Math.random() * 2 - 1));
466
+ return new Promise((resolve) => setTimeout(resolve, backoff));
467
+ }
468
+ /**
469
+ * Determine whether the given error is retriable for the method idempotency.
470
+ * @param error - Error thrown by the request
471
+ * @param idempotency - Method idempotency level
472
+ * @returns True if the error should be retried
473
+ */
474
+ function isRetirable(error, idempotency) {
475
+ if (!(error instanceof ConnectError)) return false;
476
+ switch (error.code) {
477
+ case Code.ResourceExhausted:
478
+ case Code.Unavailable: return true;
479
+ case Code.Internal: return idempotency === MethodOptions_IdempotencyLevel.NO_SIDE_EFFECTS || idempotency === MethodOptions_IdempotencyLevel.IDEMPOTENT;
480
+ default: return false;
481
+ }
482
+ }
483
+ /**
484
+ * Create an interceptor that enhances error messages from the Operator API.
485
+ * @returns Error handling interceptor
486
+ */
487
+ function errorHandlingInterceptor() {
488
+ return (next) => async (req) => {
489
+ try {
490
+ return await next(req);
491
+ } catch (error) {
492
+ if (error instanceof ConnectError) {
493
+ const { operation, resourceType } = parseMethodName(req.method.name);
494
+ const requestParams = formatRequestParams(req.message);
495
+ throw new ConnectError(`Failed to ${operation} ${resourceType}: ${error.rawMessage}\nRequest: ${requestParams}`, error.code, error.metadata);
496
+ }
497
+ throw error;
498
+ }
499
+ };
500
+ }
501
+ /**
502
+ * @internal
503
+ * @param methodName - RPC method name (e.g., "CreateWorkspace")
504
+ * @returns Parsed operation and resource type
505
+ */
506
+ function parseMethodName(methodName) {
507
+ const match = methodName.match(/^(Create|Update|Delete|Set|List|Get)(.+)$/);
508
+ if (!match) return {
509
+ operation: "perform",
510
+ resourceType: "resource"
511
+ };
512
+ const [, action, resource] = match;
513
+ return {
514
+ operation: action.toLowerCase(),
515
+ resourceType: resource
516
+ };
517
+ }
518
+ /**
519
+ * JSON.stringify replacer that converts BigInt values to strings.
520
+ * @param _key - Object key (unused)
521
+ * @param value - Value to serialize
522
+ * @returns Serializable value
523
+ */
524
+ function bigIntReplacer(_key, value) {
525
+ if (typeof value === "bigint") return value.toString();
526
+ return value;
527
+ }
528
+ /**
529
+ * @internal
530
+ * @param message - Request message to format
531
+ * @returns Pretty-printed JSON or error placeholder
532
+ */
533
+ function formatRequestParams(message) {
534
+ try {
535
+ if (message && typeof message === "object" && "toJson" in message) return JSON.stringify(message.toJson(), bigIntReplacer, 2);
536
+ return JSON.stringify(message, bigIntReplacer, 2);
537
+ } catch {
538
+ return "(unable to serialize request)";
539
+ }
540
+ }
541
+ const MAX_PAGE_SIZE = 1e3;
542
+ /**
543
+ * Fetch all paginated resources by repeatedly calling the given function.
544
+ * @template T
545
+ * @param fn - Page fetcher returning items and next page token
546
+ * @returns All fetched items
547
+ */
548
+ async function fetchAll(fn) {
549
+ const items = [];
550
+ let pageToken = "";
551
+ while (true) {
552
+ const [batch, nextPageToken] = await fn(pageToken, MAX_PAGE_SIZE);
553
+ items.push(...batch);
554
+ if (!nextPageToken) break;
555
+ pageToken = nextPageToken;
556
+ }
557
+ return items;
558
+ }
559
+ /**
560
+ * @internal
561
+ * @param error - Error value to inspect
562
+ * @returns Whether the error is a Connect NotFound error
563
+ */
564
+ function isNotFoundError(error) {
565
+ return error instanceof ConnectError && error.code === Code.NotFound;
566
+ }
567
+ /**
568
+ * Fetch all paginated resources, treating an absent resource group as empty.
569
+ * @template T
570
+ * @param fn - Page fetcher returning items and next page token
571
+ * @returns Items fetched before pagination completes or the fetcher raises NotFound
572
+ */
573
+ async function fetchAllTolerant(fn) {
574
+ return await fetchAll(async (pageToken, maxPageSize) => {
575
+ try {
576
+ return await fn(pageToken, maxPageSize);
577
+ } catch (error) {
578
+ if (isNotFoundError(error)) return [[], ""];
579
+ throw error;
580
+ }
581
+ });
582
+ }
583
+ /**
584
+ * Fetch a single resource, treating NotFound as an absent value.
585
+ * @template T
586
+ * @param fn - Resource getter
587
+ * @returns Fetched resource, or undefined when the getter raises NotFound
588
+ */
589
+ async function getOrNull(fn) {
590
+ try {
591
+ return await fn();
592
+ } catch (error) {
593
+ if (isNotFoundError(error)) return;
594
+ throw error;
595
+ }
596
+ }
597
+ /**
598
+ * Fetch paginated resources with an optional upper bound on the number of
599
+ * items returned. When `limit` is 0 or undefined the function behaves
600
+ * like `fetchAll` and returns every page. When `limit` is positive the
601
+ * function stops once enough items are collected, requesting smaller
602
+ * pages as it approaches the boundary.
603
+ * @template T
604
+ * @param fn - Page fetcher returning items and next page token
605
+ * @param options - Pagination options
606
+ * @returns Fetched items (length <= limit when limit > 0)
607
+ */
608
+ async function fetchPaged(fn, options) {
609
+ const limit = options?.limit;
610
+ const unbounded = limit === void 0 || limit === 0;
611
+ const items = [];
612
+ let pageToken = "";
613
+ while (true) {
614
+ const pageSize = unbounded ? MAX_PAGE_SIZE : Math.min(limit - items.length, MAX_PAGE_SIZE);
615
+ if (!unbounded && pageSize <= 0) break;
616
+ const [batch, nextPageToken] = await fn(pageToken, pageSize);
617
+ items.push(...batch);
618
+ if (!unbounded && items.length >= limit) break;
619
+ if (!nextPageToken) break;
620
+ pageToken = nextPageToken;
621
+ }
622
+ if (!unbounded && items.length > limit) return items.slice(0, limit);
623
+ return items;
624
+ }
625
+ /**
626
+ * Fetch user info from the Tailor Platform userinfo endpoint.
627
+ * @param accessToken - Access token for the current user
628
+ * @param config - Optional platform connection settings
629
+ * @returns Parsed user info
630
+ */
631
+ async function fetchUserInfo(accessToken, config) {
632
+ const userInfoUrl = new URL("/auth/platform/userinfo", getPlatformBaseUrl(config)).href;
633
+ const resp = await fetch(userInfoUrl, { headers: {
634
+ Authorization: `Bearer ${accessToken}`,
635
+ "User-Agent": await userAgent()
636
+ } });
637
+ if (!resp.ok) throw new Error(`Failed to fetch user info: ${resp.statusText}`);
638
+ const rawJson = await resp.json();
639
+ return z.object({
640
+ sub: z.string(),
641
+ email: z.string()
642
+ }).parse(rawJson);
643
+ }
644
+ /**
645
+ * Resolve "name:url" patterns to actual Static Website URLs.
646
+ * @param client - Operator client instance
647
+ * @param workspaceId - Workspace ID
648
+ * @param urls - URLs or name:url patterns
649
+ * @param context - Logging context (e.g., "CORS", "OAuth2 redirect URIs")
650
+ * @param options - Optional behavior overrides
651
+ * @returns Resolved URLs (or the original pattern for entries marked as
652
+ * expected-but-not-yet-deployed via `options.expectedLocalNames`)
653
+ */
654
+ async function resolveStaticWebsiteUrls(client, workspaceId, urls, context, options = {}) {
655
+ if (!urls) return [];
656
+ const { expectedLocalNames } = options;
657
+ return (await Promise.all(urls.map(async (url) => {
658
+ const match = url.match(/:url(\/.*)?$/);
659
+ if (match && match.index !== void 0) {
660
+ const siteName = url.substring(0, match.index);
661
+ const pathSuffix = match[1] || "";
662
+ try {
663
+ const response = await client.getStaticWebsite({
664
+ workspaceId,
665
+ name: siteName
666
+ });
667
+ if (response.staticwebsite?.url) return [response.staticwebsite.url + pathSuffix];
668
+ logger.warn(`Static website "${siteName}" has no URL assigned yet. Excluding from ${context}.`);
669
+ return [];
670
+ } catch (error) {
671
+ if (isNotFoundError(error) && expectedLocalNames?.has(siteName)) return [url];
672
+ logger.warn(`Static website "${siteName}" not found for ${context} configuration. Excluding from ${context}.`);
673
+ return [];
674
+ }
675
+ }
676
+ return [url];
677
+ }))).flat();
678
+ }
679
+ /**
680
+ * Fetch an OAuth2 access token for a machine user.
681
+ * @param url - OAuth2 server base URL
682
+ * @param clientId - Client ID for the machine user
683
+ * @param clientSecret - Client secret for the machine user
684
+ * @returns Access token
685
+ */
686
+ async function fetchMachineUserToken(url, clientId, clientSecret) {
687
+ const tokenEndpoint = new URL("/oauth2/token", url).href;
688
+ const formData = new URLSearchParams();
689
+ formData.append("grant_type", "client_credentials");
690
+ formData.append("client_id", clientId);
691
+ formData.append("client_secret", clientSecret);
692
+ const resp = await fetch(tokenEndpoint, {
693
+ method: "POST",
694
+ headers: {
695
+ "User-Agent": await userAgent(),
696
+ "Content-Type": "application/x-www-form-urlencoded"
697
+ },
698
+ body: formData
699
+ });
700
+ if (!resp.ok) {
701
+ const body = await resp.text().catch(() => "");
702
+ throw new Error(`Failed to fetch machine user token: ${resp.status} ${resp.statusText} ${body.slice(0, 500)}`);
703
+ }
704
+ const rawJson = await resp.json();
705
+ return z.object({
706
+ token_type: z.string(),
707
+ access_token: z.string(),
708
+ expires_in: z.number()
709
+ }).parse(rawJson);
710
+ }
711
+ /**
712
+ * Fetch an OAuth2 token for a platform machine user via client_credentials grant.
713
+ * @param clientId - Client ID for the platform machine user
714
+ * @param clientSecret - Client secret for the platform machine user
715
+ * @param config - Optional platform connection settings
716
+ * @returns OAuth2 token
717
+ */
718
+ async function fetchPlatformMachineUserToken(clientId, clientSecret, config) {
719
+ return await new OAuth2Client({
720
+ clientId,
721
+ clientSecret,
722
+ server: getPlatformBaseUrl(config),
723
+ discoveryEndpoint: oauth2DiscoveryEndpoint
724
+ }).clientCredentials();
725
+ }
726
+ /**
727
+ * Close undici's global HTTP connection pool to prevent libuv UV_HANDLE_CLOSING
728
+ * assertion failure on Windows at process exit (Node.js 23.x+).
729
+ * See: https://github.com/nodejs/node/issues/56645
730
+ */
731
+ async function closeConnectionPool() {
732
+ await getGlobalDispatcher().close();
733
+ }
734
+
103
735
  //#endregion
104
736
  //#region src/cli/shared/token-store.ts
105
737
  const SERVICE_NAME = "tailor-platform-cli";
@@ -170,7 +802,10 @@ const pfProfileSchema = z.object({
170
802
  workspace_id: z.string(),
171
803
  readonly: z.boolean().optional(),
172
804
  machine_user: z.string().optional(),
173
- machine_user_override: z.enum(["allow", "deny"]).optional()
805
+ machine_user_override: z.enum(["allow", "deny"]).optional(),
806
+ platform_url: z.url().optional(),
807
+ oauth2_client_id: z.string().optional(),
808
+ console_url: z.url().optional()
174
809
  });
175
810
  const pfUserSchemaV1 = z.object({
176
811
  access_token: z.string(),
@@ -231,6 +866,114 @@ function platformConfigPath() {
231
866
  return path.join(xdgConfig, "tailor-platform", "config.yaml");
232
867
  }
233
868
  /**
869
+ * Convert stored profile platform fields to platform client settings.
870
+ * @param profile - Profile platform settings
871
+ * @returns Platform client settings
872
+ */
873
+ function platformConfigFromProfile(profile) {
874
+ const config = {
875
+ ...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
876
+ ...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
877
+ ...profile?.console_url ? { consoleUrl: profile.console_url } : {}
878
+ };
879
+ return Object.keys(config).length > 0 ? config : void 0;
880
+ }
881
+ function platformUserKey(user, config) {
882
+ const platformUrl = getPlatformBaseUrl(config);
883
+ if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
884
+ return `${platformUrl}|${user}`;
885
+ }
886
+ function userFromPlatformUserKey(userKey, config) {
887
+ const platformPrefix = `${getPlatformBaseUrl(config)}|`;
888
+ return userKey.startsWith(platformPrefix) ? userKey.slice(platformPrefix.length) : userKey;
889
+ }
890
+ function canUseLegacyUserKey(platformUrl) {
891
+ try {
892
+ return getPlatformBaseUrl() === platformUrl;
893
+ } catch {
894
+ return false;
895
+ }
896
+ }
897
+ function findUserByEmail(users, email, platformConfig) {
898
+ const platformUrl = getPlatformBaseUrl(platformConfig);
899
+ const platformPrefix = `${platformUrl}|`;
900
+ const defaultPlatform = platformUrl === normalizeBaseUrl(defaultPlatformBaseUrl);
901
+ return Object.entries(users).find(([key, entry]) => {
902
+ if (entry?.email !== email) return false;
903
+ return defaultPlatform ? !key.includes("|") : key.startsWith(platformPrefix);
904
+ });
905
+ }
906
+ function findUserEntry(config, user, platformConfig, opts = {}) {
907
+ const userKey = platformUserKey(user, platformConfig);
908
+ const userEntry = config.users[userKey];
909
+ if (userEntry) return {
910
+ userKey,
911
+ userEntry
912
+ };
913
+ const emailMatch = findUserByEmail(config.users, user, platformConfig);
914
+ if (emailMatch?.[1]) return {
915
+ userKey: emailMatch[0],
916
+ userEntry: emailMatch[1]
917
+ };
918
+ const platformUrl = getPlatformBaseUrl(platformConfig);
919
+ if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
920
+ userKey,
921
+ userEntry
922
+ };
923
+ const legacyEntry = config.users[user];
924
+ return legacyEntry ? {
925
+ userKey: user,
926
+ userEntry: legacyEntry
927
+ } : {
928
+ userKey,
929
+ userEntry
930
+ };
931
+ }
932
+ /**
933
+ * Resolve the config user key that would be used for a user on the selected platform.
934
+ * @param config - Platform config
935
+ * @param user - User name
936
+ * @param platformConfig - Optional platform connection settings
937
+ * @param opts - Token lookup options
938
+ * @returns Resolved config user key
939
+ */
940
+ function resolveUserTokenKey(config, user, platformConfig, opts) {
941
+ return findUserEntry(config, user, platformConfig, opts).userKey;
942
+ }
943
+ function resolveConfigUser(config, user, platformConfig, opts) {
944
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
945
+ return userEntry ? userFromPlatformUserKey(userKey, platformConfig) : void 0;
946
+ }
947
+ /**
948
+ * Check whether tokens are registered for a user on the selected platform.
949
+ * @param config - Platform config
950
+ * @param user - User name
951
+ * @param platformConfig - Optional platform connection settings
952
+ * @param opts - Token lookup options
953
+ * @returns True when the user has a registered token entry
954
+ */
955
+ function hasUserTokenEntry(config, user, platformConfig, opts) {
956
+ return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
957
+ }
958
+ function hasUserKeyForName(users, user) {
959
+ return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
960
+ }
961
+ function hasUserEmailEntry(users, user) {
962
+ return Object.values(users).some((entry) => entry?.email === user);
963
+ }
964
+ /**
965
+ * Check whether any platform has tokens registered for a user.
966
+ * @param config - Platform config
967
+ * @param user - User name
968
+ * @returns True when the user has a token entry for any platform
969
+ */
970
+ function hasAnyUserTokenEntry(config, user) {
971
+ return hasUserKeyForName(config.users, user) || hasUserEmailEntry(config.users, user);
972
+ }
973
+ function hasCurrentUserEntry(users, currentUser) {
974
+ return hasUserKeyForName(users, currentUser);
975
+ }
976
+ /**
234
977
  * Migrate a v1 config to v2.
235
978
  * Tokens are kept in the config file (storage: "file") during migration.
236
979
  * They will be moved to the OS keyring on next login or token refresh.
@@ -259,10 +1002,6 @@ function migrateV1ToV2(v1Config) {
259
1002
  function inferEmailFromUserId(user) {
260
1003
  return z.email().safeParse(user).success ? user : void 0;
261
1004
  }
262
- function findConfigUserKey(config, user) {
263
- if (config.users[user]) return user;
264
- return Object.entries(config.users).find(([, entry]) => entry?.email === user)?.[0];
265
- }
266
1005
  function migrateV2ToV3(v2Config) {
267
1006
  const users = {};
268
1007
  for (const [user, entry] of Object.entries(v2Config.users)) {
@@ -284,6 +1023,19 @@ function migrateV2ToV3(v2Config) {
284
1023
  function migrateV1ToV3(v1Config) {
285
1024
  return migrateV2ToV3(migrateV1ToV2(v1Config));
286
1025
  }
1026
+ function formatUnknownError(error) {
1027
+ return error instanceof Error ? error.message : String(error);
1028
+ }
1029
+ async function trySaveTokensInKeyring(user, tokens) {
1030
+ if (!await isKeyringAvailable()) return false;
1031
+ try {
1032
+ await saveKeyringTokens(user, tokens);
1033
+ return true;
1034
+ } catch (error) {
1035
+ logger.warn(`System keyring failed to store credentials. Tokens will be stored in the config file. ${formatUnknownError(error)}`);
1036
+ return false;
1037
+ }
1038
+ }
287
1039
  async function warnIfNewerConfigAvailable(config) {
288
1040
  if (!config.latest_min_sdk_version) return;
289
1041
  if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
@@ -292,22 +1044,21 @@ async function warnIfNewerConfigAvailable(config) {
292
1044
  `);
293
1045
  }
294
1046
  /**
295
- * Read Tailor Platform CLI configuration, migrating from tailorctl or v1 if necessary.
1047
+ * Read Tailor Platform CLI configuration, migrating from v1 if necessary.
296
1048
  * @returns Parsed platform configuration
297
1049
  */
298
1050
  async function readPlatformConfig() {
299
1051
  const configPath = platformConfigPath();
300
1052
  if (!fs$1.existsSync(configPath)) {
301
- logger.warn(`Config not found at ${configPath}, migrating from tailorctl config...`);
302
- const tcConfig = readTailorctlConfig();
303
- const v1Config = tcConfig ? fromTailorctlConfig(tcConfig) : {
304
- version: 1,
1053
+ const config = {
1054
+ version: LATEST_CONFIG_VERSION,
1055
+ min_sdk_version: V3_MIN_SDK_VERSION,
305
1056
  users: {},
306
1057
  profiles: {},
307
1058
  current_user: null
308
1059
  };
309
- writePlatformConfig(v1Config);
310
- return migrateV1ToV3(v1Config);
1060
+ writePlatformConfig(config);
1061
+ return config;
311
1062
  }
312
1063
  const rawConfig = parseYAML(fs$1.readFileSync(configPath, "utf-8"));
313
1064
  tightenSecretFilePermissions(configPath);
@@ -347,7 +1098,7 @@ function toV1ForDisk(config) {
347
1098
  token_expires_at: entry.token_expires_at
348
1099
  };
349
1100
  }
350
- const currentUser = config.current_user && users[config.current_user] ? config.current_user : null;
1101
+ const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
351
1102
  return {
352
1103
  version: 1,
353
1104
  users,
@@ -355,15 +1106,31 @@ function toV1ForDisk(config) {
355
1106
  current_user: currentUser
356
1107
  };
357
1108
  }
1109
+ function hasProfilePlatformSettings(config) {
1110
+ return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
1111
+ }
1112
+ function hasScopedUserKeys(config) {
1113
+ return Object.keys(config.users).some((userKey) => userKey.includes("|"));
1114
+ }
1115
+ function hasUserEmailMetadata(config) {
1116
+ return Object.values(config.users).some((user) => user != null && "email" in user && user.email !== void 0);
1117
+ }
1118
+ function toLatestForDisk(config) {
1119
+ return {
1120
+ ...config.version === 1 ? migrateV1ToV2(config) : config,
1121
+ version: LATEST_CONFIG_VERSION,
1122
+ min_sdk_version: V3_MIN_SDK_VERSION
1123
+ };
1124
+ }
358
1125
  /**
359
1126
  * Write Tailor Platform CLI configuration to disk.
360
- * By default, V2 configs are converted to V1 for backward compatibility, so an
361
- * older SDK can still read the file. Configs containing a keyring user are kept
362
- * as V2 regardless, because the keyring storage variant is not representable in
363
- * V1 and downgrading it would silently drop the user's login. V3 configs are
364
- * kept as V3 because user keys are canonical subject IDs and may include email
365
- * metadata that is not representable in older versions.
366
- * Set TAILOR_USE_KEYRING to write V2 format unconditionally.
1127
+ * By default, file-backed configs without newer fields are converted to V1 for
1128
+ * backward compatibility, so an older SDK can still read the file. Configs
1129
+ * containing a keyring user are kept in V2 or later because the keyring storage
1130
+ * variant is not representable in V1. Configs containing profile-level Platform
1131
+ * settings, platform-scoped user tokens, canonical user IDs, or email metadata
1132
+ * are written in the latest min-SDK-gated format because older SDKs would
1133
+ * silently drop or misread those settings.
367
1134
  *
368
1135
  * The config file may contain access/refresh tokens when the OS keyring is
369
1136
  * unavailable, so it is written via {@link writeSecretFile} so other users
@@ -372,50 +1139,8 @@ function toV1ForDisk(config) {
372
1139
  */
373
1140
  function writePlatformConfig(config) {
374
1141
  const configPath = platformConfigPath();
375
- const hasKeyringUser = config.version === 2 && Object.values(config.users).some((u) => u?.storage === "keyring");
376
- writeSecretFile(configPath, stringifyYAML(config.version === 2 && !process.env.TAILOR_USE_KEYRING && !hasKeyringUser ? toV1ForDisk(config) : config));
377
- }
378
- const tcContextConfigSchema = z.object({
379
- username: z.string().optional(),
380
- controlplaneaccesstoken: z.string().optional(),
381
- controlplanerefreshtoken: z.string().optional(),
382
- controlplanetokenexpiresat: z.string().optional(),
383
- workspaceid: z.string().optional()
384
- });
385
- const tcConfigSchema = z.object({ global: z.object({ context: z.string().optional() }).optional() }).catchall(tcContextConfigSchema.optional());
386
- function readTailorctlConfig() {
387
- const configPath = path.join(os.homedir(), ".tailorctl", "config");
388
- if (!fs$1.existsSync(configPath)) return;
389
- const rawConfig = parseTOML(fs$1.readFileSync(configPath, "utf-8"));
390
- return tcConfigSchema.parse(rawConfig);
391
- }
392
- function fromTailorctlConfig(config) {
393
- const users = {};
394
- const profiles = {};
395
- let currentUser = null;
396
- const currentContext = config.global?.context || "default";
397
- for (const [key, val] of Object.entries(config)) {
398
- if (key === "global") continue;
399
- const context = val;
400
- if (!context.username || !context.controlplaneaccesstoken || !context.controlplanerefreshtoken || !context.controlplanetokenexpiresat || !context.workspaceid) continue;
401
- if (key === currentContext) currentUser = context.username;
402
- profiles[key] = {
403
- user: context.username,
404
- workspace_id: context.workspaceid
405
- };
406
- const user = users[context.username];
407
- if (!user || new Date(user.token_expires_at) < new Date(context.controlplanetokenexpiresat)) users[context.username] = {
408
- access_token: context.controlplaneaccesstoken,
409
- refresh_token: context.controlplanerefreshtoken,
410
- token_expires_at: context.controlplanetokenexpiresat
411
- };
412
- }
413
- return {
414
- version: 1,
415
- users,
416
- profiles,
417
- current_user: currentUser
418
- };
1142
+ const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
1143
+ writeSecretFile(configPath, stringifyYAML(config.version === LATEST_CONFIG_VERSION || hasProfilePlatformSettings(config) || hasScopedUserKeys(config) || hasUserEmailMetadata(config) ? toLatestForDisk(config) : config.version === V2_CONFIG_VERSION && !hasKeyringUser ? toV1ForDisk(config) : config));
419
1144
  }
420
1145
  function validateUUID(value, source) {
421
1146
  const result = z.uuid().safeParse(value);
@@ -430,9 +1155,9 @@ function validateUUID(value, source) {
430
1155
  * @returns Resolved workspace ID
431
1156
  */
432
1157
  async function loadWorkspaceId(opts) {
1158
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
433
1159
  if (opts?.workspaceId) return validateUUID(opts.workspaceId, "--workspace-id option");
434
1160
  if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
435
- const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
436
1161
  if (profile) {
437
1162
  const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
438
1163
  if (!wsId) throw new Error(`Profile "${profile}" not found`);
@@ -471,7 +1196,7 @@ async function loadMachineUserName(opts) {
471
1196
  code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
472
1197
  message: `Profile "${profile}" denies overriding the machine user.`,
473
1198
  details: `This profile fixes the machine user to "${entry.machine_user}" for application-data commands.`,
474
- suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor-sdk profile update ${profile} --machine-user-override allow'.`
1199
+ suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor profile update ${profile} --machine-user-override allow'.`
475
1200
  });
476
1201
  return entry.machine_user;
477
1202
  }
@@ -485,40 +1210,69 @@ async function loadMachineUserName(opts) {
485
1210
  * @returns Resolved access token
486
1211
  */
487
1212
  async function loadAccessToken(opts) {
488
- if (process.env.TAILOR_PLATFORM_TOKEN) return process.env.TAILOR_PLATFORM_TOKEN;
489
- if (process.env.TAILOR_TOKEN) {
490
- logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
491
- return process.env.TAILOR_TOKEN;
1213
+ const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
1214
+ const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
1215
+ if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
1216
+ if (envToken) {
1217
+ rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
1218
+ profile,
1219
+ allowMissingProfile: true
1220
+ }));
1221
+ return envToken;
492
1222
  }
493
1223
  const pfConfig = await readPlatformConfig();
494
- let user;
1224
+ const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
1225
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
1226
+ const user = profileEntry?.user ?? pfConfig.current_user;
1227
+ if (!user) throw new Error(multiline`
1228
+ Tailor Platform token not found.
1229
+ Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor login' command.
1230
+ `);
1231
+ return (await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0)).accessToken;
1232
+ }
1233
+ /**
1234
+ * Load platform connection settings from the active profile.
1235
+ * @param opts - Profile options
1236
+ * @returns Resolved platform connection settings, or undefined for the default environment
1237
+ */
1238
+ async function loadPlatformClientConfig(opts) {
495
1239
  const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
496
- if (profile) {
497
- const u = pfConfig.profiles[profile]?.user;
498
- if (!u) throw new Error(`Profile "${profile}" not found`);
499
- user = u;
500
- } else {
501
- const u = pfConfig.current_user;
502
- if (!u) throw new Error(multiline`
503
- Tailor Platform token not found.
504
- Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor-sdk login' command.
505
- `);
506
- user = u;
1240
+ if (!profile) return;
1241
+ let pfConfig;
1242
+ try {
1243
+ pfConfig = await readPlatformConfig();
1244
+ } catch (error) {
1245
+ if (opts?.allowMissingProfile) return;
1246
+ throw error;
507
1247
  }
508
- return (await fetchLatestToken(pfConfig, user)).accessToken;
1248
+ const profileEntry = pfConfig.profiles[profile];
1249
+ if (!profileEntry) {
1250
+ if (opts?.allowMissingProfile) return;
1251
+ throw new Error(`Profile "${profile}" not found`);
1252
+ }
1253
+ return platformConfigFromProfile(profileEntry);
1254
+ }
1255
+ /**
1256
+ * Load the Tailor Platform Console base URL from environment variables or the active profile.
1257
+ * @param opts - Profile options
1258
+ * @returns Resolved Console base URL
1259
+ */
1260
+ async function loadConsoleBaseUrl(opts) {
1261
+ return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
509
1262
  }
510
1263
  /**
511
1264
  * Resolve the actual token values for a user, reading from keyring or config as appropriate.
512
1265
  * @param userEntry - User entry from the config
513
1266
  * @param user - User identifier
1267
+ * @param label - User-facing identifier used in error messages
514
1268
  * @returns Access token and optional refresh token
515
1269
  */
516
- async function resolveTokens(userEntry, user) {
1270
+ async function resolveTokens(userEntry, user, label = user) {
517
1271
  if (userEntry.storage === "keyring") {
518
1272
  const tokens = await loadKeyringTokens(user);
519
1273
  if (!tokens) throw new Error(multiline`
520
- Credentials not found in OS keyring for "${user}".
521
- Please run 'tailor-sdk login' and try again.
1274
+ Credentials not found in OS keyring for "${label}".
1275
+ Please run 'tailor login' and try again.
522
1276
  `);
523
1277
  return tokens;
524
1278
  }
@@ -528,39 +1282,61 @@ async function resolveTokens(userEntry, user) {
528
1282
  };
529
1283
  }
530
1284
  /**
531
- * Save tokens for a user, writing to keyring or config as appropriate.
1285
+ * Save tokens for a user, writing to keyring by default when available.
532
1286
  * @param config - Platform config
533
1287
  * @param user - User identifier
534
1288
  * @param tokens - Token data to save
535
1289
  * @param tokens.accessToken - Access token to save
536
1290
  * @param tokens.refreshToken - Optional refresh token to save
537
1291
  * @param expiresAt - Token expiration date
538
- * @param metadata - Optional user metadata to persist with the token entry
1292
+ * @param opts - Optional platform and user metadata
539
1293
  */
540
- async function saveUserTokens(config, user, tokens, expiresAt, metadata) {
541
- const email = metadata?.email ?? config.users[user]?.email;
542
- if (process.env.TAILOR_USE_KEYRING && await isKeyringAvailable()) {
543
- await saveKeyringTokens(user, tokens);
544
- config.users[user] = {
545
- token_expires_at: expiresAt,
546
- storage: "keyring",
547
- ...email ? { email } : {}
548
- };
549
- } else config.users[user] = {
550
- access_token: tokens.accessToken,
551
- refresh_token: tokens.refreshToken,
1294
+ async function saveUserTokens(config, user, tokens, expiresAt, opts = {}) {
1295
+ const userKey = platformUserKey(user, opts.platformConfig);
1296
+ const email = opts.email ?? config.users[userKey]?.email;
1297
+ if (await trySaveTokensInKeyring(userKey, tokens)) config.users[userKey] = {
552
1298
  token_expires_at: expiresAt,
553
- storage: "file",
1299
+ storage: "keyring",
554
1300
  ...email ? { email } : {}
555
1301
  };
1302
+ else {
1303
+ if (config.users[userKey]?.storage === "keyring") await deleteKeyringTokens(userKey);
1304
+ config.users[userKey] = {
1305
+ access_token: tokens.accessToken,
1306
+ refresh_token: tokens.refreshToken,
1307
+ token_expires_at: expiresAt,
1308
+ storage: "file",
1309
+ ...email ? { email } : {}
1310
+ };
1311
+ }
556
1312
  }
557
1313
  /**
558
1314
  * Delete tokens for a user from keyring if applicable.
559
1315
  * @param config - Platform config
560
1316
  * @param user - User identifier
1317
+ * @param platformConfig - Optional platform connection settings
1318
+ * @param opts - Token lookup options
561
1319
  */
562
- async function deleteUserTokens(config, user) {
563
- if (config.users[user]?.storage === "keyring") await deleteKeyringTokens(user);
1320
+ async function deleteUserTokens(config, user, platformConfig, opts) {
1321
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
1322
+ if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
1323
+ delete config.users[userKey];
1324
+ }
1325
+ /**
1326
+ * Resolve stored tokens for a user on the selected platform.
1327
+ * @param config - Platform config
1328
+ * @param user - User name
1329
+ * @param platformConfig - Optional platform connection settings
1330
+ * @param opts - Token lookup options
1331
+ * @returns Stored user entry and token values, or undefined when the user is not logged in
1332
+ */
1333
+ async function loadStoredUserTokens(config, user, platformConfig, opts) {
1334
+ const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
1335
+ if (!userEntry) return void 0;
1336
+ return {
1337
+ userEntry,
1338
+ ...await resolveTokens(userEntry, userKey, user)
1339
+ };
564
1340
  }
565
1341
  function updateUserReferences(config, fromUser, toUser) {
566
1342
  if (fromUser === toUser) return;
@@ -572,12 +1348,18 @@ function updateUserReferences(config, fromUser, toUser) {
572
1348
  * @param config - Platform config
573
1349
  * @param legacyUser - Previous user key
574
1350
  * @param canonicalUser - Canonical user key
1351
+ * @param platformConfig - Platform settings for scoped token storage
575
1352
  */
576
- async function removeLegacyUserAlias(config, legacyUser, canonicalUser) {
1353
+ async function removeLegacyUserAlias(config, legacyUser, canonicalUser, platformConfig) {
577
1354
  if (legacyUser === canonicalUser) return;
578
1355
  updateUserReferences(config, legacyUser, canonicalUser);
579
- await deleteUserTokens(config, legacyUser);
580
- delete config.users[legacyUser];
1356
+ const canonicalKey = platformUserKey(canonicalUser, platformConfig);
1357
+ const legacyKeys = /* @__PURE__ */ new Set([legacyUser, platformUserKey(legacyUser, platformConfig)]);
1358
+ for (const legacyKey of legacyKeys) {
1359
+ if (legacyKey === canonicalKey) continue;
1360
+ if (config.users[legacyKey]?.storage === "keyring") await deleteKeyringTokens(legacyKey);
1361
+ delete config.users[legacyKey];
1362
+ }
581
1363
  }
582
1364
  function shouldResolveSubjectOnRefresh(user, userEntry) {
583
1365
  return Boolean(userEntry.email || inferEmailFromUserId(user));
@@ -586,31 +1368,31 @@ function shouldResolveSubjectOnRefresh(user, userEntry) {
586
1368
  * Fetch the latest access token, refreshing if necessary.
587
1369
  * @param config - Platform config
588
1370
  * @param user - User identifier
1371
+ * @param platformConfig - Optional platform connection settings
589
1372
  * @returns Latest access token and the canonical user ID it is stored under
590
1373
  * (the resolved subject when a legacy email key was migrated during refresh,
591
1374
  * otherwise the matching config user)
592
1375
  */
593
- async function fetchLatestToken(config, user) {
594
- const storedUser = findConfigUserKey(config, user);
595
- if (!storedUser) throw new Error(multiline`
596
- User "${user}" not found.
597
- Please verify your user name and login using 'tailor-sdk login' command.
598
- `);
599
- const userEntry = config.users[storedUser];
1376
+ async function fetchLatestToken(config, user, platformConfig) {
1377
+ const { userKey: storedUser, userEntry } = findUserEntry(config, user, platformConfig);
600
1378
  if (!userEntry) throw new Error(multiline`
601
1379
  User "${user}" not found.
602
- Please verify your user name and login using 'tailor-sdk login' command.
1380
+ Please verify your user name and login using 'tailor login' command.
603
1381
  `);
604
- const tokens = await resolveTokens(userEntry, storedUser);
605
- if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) return {
606
- accessToken: tokens.accessToken,
607
- user: storedUser
608
- };
1382
+ const storedConfigUser = userFromPlatformUserKey(storedUser, platformConfig);
1383
+ const tokens = await resolveTokens(userEntry, storedUser, user);
1384
+ if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
1385
+ rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
1386
+ return {
1387
+ accessToken: tokens.accessToken,
1388
+ user: storedConfigUser
1389
+ };
1390
+ }
609
1391
  if (!tokens.refreshToken) throw new Error(multiline`
610
1392
  Token expired.
611
- Please run 'tailor-sdk login' and try again.
1393
+ Please run 'tailor login' and try again.
612
1394
  `);
613
- const client = initOAuth2Client();
1395
+ const client = initOAuth2Client(platformConfig);
614
1396
  let resp;
615
1397
  try {
616
1398
  resp = await client.refreshToken({
@@ -621,15 +1403,15 @@ async function fetchLatestToken(config, user) {
621
1403
  } catch {
622
1404
  throw new Error(multiline`
623
1405
  Failed to refresh token. Your session may have expired.
624
- Please run 'tailor-sdk login' and try again.
1406
+ Please run 'tailor login' and try again.
625
1407
  `);
626
1408
  }
627
1409
  const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
628
- let resolvedUser = storedUser;
629
- const previousEmail = userEntry.email ?? inferEmailFromUserId(storedUser);
1410
+ let resolvedUser = storedConfigUser;
1411
+ const previousEmail = userEntry.email ?? inferEmailFromUserId(user) ?? inferEmailFromUserId(storedConfigUser);
630
1412
  let email = previousEmail;
631
- if (shouldResolveSubjectOnRefresh(storedUser, userEntry)) try {
632
- const userInfo = await fetchUserInfo(resp.accessToken);
1413
+ if (shouldResolveSubjectOnRefresh(user, userEntry) || shouldResolveSubjectOnRefresh(storedConfigUser, userEntry)) try {
1414
+ const userInfo = await fetchUserInfo(resp.accessToken, platformConfig);
633
1415
  resolvedUser = userInfo.sub;
634
1416
  email = userInfo.email;
635
1417
  } catch (error) {
@@ -638,10 +1420,18 @@ async function fetchLatestToken(config, user) {
638
1420
  await saveUserTokens(config, resolvedUser, {
639
1421
  accessToken: resp.accessToken,
640
1422
  refreshToken: resp.refreshToken ?? void 0
641
- }, newExpiresAt, { email });
642
- await removeLegacyUserAlias(config, storedUser, resolvedUser);
1423
+ }, newExpiresAt, {
1424
+ platformConfig,
1425
+ email
1426
+ });
1427
+ await removeLegacyUserAlias(config, user, resolvedUser, platformConfig);
1428
+ if (storedUser !== platformUserKey(resolvedUser, platformConfig)) {
1429
+ if (config.users[storedUser]?.storage === "keyring") await deleteKeyringTokens(storedUser);
1430
+ delete config.users[storedUser];
1431
+ }
643
1432
  if (previousEmail && email && previousEmail !== email) logger.info(`Updated local user email from "${previousEmail}" to "${email}".`);
644
1433
  writePlatformConfig(config);
1434
+ rememberPlatformConfigForToken(resp.accessToken, platformConfig);
645
1435
  return {
646
1436
  accessToken: resp.accessToken,
647
1437
  user: resolvedUser
@@ -657,7 +1447,7 @@ const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
657
1447
  */
658
1448
  function loadConfigPath(configPath) {
659
1449
  if (configPath) return configPath;
660
- if (process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH) return process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH;
1450
+ if (process.env.TAILOR_CONFIG_PATH) return process.env.TAILOR_CONFIG_PATH;
661
1451
  return findUpSync(DEFAULT_CONFIG_FILENAME);
662
1452
  }
663
1453
 
@@ -767,9 +1557,9 @@ function hashFiles(filePaths) {
767
1557
  //#region src/cli/shared/dist-dir.ts
768
1558
  let distPath = null;
769
1559
  const getDistDir = () => {
770
- const configured = process.env.TAILOR_SDK_OUTPUT_DIR;
1560
+ const configured = process.env.TAILOR_BUILD_OUTPUT_DIR;
771
1561
  if (configured && configured !== distPath) distPath = configured;
772
- else if (distPath === null) distPath = configured || ".tailor-sdk";
1562
+ else if (distPath === null) distPath = configured || ".tailor";
773
1563
  return distPath;
774
1564
  };
775
1565
 
@@ -806,8 +1596,8 @@ function createDepCollectorPlugin() {
806
1596
 
807
1597
  //#endregion
808
1598
  //#region src/cli/cache/bundle-cache.ts
809
- function buildCacheKey(kind, name) {
810
- return `${kind}:${name}`;
1599
+ function buildCacheKey(kind, name, namespace) {
1600
+ return namespace ? `${kind}:${namespace}:${name}` : `${kind}:${name}`;
811
1601
  }
812
1602
  function combineHash(fileHash, contextHash) {
813
1603
  if (!contextHash) return fileHash;
@@ -834,10 +1624,11 @@ function computeBundlerContextHash(params) {
834
1624
  * @returns The bundled code string
835
1625
  */
836
1626
  async function withCache(params) {
837
- const { cache, kind, name, sourceFile, contextHash, build } = params;
1627
+ const { cache, kind, namespace, name, sourceFile, contextHash, build } = params;
838
1628
  if (!cache) return await build([]);
839
1629
  const content = cache.tryRestore({
840
1630
  kind,
1631
+ namespace,
841
1632
  name,
842
1633
  contextHash
843
1634
  });
@@ -849,6 +1640,7 @@ async function withCache(params) {
849
1640
  const code = await build([plugin]);
850
1641
  cache.save({
851
1642
  kind,
1643
+ namespace,
852
1644
  name,
853
1645
  sourceFile,
854
1646
  content: code,
@@ -864,7 +1656,7 @@ async function withCache(params) {
864
1656
  */
865
1657
  function createBundleCache(store) {
866
1658
  function tryRestore(params) {
867
- const cacheKey = buildCacheKey(params.kind, params.name);
1659
+ const cacheKey = buildCacheKey(params.kind, params.name, params.namespace);
868
1660
  const entry = store.getEntry(cacheKey);
869
1661
  if (!entry) return;
870
1662
  let currentHash;
@@ -877,8 +1669,8 @@ function createBundleCache(store) {
877
1669
  return store.restoreBundleContent(cacheKey);
878
1670
  }
879
1671
  function save(params) {
880
- const { kind, name, sourceFile, content, dependencyPaths, contextHash } = params;
881
- const cacheKey = buildCacheKey(kind, name);
1672
+ const { kind, namespace, name, sourceFile, content, dependencyPaths, contextHash } = params;
1673
+ const cacheKey = buildCacheKey(kind, name, namespace);
882
1674
  const allDeps = dependencyPaths.includes(sourceFile) ? dependencyPaths : [sourceFile, ...dependencyPaths];
883
1675
  const inputHash = combineHash(hashFiles(allDeps), contextHash);
884
1676
  const contentHash = hashContent(content);
@@ -975,7 +1767,7 @@ function isPluginExecutorWithFile(executor) {
975
1767
  * Generate TypeScript files for plugin-generated executors.
976
1768
  * These files will be processed by the standard executor bundler.
977
1769
  * @param executors - Array of plugin executor information
978
- * @param outputDir - Base output directory (e.g., .tailor-sdk)
1770
+ * @param outputDir - Base output directory (e.g., .tailor)
979
1771
  * @param typeGenerationResult - Result from plugin type generation (for import resolution)
980
1772
  * @param sourceTypeInfoMap - Map of source type names to their source info
981
1773
  * @param configPath - Path to tailor.config.ts (used for resolving plugin import paths)
@@ -996,7 +1788,7 @@ function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult,
996
1788
  /**
997
1789
  * Generate a single executor file.
998
1790
  * @param info - Plugin executor metadata and definition
999
- * @param outputDir - Base output directory (e.g., .tailor-sdk)
1791
+ * @param outputDir - Base output directory (e.g., .tailor)
1000
1792
  * @param typeGenerationResult - Result from plugin type generation
1001
1793
  * @param sourceTypeInfoMap - Map of source type names to their source info
1002
1794
  * @param baseDirs - Base directories for resolving plugin import paths
@@ -1313,7 +2105,7 @@ function isFieldDefinition(value) {
1313
2105
  * Generate TypeScript files for plugin-generated types.
1314
2106
  * These files export the type definition and can be imported by executor files.
1315
2107
  * @param types - Array of plugin type information
1316
- * @param outputDir - Base output directory (e.g., .tailor-sdk)
2108
+ * @param outputDir - Base output directory (e.g., .tailor)
1317
2109
  * @returns Generation result with file paths
1318
2110
  */
1319
2111
  function generatePluginTypeFiles(types, outputDir) {
@@ -1551,11 +2343,11 @@ function composeFunctionTreeshakeOptions(fragments = []) {
1551
2343
 
1552
2344
  //#endregion
1553
2345
  //#region src/cli/shared/platform-bundle-plugin.ts
1554
- const GATE = /(?<![\w$.])process\.env\.TAILOR_PLATFORM_BUNDLE\b/g;
2346
+ const GATE = /(?<![\w$.])process\.env\.__TAILOR_PLATFORM_BUNDLE\b/g;
1555
2347
  const platformBundleDefinePlugin = {
1556
2348
  name: "tailor-platform-bundle-define",
1557
2349
  transform(code) {
1558
- if (!code.includes("process.env.TAILOR_PLATFORM_BUNDLE")) return null;
2350
+ if (!code.includes("process.env.__TAILOR_PLATFORM_BUNDLE")) return null;
1559
2351
  return { code: code.replace(GATE, "true") };
1560
2352
  }
1561
2353
  };
@@ -1590,6 +2382,32 @@ function getImportSource(node) {
1590
2382
  }
1591
2383
  return null;
1592
2384
  }
2385
+ function argumentSourceText(arg, sourceText) {
2386
+ if (arg && typeof arg === "object" && "start" in arg && "end" in arg) return sourceText.slice(arg.start, arg.end);
2387
+ }
2388
+ /**
2389
+ * Get metadata for a static `identifier.trigger(...)` call.
2390
+ * @param node - AST node to inspect
2391
+ * @param sourceText - Source code text
2392
+ * @returns Trigger call metadata, or null when the node is not a trigger call
2393
+ */
2394
+ function getTriggerCallInfo(node, sourceText) {
2395
+ if (!node || typeof node !== "object" || node.type !== "CallExpression") return null;
2396
+ const callExpr = node;
2397
+ const callee = callExpr.callee;
2398
+ if (callee.type !== "MemberExpression") return null;
2399
+ const memberExpr = callee;
2400
+ if (memberExpr.computed || memberExpr.object.type !== "Identifier" || memberExpr.property.name !== "trigger") return null;
2401
+ return {
2402
+ identifierName: memberExpr.object.name,
2403
+ callRange: {
2404
+ start: callExpr.start,
2405
+ end: callExpr.end
2406
+ },
2407
+ argsText: argumentSourceText(callExpr.arguments[0], sourceText) ?? "",
2408
+ optionsText: argumentSourceText(callExpr.arguments[1], sourceText)
2409
+ };
2410
+ }
1593
2411
  /**
1594
2412
  * Unwrap AwaitExpression to get the inner expression
1595
2413
  * @param node - AST expression node
@@ -1636,12 +2454,20 @@ function findProperty(properties, name) {
1636
2454
  /**
1637
2455
  * Apply string replacements to source code
1638
2456
  * Replacements are applied from end to start to maintain positions
2457
+ * Ranges must not overlap; applying an overlapping range on top of an
2458
+ * already-shifted string would splice at stale offsets and corrupt the output,
2459
+ * so overlap is rejected up front
1639
2460
  * @param source - Original source code
1640
2461
  * @param replacements - Replacements to apply
1641
2462
  * @returns Transformed source code
1642
2463
  */
1643
2464
  function applyReplacements(source, replacements) {
1644
2465
  const sorted = replacements.toSorted((a, b) => b.start - a.start);
2466
+ for (let i = 0; i + 1 < sorted.length; i++) {
2467
+ const current = assertDefined(sorted[i], `replacement missing at index ${i}`);
2468
+ const previous = assertDefined(sorted[i + 1], `replacement missing at index ${i + 1}`);
2469
+ if (previous.end > current.start) throw new Error(`applyReplacements: overlapping replacement ranges [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
2470
+ }
1645
2471
  let result = source;
1646
2472
  for (const r of sorted) result = result.slice(0, r.start) + r.text + result.slice(r.end);
1647
2473
  return result;
@@ -1836,29 +2662,12 @@ function detectTriggerCalls(program, sourceText) {
1836
2662
  const calls = [];
1837
2663
  function walk(node) {
1838
2664
  if (!node || typeof node !== "object") return;
1839
- if (node.type === "CallExpression") {
1840
- const callExpr = node;
1841
- const callee = callExpr.callee;
1842
- if (callee.type === "MemberExpression") {
1843
- const memberExpr = callee;
1844
- if (!memberExpr.computed && memberExpr.object.type === "Identifier" && memberExpr.property.name === "trigger") {
1845
- const identifierName = memberExpr.object.name;
1846
- let argsText = "";
1847
- if (callExpr.arguments.length > 0) {
1848
- const firstArg = callExpr.arguments[0];
1849
- if (firstArg && "start" in firstArg && "end" in firstArg) argsText = sourceText.slice(firstArg.start, firstArg.end);
1850
- }
1851
- calls.push({
1852
- identifierName,
1853
- callRange: {
1854
- start: callExpr.start,
1855
- end: callExpr.end
1856
- },
1857
- argsText
1858
- });
1859
- }
1860
- }
1861
- }
2665
+ const triggerCall = getTriggerCallInfo(node, sourceText);
2666
+ if (triggerCall) calls.push({
2667
+ identifierName: triggerCall.identifierName,
2668
+ callRange: triggerCall.callRange,
2669
+ argsText: triggerCall.argsText
2670
+ });
1862
2671
  for (const key of Object.keys(node)) {
1863
2672
  const child = node[key];
1864
2673
  if (Array.isArray(child)) child.forEach((c) => walk(c));
@@ -1964,44 +2773,20 @@ function detectDefaultImports(program) {
1964
2773
  * Name of the injected runtime normalizer helper. Chosen to be unique enough
1965
2774
  * to avoid collisions with user code.
1966
2775
  */
1967
- const NORMALIZER_IDENTIFIER = "__tailor_normalizeAuthInvoker";
2776
+ const NORMALIZER_IDENTIFIER = "__tailor_normalizeTriggerOptions";
1968
2777
  /**
1969
2778
  * Build the source text of the injected normalizer helper.
1970
2779
  *
1971
- * Accepts a plain string machine user name and returns the object form
1972
- * expected by the platform RPC.
1973
- * The auth namespace is baked in at bundle time.
2780
+ * Renames an `invoker` in the trigger options to the `authInvoker` form the
2781
+ * platform RPC expects: a plain string (machine user name) becomes
2782
+ * `{ namespace, machineUserName }`, while an object form passes through
2783
+ * as-is. Any other options value is unchanged. The auth namespace is baked
2784
+ * in at bundle time.
1974
2785
  * @param authNamespace - Auth service namespace to embed
1975
2786
  * @returns Source line defining the helper
1976
2787
  */
1977
2788
  function buildNormalizerHelperSource(authNamespace) {
1978
- return `const ${NORMALIZER_IDENTIFIER} = (machineUserName) => ({ namespace: ${JSON.stringify(authNamespace)}, machineUserName });\n`;
1979
- }
1980
- /**
1981
- * Extract authInvoker info from a config object expression
1982
- * Returns the authInvoker value text and whether it's a shorthand property
1983
- * @param configArg - Config argument node
1984
- * @param sourceText - Source code text
1985
- * @returns Extracted authInvoker info, if any
1986
- */
1987
- function extractAuthInvokerInfo(configArg, sourceText) {
1988
- if (!configArg || typeof configArg !== "object") return void 0;
1989
- if (configArg.type !== "ObjectExpression") return void 0;
1990
- const objExpr = configArg;
1991
- for (const prop of objExpr.properties) {
1992
- if (prop.type !== "Property") continue;
1993
- const objProp = prop;
1994
- if ((objProp.key.type === "Identifier" ? objProp.key.name : objProp.key.type === "Literal" ? objProp.key.value : null) === "authInvoker") {
1995
- if (objProp.shorthand) return {
1996
- isShorthand: true,
1997
- valueText: "authInvoker"
1998
- };
1999
- return {
2000
- isShorthand: false,
2001
- valueText: sourceText.slice(objProp.value.start, objProp.value.end)
2002
- };
2003
- }
2004
- }
2789
+ return `const ${NORMALIZER_IDENTIFIER} = (o) => { if (!o) return o; const { invoker, ...rest } = o; return typeof invoker === "string" ? { ...rest, authInvoker: { namespace: ${JSON.stringify(authNamespace)}, machineUserName: invoker } } : typeof invoker === "object" ? { ...rest, authInvoker: invoker } : o; };\n`;
2005
2790
  }
2006
2791
  /**
2007
2792
  * Check if an AST binding pattern (parameter, catch clause, etc.) contains an Identifier with the given name.
@@ -2131,48 +2916,23 @@ function detectExtendedTriggerCalls(program, sourceText, workflowNames, jobNames
2131
2916
  const calls = [];
2132
2917
  function walk(node) {
2133
2918
  if (!node || typeof node !== "object") return;
2134
- if (node.type === "CallExpression") {
2135
- const callExpr = node;
2136
- const callee = callExpr.callee;
2137
- if (callee.type === "MemberExpression") {
2138
- const memberExpr = callee;
2139
- const identifierName = !memberExpr.computed && memberExpr.object.type === "Identifier" ? memberExpr.object.name : null;
2140
- const propertyName = !memberExpr.computed ? memberExpr.property.name : null;
2141
- if (identifierName && propertyName === "trigger") {
2142
- const isWorkflow = workflowNames.has(identifierName);
2143
- const isJob = jobNames.has(identifierName);
2144
- if (isWorkflow || isJob) {
2145
- const argCount = callExpr.arguments.length;
2146
- let argsText = "";
2147
- if (argCount > 0) {
2148
- const firstArg = callExpr.arguments[0];
2149
- if (firstArg && "start" in firstArg && "end" in firstArg) argsText = sourceText.slice(firstArg.start, firstArg.end);
2150
- }
2151
- if (isWorkflow && argCount >= 2) {
2152
- const secondArg = callExpr.arguments[1];
2153
- const authInvoker = extractAuthInvokerInfo(secondArg, sourceText);
2154
- if (authInvoker) calls.push({
2155
- kind: "workflow",
2156
- identifierName,
2157
- callRange: {
2158
- start: callExpr.start,
2159
- end: callExpr.end
2160
- },
2161
- argsText,
2162
- authInvoker
2163
- });
2164
- } else if (isJob) calls.push({
2165
- kind: "job",
2166
- identifierName,
2167
- callRange: {
2168
- start: callExpr.start,
2169
- end: callExpr.end
2170
- },
2171
- argsText
2172
- });
2173
- }
2174
- }
2175
- }
2919
+ const triggerCall = getTriggerCallInfo(node, sourceText);
2920
+ if (triggerCall) {
2921
+ const isWorkflow = workflowNames.has(triggerCall.identifierName);
2922
+ const isJob = jobNames.has(triggerCall.identifierName);
2923
+ if (isWorkflow) calls.push({
2924
+ kind: "workflow",
2925
+ identifierName: triggerCall.identifierName,
2926
+ callRange: triggerCall.callRange,
2927
+ argsText: triggerCall.argsText,
2928
+ optionsText: triggerCall.optionsText
2929
+ });
2930
+ else if (isJob) calls.push({
2931
+ kind: "job",
2932
+ identifierName: triggerCall.identifierName,
2933
+ callRange: triggerCall.callRange,
2934
+ argsText: triggerCall.argsText
2935
+ });
2176
2936
  }
2177
2937
  for (const key of Object.keys(node)) {
2178
2938
  const child = node[key];
@@ -2191,7 +2951,7 @@ function detectExtendedTriggerCalls(program, sourceText, workflowNames, jobNames
2191
2951
  * @param jobNameMap - Map from variable name to job name
2192
2952
  * @param workflowFileMap - Map from file path (without extension) to workflow name for default exports
2193
2953
  * @param currentFilePath - Path of the current file being transformed (for resolving relative imports)
2194
- * @param authNamespace - Auth service namespace used to expand string-literal `authInvoker` to object form
2954
+ * @param authNamespace - Auth service namespace used to expand string-literal `invoker` to object form
2195
2955
  * @returns Transformed source code with trigger calls rewritten
2196
2956
  */
2197
2957
  function transformFunctionTriggers(source, workflowNameMap, jobNameMap, workflowFileMap, currentFilePath, authNamespace) {
@@ -2211,20 +2971,32 @@ function transformFunctionTriggers(source, workflowNameMap, jobNameMap, workflow
2211
2971
  }
2212
2972
  }
2213
2973
  }
2214
- const triggerCalls = detectExtendedTriggerCalls(program, source, new Set(localWorkflowNameMap.keys()), new Set(jobNameMap.keys()));
2974
+ const allTriggerCalls = detectExtendedTriggerCalls(program, source, new Set(localWorkflowNameMap.keys()), new Set(jobNameMap.keys()));
2975
+ const nestedTriggerCalls = [];
2976
+ const triggerCalls = allTriggerCalls.filter((call) => {
2977
+ const parent = allTriggerCalls.find((other) => other !== call && other.callRange.start <= call.callRange.start && call.callRange.end <= other.callRange.end);
2978
+ if (parent) {
2979
+ nestedTriggerCalls.push({
2980
+ call,
2981
+ parent
2982
+ });
2983
+ return false;
2984
+ }
2985
+ return true;
2986
+ });
2987
+ for (const { call, parent } of nestedTriggerCalls) logger.warn(`Nested trigger call "${call.identifierName}.trigger(...)" inside "${parent.identifierName}.trigger(...)" cannot be converted. Move it to a separate statement and pass the result instead.`);
2215
2988
  const replacements = [];
2216
2989
  let needsNormalizerHelper = false;
2217
2990
  const transformedCallsPerIdentifier = /* @__PURE__ */ new Map();
2218
- for (const call of triggerCalls) if (call.kind === "workflow" && call.authInvoker) {
2991
+ for (const call of triggerCalls) if (call.kind === "workflow") {
2219
2992
  const workflowName = localWorkflowNameMap.get(call.identifierName);
2220
2993
  if (workflowName) {
2221
- const rawExpr = call.authInvoker.isShorthand ? "authInvoker" : call.authInvoker.valueText;
2222
- let authInvokerExpr;
2223
- if (authNamespace) {
2224
- authInvokerExpr = `${NORMALIZER_IDENTIFIER}(${rawExpr})`;
2994
+ let optionsPart = "";
2995
+ if (call.optionsText !== void 0) if (authNamespace) {
2996
+ optionsPart = `, ${NORMALIZER_IDENTIFIER}(${call.optionsText})`;
2225
2997
  needsNormalizerHelper = true;
2226
- } else authInvokerExpr = rawExpr;
2227
- const transformedCall = `tailor.workflow.triggerWorkflow("${workflowName}", ${call.argsText || "undefined"}, { authInvoker: ${authInvokerExpr} })`;
2998
+ } else optionsPart = `, ${call.optionsText}`;
2999
+ const transformedCall = `tailor.workflow.triggerWorkflow("${workflowName}", ${call.argsText || "undefined"}${optionsPart})`;
2228
3000
  replacements.push({
2229
3001
  start: call.callRange.start,
2230
3002
  end: call.callRange.end,
@@ -2232,7 +3004,7 @@ function transformFunctionTriggers(source, workflowNameMap, jobNameMap, workflow
2232
3004
  });
2233
3005
  transformedCallsPerIdentifier.set(call.identifierName, (transformedCallsPerIdentifier.get(call.identifierName) ?? 0) + 1);
2234
3006
  }
2235
- } else if (call.kind === "job") {
3007
+ } else {
2236
3008
  const jobName = jobNameMap.get(call.identifierName);
2237
3009
  if (jobName) {
2238
3010
  const transformedCall = `tailor.workflow.triggerJobFunction("${jobName}", ${call.argsText || "undefined"})`;
@@ -2278,7 +3050,7 @@ function normalizeFilePath(filePath) {
2278
3050
  * Build trigger context from workflow configuration
2279
3051
  * Scans workflow files to collect workflow and job mappings
2280
3052
  * @param workflowConfig - Workflow file loading configuration
2281
- * @param authNamespace - Auth service namespace (optional, used for string-literal authInvoker expansion)
3053
+ * @param authNamespace - Auth service namespace (optional, used for string-literal invoker expansion)
2282
3054
  * @returns Trigger context built from workflow sources
2283
3055
  */
2284
3056
  async function buildTriggerContext(workflowConfig, authNamespace) {
@@ -2415,7 +3187,7 @@ async function bundleAuthHooks(options) {
2415
3187
  },
2416
3188
  tsconfig,
2417
3189
  plugins,
2418
- transform: { define: { "process.env.LOG_LEVEL": JSON.stringify(bundleLogLevel) } },
3190
+ transform: { define: { "process.env.TAILOR_APP_LOG_LEVEL": JSON.stringify(bundleLogLevel) } },
2419
3191
  treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
2420
3192
  logLevel: "silent"
2421
3193
  })).output[0].code;
@@ -2427,6 +3199,28 @@ async function bundleAuthHooks(options) {
2427
3199
  return bundledCode;
2428
3200
  }
2429
3201
 
3202
+ //#endregion
3203
+ //#region src/utils/script-expr.ts
3204
+ const MAX_GENERATED_CODE_LENGTH = 2e3;
3205
+ function formatGeneratedCode(expr) {
3206
+ if (expr.length <= MAX_GENERATED_CODE_LENGTH) return `Generated code:\n${expr}`;
3207
+ return `Generated code (truncated to ${MAX_GENERATED_CODE_LENGTH} of ${expr.length} characters):\n${expr.slice(0, MAX_GENERATED_CODE_LENGTH)}\n...`;
3208
+ }
3209
+ /**
3210
+ * Assert that a generated script expression is syntactically valid JavaScript.
3211
+ * Invalid generated code would otherwise surface much later as a confusing
3212
+ * bundler or platform-side syntax error, far from the definition that caused it.
3213
+ * @param expr - Generated JavaScript expression
3214
+ * @param context - What generated the expression, used to label the error
3215
+ * @returns The expression unchanged when it parses successfully
3216
+ */
3217
+ function assertParsableExpression(expr, context) {
3218
+ const { errors } = parseSync("generated-expr.js", `(${expr}\n);`);
3219
+ if (errors.length === 0) return expr;
3220
+ const details = errors.map((error) => ` - ${error.message}`).join("\n");
3221
+ throw new Error(`Generated ${context} script is not valid JavaScript.\nParse errors:\n${details}\n` + formatGeneratedCode(expr));
3222
+ }
3223
+
2430
3224
  //#endregion
2431
3225
  //#region src/parser/service/tailordb/hooks-validate-precompiled-expr.ts
2432
3226
  const PRECOMPILED_EXPR_KEY = "__precompiledScriptExpr";
@@ -2450,36 +3244,128 @@ function getPrecompiledScriptExpr(fn) {
2450
3244
 
2451
3245
  //#endregion
2452
3246
  //#region src/parser/service/tailordb/field.ts
2453
- const tailorUserMap = `{ id: user.id, type: user.type, workspaceId: user.workspace_id, attributes: user.attribute_map, attributeList: user.attributes }`;
3247
+ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
3248
+ /**
3249
+ * Build the server→SDK principal mapping expression shared across services.
3250
+ *
3251
+ * All principal-bearing call sites (`caller`, `actor`, `invoker`) must agree on
3252
+ * the `TailorPrincipal | null` shape, so they are generated here rather than
3253
+ * hand-written per service.
3254
+ * @param options - Mapping source and field expressions
3255
+ * @param options.source - Expression yielding the raw server payload (e.g. `user`)
3256
+ * @param options.fields - Per-source accessors for each principal field
3257
+ * @param options.normalize - When true, map `USER_TYPE_*` to SDK type literals and
3258
+ * return `null` for unspecified types or the nil-UUID id; when false, the
3259
+ * payload is already in SDK shape and only `null` pass-through is applied
3260
+ * @param options.requireId - When true, missing ids are also mapped to `null`
3261
+ * @returns A JS expression string evaluating to `TailorPrincipal | null`
3262
+ */
3263
+ function makePrincipalExpr(options) {
3264
+ const { source, fields, normalize, requireId = false } = options;
3265
+ const body = `{
3266
+ id,
3267
+ type,
3268
+ workspaceId: ${fields.workspaceId},
3269
+ attributes: ${fields.attributes},
3270
+ attributeList: ${fields.attributeList},
3271
+ }`;
3272
+ if (!normalize) return `(($raw) => {
3273
+ if (!$raw) {
3274
+ return null;
3275
+ }
3276
+ const id = ${fields.id};
3277
+ const type = ${fields.type.raw};
3278
+ return ${body};
3279
+ })(${source})`;
3280
+ const missingIdGuard = requireId ? " || !id" : "";
3281
+ return `(($raw) => {
3282
+ if (!$raw) {
3283
+ return null;
3284
+ }
3285
+ const type = ${fields.type.raw} === "USER_TYPE_USER"
3286
+ ? "user"
3287
+ : ${fields.type.raw} === "USER_TYPE_MACHINE_USER"
3288
+ ? "machine_user"
3289
+ : ${fields.type.fallback ?? fields.type.raw};
3290
+ const id = ${fields.id};
3291
+ if (!type || type === "USER_TYPE_UNSPECIFIED" || id === "${NIL_UUID}"${missingIdGuard}) {
3292
+ return null;
3293
+ }
3294
+ return ${body};
3295
+ })(${source})`;
3296
+ }
3297
+ const tailorPrincipalMap = makePrincipalExpr({
3298
+ source: "user",
3299
+ normalize: true,
3300
+ fields: {
3301
+ type: { raw: "$raw?.type" },
3302
+ id: "$raw.id",
3303
+ workspaceId: "$raw.workspace_id ?? $raw.workspaceId",
3304
+ attributes: "$raw.attribute_map ?? $raw.attributeMap ?? {}",
3305
+ attributeList: "$raw.attributes ?? []"
3306
+ }
3307
+ });
3308
+ /**
3309
+ * Parse `wrapped` and return the first property of the top-level parenthesized
3310
+ * object expression, or `undefined` if it does not parse as one.
3311
+ * @param wrapped - Source wrapped as `({ ... })`
3312
+ * @returns The first object property, or `undefined`
3313
+ */
3314
+ const firstObjectProperty = (wrapped) => {
3315
+ const parseResult = parseSync("stringify-function.ts", wrapped, { sourceType: "module" });
3316
+ if (parseResult.errors.length > 0) return;
3317
+ const expressionStatement = parseResult.program.body[0];
3318
+ const objectExpression = expressionStatement?.type === "ExpressionStatement" && expressionStatement.expression.type === "ParenthesizedExpression" ? expressionStatement.expression.expression : void 0;
3319
+ return objectExpression?.type === "ObjectExpression" ? objectExpression.properties[0] : void 0;
3320
+ };
2454
3321
  /**
2455
3322
  * Convert a function to a string representation.
2456
3323
  * Handles method shorthand syntax (e.g., `create() { ... }`) by converting it to
2457
- * a function expression (e.g., `function create() { ... }`).
3324
+ * an anonymous function expression (e.g., `function () { ... }`), including
3325
+ * `async` and generator variants and shorthand bodies that themselves contain
3326
+ * arrow functions. The result is anonymous (rather than reusing the method
3327
+ * name) so a body that references an outer variable of the same name is not
3328
+ * shadowed by the generated function's own binding.
2458
3329
  * @param fn - Function to stringify
2459
3330
  * @returns Stringified function source
2460
3331
  */
2461
3332
  const stringifyFunction = (fn) => {
2462
3333
  const src = fn.toString().trim();
2463
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/.test(src) && !src.startsWith("function") && !src.startsWith("(") && !src.includes("=>")) return `function ${src}`;
3334
+ if (firstObjectProperty(`({m: ${src}})`)) return src;
3335
+ const wrapped = `({${src}})`;
3336
+ const property = firstObjectProperty(wrapped);
3337
+ if (property?.type === "Property" && property.method && property.computed) throw new Error("Computed-key method shorthand cannot be converted to a TailorDB script expression. Use an arrow function or function expression instead.");
3338
+ if (property?.type === "Property" && property.method && property.value.type === "FunctionExpression") {
3339
+ const { async, generator } = property.value;
3340
+ const body = wrapped.slice(property.value.start, property.value.end);
3341
+ return `${async ? "async " : ""}function${generator ? "*" : ""} ${body}`;
3342
+ }
2464
3343
  return src;
2465
3344
  };
3345
+ function formatScriptContext(kind, context) {
3346
+ if (!context) return kind === "validate" ? kind : "hooks";
3347
+ return `${kind} for ${context.typeName}.${context.fieldPath.join(".")}`;
3348
+ }
2466
3349
  /**
2467
- * Convert a hook function to a script expression.
2468
- * @param fn - Hook function
2469
- * @returns JavaScript expression calling the hook
3350
+ * Convert a hook or validator function to a script expression.
3351
+ * @param fn - Hook or validator function
3352
+ * @param kind - Label naming the source of the expression in conversion errors
3353
+ * @param context - Optional field context for conversion errors
3354
+ * @returns JavaScript expression calling the function
2470
3355
  */
2471
- const convertHookToExpr = (fn) => {
3356
+ const convertToScriptExpr = (fn, kind, context) => {
2472
3357
  const precompiledExpr = getPrecompiledScriptExpr(fn);
2473
3358
  if (precompiledExpr) return precompiledExpr;
2474
- return `(${stringifyFunction(fn)})({ value: _value, data: _data, user: ${tailorUserMap} })`;
3359
+ return assertParsableExpression(`(${stringifyFunction(fn)})({ value: _value, data: _data, invoker: ${tailorPrincipalMap} })`, formatScriptContext(kind, context));
2475
3360
  };
2476
3361
  /**
2477
3362
  * Parse TailorDBField into OperatorFieldConfig.
2478
3363
  * This transforms user-defined functions into script expressions.
2479
3364
  * @param field - TailorDB field definition
3365
+ * @param context - Optional field context for conversion errors
2480
3366
  * @returns Parsed operator field configuration
2481
3367
  */
2482
- function parseFieldConfig(field) {
3368
+ function parseFieldConfig(field, context) {
2483
3369
  const metadata = field.metadata;
2484
3370
  const fieldType = field.type;
2485
3371
  const rawRelation = field.rawRelation;
@@ -2489,7 +3375,10 @@ function parseFieldConfig(field) {
2489
3375
  ...metadata,
2490
3376
  rawRelation,
2491
3377
  ...fieldType === "nested" && nestedFields && Object.keys(nestedFields).length > 0 ? { fields: Object.entries(nestedFields).reduce((acc, [key, nestedField]) => {
2492
- acc[key] = parseFieldConfig(nestedField);
3378
+ acc[key] = parseFieldConfig(nestedField, context && {
3379
+ ...context,
3380
+ fieldPath: [...context.fieldPath, key]
3381
+ });
2493
3382
  return acc;
2494
3383
  }, {}) } : {},
2495
3384
  validate: metadata.validate?.map((v) => {
@@ -2501,13 +3390,13 @@ function parseFieldConfig(field) {
2501
3390
  message: v[1]
2502
3391
  };
2503
3392
  return {
2504
- script: { expr: getPrecompiledScriptExpr(fn) ?? `(${fn.toString().trim()})({ value: _value, data: _data, user: ${tailorUserMap} })` },
3393
+ script: { expr: convertToScriptExpr(fn, "validate", context) },
2505
3394
  errorMessage: message
2506
3395
  };
2507
3396
  }),
2508
3397
  hooks: metadata.hooks ? {
2509
- create: metadata.hooks.create ? { expr: convertHookToExpr(metadata.hooks.create) } : void 0,
2510
- update: metadata.hooks.update ? { expr: convertHookToExpr(metadata.hooks.update) } : void 0
3398
+ create: metadata.hooks.create ? { expr: convertToScriptExpr(metadata.hooks.create, "hooks.create", context) } : void 0,
3399
+ update: metadata.hooks.update ? { expr: convertToScriptExpr(metadata.hooks.update, "hooks.update", context) } : void 0
2511
3400
  } : void 0,
2512
3401
  serial: metadata.serial ? {
2513
3402
  start: metadata.serial.start,
@@ -2651,92 +3540,6 @@ function findOmittedPermitRules(rawPermissions) {
2651
3540
  return locations;
2652
3541
  }
2653
3542
 
2654
- //#endregion
2655
- //#region src/parser/service/tailordb/relation.ts
2656
- const relationTypes = {
2657
- "1-1": "1-1",
2658
- oneToOne: "1-1",
2659
- "n-1": "n-1",
2660
- manyToOne: "n-1",
2661
- "N-1": "n-1",
2662
- keyOnly: "keyOnly"
2663
- };
2664
- const relationTypesKeys = Object.keys(relationTypes);
2665
- function fieldRef(context) {
2666
- return `Field "${context.fieldName}" on type "${context.typeName}"`;
2667
- }
2668
- /**
2669
- * Validate relation configuration.
2670
- * @param rawRelation - Raw relation configuration from TailorDB type definition
2671
- * @param context - Context information for the relation (type name, field name, all type names)
2672
- */
2673
- function validateRelationConfig(rawRelation, context) {
2674
- if (!rawRelation.type) throw new Error(`${fieldRef(context)} has a relation but is missing the required 'type' property. Valid values: ${relationTypesKeys.join(", ")}.`);
2675
- if (!(rawRelation.type in relationTypes)) throw new Error(`${fieldRef(context)} has invalid relation type '${rawRelation.type}'. Valid values: ${relationTypesKeys.join(", ")}.`);
2676
- if (rawRelation.toward.type !== "self" && !context.allTypeNames.has(rawRelation.toward.type)) throw new Error(`${fieldRef(context)} references unknown type "${rawRelation.toward.type}".`);
2677
- }
2678
- /**
2679
- * Process raw relation config and compute derived metadata values.
2680
- * @param rawRelation - Raw relation configuration
2681
- * @param context - Context information for the relation
2682
- * @param isArrayField - Whether the target field is an array field
2683
- * @returns Computed relation metadata to apply to field config
2684
- */
2685
- function processRelationMetadata(rawRelation, context, isArrayField = false) {
2686
- const isUnique = relationTypes[rawRelation.type] === "1-1";
2687
- const key = rawRelation.toward.key ?? "id";
2688
- const targetTypeName = rawRelation.toward.type === "self" ? context.typeName : rawRelation.toward.type;
2689
- const shouldSetIndex = !isArrayField;
2690
- const shouldSetUnique = !isArrayField && isUnique;
2691
- return {
2692
- index: shouldSetIndex,
2693
- foreignKey: true,
2694
- relationType: rawRelation.type,
2695
- unique: shouldSetUnique,
2696
- foreignKeyType: targetTypeName,
2697
- foreignKeyField: key
2698
- };
2699
- }
2700
- /**
2701
- * Build relation info for creating forward/backward relationships.
2702
- * Returns undefined for keyOnly relations.
2703
- * @param rawRelation - Raw relation configuration
2704
- * @param context - Context information for the relation
2705
- * @returns Relation information or undefined for keyOnly relations
2706
- */
2707
- function buildRelationInfo(rawRelation, context) {
2708
- if (rawRelation.type === "keyOnly") return;
2709
- const isUnique = relationTypes[rawRelation.type] === "1-1";
2710
- const key = rawRelation.toward.key ?? "id";
2711
- const targetTypeName = rawRelation.toward.type === "self" ? context.typeName : rawRelation.toward.type;
2712
- let forwardName = rawRelation.toward.as;
2713
- if (!forwardName) if (rawRelation.toward.type === "self") forwardName = context.fieldName.replace(/(ID|Id|id)$/u, "");
2714
- else forwardName = inflection.camelize(targetTypeName, true);
2715
- return {
2716
- targetType: targetTypeName,
2717
- forwardName,
2718
- backwardName: rawRelation.backward ?? "",
2719
- key,
2720
- unique: isUnique
2721
- };
2722
- }
2723
- /**
2724
- * Apply processed relation metadata to field config.
2725
- * @param fieldConfig - Original operator field configuration
2726
- * @param metadata - Processed relation metadata to apply
2727
- * @returns Field config with relation metadata applied
2728
- */
2729
- function applyRelationMetadataToFieldConfig(fieldConfig, metadata) {
2730
- return {
2731
- ...fieldConfig,
2732
- index: metadata.index,
2733
- foreignKey: metadata.foreignKey,
2734
- unique: metadata.unique,
2735
- foreignKeyType: metadata.foreignKeyType,
2736
- foreignKeyField: metadata.foreignKeyField
2737
- };
2738
- }
2739
-
2740
3543
  //#endregion
2741
3544
  //#region src/parser/service/tailordb/type-parser.ts
2742
3545
  /**
@@ -2750,7 +3553,7 @@ function applyRelationMetadataToFieldConfig(fieldConfig, metadata) {
2750
3553
  function parseTypes(rawTypes, namespace, typeSourceInfo) {
2751
3554
  const types = createRecord();
2752
3555
  const allTypeNames = new Set(Object.keys(rawTypes));
2753
- for (const [typeName, type] of Object.entries(rawTypes)) types[typeName] = parseTailorDBType(type, allTypeNames, rawTypes);
3556
+ for (const [typeName, type] of Object.entries(rawTypes)) types[typeName] = parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo);
2754
3557
  buildBackwardRelationships(types, namespace, typeSourceInfo);
2755
3558
  validatePluralFormUniqueness(types, namespace, typeSourceInfo);
2756
3559
  return types;
@@ -2760,21 +3563,26 @@ function parseTypes(rawTypes, namespace, typeSourceInfo) {
2760
3563
  * @param type - TailorDB type to parse
2761
3564
  * @param allTypeNames - Set of all TailorDB type names
2762
3565
  * @param rawTypes - All raw TailorDB types keyed by name
3566
+ * @param typeSourceInfo - Optional type source information
2763
3567
  * @returns Parsed TailorDB type
2764
3568
  */
2765
- function parseTailorDBType(type, allTypeNames, rawTypes) {
3569
+ function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
2766
3570
  const metadata = type.metadata;
2767
3571
  const pluralForm = metadata.settings?.pluralForm || inflection.pluralize(type.name);
3572
+ const typeLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, type.name));
2768
3573
  const fields = createRecord();
2769
3574
  const forwardRelationships = createRecord();
2770
3575
  for (const [fieldName, fieldDef] of Object.entries(type.fields)) {
2771
- let fieldConfig = parseFieldConfig(fieldDef);
2772
- const rawRelation = fieldConfig.rawRelation;
2773
3576
  const context = {
2774
3577
  typeName: type.name,
2775
3578
  fieldName,
2776
3579
  allTypeNames
2777
3580
  };
3581
+ let fieldConfig = parseFieldConfig(fieldDef, {
3582
+ typeName: type.name,
3583
+ fieldPath: [fieldName]
3584
+ });
3585
+ const rawRelation = fieldConfig.rawRelation;
2778
3586
  if (rawRelation) {
2779
3587
  validateRelationConfig(rawRelation, context);
2780
3588
  if ([
@@ -2794,9 +3602,18 @@ function parseTailorDBType(type, allTypeNames, rawTypes) {
2794
3602
  const relationInfo = rawRelation ? buildRelationInfo(rawRelation, context) : void 0;
2795
3603
  if (relationInfo) {
2796
3604
  parsedField.relation = { ...relationInfo };
3605
+ const forwardName = relationInfo.forwardName;
3606
+ if (forwardName.length === 0) throw new Error(`Forward relation name for field "${fieldName}" on type "${type.name}"${typeLocation} cannot be empty. Use the "as" option in .relation({ toward: { as: ... } }) to specify a non-empty name.`);
3607
+ const existingForward = forwardRelationships[forwardName];
3608
+ if (existingForward) throw new Error(`Forward relation name "${forwardName}" on type "${type.name}"${typeLocation} is duplicated between fields "${existingForward.targetField}" and "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a unique name.`);
3609
+ if (Object.hasOwn(type.fields, forwardName)) {
3610
+ const message = forwardName === fieldName ? `Forward relation name "${forwardName}" on type "${type.name}"${typeLocation} is the same as its own relation field "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.` : `Forward relation name "${forwardName}" from field "${fieldName}" on type "${type.name}"${typeLocation} conflicts with existing field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`;
3611
+ throw new Error(message);
3612
+ }
3613
+ if (Object.hasOwn(metadata.files, forwardName)) throw new Error(`Forward relation name "${forwardName}" from field "${fieldName}" on type "${type.name}"${typeLocation} conflicts with files field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`);
2797
3614
  const targetType = rawTypes[relationInfo.targetType];
2798
- forwardRelationships[relationInfo.forwardName] = {
2799
- name: relationInfo.forwardName,
3615
+ forwardRelationships[forwardName] = {
3616
+ name: forwardName,
2800
3617
  targetType: relationInfo.targetType,
2801
3618
  targetField: fieldName,
2802
3619
  sourceField: relationInfo.key,
@@ -2857,13 +3674,11 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
2857
3674
  for (const [targetTypeName, backwardNames] of Object.entries(backwardNameSources)) {
2858
3675
  const targetType = types[targetTypeName];
2859
3676
  if (targetType === void 0) throw new Error(`type not found: ${targetTypeName}`);
2860
- const targetTypeSourceInfo = getTypeSourceInfo(typeSourceInfo, targetTypeName);
2861
- const targetLocation = targetTypeSourceInfo ? isPluginGeneratedType(targetTypeSourceInfo) ? ` (plugin: ${targetTypeSourceInfo.pluginId})` : ` (${targetTypeSourceInfo.filePath})` : "";
3677
+ const targetLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, targetTypeName));
2862
3678
  for (const [backwardName, sources] of Object.entries(backwardNames)) {
2863
3679
  if (sources.length > 1) {
2864
3680
  const sourceList = sources.map((s) => {
2865
- const sourceInfo = getTypeSourceInfo(typeSourceInfo, s.sourceType);
2866
- const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
3681
+ const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, s.sourceType));
2867
3682
  return `${s.sourceType}.${s.fieldName}${location}`;
2868
3683
  }).join(", ");
2869
3684
  errors.push(`Backward relation name "${backwardName}" on type "${targetTypeName}" is duplicated from: ${sourceList}. Use the "backward" option in .relation() to specify unique names.`);
@@ -2871,17 +3686,21 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
2871
3686
  if (Object.hasOwn(targetType.fields, backwardName)) {
2872
3687
  const source = sources[0];
2873
3688
  if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
2874
- const sourceInfo = getTypeSourceInfo(typeSourceInfo, source.sourceType);
2875
- const sourceLocation = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
3689
+ const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
2876
3690
  errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with existing field "${backwardName}" on type "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
2877
3691
  }
2878
3692
  if (targetType.files && Object.hasOwn(targetType.files, backwardName)) {
2879
3693
  const source = sources[0];
2880
3694
  if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
2881
- const sourceInfo = getTypeSourceInfo(typeSourceInfo, source.sourceType);
2882
- const sourceLocation = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
3695
+ const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
2883
3696
  errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with files field "${backwardName}" on type "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
2884
3697
  }
3698
+ if (Object.hasOwn(targetType.forwardRelationships, backwardName)) {
3699
+ const source = sources[0];
3700
+ if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
3701
+ const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
3702
+ errors.push(`Relation name "${backwardName}" on type "${targetTypeName}"${targetLocation} is used by both a forward relationship and a backward relationship from ${source.sourceType}.${source.fieldName}${sourceLocation}. Use the "as" option in .relation({ toward: { as: ... } }) or the "backward" option in .relation() to specify unique names.`);
3703
+ }
2885
3704
  }
2886
3705
  }
2887
3706
  if (errors.length > 0) throw new Error(`Backward relation name conflicts detected in TailorDB service "${namespace}".\n${errors.map((e) => ` - ${e}`).join("\n")}`);
@@ -2900,8 +3719,7 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
2900
3719
  for (const [, parsedType] of Object.entries(types)) {
2901
3720
  const singularQuery = inflection.camelize(parsedType.name, true);
2902
3721
  if (singularQuery === inflection.camelize(parsedType.pluralForm, true)) {
2903
- const sourceInfo = getTypeSourceInfo(typeSourceInfo, parsedType.name);
2904
- const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
3722
+ const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, parsedType.name));
2905
3723
  errors.push(`Type "${parsedType.name}"${location} has identical singular and plural query names "${singularQuery}". Use db.type(["${parsedType.name}", "UniquePluralForm"], {...}) to set a unique pluralForm.`);
2906
3724
  }
2907
3725
  }
@@ -2927,8 +3745,7 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
2927
3745
  const duplicates = [...queryNameToSource].filter(([, sources]) => sources.length > 1);
2928
3746
  for (const [queryName, sources] of duplicates) {
2929
3747
  const sourceList = sources.map((s) => {
2930
- const sourceInfo = getTypeSourceInfo(typeSourceInfo, s.typeName);
2931
- const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
3748
+ const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, s.typeName));
2932
3749
  return `"${s.typeName}"${location} (${s.kind})`;
2933
3750
  }).join(", ");
2934
3751
  errors.push(`GraphQL query field "${queryName}" conflicts between: ${sourceList}`);
@@ -2938,220 +3755,14 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
2938
3755
  function getTypeSourceInfo(typeSourceInfo, typeName) {
2939
3756
  return typeSourceInfo && Object.hasOwn(typeSourceInfo, typeName) ? typeSourceInfo[typeName] : void 0;
2940
3757
  }
3758
+ function formatTypeSourceLocation(sourceInfo) {
3759
+ if (!sourceInfo) return "";
3760
+ return isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})`;
3761
+ }
2941
3762
  function createRecord() {
2942
3763
  return Object.create(null);
2943
3764
  }
2944
3765
 
2945
- //#endregion
2946
- //#region src/parser/service/tailordb/schema.ts
2947
- /**
2948
- * Normalize GqlOperationsConfig (alias or object) to GqlOperations object.
2949
- * "query" alias expands to read-only mode: { create: false, update: false, delete: false, read: true }
2950
- * @param config - The config to normalize
2951
- * @returns The normalized GqlOperations object
2952
- */
2953
- function normalizeGqlOperations(config) {
2954
- if (config === "query") return {
2955
- create: false,
2956
- update: false,
2957
- delete: false,
2958
- read: true
2959
- };
2960
- return config;
2961
- }
2962
- /**
2963
- * Zod schema for GqlOperations configuration with normalization transform.
2964
- * Accepts "query" alias or detailed object, normalizes to GqlOperations object.
2965
- */
2966
- const GqlOperationsSchema = z.union([z.literal("query"), z.object({
2967
- create: z.boolean().optional().describe("Enable create mutation (default: true)"),
2968
- update: z.boolean().optional().describe("Enable update mutation (default: true)"),
2969
- delete: z.boolean().optional().describe("Enable delete mutation (default: true)"),
2970
- read: z.boolean().optional().describe("Enable read queries - get, list, aggregation (default: true)")
2971
- })]).describe("Configuration for GraphQL operations on a TailorDB type.\nAll operations are enabled by default (undefined or true = enabled, false = disabled).").transform((val) => normalizeGqlOperations(val));
2972
- const TailorFieldTypeSchema = z.enum([
2973
- "uuid",
2974
- "string",
2975
- "boolean",
2976
- "integer",
2977
- "float",
2978
- "decimal",
2979
- "enum",
2980
- "date",
2981
- "datetime",
2982
- "time",
2983
- "nested"
2984
- ]);
2985
- const AllowedValueSchema = z.object({
2986
- value: z.string(),
2987
- description: z.string().optional()
2988
- });
2989
- const DBFieldMetadataSchema = z.object({
2990
- required: z.boolean().optional().describe("Whether the field is required"),
2991
- array: z.boolean().optional().describe("Whether the field is an array"),
2992
- description: z.string().optional().describe("Field description"),
2993
- typeName: z.string().optional().describe("Type name for nested or enum fields"),
2994
- allowedValues: z.array(AllowedValueSchema).optional().describe("Allowed values for enum fields"),
2995
- index: z.boolean().optional().describe("Whether the field is indexed for faster queries"),
2996
- unique: z.boolean().optional().describe("Whether the field value must be unique"),
2997
- vector: z.boolean().optional().describe("Whether the field is a vector field for similarity search"),
2998
- foreignKey: z.boolean().optional().describe("Whether the field is a foreign key"),
2999
- foreignKeyType: z.string().optional().describe("Target type name for foreign key relations"),
3000
- foreignKeyField: z.string().optional().describe("Target field name for foreign key relations"),
3001
- hooks: z.object({
3002
- create: functionSchema.optional().describe("Hook function called on record creation"),
3003
- update: functionSchema.optional().describe("Hook function called on record update")
3004
- }).optional().describe("Lifecycle hooks for the field"),
3005
- validate: z.array(z.union([functionSchema, z.tuple([functionSchema, z.string()])])).optional().describe("Validation functions for the field"),
3006
- serial: z.object({
3007
- start: z.number().describe("Starting value for the serial sequence"),
3008
- maxValue: z.number().optional().describe("Maximum value for the serial sequence"),
3009
- format: z.string().optional().describe("Format string for serial value (string type only)")
3010
- }).optional().describe("Serial (auto-increment) configuration"),
3011
- scale: z.number().int().min(0).max(12).optional().describe("Decimal scale (number of digits after decimal point, 0-12)")
3012
- });
3013
- const RelationTypeSchema = z.enum(relationTypesKeys);
3014
- const RawRelationConfigSchema = z.object({
3015
- type: RelationTypeSchema.describe("Relation cardinality type"),
3016
- toward: z.object({
3017
- type: z.string().describe("Target type name, or 'self' for self-relations"),
3018
- as: z.string().optional().describe("Custom forward relation name"),
3019
- key: z.string().optional().describe("Target field to join on (default: 'id')")
3020
- }),
3021
- backward: z.string().optional().describe("Backward relation name on the target type")
3022
- });
3023
- const TailorDBFieldSchema = z.lazy(() => z.object({
3024
- type: TailorFieldTypeSchema,
3025
- fields: z.record(z.string(), TailorDBFieldSchema).optional(),
3026
- metadata: DBFieldMetadataSchema,
3027
- rawRelation: RawRelationConfigSchema.optional()
3028
- }));
3029
- /**
3030
- * Schema for TailorDB type settings.
3031
- * Normalizes gqlOperations from alias ("query") to object format.
3032
- */
3033
- const TailorDBTypeSettingsSchema = z.object({
3034
- pluralForm: z.string().optional().describe("Custom plural form of the type name for GraphQL"),
3035
- aggregation: z.boolean().optional().describe("Enable aggregation queries for this type"),
3036
- bulkUpsert: z.boolean().optional().describe("Enable bulk upsert mutation for this type"),
3037
- gqlOperations: GqlOperationsSchema.optional().describe("Configure GraphQL operations for this type. Use \"query\" for read-only mode, or an object for granular control."),
3038
- publishEvents: z.boolean().optional().describe("Enable publishing events for this type.\nWhen enabled, record creation/update/deletion events are published.\nIf not specified, this is automatically set to true when an executor uses this type\nwith recordCreated/recordUpdated/recordDeleted triggers. If explicitly set to false\nwhile an executor uses this type, an error will be thrown during apply.")
3039
- });
3040
- const GQL_PERMISSION_INVALID_OPERAND_MESSAGE = "operand is not supported in gqlPermission. Use permission() for record-level conditions.";
3041
- const GqlPermissionOperandSchema = z.union([
3042
- z.object({ user: z.string() }).strict(),
3043
- z.string(),
3044
- z.boolean(),
3045
- z.array(z.string()),
3046
- z.array(z.boolean())
3047
- ], { error: (issue) => {
3048
- if (typeof issue.input === "object" && issue.input !== null) {
3049
- const keys = Object.keys(issue.input);
3050
- if (keys.length === 1) return `"${keys[0]}" ${GQL_PERMISSION_INVALID_OPERAND_MESSAGE}`;
3051
- return "Operand object must have exactly 1 key";
3052
- }
3053
- return "Invalid operand in gqlPermission";
3054
- } });
3055
- const RecordPermissionOperandSchema = z.union([
3056
- GqlPermissionOperandSchema,
3057
- z.object({ record: z.string() }),
3058
- z.object({ oldRecord: z.string() }),
3059
- z.object({ newRecord: z.string() })
3060
- ]);
3061
- const PermissionOperatorSchema = z.enum([
3062
- "=",
3063
- "!=",
3064
- "in",
3065
- "not in",
3066
- "hasAny",
3067
- "not hasAny"
3068
- ]);
3069
- const RecordPermissionConditionSchema = z.tuple([
3070
- RecordPermissionOperandSchema,
3071
- PermissionOperatorSchema,
3072
- RecordPermissionOperandSchema
3073
- ]).readonly();
3074
- const GqlPermissionConditionSchema = z.tuple([
3075
- GqlPermissionOperandSchema,
3076
- PermissionOperatorSchema,
3077
- GqlPermissionOperandSchema
3078
- ]).readonly();
3079
- const ActionPermissionSchema = z.union([
3080
- z.object({
3081
- conditions: z.union([RecordPermissionConditionSchema, z.array(RecordPermissionConditionSchema).readonly()]),
3082
- description: z.string().optional(),
3083
- permit: z.boolean().optional()
3084
- }),
3085
- z.tuple([
3086
- RecordPermissionOperandSchema,
3087
- PermissionOperatorSchema,
3088
- RecordPermissionOperandSchema
3089
- ]).readonly(),
3090
- z.tuple([
3091
- RecordPermissionOperandSchema,
3092
- PermissionOperatorSchema,
3093
- RecordPermissionOperandSchema,
3094
- z.boolean()
3095
- ]).readonly(),
3096
- z.array(z.union([RecordPermissionConditionSchema, z.boolean()])).refine((arr) => {
3097
- const boolIndex = arr.findIndex((item) => typeof item === "boolean");
3098
- return boolIndex === -1 || boolIndex === arr.length - 1;
3099
- }, { message: "Boolean permit flag must only appear at the end" }).readonly()
3100
- ]);
3101
- const GqlPermissionActionSchema = z.enum([
3102
- "read",
3103
- "create",
3104
- "update",
3105
- "delete",
3106
- "aggregate",
3107
- "bulkUpsert"
3108
- ]);
3109
- const GqlPermissionPolicySchema = z.object({
3110
- conditions: z.array(GqlPermissionConditionSchema).readonly(),
3111
- actions: z.union([z.literal("all"), z.array(GqlPermissionActionSchema).readonly()]),
3112
- permit: z.boolean().optional(),
3113
- description: z.string().optional()
3114
- });
3115
- const RawPermissionsSchema = z.object({
3116
- record: z.object({
3117
- create: z.array(ActionPermissionSchema).readonly(),
3118
- read: z.array(ActionPermissionSchema).readonly(),
3119
- update: z.array(ActionPermissionSchema).readonly(),
3120
- delete: z.array(ActionPermissionSchema).readonly()
3121
- }).optional(),
3122
- gql: z.array(GqlPermissionPolicySchema).readonly().optional()
3123
- });
3124
- const TailorDBTypeSchema = z.object({
3125
- name: z.string(),
3126
- fields: z.record(z.string(), TailorDBFieldSchema),
3127
- metadata: z.object({
3128
- name: z.string(),
3129
- description: z.string().optional(),
3130
- settings: TailorDBTypeSettingsSchema.optional(),
3131
- permissions: RawPermissionsSchema,
3132
- files: z.record(z.string(), z.string()),
3133
- indexes: z.record(z.string(), z.object({
3134
- fields: z.array(z.string()),
3135
- unique: z.boolean().optional()
3136
- })).optional()
3137
- })
3138
- });
3139
- const TailorDBMigrationConfigSchema = z.object({
3140
- directory: z.string().describe("Directory containing migration files"),
3141
- machineUser: z.string().optional().describe("Machine user name for migration execution")
3142
- });
3143
- /**
3144
- * Schema for TailorDB service configuration.
3145
- * Normalizes gqlOperations from alias ("query") to object format.
3146
- */
3147
- const TailorDBServiceConfigSchema = z.object({
3148
- files: z.array(z.string()).describe("Glob patterns for TailorDB type definition files"),
3149
- ignores: z.array(z.string()).optional().describe("Glob patterns to exclude from type discovery"),
3150
- erdSite: z.string().optional().describe("URL for the ERD (Entity Relationship Diagram) site"),
3151
- migration: TailorDBMigrationConfigSchema.optional().describe("Migration configuration"),
3152
- gqlOperations: GqlOperationsSchema.optional().describe("Default GraphQL operations for all types in this service")
3153
- });
3154
-
3155
3766
  //#endregion
3156
3767
  //#region src/cli/services/tailordb/es-builtins.ts
3157
3768
  const globalsMap = globals.default ?? globals;
@@ -3164,7 +3775,7 @@ const globalsMap = globals.default ?? globals;
3164
3775
  * globals['shared-node-browser'] (shared runtime globals like
3165
3776
  * console, fetch, setTimeout, etc.) from the `globals` npm package.
3166
3777
  */
3167
- const ES_BUILTINS = new Set([...Object.keys(globalsMap.builtin ?? {}), ...Object.keys(globalsMap["shared-node-browser"] ?? {})]);
3778
+ const ES_BUILTINS = /* @__PURE__ */ new Set([...Object.keys(globalsMap.builtin ?? {}), ...Object.keys(globalsMap["shared-node-browser"] ?? {})]);
3168
3779
 
3169
3780
  //#endregion
3170
3781
  //#region src/cli/services/tailordb/hooks-validate-bundler.ts
@@ -3192,7 +3803,7 @@ function collectBindingsFromPattern(pattern, bindings) {
3192
3803
  }
3193
3804
  }
3194
3805
  /** Fields that contain TypeScript type annotations (not runtime references). */
3195
- const TS_TYPE_FIELDS = new Set([
3806
+ const TS_TYPE_FIELDS = /* @__PURE__ */ new Set([
3196
3807
  "typeAnnotation",
3197
3808
  "typeParameters",
3198
3809
  "returnType",
@@ -3433,7 +4044,7 @@ function buildPrecompiledExpr(bundleCode) {
3433
4044
  return `(() => {
3434
4045
  const module = { exports: {} };
3435
4046
  const exports = module.exports;
3436
- ${bundleCode}\n return module.exports.main({ value: _value, data: _data, user: ${tailorUserMap} });\n})()`;
4047
+ ${bundleCode}\n return module.exports.main({ value: _value, data: _data, invoker: ${tailorPrincipalMap} });\n})()`;
3437
4048
  }
3438
4049
  /**
3439
4050
  * Build entry file content from already-resolved imports and declarations.
@@ -3453,12 +4064,13 @@ function buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFi
3453
4064
  }
3454
4065
  async function bundleScriptTarget(args) {
3455
4066
  const { fn, kind, sourceFilePath, sourceBindings, tempDir, targetIndex, tsconfig } = args;
4067
+ const context = `${kind} in ${sourceFilePath}`;
3456
4068
  const fnSource = stringifyFunction(fn);
3457
- const inlineExpr = `(${fnSource})({ value: _value, data: _data, user: ${tailorUserMap} })`;
4069
+ const inlineExpr = assertParsableExpression(`(${fnSource})({ value: _value, data: _data, invoker: ${tailorPrincipalMap} })`, context);
3458
4070
  const freeVars = findUndefinedReferences(`const __fn = ${fnSource};`);
3459
4071
  if (freeVars.size === 0) return inlineExpr;
3460
4072
  const { imports, declarations, unresolved } = resolveNeededBindings(freeVars, sourceBindings);
3461
- if (unresolved.length > 0) throw new Error(`${kind} in ${sourceFilePath} captures unresolvable variables (${unresolved.join(", ")}). Hooks and validators must not reference variables that cannot be resolved from the source file.
4073
+ if (unresolved.length > 0) throw new Error(`${context} captures unresolvable variables (${unresolved.join(", ")}). Hooks and validators must not reference variables that cannot be resolved from the source file.
3462
4074
  ${kind}: ${fnSource}`);
3463
4075
  const entryContent = buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFilePath);
3464
4076
  const entryPath = join(tempDir, `tailordb-script-${targetIndex}.entry.ts`);
@@ -3481,7 +4093,7 @@ async function bundleScriptTarget(args) {
3481
4093
  },
3482
4094
  logLevel: "silent"
3483
4095
  })).output[0].code;
3484
- return buildPrecompiledExpr(bundledCode);
4096
+ return assertParsableExpression(buildPrecompiledExpr(bundledCode), context);
3485
4097
  }
3486
4098
  /**
3487
4099
  * Precompile TailorDB hooks/validators into self-contained script expressions using rolldown.
@@ -3569,7 +4181,7 @@ async function fetchExternalTailorDBTypeNameSources(args) {
3569
4181
  });
3570
4182
  return [tailordbTypes, nextPageToken ?? ""];
3571
4183
  } catch (error) {
3572
- if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
4184
+ if (isNotFoundError(error)) return [[], ""];
3573
4185
  throw error;
3574
4186
  }
3575
4187
  });
@@ -3614,8 +4226,23 @@ function assertUniqueLocalTailorDBTypeNames(args) {
3614
4226
  */
3615
4227
  async function assertUniqueTailorDBTypeNamesWithExternal(args) {
3616
4228
  const localSources = collectLocalTailorDBTypeNameSources(args);
3617
- const externalSources = args.externalTailorDBNamespaces.length > 0 ? await fetchExternalTailorDBTypeNameSources(args) : [];
3618
- assertUniqueTailorDBTypeNames({ sources: [...localSources, ...externalSources] });
4229
+ const plannedExternalServices = args.plannedExternalTailorDBServices ?? [];
4230
+ const plannedExternalNamespaces = new Set(plannedExternalServices.map((service) => service.namespace));
4231
+ const plannedExternalSources = collectLocalTailorDBTypeNameSources({ tailorDBServices: plannedExternalServices }).map((source) => ({
4232
+ ...source,
4233
+ kind: "external"
4234
+ }));
4235
+ const remoteExternalNamespaces = args.externalTailorDBNamespaces.filter((namespace) => !plannedExternalNamespaces.has(namespace));
4236
+ const externalSources = remoteExternalNamespaces.length > 0 ? await fetchExternalTailorDBTypeNameSources({
4237
+ client: args.client,
4238
+ workspaceId: args.workspaceId,
4239
+ externalTailorDBNamespaces: remoteExternalNamespaces
4240
+ }) : [];
4241
+ assertUniqueTailorDBTypeNames({ sources: [
4242
+ ...localSources,
4243
+ ...plannedExternalSources,
4244
+ ...externalSources
4245
+ ] });
3619
4246
  }
3620
4247
  function formatTailorDBTypeNameSource(source) {
3621
4248
  const namespaceLabel = source.kind === "external" ? `external namespace "${source.namespace}"` : `namespace "${source.namespace}"`;
@@ -3695,7 +4322,7 @@ function createTailorDBService(params) {
3695
4322
  const module = await import(pathToFileURL(typeFile).href);
3696
4323
  for (const exportName of Object.keys(module)) {
3697
4324
  const exportedValue = module[exportName];
3698
- const result = TailorDBTypeSchema.safeParse(exportedValue);
4325
+ const result = TailorDBTypeSchema.safeParse(stripTailorDBTypeBuilderHelpers(exportedValue));
3699
4326
  if (!result.success) {
3700
4327
  if (isSdkBranded(exportedValue, "tailordb-type")) throw result.error;
3701
4328
  continue;
@@ -3894,32 +4521,51 @@ function withBundleConcurrency(items, worker) {
3894
4521
  * - Bundle inline: interpolated into the generated `.entry.js` wrapper and
3895
4522
  * evaluated inside the bundled script at function entry.
3896
4523
  *
3897
- * The user field mapping (server → SDK) shared across services is defined in
3898
- * `@/parser/service/tailordb` as `tailorUserMap`.
4524
+ * The principal field mapping (server → SDK) shared across services is built by
4525
+ * `makePrincipalExpr` from `@/parser/service/tailordb`; `tailorPrincipalMap`
4526
+ * (the `caller` mapping) is one of its outputs. `INVOKER_EXPR` and
4527
+ * `ACTOR_TRANSFORM_EXPR` below come from the same factory so the three stay in
4528
+ * sync.
3899
4529
  */
3900
4530
  /**
3901
4531
  * `invoker` value expression, inlined into bundler entry wrappers.
3902
4532
  *
3903
4533
  * Calls `tailor.context.getInvoker()` at function entry and maps the server
3904
- * shape to TailorInvoker. Anonymous callers (`null`) pass through as `null`.
3905
- */
3906
- const INVOKER_EXPR = `(($raw) => $raw ? ({
3907
- id: $raw.id,
3908
- type: $raw.type,
3909
- workspaceId: $raw.workspaceId,
3910
- attributes: $raw.attributeMap,
3911
- attributeList: $raw.attributes,
3912
- }) : null)(tailor.context.getInvoker())`;
4534
+ * shape to `TailorPrincipal | null`. The payload is already in SDK type shape,
4535
+ * so no `USER_TYPE_*` normalization is needed; anonymous callers (`null`) pass
4536
+ * through as `null`.
4537
+ */
4538
+ const INVOKER_EXPR = makePrincipalExpr({
4539
+ source: "tailor.context.getInvoker()",
4540
+ normalize: false,
4541
+ fields: {
4542
+ type: { raw: "$raw.type" },
4543
+ id: "$raw.id",
4544
+ workspaceId: "$raw.workspaceId",
4545
+ attributes: "$raw.attributeMap",
4546
+ attributeList: "$raw.attributes"
4547
+ }
4548
+ });
3913
4549
  /**
3914
4550
  * Actor field transformation expression.
3915
4551
  *
3916
- * Transforms the server's actor object to match the SDK's TailorActor type:
3917
- * server `attributeMap` → SDK `attributes`
3918
- * server `attributes` → SDK `attributeList`
3919
- * other fields → passed through
3920
- * null/undefined actor → null
3921
- */
3922
- const ACTOR_TRANSFORM_EXPR = "actor: args.actor ? (({ attributeMap, attributes: attrList, ...rest }) => ({ ...rest, attributes: attributeMap, attributeList: attrList }))(args.actor) : null";
4552
+ * Transforms the server's actor object to match `TailorPrincipal | null`.
4553
+ */
4554
+ const ACTOR_TRANSFORM_EXPR = `actor: ${makePrincipalExpr({
4555
+ source: "args.actor",
4556
+ normalize: true,
4557
+ requireId: true,
4558
+ fields: {
4559
+ type: {
4560
+ raw: "$raw?.userType",
4561
+ fallback: "$raw?.type"
4562
+ },
4563
+ id: "$raw?.userId ?? $raw?.id",
4564
+ workspaceId: "$raw.workspaceId",
4565
+ attributes: "$raw.attributeMap ?? {}",
4566
+ attributeList: "$raw.attributes ?? []"
4567
+ }
4568
+ })}`;
3923
4569
  /**
3924
4570
  * Build the JavaScript expression that transforms server-format executor event
3925
4571
  * args into SDK-format args at runtime.
@@ -3947,27 +4593,13 @@ function buildExecutorArgsExpr(triggerKind, env) {
3947
4593
  * Transforms server context to SDK resolver context:
3948
4594
  * context.args → input
3949
4595
  * context.pipeline → spread into result
3950
- * user (global var) → TailorUser (via tailorUserMap: workspace_id→workspaceId, attribute_map→attributes, attributes→attributeList)
4596
+ * user (global var) → caller (`TailorPrincipal | null`)
3951
4597
  * env → injected as JSON
3952
4598
  * @param env - Application env record to embed in the expression
3953
4599
  * @returns A JavaScript expression string for the operationHook
3954
4600
  */
3955
4601
  function buildResolverOperationHookExpr(env) {
3956
- return `({ ...context.pipeline, input: context.args, user: ${tailorUserMap}, env: ${JSON.stringify(env)} });`;
3957
- }
3958
-
3959
- //#endregion
3960
- //#region src/cli/services/executor/loader.ts
3961
- /**
3962
- * Load and validate an executor definition from a file.
3963
- * @param executorFilePath - Path to the executor file
3964
- * @returns Parsed executor or null if invalid
3965
- */
3966
- async function loadExecutor(executorFilePath) {
3967
- const executor = (await import(pathToFileURL(executorFilePath).href)).default;
3968
- const parseResult = ExecutorSchema.safeParse(executor);
3969
- if (!parseResult.success) return null;
3970
- return parseResult.data;
4602
+ return `({ ...context.pipeline, input: context.args, caller: ${tailorPrincipalMap}, env: ${JSON.stringify(env)} });`;
3971
4603
  }
3972
4604
 
3973
4605
  //#endregion
@@ -4123,6 +4755,7 @@ const HttpAdapterConfigSchema = z.strictObject({
4123
4755
  //#region src/cli/services/http-adapter/bundler.ts
4124
4756
  const ADAPTER_BUNDLE_WARN_BYTES = 64 * 1024;
4125
4757
  const ADAPTER_BUNDLE_ERROR_BYTES = 256 * 1024;
4758
+ const GRAPHQL_WEB_MODULE = createRequire(import.meta.url).resolve("@0no-co/graphql.web");
4126
4759
  /**
4127
4760
  * Bundle each adapter's `input` (and `output`, if present) into a standalone
4128
4761
  * IIFE defining a global `transform(input)` entry point. `input` gets a
@@ -4170,7 +4803,7 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
4170
4803
  tsconfig,
4171
4804
  inlineSourcemap: false,
4172
4805
  bundleLogLevel,
4173
- prefix: kind
4806
+ prefix: `${kind}:document-query-normalize-v1`
4174
4807
  });
4175
4808
  const code = await withCache({
4176
4809
  cache,
@@ -4181,8 +4814,6 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
4181
4814
  async build(cachePlugins) {
4182
4815
  const entryPath = path.join(outputDir, `${adapter.name}.${kind}.entry.js`);
4183
4816
  const absoluteSourcePath = path.resolve(adapter.sourceFile);
4184
- const entryContent = kind === "input" ? buildInputEntry(absoluteSourcePath, adapter.methods) : buildOutputEntry(absoluteSourcePath);
4185
- fs$1.writeFileSync(entryPath, entryContent);
4186
4817
  const plugins = [
4187
4818
  {
4188
4819
  name: "http-adapter-reject-node-imports",
@@ -4209,6 +4840,8 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
4209
4840
  ];
4210
4841
  let bundled;
4211
4842
  try {
4843
+ const entryContent = kind === "input" ? buildInputEntry(absoluteSourcePath, adapter.methods, GRAPHQL_WEB_MODULE) : buildOutputEntry(absoluteSourcePath);
4844
+ fs$1.writeFileSync(entryPath, entryContent);
4212
4845
  bundled = (await rolldown.build({
4213
4846
  input: entryPath,
4214
4847
  write: false,
@@ -4242,10 +4875,20 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
4242
4875
  code
4243
4876
  ];
4244
4877
  }
4245
- function buildInputEntry(absoluteSourcePath, methods) {
4246
- const cases = methods.map((method) => ` case "${HTTP_METHODS[method]}": return __adapter.input.${method}(req);`).join("\n");
4878
+ function buildInputEntry(absoluteSourcePath, methods, graphqlPrinterModule) {
4879
+ const cases = methods.map((method) => {
4880
+ const expression = `__normalizeHttpAdapterGraphQLRequest(${`__adapter.input.${method}(req)`})`;
4881
+ return ` case "${HTTP_METHODS[method]}": return ${expression};`;
4882
+ }).join("\n");
4247
4883
  const supported = methods.map((m) => HTTP_METHODS[m]).join(", ");
4248
- return `import __adapter from ${JSON.stringify(absoluteSourcePath)};
4884
+ return `${`import { print as __printHttpAdapterDocument } from ${JSON.stringify(graphqlPrinterModule)};
4885
+ function __normalizeHttpAdapterGraphQLRequest(result) {
4886
+ if (!result || typeof result.query === "string") {
4887
+ return result;
4888
+ }
4889
+ return { ...result, query: __printHttpAdapterDocument(result.query) };
4890
+ }
4891
+ `}import __adapter from ${JSON.stringify(absoluteSourcePath)};
4249
4892
  globalThis.transform = function(req) {
4250
4893
  switch (req.method) {
4251
4894
  ${cases}
@@ -4385,7 +5028,7 @@ function isAsyncFunction(fn) {
4385
5028
  //#endregion
4386
5029
  //#region src/parser/service/resolver/schema.ts
4387
5030
  const QueryTypeSchema = z.union([z.literal("query"), z.literal("mutation")]).describe("GraphQL operation type");
4388
- const ResolverSchema = z.object({
5031
+ const ResolverSchema = z.strictObject({
4389
5032
  operation: QueryTypeSchema.describe("GraphQL operation type (query or mutation)"),
4390
5033
  name: z.string().describe("Resolver name"),
4391
5034
  description: z.string().optional().describe("Resolver description"),
@@ -4393,7 +5036,7 @@ const ResolverSchema = z.object({
4393
5036
  body: functionSchema.describe("Resolver implementation function"),
4394
5037
  output: TailorFieldSchema.describe("Output field definition"),
4395
5038
  publishEvents: z.boolean().optional().describe("Enable publishing events from this resolver"),
4396
- authInvoker: AuthInvokerSchema.optional().describe("Machine user to execute this resolver as")
5039
+ invoker: AuthInvokerSchema.optional().describe("Machine user to execute this resolver as")
4397
5040
  });
4398
5041
 
4399
5042
  //#endregion
@@ -4457,12 +5100,12 @@ async function bundleResolvers(namespace, config, triggerContext, cache, inlineS
4457
5100
  } catch {
4458
5101
  tsconfig = void 0;
4459
5102
  }
4460
- const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(resolver, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
5103
+ const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(namespace, resolver, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
4461
5104
  for (const [name, code] of results) bundledCode.set(name, code);
4462
5105
  logger.log(`${styles.success("Bundled")} ${styles.info(`"${namespace}"`)}`);
4463
5106
  return bundledCode;
4464
5107
  }
4465
- async function bundleSingleResolver(resolver, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
5108
+ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
4466
5109
  const serializedTriggerContext = serializeTriggerContext(triggerContext);
4467
5110
  const contextHash = computeBundlerContextHash({
4468
5111
  sourceFile: resolver.sourceFile,
@@ -4474,6 +5117,7 @@ async function bundleSingleResolver(resolver, outputDir, tsconfig, triggerContex
4474
5117
  const code = await withCache({
4475
5118
  cache,
4476
5119
  kind: "resolver",
5120
+ namespace,
4477
5121
  name: resolver.name,
4478
5122
  sourceFile: resolver.sourceFile,
4479
5123
  contextHash,
@@ -4489,7 +5133,7 @@ async function bundleSingleResolver(resolver, outputDir, tsconfig, triggerContex
4489
5133
  const result = t.object(_internalResolver.input).parse({
4490
5134
  value: context.input,
4491
5135
  data: context.input,
4492
- user: context.user,
5136
+ invoker,
4493
5137
  });
4494
5138
 
4495
5139
  if (result.issues) {
@@ -4568,9 +5212,26 @@ function createResolverService(namespace, config) {
4568
5212
  const resolverFiles = loadFilesWithIgnores(config);
4569
5213
  logger.log(`Found ${styles.highlight(resolverFiles.length.toString())} resolver files for service ${styles.highlight(`"${namespace}"`)}`);
4570
5214
  await Promise.all(resolverFiles.map((resolverFile) => loadResolverForFile(resolverFile)));
5215
+ assertUniqueResolverNames(resolvers, namespace);
4571
5216
  }
4572
5217
  };
4573
5218
  }
5219
+ /**
5220
+ * Assert that every loaded resolver in a namespace has a unique name.
5221
+ * Resolvers are stored by source file, so two files declaring the same
5222
+ * `name` would otherwise silently share a single bundle cache entry.
5223
+ * @param resolvers - Loaded resolvers keyed by source file
5224
+ * @param namespace - The namespace the resolvers belong to
5225
+ */
5226
+ function assertUniqueResolverNames(resolvers, namespace) {
5227
+ const seenNames = /* @__PURE__ */ new Map();
5228
+ for (const [file, resolver] of Object.entries(resolvers)) {
5229
+ const relativePath = path.relative(process.cwd(), file);
5230
+ const existing = seenNames.get(resolver.name);
5231
+ if (existing) throw new Error(`Duplicate resolver name "${resolver.name}" found in namespace "${namespace}":\n - ${existing}\n - ${relativePath}\nEach resolver must have a unique name within a namespace.`);
5232
+ seenNames.set(resolver.name, relativePath);
5233
+ }
5234
+ }
4574
5235
 
4575
5236
  //#endregion
4576
5237
  //#region src/cli/services/workflow/source-transformer.ts
@@ -4675,11 +5336,17 @@ function transformWorkflowSource(source, targetJobName, targetJobExportName, oth
4675
5336
  end: endPos,
4676
5337
  text: ""
4677
5338
  });
4678
- } else if (!job.statementRange) replacements.push({
4679
- start: job.bodyValueRange.start,
4680
- end: job.bodyValueRange.end,
4681
- text: "() => {}"
4682
- });
5339
+ } else if (!job.statementRange) {
5340
+ removedRanges.push({
5341
+ start: job.bodyValueRange.start,
5342
+ end: job.bodyValueRange.end
5343
+ });
5344
+ replacements.push({
5345
+ start: job.bodyValueRange.start,
5346
+ end: job.bodyValueRange.end,
5347
+ text: "() => {}"
5348
+ });
5349
+ }
4683
5350
  }
4684
5351
  if (otherJobExportNames) for (const exportName of otherJobExportNames) {
4685
5352
  if (exportName === targetJobExportName) continue;
@@ -4933,7 +5600,7 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
4933
5600
 
4934
5601
  //#endregion
4935
5602
  //#region src/parser/service/workflow/schema.ts
4936
- const WorkflowJobSchema = z.object({
5603
+ const WorkflowJobSchema = z.strictObject({
4937
5604
  name: z.string().describe("Job name (must be unique across the project)"),
4938
5605
  trigger: functionSchema.describe("Trigger function that initiates the job"),
4939
5606
  body: functionSchema.describe("Job implementation function")
@@ -4958,7 +5625,7 @@ function durationToSeconds(duration) {
4958
5625
  }
4959
5626
  const baseDurationSchema = z.templateLiteral([z.number().int().positive(), z.enum(durationUnits)]);
4960
5627
  const durationSchema = (maxSeconds) => baseDurationSchema.refine((val) => durationToSeconds(val) <= maxSeconds, { message: `Duration must be at most ${maxSeconds} seconds` });
4961
- const RetryPolicySchema = z.object({
5628
+ const RetryPolicySchema = z.strictObject({
4962
5629
  maxRetries: z.number().int().min(1).max(10).describe("Maximum number of retries (1-10)"),
4963
5630
  initialBackoff: durationSchema(3600).describe("Initial backoff duration (e.g., '1s', '500ms', '1m', max 1h)"),
4964
5631
  maxBackoff: durationSchema(86400).describe("Maximum backoff duration (e.g., '30s', '5m', max 24h)"),
@@ -4970,8 +5637,8 @@ const RetryPolicySchema = z.object({
4970
5637
  message: "initialBackoff must be greater than 0",
4971
5638
  path: ["initialBackoff"]
4972
5639
  });
4973
- const ConcurrencyPolicySchema = z.object({ maxConcurrentExecutions: z.number().int().min(1).max(1e3).describe("Maximum number of concurrent executions (1-1000)") });
4974
- const WorkflowSchema = z.object({
5640
+ const ConcurrencyPolicySchema = z.strictObject({ maxConcurrentExecutions: z.number().int().min(1).max(1e3).describe("Maximum number of concurrent executions (1-1000)") });
5641
+ const WorkflowSchema = z.strictObject({
4975
5642
  name: z.string().describe("Workflow name"),
4976
5643
  mainJob: WorkflowJobSchema.describe("Main job that starts the workflow"),
4977
5644
  retryPolicy: RetryPolicySchema.optional().describe("Retry policy for the workflow"),
@@ -4980,6 +5647,11 @@ const WorkflowSchema = z.object({
4980
5647
 
4981
5648
  //#endregion
4982
5649
  //#region src/cli/services/workflow/service.ts
5650
+ function stripRuntimeTrigger(workflow) {
5651
+ if (!isSdkBranded(workflow, "workflow") || workflow === null || typeof workflow !== "object" || !("trigger" in workflow)) return workflow;
5652
+ const { trigger: _trigger, ...rest } = workflow;
5653
+ return rest;
5654
+ }
4983
5655
  /**
4984
5656
  * Creates a new WorkflowService instance.
4985
5657
  * @param params - Parameters for creating the service
@@ -5087,7 +5759,7 @@ async function loadFileContent(filePath) {
5087
5759
  const module = await import(pathToFileURL(filePath).href);
5088
5760
  for (const [exportName, exportValue] of Object.entries(module)) {
5089
5761
  if (exportName === "default") {
5090
- const workflowResult = WorkflowSchema.safeParse(exportValue);
5762
+ const workflowResult = WorkflowSchema.safeParse(stripRuntimeTrigger(exportValue));
5091
5763
  if (workflowResult.success) workflow = workflowResult.data;
5092
5764
  else if (isSdkBranded(exportValue, ["workflow", "workflow-job"])) throw workflowResult.error;
5093
5765
  continue;
@@ -5130,23 +5802,29 @@ function getApplicationAuthNamespace(application) {
5130
5802
  *
5131
5803
  * Resolution order:
5132
5804
  * 1. Config value (`inlineSourcemap` in defineConfig) — if explicitly set
5133
- * 2. Environment variable `TAILOR_ENABLE_INLINE_SOURCEMAP` — if explicitly set
5805
+ * 2. Environment variable `TAILOR_INLINE_SOURCEMAP` — if explicitly set
5134
5806
  * 3. Default: `true`
5135
5807
  * @param configValue - The `inlineSourcemap` value from AppConfig
5136
5808
  * @returns Whether inline sourcemaps should be enabled
5137
5809
  */
5138
5810
  function resolveInlineSourcemap(configValue) {
5139
5811
  if (configValue !== void 0) return configValue;
5140
- const envValue = parseBoolean(process.env.TAILOR_ENABLE_INLINE_SOURCEMAP);
5812
+ const envValue = parseBoolean(process.env.TAILOR_INLINE_SOURCEMAP);
5141
5813
  if (envValue !== void 0) return envValue;
5142
5814
  return true;
5143
5815
  }
5144
5816
 
5817
+ //#endregion
5818
+ //#region src/cli/shared/resolver-bundle-key.ts
5819
+ function resolverBundleKey(namespace, resolverName) {
5820
+ return `${namespace}:${resolverName}`;
5821
+ }
5822
+
5145
5823
  //#endregion
5146
5824
  //#region src/parser/service/aigateway/schema.ts
5147
5825
  const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
5148
5826
  const AUTH_NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
5149
- const AIGatewaySchema = z.object({
5827
+ const AIGatewaySchema = z.strictObject({
5150
5828
  name: z.string().regex(NAME_PATTERN, "Must be 3-30 lowercase alphanumeric characters or hyphens").describe("AI Gateway name"),
5151
5829
  authNamespace: z.string().regex(AUTH_NAMESPACE_PATTERN, "Must be 3-63 lowercase alphanumeric characters or hyphens").describe("Auth namespace used to resolve request tokens against the workspace's auth"),
5152
5830
  cors: z.array(z.string()).optional().describe("Allowed CORS origins for browser-based clients. Each entry is `*`, `http(s)://*`, `http(s)://*.example.com`, or `http(s)://app.example.com`, optionally with `:port`. Empty list disables cross-origin access.")
@@ -5156,7 +5834,8 @@ const AIGatewaySchema = z.object({
5156
5834
  //#region src/parser/service/idp/schema.ts
5157
5835
  /**
5158
5836
  * Normalize IdPGqlOperationsConfig (alias or object) to IdPGqlOperations object.
5159
- * "query" alias expands to read-only mode: { create: false, update: false, delete: false, read: true, sendPasswordResetEmail: false }
5837
+ * "query" alias expands to read-only mode: every mutation is disabled while
5838
+ * queries (`read`, `requestMfaSettingsUrl`) stay enabled.
5160
5839
  * @param config - The config to normalize
5161
5840
  * @returns The normalized IdPGqlOperations object
5162
5841
  */
@@ -5166,7 +5845,9 @@ function normalizeIdPGqlOperations(config) {
5166
5845
  update: false,
5167
5846
  delete: false,
5168
5847
  read: true,
5169
- sendPasswordResetEmail: false
5848
+ sendPasswordResetEmail: false,
5849
+ requestMfaSettingsUrl: true,
5850
+ unenrollMfa: false
5170
5851
  };
5171
5852
  return config;
5172
5853
  }
@@ -5174,15 +5855,18 @@ function normalizeIdPGqlOperations(config) {
5174
5855
  * Zod schema for IdPGqlOperations configuration with normalization transform.
5175
5856
  * Accepts "query" alias or detailed object, normalizes to IdPGqlOperations object.
5176
5857
  */
5177
- const IdPGqlOperationsSchema = z.union([z.literal("query"), z.object({
5858
+ const IdPGqlOperationsSchema = z.union([z.literal("query"), z.strictObject({
5178
5859
  create: z.boolean().optional().describe("Enable _createUser mutation (default: true)"),
5179
5860
  update: z.boolean().optional().describe("Enable _updateUser mutation (default: true)"),
5180
5861
  delete: z.boolean().optional().describe("Enable _deleteUser mutation (default: true)"),
5181
5862
  read: z.boolean().optional().describe("Enable _users and _user queries (default: true)"),
5182
- sendPasswordResetEmail: z.boolean().optional().describe("Enable _sendPasswordResetEmail mutation (default: true)")
5863
+ sendPasswordResetEmail: z.boolean().optional().describe("Enable _sendPasswordResetEmail mutation (default: true)"),
5864
+ requestMfaSettingsUrl: z.boolean().optional().describe("Enable _requestMfaSettingsUrl query (default: true)"),
5865
+ unenrollMfa: z.boolean().optional().describe("Enable _unenrollMfa mutation (default: true)")
5183
5866
  })]).describe("Configuration for GraphQL operations on IdP users.\nAll operations are enabled by default (undefined or true = enabled, false = disabled).").transform((val) => normalizeIdPGqlOperations(val));
5184
5867
  const IdPLangSchema = z.enum(["en", "ja"]).describe("IdP UI language");
5185
- const IdPUserAuthPolicySchema = z.object({
5868
+ const allowedReturnOriginPattern = /^(https?:\/\/[a-zA-Z0-9.-]+(:[0-9]+)?|[a-z0-9][a-z0-9-]{1,61}[a-z0-9]:url)$/;
5869
+ const IdPUserAuthPolicySchema = z.strictObject({
5186
5870
  useNonEmailIdentifier: z.boolean().optional().describe("Use non-email identifier for usernames"),
5187
5871
  allowSelfPasswordReset: z.boolean().optional().describe("Allow users to reset their own passwords"),
5188
5872
  passwordRequireUppercase: z.boolean().optional().describe("Require uppercase letters in passwords"),
@@ -5194,7 +5878,11 @@ const IdPUserAuthPolicySchema = z.object({
5194
5878
  allowedEmailDomains: z.array(z.string()).optional().describe("Restrict registration to these email domains"),
5195
5879
  allowGoogleOauth: z.boolean().optional().describe("Enable Google OAuth login"),
5196
5880
  allowMicrosoftOauth: z.boolean().optional().describe("Enable Microsoft OAuth login"),
5197
- disablePasswordAuth: z.boolean().optional().describe("Disable password-based authentication")
5881
+ disablePasswordAuth: z.boolean().optional().describe("Disable password-based authentication"),
5882
+ enableMfa: z.boolean().optional().describe("Make TOTP MFA available for users in this namespace"),
5883
+ requireMfa: z.boolean().optional().describe("Require TOTP MFA enrollment and challenge for password-authenticated users (requires enableMfa)"),
5884
+ allowedReturnOrigins: z.array(z.string().regex(allowedReturnOriginPattern, "must be an http(s) origin like \"https://app.example.com\" (scheme + host + optional port, no path/query/fragment) or a static-website placeholder like \"<name>:url\"")).optional().describe("Application origins (scheme + host + optional port) allowed as MFA self-service return targets"),
5885
+ mfaIssuer: z.string().max(64, "mfaIssuer must be 64 characters or less").optional().describe("Label shown next to the user account in authenticator apps")
5198
5886
  }).refine((data) => data.passwordMinLength === void 0 || data.passwordMaxLength === void 0 || data.passwordMinLength <= data.passwordMaxLength, {
5199
5887
  message: "passwordMinLength must be less than or equal to passwordMaxLength",
5200
5888
  path: ["passwordMinLength"]
@@ -5222,9 +5910,15 @@ const IdPUserAuthPolicySchema = z.object({
5222
5910
  }).refine((data) => !data.disablePasswordAuth || !data.allowSelfPasswordReset, {
5223
5911
  message: "disablePasswordAuth cannot be used with allowSelfPasswordReset",
5224
5912
  path: ["disablePasswordAuth"]
5913
+ }).refine((data) => !data.requireMfa || data.enableMfa === true, {
5914
+ message: "requireMfa requires enableMfa to be enabled",
5915
+ path: ["requireMfa"]
5916
+ }).refine((data) => !data.enableMfa || data.allowedReturnOrigins && data.allowedReturnOrigins.length > 0, {
5917
+ message: "enableMfa requires allowedReturnOrigins to list at least one origin so MFA self-service has a valid return target",
5918
+ path: ["enableMfa"]
5225
5919
  });
5226
5920
  const emailFieldSchema = z.string().max(200, "must be 200 characters or less").regex(/^[^\r\n]*$/, "must not contain newline characters");
5227
- const IdPEmailConfigSchema = z.object({
5921
+ const IdPEmailConfigSchema = z.strictObject({
5228
5922
  fromName: emailFieldSchema.optional().describe("Default sender display name for emails"),
5229
5923
  passwordResetSubject: emailFieldSchema.optional().describe("Default subject for password reset emails")
5230
5924
  }).describe("Namespace-level email configuration defaults");
@@ -5233,18 +5927,18 @@ const IdPPermissionOperandSchema = z.union([
5233
5927
  z.boolean(),
5234
5928
  z.array(z.string()).readonly(),
5235
5929
  z.array(z.boolean()).readonly(),
5236
- z.object({ user: z.string() }),
5237
- z.object({ idpUser: z.enum([
5930
+ z.strictObject({ user: z.string() }),
5931
+ z.strictObject({ idpUser: z.enum([
5238
5932
  "id",
5239
5933
  "name",
5240
5934
  "disabled"
5241
5935
  ]) }),
5242
- z.object({ oldIdpUser: z.enum([
5936
+ z.strictObject({ oldIdpUser: z.enum([
5243
5937
  "id",
5244
5938
  "name",
5245
5939
  "disabled"
5246
5940
  ]) }),
5247
- z.object({ newIdpUser: z.enum([
5941
+ z.strictObject({ newIdpUser: z.enum([
5248
5942
  "id",
5249
5943
  "name",
5250
5944
  "disabled"
@@ -5262,7 +5956,7 @@ const IdPPermissionConditionSchema = z.tuple([
5262
5956
  IdPPermissionOperandSchema
5263
5957
  ]).readonly();
5264
5958
  const IdPActionPermissionSchema = z.union([
5265
- z.object({
5959
+ z.strictObject({
5266
5960
  conditions: z.union([IdPPermissionConditionSchema, z.array(IdPPermissionConditionSchema).readonly()]),
5267
5961
  description: z.string().optional(),
5268
5962
  permit: z.boolean().optional()
@@ -5283,19 +5977,20 @@ const IdPActionPermissionSchema = z.union([
5283
5977
  return boolIndex === -1 || boolIndex === arr.length - 1;
5284
5978
  }, { message: "Boolean permit flag must only appear at the end" }).readonly()
5285
5979
  ]);
5286
- const IdPPermissionSchema = z.object({
5980
+ const IdPPermissionSchema = z.strictObject({
5287
5981
  create: z.array(IdPActionPermissionSchema).readonly(),
5288
5982
  read: z.array(IdPActionPermissionSchema).readonly(),
5289
5983
  update: z.array(IdPActionPermissionSchema).readonly(),
5290
5984
  delete: z.array(IdPActionPermissionSchema).readonly(),
5291
- sendPasswordResetEmail: z.array(IdPActionPermissionSchema).readonly()
5985
+ sendPasswordResetEmail: z.array(IdPActionPermissionSchema).readonly().optional(),
5986
+ unenrollMfa: z.array(IdPActionPermissionSchema).readonly().optional()
5292
5987
  }).describe("Per-operation permission policies for IdP users");
5293
- const IdPSchema = z.object({
5988
+ const IdPSchema = z.strictObject({
5294
5989
  name: z.string().describe("IdP service name"),
5295
5990
  authorization: z.union([
5296
5991
  z.literal("insecure"),
5297
5992
  z.literal("loggedIn"),
5298
- z.object({ cel: z.string() })
5993
+ z.strictObject({ cel: z.string() })
5299
5994
  ]).optional().describe("Authorization mode for IdP API access"),
5300
5995
  clients: z.array(z.string()).describe("OAuth2 client names that can use this IdP"),
5301
5996
  lang: IdPLangSchema.optional().describe("UI language for IdP pages"),
@@ -5304,20 +5999,26 @@ const IdPSchema = z.object({
5304
5999
  gqlOperations: IdPGqlOperationsSchema.optional().describe("Configure which GraphQL operations are enabled"),
5305
6000
  emailConfig: IdPEmailConfigSchema.optional().describe("Namespace-level email configuration defaults"),
5306
6001
  permission: IdPPermissionSchema.optional().describe("Per-operation permission policies for IdP users")
6002
+ }).refine((data) => !data.userAuthPolicy?.enableMfa || data.gqlOperations?.unenrollMfa === false || data.permission !== void 0 && data.permission.unenrollMfa !== void 0, {
6003
+ message: "permission.unenrollMfa must be set explicitly when userAuthPolicy.enableMfa is true (set [{ conditions: [...], permit: true }] to allow, or [] to deny all). permission itself must also be defined. The requirement is only relaxed when gqlOperations.unenrollMfa is false.",
6004
+ path: ["permission", "unenrollMfa"]
6005
+ }).refine((data) => !data.permission || data.userAuthPolicy?.disablePasswordAuth === true || data.gqlOperations?.sendPasswordResetEmail === false || data.permission.sendPasswordResetEmail !== void 0, {
6006
+ message: "permission.sendPasswordResetEmail must be set explicitly when password authentication is enabled (set [{ conditions: [...], permit: true }] to allow, or [] to deny; only optional when userAuthPolicy.disablePasswordAuth is true or gqlOperations.sendPasswordResetEmail is false)",
6007
+ path: ["permission", "sendPasswordResetEmail"]
5307
6008
  }).brand("IdPConfig");
5308
6009
 
5309
6010
  //#endregion
5310
6011
  //#region src/parser/service/secrets/schema.ts
5311
6012
  const nameSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/);
5312
6013
  const secretsVaultSchema = z.record(nameSchema, z.string().nullish());
5313
- const SecretsSchema = z.object({
6014
+ const SecretsSchema = z.strictObject({
5314
6015
  vaults: z.record(nameSchema, secretsVaultSchema),
5315
- options: z.object({ ignoreNullishValues: z.boolean() })
6016
+ options: z.strictObject({ ignoreNullishValues: z.boolean() })
5316
6017
  });
5317
6018
 
5318
6019
  //#endregion
5319
6020
  //#region src/parser/service/staticwebsite/schema.ts
5320
- const StaticWebsiteSchema = z.object({
6021
+ const StaticWebsiteSchema = z.strictObject({
5321
6022
  name: z.string().describe("Static website name"),
5322
6023
  description: z.string().optional().describe("Static website description"),
5323
6024
  allowedIpAddresses: z.array(z.string()).optional().describe("IP addresses allowed to access the website"),
@@ -5378,6 +6079,12 @@ function defineResolver(config) {
5378
6079
  subgraphs
5379
6080
  };
5380
6081
  }
6082
+ function stripIdpProviderHelper(idpConfig) {
6083
+ const configWithProvider = idpConfig;
6084
+ if (typeof configWithProvider.provider !== "function") return idpConfig;
6085
+ const { provider: _provider, ...config } = configWithProvider;
6086
+ return config;
6087
+ }
5381
6088
  function defineIdp(config) {
5382
6089
  const idpServices = [];
5383
6090
  const subgraphs = [];
@@ -5391,7 +6098,7 @@ function defineIdp(config) {
5391
6098
  if (idpNames.has(name)) throw new Error(`IdP with name "${name}" already defined.`);
5392
6099
  idpNames.add(name);
5393
6100
  if (!("external" in idpConfig)) {
5394
- const idp = IdPSchema.parse(idpConfig);
6101
+ const idp = IdPSchema.parse(stripIdpProviderHelper(idpConfig));
5395
6102
  idpServices.push(idp);
5396
6103
  }
5397
6104
  subgraphs.push({
@@ -5433,11 +6140,17 @@ function defineHttpAdapterService(config) {
5433
6140
  if (!config) return;
5434
6141
  return createHttpAdapterService({ config });
5435
6142
  }
6143
+ function stripStaticWebsiteUrlHelper(config) {
6144
+ const configWithUrl = config;
6145
+ if (configWithUrl.url !== `${config.name}:url`) return config;
6146
+ const { url: _url, ...websiteConfig } = configWithUrl;
6147
+ return websiteConfig;
6148
+ }
5436
6149
  function defineStaticWebsites(websites) {
5437
6150
  const staticWebsiteServices = [];
5438
6151
  const websiteNames = /* @__PURE__ */ new Set();
5439
6152
  (websites ?? []).forEach((config) => {
5440
- const website = StaticWebsiteSchema.parse(config);
6153
+ const website = StaticWebsiteSchema.parse(stripStaticWebsiteUrlHelper(config));
5441
6154
  if (websiteNames.has(website.name)) throw new Error(`Static website with name "${website.name}" already defined.`);
5442
6155
  websiteNames.add(website.name);
5443
6156
  staticWebsiteServices.push(website);
@@ -5609,7 +6322,7 @@ async function loadApplication(params) {
5609
6322
  };
5610
6323
  for (const pipeline of resolverResult.resolverServices) {
5611
6324
  const resolverBundles = await bundleResolvers(pipeline.namespace, pipeline.config, triggerContext, bundleCache, inlineSourcemap, bundleLogLevel);
5612
- for (const [name, code] of resolverBundles) bundledScripts.resolvers.set(name, code);
6325
+ for (const [name, code] of resolverBundles) bundledScripts.resolvers.set(resolverBundleKey(pipeline.namespace, name), code);
5613
6326
  }
5614
6327
  if (executorService) bundledScripts.executors = await bundleExecutors({
5615
6328
  config: executorService.config,
@@ -5676,5 +6389,5 @@ async function loadApplication(params) {
5676
6389
  }
5677
6390
 
5678
6391
  //#endregion
5679
- export { loadAccessToken as A, getDistDir as C, deleteUserTokens as D, loadConfig as E, removeLegacyUserAlias as F, resolveTokens as I, saveUserTokens as L, loadMachineUserName as M, loadWorkspaceId as N, fetchLatestToken as O, readPlatformConfig as P, writePlatformConfig as R, createBundleCache as S, hashFile as T, composeFunctionTreeshakeOptions as _, getApplicationAuthNamespace as a, getPluginGenerationDependencies as b, HTTP_METHODS as c, buildResolverOperationHookExpr as d, assertUniqueLocalTailorDBTypeNames as f, platformBundleDefinePlugin as g, stringifyFunction as h, resolveInlineSourcemap as i, loadConfigPath as j, findConfigUserKey as k, INVOKER_EXPR as l, TailorDBTypeSchema as m, generatePluginFilesIfNeeded as n, WorkflowJobSchema as o, assertUniqueTailorDBTypeNamesWithExternal as p, loadApplication as r, ResolverSchema as s, defineApplication as t, buildExecutorArgsExpr as u, createLogLevelTreeshakeOptions as v, hashContent as w, hasGenerationHooks as x, resolveBundleLogLevel as y };
5680
- //# sourceMappingURL=application-DqS1yBg3.mjs.map
6392
+ export { getConsoleBaseUrl as $, hasUserTokenEntry as A, removeLegacyUserAlias as B, getDistDir as C, deleteUserTokens as D, loadConfig as E, loadPlatformClientConfig as F, closeConnectionPool as G, resolveUserTokenKey as H, loadStoredUserTokens as I, fetchAllTolerant as J, defaultPlatformBaseUrl as K, loadWorkspaceId as L, loadConfigPath as M, loadConsoleBaseUrl as N, fetchLatestToken as O, loadMachineUserName as P, fetchUserInfo as Q, platformConfigFromProfile as R, createBundleCache as S, hashFile as T, saveUserTokens as U, resolveConfigUser as V, writePlatformConfig as W, fetchPaged as X, fetchMachineUserToken as Y, fetchPlatformMachineUserToken as Z, composeFunctionTreeshakeOptions as _, resolveInlineSourcemap as a, isDefaultPlatform as at, getPluginGenerationDependencies as b, ResolverSchema as c, buildExecutorArgsExpr as d, getOAuth2ClientId as et, buildResolverOperationHookExpr as f, platformBundleDefinePlugin as g, stringifyFunction as h, resolverBundleKey as i, initOperatorClient as it, loadAccessToken as j, hasAnyUserTokenEntry as k, HTTP_METHODS as l, assertUniqueTailorDBTypeNamesWithExternal as m, generatePluginFilesIfNeeded as n, getPlatformBaseUrl as nt, getApplicationAuthNamespace as o, resolveStaticWebsiteUrls as ot, assertUniqueLocalTailorDBTypeNames as p, fetchAll as q, loadApplication as r, initOAuth2Client as rt, WorkflowJobSchema as s, byName as st, defineApplication as t, getOrNull as tt, INVOKER_EXPR as u, createLogLevelTreeshakeOptions as v, hashContent as w, hasGenerationHooks as x, resolveBundleLogLevel as y, readPlatformConfig as z };
6393
+ //# sourceMappingURL=application-eHp9jXld.mjs.map