@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.
- package/CHANGELOG.md +514 -2
- package/README.md +9 -9
- package/agent-skills/{tailor-sdk → tailor}/SKILL.md +3 -1
- package/dist/aigateway-B3oZZE6v.mjs +15 -0
- package/dist/aigateway-B3oZZE6v.mjs.map +1 -0
- package/dist/application-D2hSK1ZL.mjs +3 -0
- package/dist/{application-DqS1yBg3.mjs → application-eHp9jXld.mjs} +1397 -684
- package/dist/application-eHp9jXld.mjs.map +1 -0
- package/dist/{authconnection-D2MhtTN5.mjs → authconnection-BGQM8FZI.mjs} +2 -2
- package/dist/authconnection-BGQM8FZI.mjs.map +1 -0
- package/dist/cli/cache/bundle-cache.d.mts +1 -0
- package/dist/cli/cache/store.d.mts +1 -0
- package/dist/cli/cache/types.d.mts +1 -0
- package/dist/cli/commands/api/api-call.d.mts +19 -0
- package/dist/cli/commands/api/index.d.mts +3 -0
- package/dist/cli/commands/deploy/aigateway.d.mts +1 -0
- package/dist/cli/commands/deploy/application.d.mts +1 -0
- package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
- package/dist/cli/commands/deploy/auth.d.mts +1 -0
- package/dist/cli/commands/deploy/deploy.d.mts +24 -0
- package/dist/cli/commands/deploy/executor.d.mts +1 -0
- package/dist/cli/commands/deploy/function-registry-types.d.mts +12 -0
- package/dist/cli/commands/deploy/function-registry.d.mts +2 -0
- package/dist/cli/commands/deploy/idp.d.mts +1 -0
- package/dist/cli/commands/deploy/resolver.d.mts +1 -0
- package/dist/cli/commands/deploy/secret-manager.d.mts +1 -0
- package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
- package/dist/cli/commands/deploy/tailordb/index.d.mts +1 -0
- package/dist/cli/commands/deploy/types.d.mts +1 -0
- package/dist/cli/commands/deploy/workflow.d.mts +1 -0
- package/dist/cli/commands/executor/get.d.mts +29 -0
- package/dist/cli/commands/executor/jobs.d.mts +123 -0
- package/dist/cli/commands/executor/list.d.mts +19 -0
- package/dist/cli/commands/executor/transform.d.mts +41 -0
- package/dist/cli/commands/executor/trigger.d.mts +41 -0
- package/dist/cli/commands/executor/webhook.d.mts +22 -0
- package/dist/cli/commands/function/get.d.mts +18 -0
- package/dist/cli/commands/function/list.d.mts +22 -0
- package/dist/cli/commands/function/transform.d.mts +11 -0
- package/dist/cli/commands/generate/options.d.mts +7 -0
- package/dist/cli/commands/generate/seed/bundler.d.mts +26 -0
- package/dist/cli/commands/generate/service.d.mts +11 -0
- package/dist/cli/commands/machineuser/list.d.mts +27 -0
- package/dist/cli/commands/machineuser/token.d.mts +22 -0
- package/dist/cli/commands/oauth2client/get.d.mts +18 -0
- package/dist/cli/commands/oauth2client/list.d.mts +20 -0
- package/dist/cli/commands/oauth2client/transform.d.mts +20 -0
- package/dist/cli/commands/organization/folder/create.d.mts +18 -0
- package/dist/cli/commands/organization/folder/delete.d.mts +16 -0
- package/dist/cli/commands/organization/folder/get.d.mts +17 -0
- package/dist/cli/commands/organization/folder/list.d.mts +22 -0
- package/dist/cli/commands/organization/folder/update.d.mts +18 -0
- package/dist/cli/commands/organization/get.d.mts +16 -0
- package/dist/cli/commands/organization/list.d.mts +15 -0
- package/dist/cli/commands/organization/transform.d.mts +27 -0
- package/dist/cli/commands/organization/tree.d.mts +25 -0
- package/dist/cli/commands/organization/update.d.mts +17 -0
- package/dist/cli/commands/remove.d.mts +16 -0
- package/dist/cli/commands/show.d.mts +34 -0
- package/dist/cli/commands/tailordb/migrate/bundler.d.mts +28 -0
- package/dist/cli/commands/tailordb/migrate/config.d.mts +19 -0
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +223 -0
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +17 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +202 -0
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +98 -0
- package/dist/cli/commands/tailordb/migrate/types.d.mts +7 -0
- package/dist/cli/commands/tailordb/truncate.d.mts +19 -0
- package/dist/cli/commands/workflow/executions.d.mts +75 -0
- package/dist/cli/commands/workflow/get.d.mts +29 -0
- package/dist/cli/commands/workflow/list.d.mts +19 -0
- package/dist/cli/commands/workflow/resume.d.mts +23 -0
- package/dist/cli/commands/workflow/start.d.mts +61 -0
- package/dist/cli/commands/workflow/status.d.mts +5 -0
- package/dist/cli/commands/workflow/transform.d.mts +33 -0
- package/dist/cli/commands/workflow/wait.d.mts +16 -0
- package/dist/cli/commands/workflow/waiter.d.mts +23 -0
- package/dist/cli/commands/workspace/app/health.d.mts +18 -0
- package/dist/cli/commands/workspace/app/list.d.mts +22 -0
- package/dist/cli/commands/workspace/app/transform.d.mts +18 -0
- package/dist/cli/commands/workspace/create.d.mts +25 -0
- package/dist/cli/commands/workspace/delete.d.mts +15 -0
- package/dist/cli/commands/workspace/get.d.mts +17 -0
- package/dist/cli/commands/workspace/list.d.mts +17 -0
- package/dist/cli/commands/workspace/restore.d.mts +15 -0
- package/dist/cli/commands/workspace/transform.d.mts +16 -0
- package/dist/cli/commands/workspace/user/invite.d.mts +22 -0
- package/dist/cli/commands/workspace/user/list.d.mts +22 -0
- package/dist/cli/commands/workspace/user/remove.d.mts +17 -0
- package/dist/cli/commands/workspace/user/transform.d.mts +8 -0
- package/dist/cli/commands/workspace/user/update.d.mts +22 -0
- package/dist/cli/erd-viewer-assets/app.js +279 -36
- package/dist/cli/erd-viewer-assets/index.html +4 -0
- package/dist/cli/erd-viewer-assets/styles.css +252 -5
- package/dist/cli/index.d.mts +18 -3
- package/dist/cli/index.mjs +2193 -519
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +81 -21048
- package/dist/cli/lib.mjs +8 -12
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/query/index.d.mts +35 -0
- package/dist/cli/services/application.d.mts +1 -0
- package/dist/cli/services/auth/service.d.mts +1 -0
- package/dist/cli/services/executor/service.d.mts +1 -0
- package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
- package/dist/cli/services/http-adapter/service.d.mts +1 -0
- package/dist/cli/services/resolver/service.d.mts +1 -0
- package/dist/cli/services/tailordb/service.d.mts +1 -0
- package/dist/cli/services/workflow/bundler.d.mts +1 -0
- package/dist/cli/services/workflow/service.d.mts +1 -0
- package/dist/cli/shared/args.d.mts +13 -0
- package/dist/cli/shared/client.d.mts +1273 -0
- package/dist/cli/shared/config-loader.d.mts +26 -0
- package/dist/cli/shared/context.d.mts +28 -0
- package/dist/cli/shared/script-executor.d.mts +75 -0
- package/dist/cli/shared/seed-chunker.d.mts +46 -0
- package/dist/cli/shared/trigger-context.d.mts +1 -0
- package/dist/cli/shared/type-generator.d.mts +20 -0
- package/dist/cli/ts-hook.d.mts +4 -0
- package/dist/cli/ts-hook.mjs +303 -0
- package/dist/completion/zsh-worker.zsh +1577 -1098
- package/dist/configure/config/index.d.mts +21 -0
- package/dist/configure/config/types.d.mts +103 -0
- package/dist/configure/index.d.mts +122 -8
- package/dist/configure/index.mjs +74 -34
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/aigateway/index.d.mts +17 -0
- package/dist/configure/services/aigateway/types.d.mts +11 -0
- package/dist/configure/services/auth/index.d.mts +34 -0
- package/dist/configure/services/auth/types.d.mts +172 -0
- package/dist/configure/services/executor/executor.d.mts +81 -0
- package/dist/configure/services/executor/index.d.mts +8 -0
- package/dist/configure/services/executor/operation.d.mts +45 -0
- package/dist/configure/services/executor/trigger/event.d.mts +272 -0
- package/dist/configure/services/executor/trigger/index.d.mts +8 -0
- package/dist/configure/services/executor/trigger/schedule.d.mts +25 -0
- package/dist/configure/services/executor/trigger/webhook.d.mts +44 -0
- package/dist/configure/services/executor/types.d.mts +2 -0
- package/dist/configure/services/http-adapter/http-adapter.d.mts +125 -0
- package/dist/configure/services/http-adapter/index.d.mts +2 -0
- package/dist/configure/services/idp/index.d.mts +64 -0
- package/dist/configure/services/idp/permission.d.mts +80 -0
- package/dist/configure/services/idp/types.d.mts +20 -0
- package/dist/configure/services/index.d.mts +32 -0
- package/dist/configure/services/resolver/index.d.mts +5 -0
- package/dist/configure/services/resolver/resolver.d.mts +75 -0
- package/dist/configure/services/resolver/types.d.mts +6 -0
- package/dist/configure/services/secrets/index.d.mts +38 -0
- package/dist/configure/services/secrets/types.d.mts +11 -0
- package/dist/configure/services/staticwebsite/index.d.mts +19 -0
- package/dist/configure/services/staticwebsite/types.d.mts +11 -0
- package/dist/configure/services/tailordb/index.d.mts +4 -0
- package/dist/configure/services/tailordb/permission.d.mts +119 -0
- package/dist/configure/services/tailordb/schema.d.mts +456 -0
- package/dist/configure/services/tailordb/types.d.mts +149 -0
- package/dist/configure/services/workflow/index.d.mts +6 -0
- package/dist/configure/services/workflow/job.d.mts +79 -0
- package/dist/configure/services/workflow/types.d.mts +2 -0
- package/dist/configure/services/workflow/wait-point.d.mts +73 -0
- package/dist/configure/services/workflow/workflow.d.mts +54 -0
- package/dist/configure/types/aigateway-name.d.mts +12 -0
- package/dist/configure/types/connection-name.d.mts +12 -0
- package/dist/configure/types/field-runtime.d.mts +17 -0
- package/dist/configure/types/field.d.mts +9 -0
- package/dist/configure/types/field.types.d.mts +109 -0
- package/dist/configure/types/idp-name.d.mts +12 -0
- package/dist/configure/types/index.d.mts +4 -0
- package/dist/configure/types/machine-user.d.mts +2 -0
- package/dist/configure/types/type.d.mts +82 -0
- package/dist/context-Bd266-ru.mjs.map +1 -1
- package/dist/{crashreport-BsjAkFWw.mjs → crashreport-Cg-pqRJV.mjs} +9 -11
- package/dist/{crashreport-BsjAkFWw.mjs.map → crashreport-Cg-pqRJV.mjs.map} +1 -1
- package/dist/{crashreport-pr6Rhvza.mjs → crashreport-D3RjuSYb.mjs} +1 -1
- package/dist/enum-constants-j9QBF0cB.mjs.map +1 -1
- package/dist/{errors-Dtf2WPaW.mjs → errors-118wUDRr.mjs} +2 -2
- package/dist/errors-118wUDRr.mjs.map +1 -0
- package/dist/field-runtime-CAnh_oow.mjs +183 -0
- package/dist/field-runtime-CAnh_oow.mjs.map +1 -0
- package/dist/{file-_oUZo76X.mjs → file-BbdFGdMV.mjs} +2 -10
- package/dist/file-BbdFGdMV.mjs.map +1 -0
- package/dist/{file-utils-DcyIPFQh.mjs → file-utils-CYZnO1pX.mjs} +5 -5
- package/dist/file-utils-CYZnO1pX.mjs.map +1 -0
- package/dist/{globals-Crz8o65k.mjs → globals-DhwZj0RB.mjs} +5 -47
- package/dist/globals-DhwZj0RB.mjs.map +1 -0
- package/dist/{idp-BDbK5gjm.mjs → idp-Bx25ZBdN.mjs} +9 -1
- package/dist/idp-Bx25ZBdN.mjs.map +1 -0
- package/dist/{interceptor-D-q1rvRl.mjs → interceptor-e33JtsC-.mjs} +2 -2
- package/dist/interceptor-e33JtsC-.mjs.map +1 -0
- package/dist/kysely/index.d.mts +2 -3
- package/dist/{kysely-type-DR8uzZTA.mjs → kysely-type-5eAChaHO.mjs} +4 -2
- package/dist/kysely-type-5eAChaHO.mjs.map +1 -0
- package/dist/{logger-CxF-Ex5d.mjs → logger-BEiZZ3qT.mjs} +3 -3
- package/dist/{logger-CxF-Ex5d.mjs.map → logger-BEiZZ3qT.mjs.map} +1 -1
- package/dist/parser/service/auth/index.d.mts +1 -0
- package/dist/parser/service/auth/schema.d.mts +1 -0
- package/dist/parser/service/http-adapter/index.d.mts +1 -0
- package/dist/parser/service/http-adapter/schema.d.mts +1 -0
- package/dist/parser/service/idp/types.d.mts +4 -0
- package/dist/parser/service/tailordb/types.d.mts +126 -0
- package/dist/platform-serialize-RoRtBS0v.mjs +46 -0
- package/dist/platform-serialize-RoRtBS0v.mjs.map +1 -0
- package/dist/plugin/builtin/enum-constants/index.d.mts +16 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +16 -1
- package/dist/plugin/builtin/file-utils/index.mjs +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +16 -1
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +45 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/get-generated-type.d.mts +17 -0
- package/dist/plugin/index.d.mts +3 -117
- package/dist/plugin/index.mjs +1 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/manager.d.mts +1 -0
- package/dist/plugin/types.d.mts +243 -0
- package/dist/plugin/with-context.d.mts +102 -0
- package/dist/{runtime-n9NCkjee.mjs → register-ts-hook-ZpBacA07.mjs} +4374 -2446
- package/dist/register-ts-hook-ZpBacA07.mjs.map +1 -0
- package/dist/{registry-DdsYlL_P.mjs → registry-DH4m7eYo.mjs} +4 -2
- package/dist/registry-DH4m7eYo.mjs.map +1 -0
- package/dist/{repl-editor-DmGr9zMw.mjs → repl-editor-BCozyiNq.mjs} +6 -5
- package/dist/{repl-editor-DmGr9zMw.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
- package/dist/runtime/aigateway.d.mts +35 -0
- package/dist/runtime/aigateway.mjs +3 -0
- package/dist/runtime/authconnection.d.mts +31 -2
- package/dist/runtime/authconnection.mjs +1 -1
- package/dist/runtime/context.d.mts +46 -2
- package/dist/runtime/file.d.mts +210 -2
- package/dist/runtime/file.mjs +2 -2
- package/dist/runtime/globals.d.mts +31 -19
- package/dist/runtime/iconv.d.mts +121 -2
- package/dist/runtime/idp.d.mts +186 -2
- package/dist/runtime/idp.mjs +1 -1
- package/dist/runtime/index.d.mts +47 -9
- package/dist/runtime/index.mjs +6 -5
- package/dist/runtime/secretmanager.d.mts +54 -2
- package/dist/runtime/types.d.mts +20 -0
- package/dist/runtime/workflow.d.mts +112 -2
- package/dist/runtime/workflow.mjs +2 -2
- package/dist/{schema-BhkpP5Hw.mjs → schema-89dYxNC2.mjs} +25 -187
- package/dist/schema-89dYxNC2.mjs.map +1 -0
- package/dist/{secret-file-DBqZhjFQ.mjs → secret-file-aoNXJmKa.mjs} +28 -3
- package/dist/secret-file-aoNXJmKa.mjs.map +1 -0
- package/dist/secretmanager-Bd45j7an.mjs +98 -0
- package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
- package/dist/seed/index.d.mts +1 -2
- package/dist/{seed-jf3008-h.mjs → seed-CRSQJI_z.mjs} +16 -6
- package/dist/seed-CRSQJI_z.mjs.map +1 -0
- package/dist/service-CkIwEctX.mjs +3 -0
- package/dist/{service-DU1mVzri.mjs → service-DeZeYa06.mjs} +3 -3
- package/dist/{service-DU1mVzri.mjs.map → service-DeZeYa06.mjs.map} +1 -1
- package/dist/service-ufH7sS8o.mjs +797 -0
- package/dist/service-ufH7sS8o.mjs.map +1 -0
- package/dist/{client-z_oHGVNy.mjs → service_pb-CIrhGwHk.mjs} +38 -466
- package/dist/service_pb-CIrhGwHk.mjs.map +1 -0
- package/dist/service_pb-DjwIn4jO.mjs +3 -0
- package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +204 -0
- package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +58 -0
- package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +316 -0
- package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +161 -0
- package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +2070 -0
- package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +861 -0
- package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +112 -0
- package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +69 -0
- package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +439 -0
- package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +564 -0
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +145 -0
- package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +451 -0
- package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +192 -0
- package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +50 -0
- package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +407 -0
- package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +430 -0
- package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +73 -0
- package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +17 -0
- package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +309 -0
- package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +591 -0
- package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +330 -0
- package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +113 -0
- package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +346 -0
- package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +54 -0
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +3576 -0
- package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +187 -0
- package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +29 -0
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +429 -0
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +90 -0
- package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +684 -0
- package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +797 -0
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +330 -0
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +183 -0
- package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +842 -0
- package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +276 -0
- package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2015 -0
- package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +483 -0
- package/dist/{telemetry-CdqJEzkj.mjs → telemetry-BRVdwh14.mjs} +3 -3
- package/dist/telemetry-BRVdwh14.mjs.map +1 -0
- package/dist/telemetry-CkbkeJxl.mjs +3 -0
- package/dist/test-env-key-D7UkZp99.mjs +75 -0
- package/dist/test-env-key-D7UkZp99.mjs.map +1 -0
- package/dist/types/aigateway.generated.d.mts +9 -0
- package/dist/types/app-config.generated.d.mts +4 -0
- package/dist/types/auth-connection.generated.d.mts +20 -0
- package/dist/types/auth.generated.d.mts +145 -0
- package/dist/types/executor.generated.d.mts +177 -0
- package/dist/types/helpers.d.mts +39 -0
- package/dist/types/http-adapter.generated.d.mts +17 -0
- package/dist/{workflow.generated-DtQwEo-x.d.mts → types/idp.generated.d.mts} +98 -185
- package/dist/types/resolver.generated.d.mts +39 -0
- package/dist/types/secrets.generated.d.mts +14 -0
- package/dist/types/staticwebsite.generated.d.mts +10 -0
- package/dist/types/tailordb.generated.d.mts +428 -0
- package/dist/types/workflow.generated.d.mts +12 -0
- package/dist/types-C-lQ4se3.mjs +4 -0
- package/dist/types-CXF2OYdR.mjs +199 -0
- package/dist/types-CXF2OYdR.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +5 -98
- package/dist/utils/test/index.mjs +4 -13
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/utils/test/mock.d.mts +87 -0
- package/dist/vitest/environment.d.mts +1 -2
- package/dist/vitest/environment.mjs +2 -2
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.d.mts +12 -428
- package/dist/vitest/index.mjs +741 -10
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock-kysely.d.mts +62 -0
- package/dist/vitest/mock.d.mts +9 -0
- package/dist/vitest/mocks/aigateway.d.mts +30 -0
- package/dist/vitest/mocks/authconnection.d.mts +28 -0
- package/dist/vitest/mocks/file.d.mts +41 -0
- package/dist/vitest/mocks/iconv.d.mts +27 -0
- package/dist/vitest/mocks/idp.d.mts +44 -0
- package/dist/vitest/mocks/secretmanager.d.mts +33 -0
- package/dist/vitest/mocks/tailordb.d.mts +71 -0
- package/dist/vitest/mocks/workflow.d.mts +115 -0
- package/dist/vitest/setup.d.mts +1 -2
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +40 -0
- package/dist/{workflow-DgemCAz3.mjs → workflow-Bo59B75H.mjs} +16 -4
- package/dist/workflow-Bo59B75H.mjs.map +1 -0
- package/docs/cli/application.md +98 -250
- package/docs/cli/auth.md +48 -292
- package/docs/cli/completion.md +1 -25
- package/docs/cli/crashreport.md +3 -61
- package/docs/cli/executor.md +72 -183
- package/docs/cli/function.md +14 -130
- package/docs/cli/organization.md +11 -221
- package/docs/cli/plugin.md +29 -0
- package/docs/cli/query.md +2 -22
- package/docs/cli/secret.md +71 -251
- package/docs/cli/setup.md +138 -40
- package/docs/cli/skills.md +50 -39
- package/docs/cli/staticwebsite.md +32 -180
- package/docs/cli/tailordb.md +97 -323
- package/docs/cli/upgrade.md +2 -22
- package/docs/cli/user.md +78 -247
- package/docs/cli/workflow.md +150 -172
- package/docs/cli/workspace.md +165 -538
- package/docs/cli-reference.md +184 -78
- package/docs/configuration.md +10 -4
- package/docs/github-actions.md +64 -24
- package/docs/migration/v2.md +735 -0
- package/docs/multi-environment.md +29 -7
- package/docs/plugin/index.md +6 -6
- package/docs/quickstart.md +8 -7
- package/docs/runtime.md +6 -2
- package/docs/services/aigateway.md +18 -2
- package/docs/services/auth.md +44 -52
- package/docs/services/executor.md +3 -3
- package/docs/services/http-adapter.md +16 -1
- package/docs/services/idp.md +55 -2
- package/docs/services/resolver.md +7 -7
- package/docs/services/secret.md +11 -11
- package/docs/services/staticwebsite.md +8 -2
- package/docs/services/tailordb-migration.md +24 -22
- package/docs/services/tailordb.md +39 -14
- package/docs/services/workflow.md +18 -18
- package/docs/testing.md +108 -50
- package/package.json +46 -35
- package/postinstall.mjs +4 -6
- package/dist/application-DB2r36Et.mjs +0 -3
- package/dist/application-DqS1yBg3.mjs.map +0 -1
- package/dist/authconnection-D2MhtTN5.mjs.map +0 -1
- package/dist/authconnection-DvUQAjQS.d.mts +0 -39
- package/dist/client-Dbohmtkv.mjs +0 -3
- package/dist/client-z_oHGVNy.mjs.map +0 -1
- package/dist/context-BuuIb8CC.d.mts +0 -68
- package/dist/errors-Dtf2WPaW.mjs.map +0 -1
- package/dist/field-DOsJCPFa.mjs +0 -22
- package/dist/field-DOsJCPFa.mjs.map +0 -1
- package/dist/file-BB8Vs9O_.d.mts +0 -250
- package/dist/file-_oUZo76X.mjs.map +0 -1
- package/dist/file-utils-DcyIPFQh.mjs.map +0 -1
- package/dist/globals-Crz8o65k.mjs.map +0 -1
- package/dist/http-adapter.generated-WgMnb7Sb.d.mts +0 -580
- package/dist/iconv-Co-TOPuH.d.mts +0 -122
- package/dist/idp-BDbK5gjm.mjs.map +0 -1
- package/dist/idp-DrhVrLmV.d.mts +0 -160
- package/dist/index-5vPyRu1y.d.mts +0 -18
- package/dist/index-B7AKc18V.d.mts +0 -47
- package/dist/index-BlpzXncY.d.mts +0 -1016
- package/dist/index-CK7u9isy.d.mts +0 -46
- package/dist/index-CNYe5lnW.d.mts +0 -18
- package/dist/index-DjUdWlzf.d.mts +0 -18
- package/dist/index-ZePLwxw7.d.mts +0 -208
- package/dist/interceptor-D-q1rvRl.mjs.map +0 -1
- package/dist/job-fuc3j1Ma.mjs +0 -53
- package/dist/job-fuc3j1Ma.mjs.map +0 -1
- package/dist/kysely-type-DR8uzZTA.mjs.map +0 -1
- package/dist/mock-BjFj5o1I.mjs +0 -804
- package/dist/mock-BjFj5o1I.mjs.map +0 -1
- package/dist/registry-DdsYlL_P.mjs.map +0 -1
- package/dist/runtime-n9NCkjee.mjs.map +0 -1
- package/dist/schema-BhkpP5Hw.mjs.map +0 -1
- package/dist/secret-file-DBqZhjFQ.mjs.map +0 -1
- package/dist/secretmanager-B3n4KHfm.d.mts +0 -55
- package/dist/seed-jf3008-h.mjs.map +0 -1
- package/dist/service-CCL8ruDf.mjs +0 -459
- package/dist/service-CCL8ruDf.mjs.map +0 -1
- package/dist/service-D6yonf2I.mjs +0 -3
- package/dist/telemetry-CdqJEzkj.mjs.map +0 -1
- package/dist/telemetry-ClwW5ohF.mjs +0 -3
- package/dist/test-env-key-D9kM6ETE.mjs +0 -49
- package/dist/test-env-key-D9kM6ETE.mjs.map +0 -1
- package/dist/types-B2RpYyA_.mjs +0 -371
- package/dist/types-B2RpYyA_.mjs.map +0 -1
- package/dist/types-ClhIrW_C.mjs +0 -4
- package/dist/types-DCUhgpyI.d.mts +0 -784
- package/dist/types-DhO_VEZd.d.mts +0 -574
- package/dist/types-DwDgacni.d.mts +0 -338
- package/dist/workflow-BbKvGLQg.d.mts +0 -96
- 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-
|
|
5
|
-
import { a as
|
|
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 {
|
|
9
|
-
import {
|
|
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
|
|
16
|
-
import { join, resolve } from "pathe";
|
|
18
|
+
import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";
|
|
17
19
|
import { pathToFileURL } from "node:url";
|
|
18
|
-
import
|
|
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 {
|
|
24
|
-
import {
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
}).
|
|
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
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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(
|
|
310
|
-
return
|
|
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
|
|
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,
|
|
361
|
-
* older SDK can still read the file. Configs
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
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
|
|
376
|
-
writeSecretFile(configPath, stringifyYAML(config.version ===
|
|
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
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
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 "${
|
|
521
|
-
Please run 'tailor
|
|
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
|
|
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
|
|
1292
|
+
* @param opts - Optional platform and user metadata
|
|
539
1293
|
*/
|
|
540
|
-
async function saveUserTokens(config, user, tokens, expiresAt,
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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 =
|
|
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
|
|
1380
|
+
Please verify your user name and login using 'tailor login' command.
|
|
603
1381
|
`);
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
|
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
|
|
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 =
|
|
629
|
-
const previousEmail = userEntry.email ?? inferEmailFromUserId(
|
|
1410
|
+
let resolvedUser = storedConfigUser;
|
|
1411
|
+
const previousEmail = userEntry.email ?? inferEmailFromUserId(user) ?? inferEmailFromUserId(storedConfigUser);
|
|
630
1412
|
let email = previousEmail;
|
|
631
|
-
if (shouldResolveSubjectOnRefresh(
|
|
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, {
|
|
642
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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\.
|
|
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.
|
|
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
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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 = "
|
|
2776
|
+
const NORMALIZER_IDENTIFIER = "__tailor_normalizeTriggerOptions";
|
|
1968
2777
|
/**
|
|
1969
2778
|
* Build the source text of the injected normalizer helper.
|
|
1970
2779
|
*
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
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} = (
|
|
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
|
-
|
|
2135
|
-
|
|
2136
|
-
const
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
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 `
|
|
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
|
|
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"
|
|
2991
|
+
for (const call of triggerCalls) if (call.kind === "workflow") {
|
|
2219
2992
|
const workflowName = localWorkflowNameMap.get(call.identifierName);
|
|
2220
2993
|
if (workflowName) {
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
|
2227
|
-
const transformedCall = `tailor.workflow.triggerWorkflow("${workflowName}", ${call.argsText || "undefined"}
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
*
|
|
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 (
|
|
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
|
-
* @
|
|
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
|
|
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,
|
|
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:
|
|
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:
|
|
2510
|
-
update: metadata.hooks.update ? { expr:
|
|
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[
|
|
2799
|
-
name:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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(`${
|
|
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
|
|
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
|
|
3618
|
-
|
|
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
|
|
3898
|
-
* `@/parser/service/tailordb
|
|
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
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
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
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
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) →
|
|
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,
|
|
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) =>
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
4974
|
-
const WorkflowSchema = z.
|
|
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 `
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
5237
|
-
z.
|
|
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.
|
|
5936
|
+
z.strictObject({ oldIdpUser: z.enum([
|
|
5243
5937
|
"id",
|
|
5244
5938
|
"name",
|
|
5245
5939
|
"disabled"
|
|
5246
5940
|
]) }),
|
|
5247
|
-
z.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
6014
|
+
const SecretsSchema = z.strictObject({
|
|
5314
6015
|
vaults: z.record(nameSchema, secretsVaultSchema),
|
|
5315
|
-
options: z.
|
|
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.
|
|
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 {
|
|
5680
|
-
//# sourceMappingURL=application-
|
|
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
|