@prisma/orm-family-sql 0.16.0-dev.36
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/LICENSE +201 -0
- package/README.md +28 -0
- package/dist/ast-BstHjAoG.mjs +325 -0
- package/dist/ast-BstHjAoG.mjs.map +1 -0
- package/dist/ast-DSuzmsWn.d.mts +287 -0
- package/dist/ast-DSuzmsWn.d.mts.map +1 -0
- package/dist/authoring-type-constructors-Blqoa2Ua-DyCmD6mp.mjs +316 -0
- package/dist/authoring-type-constructors-Blqoa2Ua-DyCmD6mp.mjs.map +1 -0
- package/dist/build-contract-DBCz_YSs-DO0wMHai.mjs +778 -0
- package/dist/build-contract-DBCz_YSs-DO0wMHai.mjs.map +1 -0
- package/dist/builder.d.mts +3 -0
- package/dist/builder.mjs +1 -0
- package/dist/builder__runtime.d.mts +42 -0
- package/dist/builder__runtime.d.mts.map +1 -0
- package/dist/builder__runtime.mjs +955 -0
- package/dist/builder__runtime.mjs.map +1 -0
- package/dist/builder__types.d.mts +3 -0
- package/dist/builder__types.mjs +1 -0
- package/dist/canonicalization-hooks-CYnW_sGn.d.mts +10 -0
- package/dist/canonicalization-hooks-CYnW_sGn.d.mts.map +1 -0
- package/dist/canonicalization-hooks-DfIc_lOO.mjs +87 -0
- package/dist/canonicalization-hooks-DfIc_lOO.mjs.map +1 -0
- package/dist/codec-descriptor-registry-BJVJRftS.mjs +96 -0
- package/dist/codec-descriptor-registry-BJVJRftS.mjs.map +1 -0
- package/dist/codec-types-MOaceoPi-C5eL2sao.d.mts +116 -0
- package/dist/codec-types-MOaceoPi-C5eL2sao.d.mts.map +1 -0
- package/dist/config-types-47qB33rW.d.mts +21 -0
- package/dist/config-types-47qB33rW.d.mts.map +1 -0
- package/dist/config-types-N7PJc6VN.mjs +65 -0
- package/dist/config-types-N7PJc6VN.mjs.map +1 -0
- package/dist/contract-builder-MvJJrQ-5.d.mts +1306 -0
- package/dist/contract-builder-MvJJrQ-5.d.mts.map +1 -0
- package/dist/contract-builder-YjMQcvZ1.mjs +1615 -0
- package/dist/contract-builder-YjMQcvZ1.mjs.map +1 -0
- package/dist/contract-emitter.d.mts +25 -0
- package/dist/contract-emitter.d.mts.map +1 -0
- package/dist/contract-emitter.mjs +2 -0
- package/dist/contract-errors-BCN6RVI5-5IHBF_lg.mjs +9 -0
- package/dist/contract-errors-BCN6RVI5-5IHBF_lg.mjs.map +1 -0
- package/dist/contract-psl.d.mts +65 -0
- package/dist/contract-psl.d.mts.map +1 -0
- package/dist/contract-psl.mjs +2 -0
- package/dist/contract-psl__provider.d.mts +23 -0
- package/dist/contract-psl__provider.d.mts.map +1 -0
- package/dist/contract-psl__provider.mjs +105 -0
- package/dist/contract-psl__provider.mjs.map +1 -0
- package/dist/contract-ts.d.mts +3 -0
- package/dist/contract-ts.mjs +4 -0
- package/dist/contract-ts__config-types.d.mts +2 -0
- package/dist/contract-ts__config-types.mjs +2 -0
- package/dist/contract-ts__contract-builder.d.mts +2 -0
- package/dist/contract-ts__contract-builder.mjs +3 -0
- package/dist/contract-view-BMR5IWk4.mjs +39 -0
- package/dist/contract-view-BMR5IWk4.mjs.map +1 -0
- package/dist/contract-view-BbWQp6r4.d.mts +61 -0
- package/dist/contract-view-BbWQp6r4.d.mts.map +1 -0
- package/dist/contract.d.mts +26 -0
- package/dist/contract.mjs +17 -0
- package/dist/contract__canonicalization-hooks.d.mts +2 -0
- package/dist/contract__canonicalization-hooks.mjs +2 -0
- package/dist/contract__contract-view.d.mts +2 -0
- package/dist/contract__contract-view.mjs +2 -0
- package/dist/contract__entity-handle-lowering-hook.d.mts +2 -0
- package/dist/contract__entity-handle-lowering-hook.mjs +2 -0
- package/dist/contract__entity-kinds.d.mts +2 -0
- package/dist/contract__entity-kinds.mjs +2 -0
- package/dist/contract__factories.d.mts +2 -0
- package/dist/contract__factories.mjs +2 -0
- package/dist/contract__foreign-key-materialization.d.mts +2 -0
- package/dist/contract__foreign-key-materialization.mjs +2 -0
- package/dist/contract__index-naming.d.mts +2 -0
- package/dist/contract__index-naming.mjs +2 -0
- package/dist/contract__index-type-validation.d.mts +2 -0
- package/dist/contract__index-type-validation.mjs +2 -0
- package/dist/contract__index-types.d.mts +3 -0
- package/dist/contract__index-types.mjs +2 -0
- package/dist/contract__pack-types.d.mts +2 -0
- package/dist/contract__pack-types.mjs +1 -0
- package/dist/contract__referential-action-sql.d.mts +2 -0
- package/dist/contract__referential-action-sql.mjs +2 -0
- package/dist/contract__resolve-storage-table.d.mts +2 -0
- package/dist/contract__resolve-storage-table.mjs +2 -0
- package/dist/contract__types.d.mts +11 -0
- package/dist/contract__types.mjs +4 -0
- package/dist/contract__validators.d.mts +2 -0
- package/dist/contract__validators.mjs +3 -0
- package/dist/contract__value-set-derivation-hook.d.mts +2 -0
- package/dist/contract__value-set-derivation-hook.mjs +2 -0
- package/dist/control-B7iS4jv4.mjs +1326 -0
- package/dist/control-B7iS4jv4.mjs.map +1 -0
- package/dist/control-BHmsZudH.d.mts +948 -0
- package/dist/control-BHmsZudH.d.mts.map +1 -0
- package/dist/control-adapter-4y1O8Ps3.d.mts +1 -0
- package/dist/control-adapter-C99J7aEF-C-17lFY8.d.mts +191 -0
- package/dist/control-adapter-C99J7aEF-C-17lFY8.d.mts.map +1 -0
- package/dist/db-DhMsmBS--DKENDi3l.d.mts +365 -0
- package/dist/db-DhMsmBS--DKENDi3l.d.mts.map +1 -0
- package/dist/ddl-types-BMp2bWVS-D1waZJ4n.mjs +153 -0
- package/dist/ddl-types-BMp2bWVS-D1waZJ4n.mjs.map +1 -0
- package/dist/ddl-types-rrMsf77U-DGctYf-T.d.mts +149 -0
- package/dist/ddl-types-rrMsf77U-DGctYf-T.d.mts.map +1 -0
- package/dist/diff-B16bL_IR.mjs +13 -0
- package/dist/diff-B16bL_IR.mjs.map +1 -0
- package/dist/diff-DhlzHOMD.d.mts +104 -0
- package/dist/diff-DhlzHOMD.d.mts.map +1 -0
- package/dist/dist-C_q1_QR9.mjs +9 -0
- package/dist/dist-C_q1_QR9.mjs.map +1 -0
- package/dist/dist-DrScXbhq.mjs +414 -0
- package/dist/dist-DrScXbhq.mjs.map +1 -0
- package/dist/dist-x6jvtkKM.mjs +68 -0
- package/dist/dist-x6jvtkKM.mjs.map +1 -0
- package/dist/entity-handle-lowering-hook-CIvRKBfa.d.mts +92 -0
- package/dist/entity-handle-lowering-hook-CIvRKBfa.d.mts.map +1 -0
- package/dist/entity-handle-lowering-hook-DwLIKNiS.mjs +11 -0
- package/dist/entity-handle-lowering-hook-DwLIKNiS.mjs.map +1 -0
- package/dist/entity-kinds-DKxwKolf-CcsY-iYV.mjs +136 -0
- package/dist/entity-kinds-DKxwKolf-CcsY-iYV.mjs.map +1 -0
- package/dist/entity-kinds-Dh6VH4uK.d.mts +29 -0
- package/dist/entity-kinds-Dh6VH4uK.d.mts.map +1 -0
- package/dist/errors-B5g0xWro-mWH2C07F.mjs +9 -0
- package/dist/errors-B5g0xWro-mWH2C07F.mjs.map +1 -0
- package/dist/errors.d.mts +63 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +2 -0
- package/dist/expression-DIf8F4Mc.d.mts +167 -0
- package/dist/expression-DIf8F4Mc.d.mts.map +1 -0
- package/dist/expression-RUO52bJB.mjs +109 -0
- package/dist/expression-RUO52bJB.mjs.map +1 -0
- package/dist/factories-B7tA4y9D.d.mts +39 -0
- package/dist/factories-B7tA4y9D.d.mts.map +1 -0
- package/dist/factories-CDG9zBxC.mjs +77 -0
- package/dist/factories-CDG9zBxC.mjs.map +1 -0
- package/dist/family.d.mts +13 -0
- package/dist/family.mjs +12 -0
- package/dist/family__control-adapter.d.mts +3 -0
- package/dist/family__control-adapter.mjs +1 -0
- package/dist/family__control.d.mts +4 -0
- package/dist/family__control.mjs +4 -0
- package/dist/family__diff.d.mts +3 -0
- package/dist/family__diff.mjs +3 -0
- package/dist/family__ir.d.mts +2 -0
- package/dist/family__ir.mjs +3 -0
- package/dist/family__migration.d.mts +2 -0
- package/dist/family__migration.mjs +2 -0
- package/dist/family__pack.d.mts +249 -0
- package/dist/family__pack.d.mts.map +1 -0
- package/dist/family__pack.mjs +18 -0
- package/dist/family__pack.mjs.map +1 -0
- package/dist/family__psl-infer.d.mts +2 -0
- package/dist/family__psl-infer.mjs +2 -0
- package/dist/family__runtime.d.mts +3 -0
- package/dist/family__runtime.mjs +3 -0
- package/dist/family__verify.d.mts +2 -0
- package/dist/family__verify.mjs +2 -0
- package/dist/foreign-key-BSy9u4O4-BnsOe5Nf.d.mts +85 -0
- package/dist/foreign-key-BSy9u4O4-BnsOe5Nf.d.mts.map +1 -0
- package/dist/foreign-key-materialization-Ca-wRbmj.d.mts +65 -0
- package/dist/foreign-key-materialization-Ca-wRbmj.d.mts.map +1 -0
- package/dist/foreign-key-materialization-HI7BIQrG.mjs +72 -0
- package/dist/foreign-key-materialization-HI7BIQrG.mjs.map +1 -0
- package/dist/index-Bb9U36N4.d.mts +511 -0
- package/dist/index-Bb9U36N4.d.mts.map +1 -0
- package/dist/index-BgaTJRxj.d.mts +25 -0
- package/dist/index-BgaTJRxj.d.mts.map +1 -0
- package/dist/index-naming-BZWoh45T.d.mts +62 -0
- package/dist/index-naming-BZWoh45T.d.mts.map +1 -0
- package/dist/index-naming-BoGBcMmI-DiJAr66H.mjs +108 -0
- package/dist/index-naming-BoGBcMmI-DiJAr66H.mjs.map +1 -0
- package/dist/index-type-validation-COH4d78M.mjs +17 -0
- package/dist/index-type-validation-COH4d78M.mjs.map +1 -0
- package/dist/index-type-validation-DN0L2mqm.d.mts +10 -0
- package/dist/index-type-validation-DN0L2mqm.d.mts.map +1 -0
- package/dist/index-types-CNhHgKHi.d.mts +1 -0
- package/dist/index-types-DJQ_xwaE.mjs +40 -0
- package/dist/index-types-DJQ_xwaE.mjs.map +1 -0
- package/dist/index-types-DPVhsW0v-D-BikM4h.d.mts +31 -0
- package/dist/index-types-DPVhsW0v-D-BikM4h.d.mts.map +1 -0
- package/dist/interpreter-5WgpL-ax-_nqHLUTr.mjs +3526 -0
- package/dist/interpreter-5WgpL-ax-_nqHLUTr.mjs.map +1 -0
- package/dist/ir-B3bKMnGQ.d.mts +123 -0
- package/dist/ir-B3bKMnGQ.d.mts.map +1 -0
- package/dist/ir-I-xciGUH.mjs +36 -0
- package/dist/ir-I-xciGUH.mjs.map +1 -0
- package/dist/lane-query-builder.d.mts +236 -0
- package/dist/lane-query-builder.d.mts.map +1 -0
- package/dist/lane-query-builder.mjs +67 -0
- package/dist/lane-query-builder.mjs.map +1 -0
- package/dist/middleware-B4XbF4d4-tfwBl6mX.d.mts +119 -0
- package/dist/middleware-B4XbF4d4-tfwBl6mX.d.mts.map +1 -0
- package/dist/middleware-ByRsXxxq.d.mts +1 -0
- package/dist/middleware-CMr4CHNz-DHIUdxlm.mjs +80 -0
- package/dist/middleware-CMr4CHNz-DHIUdxlm.mjs.map +1 -0
- package/dist/migration-CK4oePIo.mjs +44 -0
- package/dist/migration-CK4oePIo.mjs.map +1 -0
- package/dist/migration-CW0ZJLxP.d.mts +44 -0
- package/dist/migration-CW0ZJLxP.d.mts.map +1 -0
- package/dist/naming-BwqqVcZy-BgTObIQw.mjs +177 -0
- package/dist/naming-BwqqVcZy-BgTObIQw.mjs.map +1 -0
- package/dist/naming-jLgdauLi-C84G_UHi.d.mts +133 -0
- package/dist/naming-jLgdauLi-C84G_UHi.d.mts.map +1 -0
- package/dist/operations.d.mts +2 -0
- package/dist/operations.mjs +2 -0
- package/dist/orm-client.d.mts +1316 -0
- package/dist/orm-client.d.mts.map +1 -0
- package/dist/orm-client.mjs +4544 -0
- package/dist/orm-client.mjs.map +1 -0
- package/dist/pack-types-BcLTpwbv.d.mts +14 -0
- package/dist/pack-types-BcLTpwbv.d.mts.map +1 -0
- package/dist/plan-BTD8xFfa-Dd_K-A1R.d.mts +45 -0
- package/dist/plan-BTD8xFfa-Dd_K-A1R.d.mts.map +1 -0
- package/dist/plan-CkpcgUaM.mjs +34 -0
- package/dist/plan-CkpcgUaM.mjs.map +1 -0
- package/dist/plan-Cm4jaoNM.d.mts +2 -0
- package/dist/psl-infer-6je61Z6J.d.mts +119 -0
- package/dist/psl-infer-6je61Z6J.d.mts.map +1 -0
- package/dist/psl-infer-DGftl-HW.mjs +362 -0
- package/dist/psl-infer-DGftl-HW.mjs.map +1 -0
- package/dist/query-lane-context-Bz8XJZ45.d.mts +1 -0
- package/dist/query-lane-context-DtdVMqoz-C2Tj5QHA.d.mts +94 -0
- package/dist/query-lane-context-DtdVMqoz-C2Tj5QHA.d.mts.map +1 -0
- package/dist/referential-action-sql-Csd6dA8G.d.mts +12 -0
- package/dist/referential-action-sql-Csd6dA8G.d.mts.map +1 -0
- package/dist/referential-action-sql-DSqkrySN.mjs +17 -0
- package/dist/referential-action-sql-DSqkrySN.mjs.map +1 -0
- package/dist/relational-core.d.mts +13 -0
- package/dist/relational-core.mjs +8 -0
- package/dist/relational-core__ast.d.mts +5 -0
- package/dist/relational-core__ast.mjs +5 -0
- package/dist/relational-core__codec-descriptor-registry.d.mts +39 -0
- package/dist/relational-core__codec-descriptor-registry.d.mts.map +1 -0
- package/dist/relational-core__codec-descriptor-registry.mjs +2 -0
- package/dist/relational-core__contract-free.d.mts +220 -0
- package/dist/relational-core__contract-free.d.mts.map +1 -0
- package/dist/relational-core__contract-free.mjs +393 -0
- package/dist/relational-core__contract-free.mjs.map +1 -0
- package/dist/relational-core__expression.d.mts +2 -0
- package/dist/relational-core__expression.mjs +2 -0
- package/dist/relational-core__middleware.d.mts +3 -0
- package/dist/relational-core__middleware.mjs +2 -0
- package/dist/relational-core__plan.d.mts +4 -0
- package/dist/relational-core__plan.mjs +2 -0
- package/dist/relational-core__query-lane-context.d.mts +3 -0
- package/dist/relational-core__query-lane-context.mjs +1 -0
- package/dist/relational-core__types.d.mts +3 -0
- package/dist/relational-core__types.mjs +1 -0
- package/dist/resolve-storage-table-DUwyRJmP.d.mts +21 -0
- package/dist/resolve-storage-table-DUwyRJmP.d.mts.map +1 -0
- package/dist/resolve-storage-table-DZsfnzeW.mjs +46 -0
- package/dist/resolve-storage-table-DZsfnzeW.mjs.map +1 -0
- package/dist/runtime-DNewRuec.d.mts +46 -0
- package/dist/runtime-DNewRuec.d.mts.map +1 -0
- package/dist/runtime-tIHpGCcG.mjs +55 -0
- package/dist/runtime-tIHpGCcG.mjs.map +1 -0
- package/dist/runtime.d.mts +4 -0
- package/dist/runtime.mjs +1606 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/schema-differ-DpG_gnur-g5_iks8G.d.mts +47 -0
- package/dist/schema-differ-DpG_gnur-g5_iks8G.d.mts.map +1 -0
- package/dist/schema-ir.d.mts +2 -0
- package/dist/schema-ir.mjs +2 -0
- package/dist/schema-ir__naming.d.mts +2 -0
- package/dist/schema-ir__naming.mjs +2 -0
- package/dist/schema-ir__types.d.mts +3 -0
- package/dist/schema-ir__types.mjs +2 -0
- package/dist/schema-verify-vtFZbmGq-BNfTr6fU.mjs +222 -0
- package/dist/schema-verify-vtFZbmGq-BNfTr6fU.mjs.map +1 -0
- package/dist/serialized-index-B96AwqtU-DcqcB4q_.d.mts +26 -0
- package/dist/serialized-index-B96AwqtU-DcqcB4q_.d.mts.map +1 -0
- package/dist/serialized-index-Dp5_EjXD-DMsw8jf3.mjs +64 -0
- package/dist/serialized-index-Dp5_EjXD-DMsw8jf3.mjs.map +1 -0
- package/dist/sql-contract-serializer-2oBWuxTe-B9hzBBv1.mjs +171 -0
- package/dist/sql-contract-serializer-2oBWuxTe-B9hzBBv1.mjs.map +1 -0
- package/dist/sql-execution-plan-CJmsCP7n-DoEpNn7r.d.mts +32 -0
- package/dist/sql-execution-plan-CJmsCP7n-DoEpNn7r.d.mts.map +1 -0
- package/dist/sql-index-DsI8HAeK-BJsn-jvD.d.mts +72 -0
- package/dist/sql-index-DsI8HAeK-BJsn-jvD.d.mts.map +1 -0
- package/dist/sql-node-kEz6YFZo-v2F7delx.d.mts +42 -0
- package/dist/sql-node-kEz6YFZo-v2F7delx.d.mts.map +1 -0
- package/dist/sql-storage-B0QwiTg6-B8DVctJm.d.mts +171 -0
- package/dist/sql-storage-B0QwiTg6-B8DVctJm.d.mts.map +1 -0
- package/dist/storage-table-BBaAmanc-BwR37LMA.d.mts +130 -0
- package/dist/storage-table-BBaAmanc-BwR37LMA.d.mts.map +1 -0
- package/dist/storage-table-_LWDmPfv-DFhU2dML.mjs +277 -0
- package/dist/storage-table-_LWDmPfv-DFhU2dML.mjs.map +1 -0
- package/dist/storage-value-set-BGlj5Uus-CvZD--GG.d.mts +41 -0
- package/dist/storage-value-set-BGlj5Uus-CvZD--GG.d.mts.map +1 -0
- package/dist/timestamp-now-generator-DRXygu32-DN9Qb1uI.mjs +173 -0
- package/dist/timestamp-now-generator-DRXygu32-DN9Qb1uI.mjs.map +1 -0
- package/dist/types-BchwQpmO.d.mts +16 -0
- package/dist/types-BchwQpmO.d.mts.map +1 -0
- package/dist/types-BgkZru_1-H7yfikSQ.mjs +1444 -0
- package/dist/types-BgkZru_1-H7yfikSQ.mjs.map +1 -0
- package/dist/types-CMMQS6aG.mjs +111 -0
- package/dist/types-CMMQS6aG.mjs.map +1 -0
- package/dist/types-CuDrWc5v-ISVXBmBs.d.mts +134 -0
- package/dist/types-CuDrWc5v-ISVXBmBs.d.mts.map +1 -0
- package/dist/types-CvbBw1Jo-3KQdErJY.d.mts +688 -0
- package/dist/types-CvbBw1Jo-3KQdErJY.d.mts.map +1 -0
- package/dist/types-DIBA80mc-wf5wLmON.d.mts +723 -0
- package/dist/types-DIBA80mc-wf5wLmON.d.mts.map +1 -0
- package/dist/types-DMxc5KZa.d.mts +1 -0
- package/dist/types-Dl20lxky-DC_jdA0T.d.mts +247 -0
- package/dist/types-Dl20lxky-DC_jdA0T.d.mts.map +1 -0
- package/dist/types-DmhUVq4u-BpqqjPJx.mjs +719 -0
- package/dist/types-DmhUVq4u-BpqqjPJx.mjs.map +1 -0
- package/dist/types-IqZMLrFC-DtdJNICr.d.mts +426 -0
- package/dist/types-IqZMLrFC-DtdJNICr.d.mts.map +1 -0
- package/dist/types-TvBZ2Ne9.d.mts +1 -0
- package/dist/unique-constraint-jQWfH7dg-zLWtFp99.d.mts +32 -0
- package/dist/unique-constraint-jQWfH7dg-zLWtFp99.d.mts.map +1 -0
- package/dist/util-DQQgv2j1-D6INZaRG.mjs +34 -0
- package/dist/util-DQQgv2j1-D6INZaRG.mjs.map +1 -0
- package/dist/validators-C4PItWnf.mjs +0 -0
- package/dist/validators-C4PItWnf.mjs.map +1 -0
- package/dist/validators-CxeHhHrf.d.mts +206 -0
- package/dist/validators-CxeHhHrf.d.mts.map +1 -0
- package/dist/value-set-derivation-hook-BhgGVtw0.d.mts +35 -0
- package/dist/value-set-derivation-hook-BhgGVtw0.d.mts.map +1 -0
- package/dist/value-set-derivation-hook-Di9JZ0X7.mjs +22 -0
- package/dist/value-set-derivation-hook-Di9JZ0X7.mjs.map +1 -0
- package/dist/verify-E6Dd2vYp-gYwnlhLm.mjs +86 -0
- package/dist/verify-E6Dd2vYp-gYwnlhLm.mjs.map +1 -0
- package/dist/verify-zDRnw0Rn.d.mts +26 -0
- package/dist/verify-zDRnw0Rn.d.mts.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,3526 @@
|
|
|
1
|
+
import { t as deriveValueSetFromEntity } from "./value-set-derivation-hook-Di9JZ0X7.mjs";
|
|
2
|
+
import { t as buildSqlContractFromDefinition } from "./build-contract-DBCz_YSs-DO0wMHai.mjs";
|
|
3
|
+
import { UNBOUND_NAMESPACE_ID } from "@prisma/orm-framework/components/ir";
|
|
4
|
+
import { structuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
5
|
+
import { InternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
6
|
+
import { crossRef } from "@prisma/orm-framework/contract/types";
|
|
7
|
+
import { hasRegisteredFieldNamespace, instantiateAuthoringEntityType, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringModelAttributeDescriptor, isAuthoringPslBlockDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma/orm-framework/components/authoring";
|
|
8
|
+
import { blindCast } from "@prisma/orm-framework/utils/casts";
|
|
9
|
+
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
10
|
+
import { bool, entityRef, fieldAttribute, fieldRef, findBlockDescriptor, funcCall, identifier, interpretAttribute, keywordPslSpan, leafDiagnostic, list, modelAttribute, nodePslSpan, num, oneOf, optional, record, str } from "@prisma/orm-framework/psl-parser";
|
|
11
|
+
import { assertDefined, invariant } from "@prisma/orm-framework/utils/assertions";
|
|
12
|
+
import { notOk, ok } from "@prisma/orm-framework/utils/result";
|
|
13
|
+
//#region ../../../2-sql/2-authoring/contract-psl/dist/interpreter-5WgpL-ax.mjs
|
|
14
|
+
function contractError(code, message, options) {
|
|
15
|
+
return structuredError(code, message, options);
|
|
16
|
+
}
|
|
17
|
+
function lowerFirst(value) {
|
|
18
|
+
if (value.length === 0) return value;
|
|
19
|
+
return value[0]?.toLowerCase() + value.slice(1);
|
|
20
|
+
}
|
|
21
|
+
function getAttribute(attributes, name) {
|
|
22
|
+
return attributes?.find((attribute) => attribute.name === name);
|
|
23
|
+
}
|
|
24
|
+
function formatDbAttributeMigrationMessage(attribute) {
|
|
25
|
+
const renderedArguments = attribute.args.map((argument) => argument.kind === "named" && argument.name !== void 0 ? `${argument.name}: ${argument.value}` : argument.value).join(", ");
|
|
26
|
+
const argumentList = attribute.args.length === 0 ? "" : `(${renderedArguments})`;
|
|
27
|
+
const constructorName = attribute.name.slice(3);
|
|
28
|
+
return `@${attribute.name}${argumentList} is no longer supported; use ${constructorName}${argumentList} in type position`;
|
|
29
|
+
}
|
|
30
|
+
function getNamedArgument(attribute, name) {
|
|
31
|
+
const entry = attribute.args.find((arg) => arg.kind === "named" && arg.name === name);
|
|
32
|
+
if (entry?.kind !== "named") return;
|
|
33
|
+
return entry.value;
|
|
34
|
+
}
|
|
35
|
+
function unquoteStringLiteral(value) {
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
const match = trimmed.match(/^(['"])(.*)\1$/);
|
|
38
|
+
if (!match) return trimmed;
|
|
39
|
+
return match[2] ?? "";
|
|
40
|
+
}
|
|
41
|
+
function mapFieldNamesToColumns(input) {
|
|
42
|
+
const columns = [];
|
|
43
|
+
for (const fieldName of input.fieldNames) {
|
|
44
|
+
const columnName = input.mapping.fieldColumns.get(fieldName);
|
|
45
|
+
if (!columnName) {
|
|
46
|
+
input.diagnostics.push({
|
|
47
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
48
|
+
message: `${input.entityLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
49
|
+
sourceId: input.sourceId,
|
|
50
|
+
span: input.span
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
columns.push(columnName);
|
|
55
|
+
}
|
|
56
|
+
return columns;
|
|
57
|
+
}
|
|
58
|
+
function formatSupportedFunctionList(registry) {
|
|
59
|
+
const signatures = Array.from(registry.entries()).sort(([a], [b]) => a.localeCompare(b)).flatMap(([functionName, entry]) => {
|
|
60
|
+
const usageSignatures = entry.usageSignatures?.filter((signature) => signature.length > 0);
|
|
61
|
+
return usageSignatures && usageSignatures.length > 0 ? usageSignatures : [`${functionName}()`];
|
|
62
|
+
});
|
|
63
|
+
return signatures.length > 0 ? signatures.join(", ") : "none";
|
|
64
|
+
}
|
|
65
|
+
function lowerDefaultFunctionWithRegistry(input) {
|
|
66
|
+
const entry = input.registry.get(input.call.fn);
|
|
67
|
+
if (entry) return entry.lower({
|
|
68
|
+
call: input.call,
|
|
69
|
+
context: input.context
|
|
70
|
+
});
|
|
71
|
+
const supportedFunctionList = formatSupportedFunctionList(input.registry);
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
diagnostic: {
|
|
75
|
+
code: "PSL_UNKNOWN_DEFAULT_FUNCTION",
|
|
76
|
+
message: `Default function "${input.call.fn}" is not supported in SQL PSL provider v1. Supported functions: ${supportedFunctionList}.`,
|
|
77
|
+
sourceId: input.context.sourceId,
|
|
78
|
+
span: input.call.span
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const INVALID_AUTHORING_ARGUMENT = Symbol("invalidAuthoringArgument");
|
|
83
|
+
function isIdentifierStartCharacter(character) {
|
|
84
|
+
return character !== void 0 && /[A-Za-z_$]/.test(character);
|
|
85
|
+
}
|
|
86
|
+
function isIdentifierCharacter(character) {
|
|
87
|
+
return character !== void 0 && /[A-Za-z0-9_$]/.test(character);
|
|
88
|
+
}
|
|
89
|
+
function parseJsLikeLiteral(value) {
|
|
90
|
+
let index = 0;
|
|
91
|
+
function skipWhitespace() {
|
|
92
|
+
while (/\s/.test(value[index] ?? "")) index += 1;
|
|
93
|
+
}
|
|
94
|
+
function parseIdentifier() {
|
|
95
|
+
const first = value[index];
|
|
96
|
+
if (!isIdentifierStartCharacter(first)) return INVALID_AUTHORING_ARGUMENT;
|
|
97
|
+
let end = index + 1;
|
|
98
|
+
while (isIdentifierCharacter(value[end])) end += 1;
|
|
99
|
+
const identifier = value.slice(index, end);
|
|
100
|
+
index = end;
|
|
101
|
+
return identifier;
|
|
102
|
+
}
|
|
103
|
+
function parseString() {
|
|
104
|
+
const quote = value[index];
|
|
105
|
+
if (quote !== "\"" && quote !== "'") return INVALID_AUTHORING_ARGUMENT;
|
|
106
|
+
index += 1;
|
|
107
|
+
let result = "";
|
|
108
|
+
while (index < value.length) {
|
|
109
|
+
const character = value[index];
|
|
110
|
+
index += 1;
|
|
111
|
+
if (character === void 0) return INVALID_AUTHORING_ARGUMENT;
|
|
112
|
+
if (character === quote) return result;
|
|
113
|
+
if (character !== "\\") {
|
|
114
|
+
result += character;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const escaped = value[index];
|
|
118
|
+
index += 1;
|
|
119
|
+
if (escaped === void 0) return INVALID_AUTHORING_ARGUMENT;
|
|
120
|
+
switch (escaped) {
|
|
121
|
+
case "'":
|
|
122
|
+
case "\"":
|
|
123
|
+
case "\\":
|
|
124
|
+
case "/":
|
|
125
|
+
result += escaped;
|
|
126
|
+
break;
|
|
127
|
+
case "b":
|
|
128
|
+
result += "\b";
|
|
129
|
+
break;
|
|
130
|
+
case "f":
|
|
131
|
+
result += "\f";
|
|
132
|
+
break;
|
|
133
|
+
case "n":
|
|
134
|
+
result += "\n";
|
|
135
|
+
break;
|
|
136
|
+
case "r":
|
|
137
|
+
result += "\r";
|
|
138
|
+
break;
|
|
139
|
+
case "t":
|
|
140
|
+
result += " ";
|
|
141
|
+
break;
|
|
142
|
+
case "u": {
|
|
143
|
+
const hex = value.slice(index, index + 4);
|
|
144
|
+
if (!/^[0-9A-Fa-f]{4}$/.test(hex)) return INVALID_AUTHORING_ARGUMENT;
|
|
145
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
146
|
+
index += 4;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
default: return INVALID_AUTHORING_ARGUMENT;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
153
|
+
}
|
|
154
|
+
function parseNumber() {
|
|
155
|
+
const raw = value.slice(index).match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0];
|
|
156
|
+
if (!raw) return INVALID_AUTHORING_ARGUMENT;
|
|
157
|
+
const parsed = Number(raw);
|
|
158
|
+
if (!Number.isFinite(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
159
|
+
index += raw.length;
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
function parseArray() {
|
|
163
|
+
if (value[index] !== "[") return INVALID_AUTHORING_ARGUMENT;
|
|
164
|
+
index += 1;
|
|
165
|
+
const result = [];
|
|
166
|
+
skipWhitespace();
|
|
167
|
+
if (value[index] === "]") {
|
|
168
|
+
index += 1;
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
while (index < value.length) {
|
|
172
|
+
const entry = parseValue();
|
|
173
|
+
if (entry === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
174
|
+
result.push(entry);
|
|
175
|
+
skipWhitespace();
|
|
176
|
+
if (value[index] === ",") {
|
|
177
|
+
index += 1;
|
|
178
|
+
skipWhitespace();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (value[index] === "]") {
|
|
182
|
+
index += 1;
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
186
|
+
}
|
|
187
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
188
|
+
}
|
|
189
|
+
function parseObject() {
|
|
190
|
+
if (value[index] !== "{") return INVALID_AUTHORING_ARGUMENT;
|
|
191
|
+
index += 1;
|
|
192
|
+
const result = {};
|
|
193
|
+
skipWhitespace();
|
|
194
|
+
if (value[index] === "}") {
|
|
195
|
+
index += 1;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
while (index < value.length) {
|
|
199
|
+
skipWhitespace();
|
|
200
|
+
const key = value[index] === "\"" || value[index] === "'" ? parseString() : parseIdentifier();
|
|
201
|
+
if (key === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
202
|
+
skipWhitespace();
|
|
203
|
+
if (value[index] !== ":") return INVALID_AUTHORING_ARGUMENT;
|
|
204
|
+
index += 1;
|
|
205
|
+
const entry = parseValue();
|
|
206
|
+
if (entry === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
207
|
+
result[key] = entry;
|
|
208
|
+
skipWhitespace();
|
|
209
|
+
if (value[index] === ",") {
|
|
210
|
+
index += 1;
|
|
211
|
+
skipWhitespace();
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (value[index] === "}") {
|
|
215
|
+
index += 1;
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
219
|
+
}
|
|
220
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
221
|
+
}
|
|
222
|
+
function parseValue() {
|
|
223
|
+
skipWhitespace();
|
|
224
|
+
const character = value[index];
|
|
225
|
+
if (character === "{") return parseObject();
|
|
226
|
+
if (character === "[") return parseArray();
|
|
227
|
+
if (character === "\"" || character === "'") return parseString();
|
|
228
|
+
if (character === "-" || /\d/.test(character ?? "")) return parseNumber();
|
|
229
|
+
const identifier = parseIdentifier();
|
|
230
|
+
if (identifier === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
231
|
+
if (identifier === "true") return true;
|
|
232
|
+
if (identifier === "false") return false;
|
|
233
|
+
if (identifier === "null") return null;
|
|
234
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
235
|
+
}
|
|
236
|
+
skipWhitespace();
|
|
237
|
+
const parsed = parseValue();
|
|
238
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT) return parsed;
|
|
239
|
+
skipWhitespace();
|
|
240
|
+
return index === value.length ? parsed : INVALID_AUTHORING_ARGUMENT;
|
|
241
|
+
}
|
|
242
|
+
function parseStringArrayLiteral(value) {
|
|
243
|
+
const parsed = parseJsLikeLiteral(value);
|
|
244
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT || !Array.isArray(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
245
|
+
if (!parsed.every((item) => typeof item === "string")) return INVALID_AUTHORING_ARGUMENT;
|
|
246
|
+
return parsed;
|
|
247
|
+
}
|
|
248
|
+
function isPlainObject(value) {
|
|
249
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
250
|
+
}
|
|
251
|
+
function parsePslObjectLiteral(value) {
|
|
252
|
+
const trimmed = value.trim();
|
|
253
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return INVALID_AUTHORING_ARGUMENT;
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = JSON.parse(trimmed);
|
|
257
|
+
} catch {
|
|
258
|
+
parsed = parseJsLikeLiteral(trimmed);
|
|
259
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
260
|
+
}
|
|
261
|
+
if (!isPlainObject(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
262
|
+
return parsed;
|
|
263
|
+
}
|
|
264
|
+
function parsePslAuthoringArgumentValue(descriptor, rawValue) {
|
|
265
|
+
switch (descriptor.kind) {
|
|
266
|
+
case "string": return unquoteStringLiteral(rawValue);
|
|
267
|
+
case "boolean": {
|
|
268
|
+
const trimmed = rawValue.trim();
|
|
269
|
+
if (trimmed === "true") return true;
|
|
270
|
+
if (trimmed === "false") return false;
|
|
271
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
272
|
+
}
|
|
273
|
+
case "number": {
|
|
274
|
+
const parsed = Number(unquoteStringLiteral(rawValue));
|
|
275
|
+
return Number.isNaN(parsed) ? INVALID_AUTHORING_ARGUMENT : parsed;
|
|
276
|
+
}
|
|
277
|
+
case "stringArray": return parseStringArrayLiteral(rawValue);
|
|
278
|
+
case "object": return parsePslObjectLiteral(rawValue);
|
|
279
|
+
case "option": {
|
|
280
|
+
const trimmed = rawValue.trim();
|
|
281
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed) ? trimmed : INVALID_AUTHORING_ARGUMENT;
|
|
282
|
+
}
|
|
283
|
+
default: return INVALID_AUTHORING_ARGUMENT;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function pushInvalidPslHelperArgument(input) {
|
|
287
|
+
input.diagnostics.push({
|
|
288
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
289
|
+
message: `${input.entityLabel} ${input.helperLabel} ${input.message}`,
|
|
290
|
+
sourceId: input.sourceId,
|
|
291
|
+
span: input.span
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function mapPslHelperArgs(input) {
|
|
295
|
+
const mappedArgs = input.descriptors.map(() => void 0);
|
|
296
|
+
const positionalArgs = input.args.filter((arg) => arg.kind === "positional");
|
|
297
|
+
const namedArgs = input.args.filter((arg) => arg.kind === "named");
|
|
298
|
+
if (positionalArgs.length > input.descriptors.length) return pushInvalidPslHelperArgument({
|
|
299
|
+
diagnostics: input.diagnostics,
|
|
300
|
+
sourceId: input.sourceId,
|
|
301
|
+
span: input.span,
|
|
302
|
+
entityLabel: input.entityLabel,
|
|
303
|
+
helperLabel: input.helperLabel,
|
|
304
|
+
message: `accepts at most ${input.descriptors.length} argument(s), received ${positionalArgs.length}.`
|
|
305
|
+
});
|
|
306
|
+
for (const [index, argument] of positionalArgs.entries()) {
|
|
307
|
+
const descriptor = input.descriptors[index];
|
|
308
|
+
if (!descriptor) return pushInvalidPslHelperArgument({
|
|
309
|
+
diagnostics: input.diagnostics,
|
|
310
|
+
sourceId: input.sourceId,
|
|
311
|
+
span: argument.span,
|
|
312
|
+
entityLabel: input.entityLabel,
|
|
313
|
+
helperLabel: input.helperLabel,
|
|
314
|
+
message: `does not define positional argument #${index + 1}.`
|
|
315
|
+
});
|
|
316
|
+
const value = parsePslAuthoringArgumentValue(descriptor, argument.value);
|
|
317
|
+
if (value === INVALID_AUTHORING_ARGUMENT) return pushInvalidPslHelperArgument({
|
|
318
|
+
diagnostics: input.diagnostics,
|
|
319
|
+
sourceId: input.sourceId,
|
|
320
|
+
span: argument.span,
|
|
321
|
+
entityLabel: input.entityLabel,
|
|
322
|
+
helperLabel: input.helperLabel,
|
|
323
|
+
message: `cannot parse argument #${index + 1} for descriptor kind "${descriptor.kind}".`
|
|
324
|
+
});
|
|
325
|
+
mappedArgs[index] = value;
|
|
326
|
+
}
|
|
327
|
+
for (const argument of namedArgs) {
|
|
328
|
+
const descriptorIndex = input.descriptors.findIndex((descriptor) => descriptor.name === argument.name);
|
|
329
|
+
if (descriptorIndex < 0) return pushInvalidPslHelperArgument({
|
|
330
|
+
diagnostics: input.diagnostics,
|
|
331
|
+
sourceId: input.sourceId,
|
|
332
|
+
span: argument.span,
|
|
333
|
+
entityLabel: input.entityLabel,
|
|
334
|
+
helperLabel: input.helperLabel,
|
|
335
|
+
message: `received unknown named argument "${argument.name}".`
|
|
336
|
+
});
|
|
337
|
+
if (mappedArgs[descriptorIndex] !== void 0) return pushInvalidPslHelperArgument({
|
|
338
|
+
diagnostics: input.diagnostics,
|
|
339
|
+
sourceId: input.sourceId,
|
|
340
|
+
span: argument.span,
|
|
341
|
+
entityLabel: input.entityLabel,
|
|
342
|
+
helperLabel: input.helperLabel,
|
|
343
|
+
message: `received duplicate value for argument "${argument.name}".`
|
|
344
|
+
});
|
|
345
|
+
const descriptor = input.descriptors[descriptorIndex];
|
|
346
|
+
if (!descriptor) return pushInvalidPslHelperArgument({
|
|
347
|
+
diagnostics: input.diagnostics,
|
|
348
|
+
sourceId: input.sourceId,
|
|
349
|
+
span: argument.span,
|
|
350
|
+
entityLabel: input.entityLabel,
|
|
351
|
+
helperLabel: input.helperLabel,
|
|
352
|
+
message: `does not define named argument "${argument.name}".`
|
|
353
|
+
});
|
|
354
|
+
const value = parsePslAuthoringArgumentValue(descriptor, argument.value);
|
|
355
|
+
if (value === INVALID_AUTHORING_ARGUMENT) return pushInvalidPslHelperArgument({
|
|
356
|
+
diagnostics: input.diagnostics,
|
|
357
|
+
sourceId: input.sourceId,
|
|
358
|
+
span: argument.span,
|
|
359
|
+
entityLabel: input.entityLabel,
|
|
360
|
+
helperLabel: input.helperLabel,
|
|
361
|
+
message: `cannot parse named argument "${argument.name}" for descriptor kind "${descriptor.kind}".`
|
|
362
|
+
});
|
|
363
|
+
mappedArgs[descriptorIndex] = value;
|
|
364
|
+
}
|
|
365
|
+
return mappedArgs;
|
|
366
|
+
}
|
|
367
|
+
function findModelAttributeNode(model, name) {
|
|
368
|
+
for (const attribute of model.node.attributes()) if (attribute.name()?.isSimpleName(name) === true) return attribute;
|
|
369
|
+
}
|
|
370
|
+
function findFieldAttributeNode(field, name) {
|
|
371
|
+
for (const attribute of field.node.attributes()) if (attribute.name()?.isSimpleName(name) === true) return attribute;
|
|
372
|
+
}
|
|
373
|
+
function buildModelInterpretCtx(input) {
|
|
374
|
+
return {
|
|
375
|
+
level: "model",
|
|
376
|
+
sourceId: input.sourceId,
|
|
377
|
+
sourceFile: input.sourceFile,
|
|
378
|
+
selfModel: input.selfModel,
|
|
379
|
+
resolveReferencedModel: () => void 0
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function buildFieldInterpretCtx(input) {
|
|
383
|
+
return {
|
|
384
|
+
level: "field",
|
|
385
|
+
sourceId: input.sourceId,
|
|
386
|
+
sourceFile: input.sourceFile,
|
|
387
|
+
selfModel: input.selfModel,
|
|
388
|
+
resolveReferencedModel: input.resolveReferencedModel ?? (() => void 0),
|
|
389
|
+
field: input.field
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function interpretModelAttribute(input) {
|
|
393
|
+
const result = interpretAttribute(input.node, input.spec, buildModelInterpretCtx({
|
|
394
|
+
selfModel: input.model,
|
|
395
|
+
sourceFile: input.sourceFile,
|
|
396
|
+
sourceId: input.sourceId
|
|
397
|
+
}));
|
|
398
|
+
if (!result.ok) {
|
|
399
|
+
for (const failure of result.failure) input.diagnostics.push(failure);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
return result.value;
|
|
403
|
+
}
|
|
404
|
+
function interpretFieldAttribute(input) {
|
|
405
|
+
const result = interpretAttribute(input.node, input.spec, buildFieldInterpretCtx({
|
|
406
|
+
selfModel: input.model,
|
|
407
|
+
field: input.field,
|
|
408
|
+
sourceFile: input.sourceFile,
|
|
409
|
+
sourceId: input.sourceId,
|
|
410
|
+
resolveReferencedModel: input.resolveReferencedModel
|
|
411
|
+
}));
|
|
412
|
+
if (!result.ok) {
|
|
413
|
+
for (const failure of result.failure) input.diagnostics.push(failure);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
return result.value;
|
|
417
|
+
}
|
|
418
|
+
const mapModelSpec = modelAttribute("map", { positional: [{
|
|
419
|
+
key: "name",
|
|
420
|
+
type: str()
|
|
421
|
+
}] });
|
|
422
|
+
const mapFieldSpec = fieldAttribute("map", { positional: [{
|
|
423
|
+
key: "name",
|
|
424
|
+
type: str()
|
|
425
|
+
}] });
|
|
426
|
+
function buildDefaultSpec(input) {
|
|
427
|
+
const literal = () => oneOf(str(), num(), bool());
|
|
428
|
+
const funcArms = [...input.registry.entries()].map(([name, entry]) => funcCall(name, blindCast(entry.signature)));
|
|
429
|
+
return fieldAttribute("default", { positional: [{
|
|
430
|
+
key: "value",
|
|
431
|
+
type: oneOf(...input.isList ? [list(literal()), ...funcArms] : [
|
|
432
|
+
str(),
|
|
433
|
+
num(),
|
|
434
|
+
bool(),
|
|
435
|
+
...funcArms
|
|
436
|
+
])
|
|
437
|
+
}] });
|
|
438
|
+
}
|
|
439
|
+
function buildEnumDefaultSpec(memberNames) {
|
|
440
|
+
const [first, ...rest] = memberNames;
|
|
441
|
+
return fieldAttribute("default", { positional: [{
|
|
442
|
+
key: "member",
|
|
443
|
+
type: oneOf(...[identifier(first), ...rest.map((name) => identifier(name))])
|
|
444
|
+
}] });
|
|
445
|
+
}
|
|
446
|
+
const idFieldSpec = fieldAttribute("id", { named: { map: optional(str()) } });
|
|
447
|
+
const uniqueFieldSpec = fieldAttribute("unique", { named: { map: optional(str()) } });
|
|
448
|
+
const idModelSpec = modelAttribute("id", {
|
|
449
|
+
positional: [{
|
|
450
|
+
key: "fields",
|
|
451
|
+
type: list(fieldRef("self"), {
|
|
452
|
+
nonEmpty: true,
|
|
453
|
+
unique: true
|
|
454
|
+
})
|
|
455
|
+
}],
|
|
456
|
+
named: { map: optional(str()) }
|
|
457
|
+
});
|
|
458
|
+
const uniqueModelSpec = modelAttribute("unique", {
|
|
459
|
+
positional: [{
|
|
460
|
+
key: "fields",
|
|
461
|
+
type: list(fieldRef("self"), {
|
|
462
|
+
nonEmpty: true,
|
|
463
|
+
unique: true
|
|
464
|
+
})
|
|
465
|
+
}],
|
|
466
|
+
named: { map: optional(str()) }
|
|
467
|
+
});
|
|
468
|
+
const PSL_INDEX_FIELDS_XOR_EXPRESSION = "PSL_INDEX_FIELDS_XOR_EXPRESSION";
|
|
469
|
+
const PSL_INDEX_EXPRESSION_REQUIRES_NAME = "PSL_INDEX_EXPRESSION_REQUIRES_NAME";
|
|
470
|
+
const PSL_INDEX_NAME_XOR_MAP = "PSL_INDEX_NAME_XOR_MAP";
|
|
471
|
+
const indexModelSpec = modelAttribute("index", {
|
|
472
|
+
positional: [{
|
|
473
|
+
key: "fields",
|
|
474
|
+
type: optional(list(fieldRef("self"), {
|
|
475
|
+
nonEmpty: true,
|
|
476
|
+
unique: true
|
|
477
|
+
}))
|
|
478
|
+
}],
|
|
479
|
+
named: {
|
|
480
|
+
expression: optional(str()),
|
|
481
|
+
where: optional(str()),
|
|
482
|
+
unique: optional(bool()),
|
|
483
|
+
name: optional(str()),
|
|
484
|
+
map: optional(str()),
|
|
485
|
+
type: optional(str()),
|
|
486
|
+
options: optional(record(str()))
|
|
487
|
+
},
|
|
488
|
+
refine: (value, ctx, attributeNode) => {
|
|
489
|
+
const diagnostics = [];
|
|
490
|
+
if (value.fields === void 0 === (value.expression === void 0)) diagnostics.push(leafDiagnostic(ctx, attributeNode, "`@@index` requires exactly one of a fields list or an `expression` argument", PSL_INDEX_FIELDS_XOR_EXPRESSION));
|
|
491
|
+
if (value.expression !== void 0 && value.name === void 0 && value.map === void 0) diagnostics.push(leafDiagnostic(ctx, attributeNode, "`@@index` with an `expression` argument requires a `name` or `map` argument (a default name cannot be derived from an expression)", PSL_INDEX_EXPRESSION_REQUIRES_NAME));
|
|
492
|
+
if (value.name !== void 0 && value.map !== void 0) diagnostics.push(leafDiagnostic(ctx, attributeNode, "`@@index` takes at most one of `name` and `map`", PSL_INDEX_NAME_XOR_MAP));
|
|
493
|
+
if (value.options !== void 0 && value.type === void 0) diagnostics.push(leafDiagnostic(ctx, attributeNode, "`@@index` options argument requires a type argument"));
|
|
494
|
+
return diagnostics;
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
const controlModelSpec = modelAttribute("control", { positional: [{
|
|
498
|
+
key: "policy",
|
|
499
|
+
type: oneOf(identifier("managed"), identifier("tolerated"), identifier("external"), identifier("observed"))
|
|
500
|
+
}] });
|
|
501
|
+
const discriminatorModelSpec = modelAttribute("discriminator", { positional: [{
|
|
502
|
+
key: "field",
|
|
503
|
+
type: fieldRef("self")
|
|
504
|
+
}] });
|
|
505
|
+
const baseModelSpec = modelAttribute("base", { positional: [{
|
|
506
|
+
key: "base",
|
|
507
|
+
type: entityRef()
|
|
508
|
+
}, {
|
|
509
|
+
key: "value",
|
|
510
|
+
type: str()
|
|
511
|
+
}] });
|
|
512
|
+
function toNamedTypeFieldDescriptor(typeRef, descriptor) {
|
|
513
|
+
return {
|
|
514
|
+
codecId: descriptor.codecId,
|
|
515
|
+
nativeType: descriptor.nativeType,
|
|
516
|
+
typeRef
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function getAuthoringTypeConstructor(contributions, path) {
|
|
520
|
+
let current = contributions?.type;
|
|
521
|
+
for (const segment of path) {
|
|
522
|
+
if (typeof current !== "object" || current === null || "kind" in current) return;
|
|
523
|
+
current = current[segment];
|
|
524
|
+
}
|
|
525
|
+
return current !== void 0 && isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Walks `authoringContributions.entityTypes` segment-by-segment and returns
|
|
529
|
+
* the entity type descriptor at the resolved path, or `undefined` if no
|
|
530
|
+
* descriptor is registered.
|
|
531
|
+
*
|
|
532
|
+
* Used by the PSL interpreter to dispatch declarative entity-shaped
|
|
533
|
+
* declarations (`enum`, future `namespace { … }`, …) through the
|
|
534
|
+
* pack entity-type mechanism — the descriptor's `factory` (or
|
|
535
|
+
* `template`) materialises the IR-class instance without the
|
|
536
|
+
* interpreter knowing target-specific construction.
|
|
537
|
+
*/
|
|
538
|
+
function getAuthoringEntity(contributions, path) {
|
|
539
|
+
let current = contributions?.entityTypes;
|
|
540
|
+
for (const segment of path) {
|
|
541
|
+
if (typeof current !== "object" || current === null || "kind" in current) return;
|
|
542
|
+
current = current[segment];
|
|
543
|
+
}
|
|
544
|
+
return current !== void 0 && isAuthoringEntityTypeDescriptor(current) ? current : void 0;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Walks `authoringContributions.field` segment-by-segment and returns the field-preset descriptor at the resolved path, or `undefined` if no descriptor is registered.
|
|
548
|
+
*
|
|
549
|
+
* Symmetric with `getAuthoringTypeConstructor`. Field presets are strictly richer than type constructors — they can contribute `default` / `executionDefaults` / `id` / `unique` / `nullable` in addition to the `codecId` / `nativeType` / `typeParams` triple. PSL resolution tries field presets first, then falls back to type constructors on miss (see `resolveFieldTypeDescriptor`).
|
|
550
|
+
*/
|
|
551
|
+
function getAuthoringFieldPreset(contributions, path) {
|
|
552
|
+
let current = contributions?.field;
|
|
553
|
+
for (const segment of path) {
|
|
554
|
+
if (typeof current !== "object" || current === null || "kind" in current) return;
|
|
555
|
+
current = current[segment];
|
|
556
|
+
}
|
|
557
|
+
return current !== void 0 && isAuthoringFieldPresetDescriptor(current) ? current : void 0;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Returns the namespace prefix of `attributeName` if it references an unrecognized extension namespace, otherwise `undefined`. A namespace is considered recognized when it is:
|
|
561
|
+
*
|
|
562
|
+
* - `db` (native-type spec, always allowed),
|
|
563
|
+
* - the active family id (e.g. `sql`),
|
|
564
|
+
* - the active target id (e.g. `postgres`),
|
|
565
|
+
* - a registered field-preset namespace (e.g. `temporal`),
|
|
566
|
+
* - present in `composedExtensions`.
|
|
567
|
+
*
|
|
568
|
+
* Family/target/field-preset namespaces are exempted so that e.g. `@sql.foo` surfaces as PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
|
|
569
|
+
*/
|
|
570
|
+
function checkUncomposedNamespace(attributeName, composedExtensions, context) {
|
|
571
|
+
const dotIndex = attributeName.indexOf(".");
|
|
572
|
+
if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
|
|
573
|
+
const namespace = attributeName.slice(0, dotIndex);
|
|
574
|
+
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
|
|
575
|
+
return namespace;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a subject (attribute, model attribute, or type constructor) that references an extension namespace which is not composed in the current contract.
|
|
579
|
+
*
|
|
580
|
+
* The `data` payload carries the missing namespace so machine consumers (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
|
|
581
|
+
*/
|
|
582
|
+
function reportUncomposedNamespace(input) {
|
|
583
|
+
input.diagnostics.push({
|
|
584
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
585
|
+
message: `${input.subjectLabel} uses unrecognized namespace "${input.namespace}". Add extension pack "${input.namespace}" to extensions in prisma-next.config.ts.`,
|
|
586
|
+
sourceId: input.sourceId,
|
|
587
|
+
span: input.span,
|
|
588
|
+
data: {
|
|
589
|
+
namespace: input.namespace,
|
|
590
|
+
suggestedPack: input.namespace
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Pushes the canonical `PSL_UNKNOWN_FIELD_PRESET` diagnostic when a typoed preset name is referenced inside a registered field-preset namespace. The `data` payload exposes the namespace and full helper path so machine consumers (agents, IDE extensions) don't have to parse the prose.
|
|
596
|
+
*/
|
|
597
|
+
function reportUnknownFieldPreset(input) {
|
|
598
|
+
input.diagnostics.push({
|
|
599
|
+
code: "PSL_UNKNOWN_FIELD_PRESET",
|
|
600
|
+
message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
|
|
601
|
+
sourceId: input.sourceId,
|
|
602
|
+
span: input.span,
|
|
603
|
+
data: {
|
|
604
|
+
namespace: input.namespace,
|
|
605
|
+
helperPath: input.helperPath
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function instantiatePslTypeConstructor(input) {
|
|
610
|
+
const helperPath = input.call.path.join(".");
|
|
611
|
+
const args = mapPslHelperArgs({
|
|
612
|
+
args: input.call.args,
|
|
613
|
+
descriptors: input.descriptor.args ?? [],
|
|
614
|
+
helperLabel: `constructor "${helperPath}"`,
|
|
615
|
+
span: input.call.span,
|
|
616
|
+
diagnostics: input.diagnostics,
|
|
617
|
+
sourceId: input.sourceId,
|
|
618
|
+
entityLabel: input.entityLabel
|
|
619
|
+
});
|
|
620
|
+
if (!args) return;
|
|
621
|
+
try {
|
|
622
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
623
|
+
return instantiateAuthoringTypeConstructor(input.descriptor, args);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
626
|
+
input.diagnostics.push({
|
|
627
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
628
|
+
message: `${input.entityLabel} constructor "${helperPath}" ${message}`,
|
|
629
|
+
sourceId: input.sourceId,
|
|
630
|
+
span: input.call.span
|
|
631
|
+
});
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function pushUnsupportedTypeConstructorDiagnostic(input) {
|
|
636
|
+
input.diagnostics.push({
|
|
637
|
+
code: input.code,
|
|
638
|
+
message: input.message,
|
|
639
|
+
sourceId: input.sourceId,
|
|
640
|
+
span: input.span
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
function resolvePslTypeConstructorDescriptor(input) {
|
|
644
|
+
const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
|
|
645
|
+
if (descriptor) return descriptor;
|
|
646
|
+
const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
|
|
647
|
+
familyId: input.familyId,
|
|
648
|
+
targetId: input.targetId,
|
|
649
|
+
authoringContributions: input.authoringContributions
|
|
650
|
+
});
|
|
651
|
+
if (uncomposedNamespace) {
|
|
652
|
+
reportUncomposedNamespace({
|
|
653
|
+
subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
|
|
654
|
+
namespace: uncomposedNamespace,
|
|
655
|
+
sourceId: input.sourceId,
|
|
656
|
+
span: input.call.span,
|
|
657
|
+
diagnostics: input.diagnostics
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
return pushUnsupportedTypeConstructorDiagnostic({
|
|
662
|
+
diagnostics: input.diagnostics,
|
|
663
|
+
sourceId: input.sourceId,
|
|
664
|
+
span: input.call.span,
|
|
665
|
+
code: input.unsupportedCode,
|
|
666
|
+
message: input.unsupportedMessage
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Instantiates a field-preset call against its descriptor, coercing PSL AST arguments into the descriptor's typed argument shape and returning the preset's full set of contract contributions.
|
|
671
|
+
*
|
|
672
|
+
* Symmetric with `instantiatePslTypeConstructor` but richer: a field preset can contribute `default`, `executionDefaults`, `id`, `unique`, and `nullable` in addition to the storage-type triple. PSL → typed-args coercion happens here (via `mapPslHelperArgs`) so that `instantiateAuthoringFieldPreset` itself stays typed-input-only and TS keeps its zero-runtime-validation cost.
|
|
673
|
+
*/
|
|
674
|
+
function instantiateFieldPreset(input) {
|
|
675
|
+
const helperPath = input.call.path.join(".");
|
|
676
|
+
const args = mapPslHelperArgs({
|
|
677
|
+
args: input.call.args,
|
|
678
|
+
descriptors: input.descriptor.args ?? [],
|
|
679
|
+
helperLabel: `preset "${helperPath}"`,
|
|
680
|
+
span: input.call.span,
|
|
681
|
+
diagnostics: input.diagnostics,
|
|
682
|
+
sourceId: input.sourceId,
|
|
683
|
+
entityLabel: input.entityLabel
|
|
684
|
+
});
|
|
685
|
+
if (!args) return;
|
|
686
|
+
try {
|
|
687
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
688
|
+
const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
|
|
689
|
+
return {
|
|
690
|
+
descriptor: {
|
|
691
|
+
codecId: instantiated.descriptor.codecId,
|
|
692
|
+
nativeType: instantiated.descriptor.nativeType,
|
|
693
|
+
...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
|
|
694
|
+
},
|
|
695
|
+
nullable: instantiated.nullable,
|
|
696
|
+
...instantiated.default !== void 0 ? { default: instantiated.default } : {},
|
|
697
|
+
...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
|
|
698
|
+
id: instantiated.id,
|
|
699
|
+
unique: instantiated.unique
|
|
700
|
+
};
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
+
input.diagnostics.push({
|
|
704
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
705
|
+
message: `${input.entityLabel} preset "${helperPath}" ${message}`,
|
|
706
|
+
sourceId: input.sourceId,
|
|
707
|
+
span: input.call.span
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Structural check for a codec descriptor exposing the authoring-time
|
|
714
|
+
* `columnFromEntity` hook a type constructor's `entityRefArg` resolves
|
|
715
|
+
* through (e.g. the `pg/enum@1` codec descriptor). No casts.
|
|
716
|
+
*/
|
|
717
|
+
function hasColumnFromEntityHook(descriptor) {
|
|
718
|
+
return "columnFromEntity" in descriptor && typeof descriptor.columnFromEntity === "function";
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Resolves a type-constructor call whose descriptor declares an
|
|
722
|
+
* `entityRefArg` (e.g. `pg.enum(AalLevel)`): extracts the call's sole
|
|
723
|
+
* positional-argument ref string, resolves it against the field's
|
|
724
|
+
* namespace's already-lowered extension entities (keyed by the declared
|
|
725
|
+
* `entityRefArg.entityKind`, then block name), and converts the resolved
|
|
726
|
+
* entity to column params via the `columnFromEntity` authoring hook on the
|
|
727
|
+
* codec descriptor registered for `descriptor.output.codecId`. The `nativeType`
|
|
728
|
+
* / `typeParams.typeName` `columnFromEntity` returns are bare — schema
|
|
729
|
+
* qualification (e.g. `auth.aal_level`) is a target concern, applied later
|
|
730
|
+
* when the target builds the field's namespace. A `valueSet` ref is
|
|
731
|
+
* attached when the same namespace derived a value-set under the same block
|
|
732
|
+
* name (the generic `deriveValueSet` mechanism), scoped to the field's own
|
|
733
|
+
* namespace.
|
|
734
|
+
*/
|
|
735
|
+
function resolveEntityRefTypeConstructorCall(input) {
|
|
736
|
+
const entityRefArg = input.descriptor.entityRefArg;
|
|
737
|
+
if (entityRefArg === void 0) throw new InternalError("resolveEntityRefTypeConstructorCall called with a descriptor that does not declare an entityRefArg. This is an interpreter bug.");
|
|
738
|
+
const helperPath = input.call.path.join(".");
|
|
739
|
+
const positionalArgs = input.call.args.filter((arg) => arg.kind === "positional");
|
|
740
|
+
const ref = positionalArgs[entityRefArg.index]?.value;
|
|
741
|
+
if (input.call.args.length !== 1 || positionalArgs.length !== 1 || ref === void 0) {
|
|
742
|
+
input.diagnostics.push({
|
|
743
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
744
|
+
message: `${input.entityLabel} type constructor "${helperPath}" expects exactly one positional argument naming the referenced entity`,
|
|
745
|
+
sourceId: input.sourceId,
|
|
746
|
+
span: input.call.span
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
alreadyReported: true
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const reportUnknownRef = () => {
|
|
754
|
+
input.diagnostics.push({
|
|
755
|
+
code: "PSL_UNKNOWN_ENTITY_REF",
|
|
756
|
+
message: `${input.entityLabel} type constructor "${helperPath}(${ref})" does not resolve — no entity named "${ref}" was found in namespace "${input.namespaceId ?? "(unspecified)"}"`,
|
|
757
|
+
sourceId: input.sourceId,
|
|
758
|
+
span: input.call.span
|
|
759
|
+
});
|
|
760
|
+
return {
|
|
761
|
+
ok: false,
|
|
762
|
+
alreadyReported: true
|
|
763
|
+
};
|
|
764
|
+
};
|
|
765
|
+
const entity = input.namespaceExtensionEntities?.[entityRefArg.entityKind]?.[ref];
|
|
766
|
+
if (entity === void 0) return reportUnknownRef();
|
|
767
|
+
const codecId = input.descriptor.output.codecId;
|
|
768
|
+
const codecDescriptor = input.codecLookup?.descriptorFor?.(codecId);
|
|
769
|
+
if (codecDescriptor === void 0 || !hasColumnFromEntityHook(codecDescriptor)) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Type constructor "${helperPath}" registers codecId "${codecId}" with an entity-ref argument, but its codec descriptor has no "columnFromEntity" authoring hook. This is a contributor bug in the pack registering "${helperPath}", not a user-schema error.`, { meta: {
|
|
770
|
+
helperPath,
|
|
771
|
+
codecId
|
|
772
|
+
} });
|
|
773
|
+
const resolved = codecDescriptor.columnFromEntity(entity);
|
|
774
|
+
if (resolved === void 0) return reportUnknownRef();
|
|
775
|
+
const derivedValueSet = input.namespaceExtensionEntities?.["valueSet"]?.[ref];
|
|
776
|
+
if (derivedValueSet !== void 0 && input.namespaceId === void 0) {
|
|
777
|
+
input.diagnostics.push({
|
|
778
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
779
|
+
message: `${input.entityLabel} type constructor "${helperPath}(${ref})" resolves to a value-set-typed entity, but the field has no resolvable namespace to scope the value-set ref to`,
|
|
780
|
+
sourceId: input.sourceId,
|
|
781
|
+
span: input.call.span
|
|
782
|
+
});
|
|
783
|
+
return {
|
|
784
|
+
ok: false,
|
|
785
|
+
alreadyReported: true
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
const valueSet = derivedValueSet !== void 0 && input.namespaceId !== void 0 ? {
|
|
789
|
+
plane: "storage",
|
|
790
|
+
entityKind: "valueSet",
|
|
791
|
+
namespaceId: input.namespaceId,
|
|
792
|
+
entityName: ref
|
|
793
|
+
} : void 0;
|
|
794
|
+
return {
|
|
795
|
+
ok: true,
|
|
796
|
+
descriptor: {
|
|
797
|
+
codecId,
|
|
798
|
+
nativeType: resolved.nativeType,
|
|
799
|
+
...resolved.typeParams !== void 0 ? { typeParams: resolved.typeParams } : {},
|
|
800
|
+
...valueSet !== void 0 ? { valueSet } : {}
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function resolveFieldTypeDescriptor(input) {
|
|
805
|
+
if (input.field.malformedType) return {
|
|
806
|
+
ok: false,
|
|
807
|
+
alreadyReported: true
|
|
808
|
+
};
|
|
809
|
+
if (input.field.typeConstructor) {
|
|
810
|
+
const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
|
|
811
|
+
if (presetDescriptor) {
|
|
812
|
+
const instantiated = instantiateFieldPreset({
|
|
813
|
+
call: input.field.typeConstructor,
|
|
814
|
+
descriptor: presetDescriptor,
|
|
815
|
+
diagnostics: input.diagnostics,
|
|
816
|
+
sourceId: input.sourceId,
|
|
817
|
+
entityLabel: input.entityLabel
|
|
818
|
+
});
|
|
819
|
+
if (!instantiated) return {
|
|
820
|
+
ok: false,
|
|
821
|
+
alreadyReported: true
|
|
822
|
+
};
|
|
823
|
+
const presetContributions = {
|
|
824
|
+
nullable: instantiated.nullable,
|
|
825
|
+
id: instantiated.id,
|
|
826
|
+
unique: instantiated.unique,
|
|
827
|
+
...instantiated.default !== void 0 ? { default: instantiated.default } : {},
|
|
828
|
+
...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {}
|
|
829
|
+
};
|
|
830
|
+
return {
|
|
831
|
+
ok: true,
|
|
832
|
+
descriptor: instantiated.descriptor,
|
|
833
|
+
presetContributions
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const helperPath = input.field.typeConstructor.path.join(".");
|
|
837
|
+
const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
|
|
838
|
+
const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
|
|
839
|
+
if (typeDescriptor?.entityRefArg) return resolveEntityRefTypeConstructorCall({
|
|
840
|
+
call: input.field.typeConstructor,
|
|
841
|
+
descriptor: typeDescriptor,
|
|
842
|
+
namespaceId: input.namespaceId,
|
|
843
|
+
namespaceExtensionEntities: input.namespaceExtensionEntities,
|
|
844
|
+
codecLookup: input.codecLookup,
|
|
845
|
+
diagnostics: input.diagnostics,
|
|
846
|
+
sourceId: input.sourceId,
|
|
847
|
+
entityLabel: input.entityLabel
|
|
848
|
+
});
|
|
849
|
+
if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
|
|
850
|
+
reportUnknownFieldPreset({
|
|
851
|
+
entityLabel: input.entityLabel,
|
|
852
|
+
namespace: namespacePrefix,
|
|
853
|
+
helperPath,
|
|
854
|
+
sourceId: input.sourceId,
|
|
855
|
+
span: input.field.typeConstructor.span,
|
|
856
|
+
diagnostics: input.diagnostics
|
|
857
|
+
});
|
|
858
|
+
return {
|
|
859
|
+
ok: false,
|
|
860
|
+
alreadyReported: true
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
const descriptor = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
|
|
864
|
+
call: input.field.typeConstructor,
|
|
865
|
+
authoringContributions: input.authoringContributions,
|
|
866
|
+
composedExtensions: input.composedExtensions,
|
|
867
|
+
familyId: input.familyId,
|
|
868
|
+
targetId: input.targetId,
|
|
869
|
+
diagnostics: input.diagnostics,
|
|
870
|
+
sourceId: input.sourceId,
|
|
871
|
+
unsupportedCode: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
872
|
+
unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`
|
|
873
|
+
});
|
|
874
|
+
if (!descriptor) return {
|
|
875
|
+
ok: false,
|
|
876
|
+
alreadyReported: true
|
|
877
|
+
};
|
|
878
|
+
const instantiated = instantiatePslTypeConstructor({
|
|
879
|
+
call: input.field.typeConstructor,
|
|
880
|
+
descriptor,
|
|
881
|
+
diagnostics: input.diagnostics,
|
|
882
|
+
sourceId: input.sourceId,
|
|
883
|
+
entityLabel: input.entityLabel
|
|
884
|
+
});
|
|
885
|
+
if (!instantiated) return {
|
|
886
|
+
ok: false,
|
|
887
|
+
alreadyReported: true
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
ok: true,
|
|
891
|
+
descriptor: instantiated
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
const descriptor = resolveColumnDescriptor(input.field, input.enumTypeDescriptors, input.namedTypeDescriptors, input.scalarColumnDescriptors);
|
|
895
|
+
if (!descriptor) return {
|
|
896
|
+
ok: false,
|
|
897
|
+
alreadyReported: false
|
|
898
|
+
};
|
|
899
|
+
return {
|
|
900
|
+
ok: true,
|
|
901
|
+
descriptor
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function lowerDefaultForField(input) {
|
|
905
|
+
const node = findFieldAttributeNode(input.field, "default");
|
|
906
|
+
if (node === void 0) return {};
|
|
907
|
+
const interpreted = interpretFieldAttribute({
|
|
908
|
+
node,
|
|
909
|
+
spec: buildDefaultSpec({
|
|
910
|
+
isList: input.isList ?? false,
|
|
911
|
+
registry: input.defaultFunctionRegistry
|
|
912
|
+
}),
|
|
913
|
+
model: input.model,
|
|
914
|
+
field: input.field,
|
|
915
|
+
sourceFile: input.sourceFile,
|
|
916
|
+
sourceId: input.sourceId,
|
|
917
|
+
diagnostics: input.diagnostics
|
|
918
|
+
});
|
|
919
|
+
if (interpreted === void 0) return {};
|
|
920
|
+
const value = interpreted.value;
|
|
921
|
+
if (Array.isArray(value)) return { defaultValue: {
|
|
922
|
+
kind: "literal",
|
|
923
|
+
value: [...value]
|
|
924
|
+
} };
|
|
925
|
+
if (typeof value === "object") {
|
|
926
|
+
const lowered = lowerDefaultFunctionWithRegistry({
|
|
927
|
+
call: value,
|
|
928
|
+
registry: input.defaultFunctionRegistry,
|
|
929
|
+
context: {
|
|
930
|
+
sourceId: input.sourceId,
|
|
931
|
+
modelName: input.modelName,
|
|
932
|
+
fieldName: input.fieldName,
|
|
933
|
+
columnCodecId: input.columnDescriptor.codecId
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
if (!lowered.ok) {
|
|
937
|
+
input.diagnostics.push(lowered.diagnostic);
|
|
938
|
+
return {};
|
|
939
|
+
}
|
|
940
|
+
if (lowered.value.kind === "storage") return { defaultValue: lowered.value.defaultValue };
|
|
941
|
+
const generatorDescriptor = input.generatorDescriptorById.get(lowered.value.generated.id);
|
|
942
|
+
if (!generatorDescriptor) {
|
|
943
|
+
input.diagnostics.push({
|
|
944
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
945
|
+
message: `Default generator "${lowered.value.generated.id}" is not available in the composed mutation default registry.`,
|
|
946
|
+
sourceId: input.sourceId,
|
|
947
|
+
span: value.span
|
|
948
|
+
});
|
|
949
|
+
return {};
|
|
950
|
+
}
|
|
951
|
+
if (generatorDescriptor.applicableCodecIds === void 0) {
|
|
952
|
+
input.diagnostics.push({
|
|
953
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
954
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "@default(...)" lowering. Use the corresponding field preset (e.g. \`temporal.${generatorDescriptor.id === "timestampNow" ? "updatedAt" : generatorDescriptor.id}()\`) instead.`,
|
|
955
|
+
sourceId: input.sourceId,
|
|
956
|
+
span: value.span
|
|
957
|
+
});
|
|
958
|
+
return {};
|
|
959
|
+
}
|
|
960
|
+
if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
|
|
961
|
+
input.diagnostics.push({
|
|
962
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
963
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "${input.modelName}.${input.fieldName}" with codecId "${input.columnDescriptor.codecId}".`,
|
|
964
|
+
sourceId: input.sourceId,
|
|
965
|
+
span: value.span
|
|
966
|
+
});
|
|
967
|
+
return {};
|
|
968
|
+
}
|
|
969
|
+
return { executionDefaults: { onCreate: lowered.value.generated } };
|
|
970
|
+
}
|
|
971
|
+
return { defaultValue: {
|
|
972
|
+
kind: "literal",
|
|
973
|
+
value
|
|
974
|
+
} };
|
|
975
|
+
}
|
|
976
|
+
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarColumnDescriptors) {
|
|
977
|
+
if (namedTypeDescriptors.has(field.typeName)) return namedTypeDescriptors.get(field.typeName);
|
|
978
|
+
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
979
|
+
return scalarColumnDescriptors.get(field.typeName);
|
|
980
|
+
}
|
|
981
|
+
function lowerEnumDefaultForField(input) {
|
|
982
|
+
const { field, model, sourceFile, enumHandle, sourceId, diagnostics } = input;
|
|
983
|
+
const node = findFieldAttributeNode(field, "default");
|
|
984
|
+
if (node === void 0) return {};
|
|
985
|
+
const [firstMember, ...restMembers] = enumHandle.enumMembers.map((m) => m.name);
|
|
986
|
+
if (firstMember === void 0) return {};
|
|
987
|
+
const interpreted = interpretFieldAttribute({
|
|
988
|
+
node,
|
|
989
|
+
spec: buildEnumDefaultSpec([firstMember, ...restMembers]),
|
|
990
|
+
model,
|
|
991
|
+
field,
|
|
992
|
+
sourceFile,
|
|
993
|
+
sourceId,
|
|
994
|
+
diagnostics
|
|
995
|
+
});
|
|
996
|
+
if (interpreted === void 0) return {};
|
|
997
|
+
const member = interpreted.member;
|
|
998
|
+
const match = enumHandle.enumMembers.find((m) => m.name === member);
|
|
999
|
+
if (!match) return {};
|
|
1000
|
+
return { defaultValue: {
|
|
1001
|
+
kind: "literal",
|
|
1002
|
+
value: blindCast(match.value)
|
|
1003
|
+
} };
|
|
1004
|
+
}
|
|
1005
|
+
const MODEL_COORDINATE_SEPARATOR = "\0";
|
|
1006
|
+
function modelCoordinateKey(namespaceId, modelName) {
|
|
1007
|
+
return `${namespaceId}${MODEL_COORDINATE_SEPARATOR}${modelName}`;
|
|
1008
|
+
}
|
|
1009
|
+
const BUILTIN_FIELD_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
|
|
1010
|
+
"id",
|
|
1011
|
+
"unique",
|
|
1012
|
+
"default",
|
|
1013
|
+
"relation",
|
|
1014
|
+
"map"
|
|
1015
|
+
]);
|
|
1016
|
+
const REMOVED_ATTRIBUTE_RULES = /* @__PURE__ */ new Map([["updatedAt", {
|
|
1017
|
+
hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
|
|
1018
|
+
suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
|
|
1019
|
+
}]]);
|
|
1020
|
+
{
|
|
1021
|
+
const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
|
|
1022
|
+
if (overlap.length > 0) throw new InternalError(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
|
|
1023
|
+
}
|
|
1024
|
+
function validateFieldAttributes(input) {
|
|
1025
|
+
for (const attribute of input.field.attributes) {
|
|
1026
|
+
if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
|
|
1027
|
+
if (attribute.name.startsWith("db.")) {
|
|
1028
|
+
input.diagnostics.push({
|
|
1029
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1030
|
+
message: formatDbAttributeMigrationMessage(attribute),
|
|
1031
|
+
sourceId: input.sourceId,
|
|
1032
|
+
span: attribute.span
|
|
1033
|
+
});
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1037
|
+
familyId: input.familyId,
|
|
1038
|
+
targetId: input.targetId,
|
|
1039
|
+
authoringContributions: input.authoringContributions
|
|
1040
|
+
});
|
|
1041
|
+
if (uncomposedNamespace) {
|
|
1042
|
+
reportUncomposedNamespace({
|
|
1043
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1044
|
+
namespace: uncomposedNamespace,
|
|
1045
|
+
sourceId: input.sourceId,
|
|
1046
|
+
span: attribute.span,
|
|
1047
|
+
diagnostics: input.diagnostics
|
|
1048
|
+
});
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
|
|
1052
|
+
const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
|
|
1053
|
+
const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
|
|
1054
|
+
input.diagnostics.push({
|
|
1055
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1056
|
+
message,
|
|
1057
|
+
sourceId: input.sourceId,
|
|
1058
|
+
span: attribute.span
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function extractFieldConstraintNames(input) {
|
|
1063
|
+
const idAttribute = getAttribute(input.field.attributes, "id");
|
|
1064
|
+
const uniqueAttribute = getAttribute(input.field.attributes, "unique");
|
|
1065
|
+
const idNode = findFieldAttributeNode(input.field, "id");
|
|
1066
|
+
const idName = idNode === void 0 ? void 0 : interpretFieldAttribute({
|
|
1067
|
+
node: idNode,
|
|
1068
|
+
spec: idFieldSpec,
|
|
1069
|
+
model: input.model,
|
|
1070
|
+
field: input.field,
|
|
1071
|
+
sourceFile: input.sourceFile,
|
|
1072
|
+
sourceId: input.sourceId,
|
|
1073
|
+
diagnostics: input.diagnostics
|
|
1074
|
+
})?.map;
|
|
1075
|
+
const uniqueNode = findFieldAttributeNode(input.field, "unique");
|
|
1076
|
+
return {
|
|
1077
|
+
idAttribute,
|
|
1078
|
+
uniqueAttribute,
|
|
1079
|
+
idName,
|
|
1080
|
+
uniqueName: uniqueNode === void 0 ? void 0 : interpretFieldAttribute({
|
|
1081
|
+
node: uniqueNode,
|
|
1082
|
+
spec: uniqueFieldSpec,
|
|
1083
|
+
model: input.model,
|
|
1084
|
+
field: input.field,
|
|
1085
|
+
sourceFile: input.sourceFile,
|
|
1086
|
+
sourceId: input.sourceId,
|
|
1087
|
+
diagnostics: input.diagnostics
|
|
1088
|
+
})?.map
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function collectResolvedFields(input) {
|
|
1092
|
+
const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarColumnDescriptors, enumHandles, capabilities, namespaceId, namespaceExtensionEntities, codecLookup } = input;
|
|
1093
|
+
const resolvedFields = [];
|
|
1094
|
+
const valueObjectStorageTypeName = authoringContributions?.valueObjectStorageType;
|
|
1095
|
+
for (const field of Object.values(model.fields)) {
|
|
1096
|
+
const isModelField = modelNames.has(field.typeName);
|
|
1097
|
+
if (field.list && isModelField) continue;
|
|
1098
|
+
validateFieldAttributes({
|
|
1099
|
+
model,
|
|
1100
|
+
field,
|
|
1101
|
+
composedExtensions,
|
|
1102
|
+
authoringContributions,
|
|
1103
|
+
diagnostics,
|
|
1104
|
+
sourceId,
|
|
1105
|
+
familyId,
|
|
1106
|
+
targetId
|
|
1107
|
+
});
|
|
1108
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1109
|
+
if (isModelField && relationAttribute) continue;
|
|
1110
|
+
if (field.typeContractSpaceId !== void 0 && relationAttribute) continue;
|
|
1111
|
+
if (isModelField) continue;
|
|
1112
|
+
const isValueObjectField = compositeTypeNames.has(field.typeName);
|
|
1113
|
+
const isListField = field.list;
|
|
1114
|
+
let descriptor;
|
|
1115
|
+
let scalarCodecId;
|
|
1116
|
+
let presetContributions;
|
|
1117
|
+
const resolveInput = {
|
|
1118
|
+
field,
|
|
1119
|
+
enumTypeDescriptors,
|
|
1120
|
+
namedTypeDescriptors,
|
|
1121
|
+
scalarColumnDescriptors,
|
|
1122
|
+
authoringContributions,
|
|
1123
|
+
composedExtensions,
|
|
1124
|
+
familyId,
|
|
1125
|
+
targetId,
|
|
1126
|
+
diagnostics,
|
|
1127
|
+
sourceId,
|
|
1128
|
+
entityLabel: `Field "${model.name}.${field.name}"`,
|
|
1129
|
+
...ifDefined("namespaceId", namespaceId),
|
|
1130
|
+
...ifDefined("namespaceExtensionEntities", namespaceExtensionEntities),
|
|
1131
|
+
...ifDefined("codecLookup", codecLookup)
|
|
1132
|
+
};
|
|
1133
|
+
if (isValueObjectField) descriptor = valueObjectStorageTypeName !== void 0 ? scalarColumnDescriptors.get(valueObjectStorageTypeName) : void 0;
|
|
1134
|
+
else if (isListField) {
|
|
1135
|
+
if (capabilities["sql"]?.["scalarList"] !== true) {
|
|
1136
|
+
diagnostics.push({
|
|
1137
|
+
code: "PSL_SCALAR_LIST_UNSUPPORTED_TARGET",
|
|
1138
|
+
message: `Field "${model.name}.${field.name}" is a scalar list, but target "${targetId}" does not support scalar lists (the adapter does not report the "scalarList" capability). Remove the list or author it against a target that supports scalar lists.`,
|
|
1139
|
+
sourceId,
|
|
1140
|
+
span: field.span
|
|
1141
|
+
});
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
const resolved = resolveFieldTypeDescriptor(resolveInput);
|
|
1145
|
+
if (!resolved.ok) {
|
|
1146
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
1147
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
1148
|
+
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
1149
|
+
sourceId,
|
|
1150
|
+
span: field.span
|
|
1151
|
+
});
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (resolved.presetContributions) {
|
|
1155
|
+
diagnostics.push({
|
|
1156
|
+
code: "PSL_PRESET_NOT_LIST",
|
|
1157
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call as a list element type. Presets cannot be list elements; remove "[]" or use a scalar type.`,
|
|
1158
|
+
sourceId,
|
|
1159
|
+
span: field.span
|
|
1160
|
+
});
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
scalarCodecId = resolved.descriptor.codecId;
|
|
1164
|
+
descriptor = resolved.descriptor;
|
|
1165
|
+
} else {
|
|
1166
|
+
const resolved = resolveFieldTypeDescriptor(resolveInput);
|
|
1167
|
+
if (!resolved.ok) {
|
|
1168
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
1169
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
1170
|
+
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
1171
|
+
sourceId,
|
|
1172
|
+
span: field.span
|
|
1173
|
+
});
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
descriptor = resolved.descriptor;
|
|
1177
|
+
presetContributions = resolved.presetContributions;
|
|
1178
|
+
}
|
|
1179
|
+
if (!descriptor) continue;
|
|
1180
|
+
if (presetContributions && field.optional) {
|
|
1181
|
+
diagnostics.push({
|
|
1182
|
+
code: "PSL_PRESET_NOT_OPTIONAL",
|
|
1183
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
|
|
1184
|
+
sourceId,
|
|
1185
|
+
span: field.span
|
|
1186
|
+
});
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
1190
|
+
if (presetContributions && defaultAttribute) {
|
|
1191
|
+
diagnostics.push({
|
|
1192
|
+
code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
|
|
1193
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
|
|
1194
|
+
sourceId,
|
|
1195
|
+
span: defaultAttribute.span
|
|
1196
|
+
});
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
const enumHandle = enumHandles?.get(field.typeName);
|
|
1200
|
+
const loweredDefault = defaultAttribute ? enumHandle ? lowerEnumDefaultForField({
|
|
1201
|
+
modelName: model.name,
|
|
1202
|
+
fieldName: field.name,
|
|
1203
|
+
field,
|
|
1204
|
+
model,
|
|
1205
|
+
sourceFile: input.sourceFile,
|
|
1206
|
+
enumHandle,
|
|
1207
|
+
sourceId,
|
|
1208
|
+
diagnostics
|
|
1209
|
+
}) : lowerDefaultForField({
|
|
1210
|
+
modelName: model.name,
|
|
1211
|
+
fieldName: field.name,
|
|
1212
|
+
field,
|
|
1213
|
+
model,
|
|
1214
|
+
sourceFile: input.sourceFile,
|
|
1215
|
+
columnDescriptor: descriptor,
|
|
1216
|
+
generatorDescriptorById,
|
|
1217
|
+
sourceId,
|
|
1218
|
+
defaultFunctionRegistry,
|
|
1219
|
+
diagnostics,
|
|
1220
|
+
isList: isListField
|
|
1221
|
+
}) : {};
|
|
1222
|
+
const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
|
|
1223
|
+
const loweredFunctionDefault = loweredDefault.defaultValue?.kind === "function";
|
|
1224
|
+
if (isListField && (loweredOnCreate || loweredFunctionDefault)) {
|
|
1225
|
+
const defaultExpression = defaultAttribute?.args.find((arg) => arg.kind === "positional")?.value.trim() ?? "this function";
|
|
1226
|
+
diagnostics.push({
|
|
1227
|
+
code: "PSL_LIST_EXECUTION_DEFAULT_UNSUPPORTED",
|
|
1228
|
+
message: `Field "${model.name}.${field.name}" is a list and cannot use an execution default ("${defaultExpression}"). Lists have no per-element execution-default semantics; use a literal list @default or remove the default.`,
|
|
1229
|
+
sourceId,
|
|
1230
|
+
span: defaultAttribute?.span ?? field.span
|
|
1231
|
+
});
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (field.optional && loweredOnCreate) {
|
|
1235
|
+
const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
|
|
1236
|
+
diagnostics.push({
|
|
1237
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
1238
|
+
message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
|
|
1239
|
+
sourceId,
|
|
1240
|
+
span: defaultAttribute?.span ?? field.span
|
|
1241
|
+
});
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
1245
|
+
const { idAttribute, uniqueAttribute, idName, uniqueName } = extractFieldConstraintNames({
|
|
1246
|
+
model,
|
|
1247
|
+
field,
|
|
1248
|
+
sourceFile: input.sourceFile,
|
|
1249
|
+
sourceId,
|
|
1250
|
+
diagnostics
|
|
1251
|
+
});
|
|
1252
|
+
let isIdField = Boolean(idAttribute);
|
|
1253
|
+
if (idAttribute && isListField) {
|
|
1254
|
+
diagnostics.push({
|
|
1255
|
+
code: "PSL_LIST_ID_UNSUPPORTED",
|
|
1256
|
+
message: `Field "${model.name}.${field.name}" is a list and cannot be a primary key. Remove @id; a list cannot be an identity column.`,
|
|
1257
|
+
sourceId,
|
|
1258
|
+
span: idAttribute.span
|
|
1259
|
+
});
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (idAttribute && field.optional) {
|
|
1263
|
+
diagnostics.push({
|
|
1264
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1265
|
+
message: `Field "${model.name}.${field.name}" @id cannot be optional; primary key columns must be NOT NULL`,
|
|
1266
|
+
sourceId,
|
|
1267
|
+
span: idAttribute.span
|
|
1268
|
+
});
|
|
1269
|
+
isIdField = false;
|
|
1270
|
+
}
|
|
1271
|
+
if (presetContributions && idAttribute && !presetContributions.id) {
|
|
1272
|
+
diagnostics.push({
|
|
1273
|
+
code: "PSL_PRESET_AND_ID_CONFLICT",
|
|
1274
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @id. Use a preset that contributes id semantics, or drop @id.`,
|
|
1275
|
+
sourceId,
|
|
1276
|
+
span: idAttribute.span
|
|
1277
|
+
});
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
|
|
1281
|
+
const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
|
|
1282
|
+
resolvedFields.push({
|
|
1283
|
+
field,
|
|
1284
|
+
columnName: mappedColumnName,
|
|
1285
|
+
descriptor,
|
|
1286
|
+
nullable: presetContributions?.nullable ?? field.optional,
|
|
1287
|
+
...ifDefined("defaultValue", fieldDefaultValue),
|
|
1288
|
+
...ifDefined("executionDefaults", fieldExecutionDefaults),
|
|
1289
|
+
isId: isIdField || Boolean(presetContributions?.id),
|
|
1290
|
+
isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
|
|
1291
|
+
...ifDefined("idName", idName),
|
|
1292
|
+
...ifDefined("uniqueName", uniqueName),
|
|
1293
|
+
...ifDefined("many", isListField ? true : void 0),
|
|
1294
|
+
...ifDefined("valueObjectTypeName", isValueObjectField ? field.typeName : void 0),
|
|
1295
|
+
...ifDefined("scalarCodecId", scalarCodecId)
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
return resolvedFields;
|
|
1299
|
+
}
|
|
1300
|
+
function buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourceId, sourceFile) {
|
|
1301
|
+
const result = /* @__PURE__ */ new Map();
|
|
1302
|
+
for (const { model, namespaceId } of modelEntries) {
|
|
1303
|
+
const mapNode = findModelAttributeNode(model, "map");
|
|
1304
|
+
const tableName = mapNode === void 0 ? lowerFirst(model.name) : interpretModelAttribute({
|
|
1305
|
+
node: mapNode,
|
|
1306
|
+
spec: mapModelSpec,
|
|
1307
|
+
model,
|
|
1308
|
+
sourceFile,
|
|
1309
|
+
sourceId,
|
|
1310
|
+
diagnostics
|
|
1311
|
+
})?.name ?? lowerFirst(model.name);
|
|
1312
|
+
const fieldColumns = /* @__PURE__ */ new Map();
|
|
1313
|
+
for (const field of Object.values(model.fields)) {
|
|
1314
|
+
const fieldMapNode = findFieldAttributeNode(field, "map");
|
|
1315
|
+
const columnName = fieldMapNode === void 0 ? field.name : interpretFieldAttribute({
|
|
1316
|
+
node: fieldMapNode,
|
|
1317
|
+
spec: mapFieldSpec,
|
|
1318
|
+
model,
|
|
1319
|
+
field,
|
|
1320
|
+
sourceFile,
|
|
1321
|
+
sourceId,
|
|
1322
|
+
diagnostics
|
|
1323
|
+
})?.name ?? field.name;
|
|
1324
|
+
fieldColumns.set(field.name, columnName);
|
|
1325
|
+
}
|
|
1326
|
+
result.set(modelCoordinateKey(namespaceId ?? defaultNamespaceId, model.name), {
|
|
1327
|
+
model,
|
|
1328
|
+
tableName,
|
|
1329
|
+
fieldColumns
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
return result;
|
|
1333
|
+
}
|
|
1334
|
+
function validateNamedTypeAttributes(input) {
|
|
1335
|
+
let hasUnsupportedNamedTypeAttribute = false;
|
|
1336
|
+
for (const attribute of input.declaration.attributes) {
|
|
1337
|
+
if (attribute.name.startsWith("db.")) {
|
|
1338
|
+
input.diagnostics.push({
|
|
1339
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
1340
|
+
message: formatDbAttributeMigrationMessage(attribute),
|
|
1341
|
+
sourceId: input.sourceId,
|
|
1342
|
+
span: attribute.span
|
|
1343
|
+
});
|
|
1344
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1348
|
+
familyId: input.familyId,
|
|
1349
|
+
targetId: input.targetId,
|
|
1350
|
+
authoringContributions: input.authoringContributions
|
|
1351
|
+
});
|
|
1352
|
+
if (uncomposedNamespace) {
|
|
1353
|
+
reportUncomposedNamespace({
|
|
1354
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1355
|
+
namespace: uncomposedNamespace,
|
|
1356
|
+
sourceId: input.sourceId,
|
|
1357
|
+
span: attribute.span,
|
|
1358
|
+
diagnostics: input.diagnostics
|
|
1359
|
+
});
|
|
1360
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
input.diagnostics.push({
|
|
1364
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
1365
|
+
message: `Named type "${input.declaration.name}" uses unsupported attribute "${attribute.name}"`,
|
|
1366
|
+
sourceId: input.sourceId,
|
|
1367
|
+
span: attribute.span
|
|
1368
|
+
});
|
|
1369
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1370
|
+
}
|
|
1371
|
+
return hasUnsupportedNamedTypeAttribute;
|
|
1372
|
+
}
|
|
1373
|
+
function resolveNamedTypeDeclarations(input) {
|
|
1374
|
+
const storageTypeEntries = [];
|
|
1375
|
+
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1376
|
+
for (const declaration of input.declarations) {
|
|
1377
|
+
if (declaration.isConstructor) {
|
|
1378
|
+
const typeConstructor = declaration.typeConstructor;
|
|
1379
|
+
if (typeConstructor === void 0) {
|
|
1380
|
+
input.diagnostics.push({
|
|
1381
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1382
|
+
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
1383
|
+
sourceId: input.sourceId,
|
|
1384
|
+
span: declaration.span
|
|
1385
|
+
});
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (validateNamedTypeAttributes({
|
|
1389
|
+
declaration,
|
|
1390
|
+
sourceId: input.sourceId,
|
|
1391
|
+
diagnostics: input.diagnostics,
|
|
1392
|
+
composedExtensions: input.composedExtensions,
|
|
1393
|
+
authoringContributions: input.authoringContributions,
|
|
1394
|
+
familyId: input.familyId,
|
|
1395
|
+
targetId: input.targetId
|
|
1396
|
+
})) continue;
|
|
1397
|
+
const helperPath = typeConstructor.path.join(".");
|
|
1398
|
+
const descriptor = resolvePslTypeConstructorDescriptor({
|
|
1399
|
+
call: typeConstructor,
|
|
1400
|
+
authoringContributions: input.authoringContributions,
|
|
1401
|
+
composedExtensions: input.composedExtensions,
|
|
1402
|
+
familyId: input.familyId,
|
|
1403
|
+
targetId: input.targetId,
|
|
1404
|
+
diagnostics: input.diagnostics,
|
|
1405
|
+
sourceId: input.sourceId,
|
|
1406
|
+
unsupportedCode: "PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR",
|
|
1407
|
+
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`
|
|
1408
|
+
});
|
|
1409
|
+
if (!descriptor) continue;
|
|
1410
|
+
const storageType = instantiatePslTypeConstructor({
|
|
1411
|
+
call: typeConstructor,
|
|
1412
|
+
descriptor,
|
|
1413
|
+
diagnostics: input.diagnostics,
|
|
1414
|
+
sourceId: input.sourceId,
|
|
1415
|
+
entityLabel: `Named type "${declaration.name}"`
|
|
1416
|
+
});
|
|
1417
|
+
if (!storageType) continue;
|
|
1418
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, storageType));
|
|
1419
|
+
storageTypeEntries.push([declaration.name, {
|
|
1420
|
+
kind: "codec-instance",
|
|
1421
|
+
codecId: storageType.codecId,
|
|
1422
|
+
nativeType: storageType.nativeType,
|
|
1423
|
+
typeParams: storageType.typeParams ?? {}
|
|
1424
|
+
}]);
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
const baseType = declaration.baseType;
|
|
1428
|
+
if (baseType === void 0) {
|
|
1429
|
+
input.diagnostics.push({
|
|
1430
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1431
|
+
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
1432
|
+
sourceId: input.sourceId,
|
|
1433
|
+
span: declaration.span
|
|
1434
|
+
});
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
const baseDescriptor = input.enumTypeDescriptors.get(baseType) ?? input.scalarColumnDescriptors.get(baseType);
|
|
1438
|
+
if (!baseDescriptor) {
|
|
1439
|
+
input.diagnostics.push({
|
|
1440
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1441
|
+
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
1442
|
+
sourceId: input.sourceId,
|
|
1443
|
+
span: declaration.span
|
|
1444
|
+
});
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (validateNamedTypeAttributes({
|
|
1448
|
+
declaration,
|
|
1449
|
+
sourceId: input.sourceId,
|
|
1450
|
+
diagnostics: input.diagnostics,
|
|
1451
|
+
composedExtensions: input.composedExtensions,
|
|
1452
|
+
authoringContributions: input.authoringContributions,
|
|
1453
|
+
familyId: input.familyId,
|
|
1454
|
+
targetId: input.targetId
|
|
1455
|
+
})) continue;
|
|
1456
|
+
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
1457
|
+
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
1458
|
+
storageTypeEntries.push([declaration.name, {
|
|
1459
|
+
kind: "codec-instance",
|
|
1460
|
+
codecId: baseDescriptor.codecId,
|
|
1461
|
+
nativeType: baseDescriptor.nativeType,
|
|
1462
|
+
typeParams: baseDescriptor.typeParams ?? {}
|
|
1463
|
+
}]);
|
|
1464
|
+
}
|
|
1465
|
+
return {
|
|
1466
|
+
storageTypes: Object.fromEntries(storageTypeEntries),
|
|
1467
|
+
namedTypeDescriptors
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
const REFERENTIAL_ACTION_MAP = {
|
|
1471
|
+
NoAction: "noAction",
|
|
1472
|
+
Restrict: "restrict",
|
|
1473
|
+
Cascade: "cascade",
|
|
1474
|
+
SetNull: "setNull",
|
|
1475
|
+
SetDefault: "setDefault",
|
|
1476
|
+
noAction: "noAction",
|
|
1477
|
+
restrict: "restrict",
|
|
1478
|
+
cascade: "cascade",
|
|
1479
|
+
setNull: "setNull",
|
|
1480
|
+
setDefault: "setDefault"
|
|
1481
|
+
};
|
|
1482
|
+
function fkRelationPairKey(declaringModelName, targetModelName) {
|
|
1483
|
+
return `${declaringModelName}::${targetModelName}`;
|
|
1484
|
+
}
|
|
1485
|
+
function normalizeReferentialAction(actionToken) {
|
|
1486
|
+
return REFERENTIAL_ACTION_MAP[actionToken];
|
|
1487
|
+
}
|
|
1488
|
+
function relationInvariants(parsed, ctx) {
|
|
1489
|
+
if (parsed.fields !== void 0 !== (parsed.references !== void 0)) return [{
|
|
1490
|
+
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
1491
|
+
message: `Relation field "${ctx.selfModel.name}.${ctx.field?.name ?? ""}" requires fields and references arguments`,
|
|
1492
|
+
sourceId: ctx.sourceId,
|
|
1493
|
+
span: relationAttributeSpan(ctx)
|
|
1494
|
+
}];
|
|
1495
|
+
return [];
|
|
1496
|
+
}
|
|
1497
|
+
const sqlRelation = fieldAttribute("relation", {
|
|
1498
|
+
positional: [{
|
|
1499
|
+
key: "name",
|
|
1500
|
+
type: optional(str())
|
|
1501
|
+
}],
|
|
1502
|
+
named: {
|
|
1503
|
+
name: optional(str()),
|
|
1504
|
+
fields: optional(list(fieldRef("self"), {
|
|
1505
|
+
nonEmpty: true,
|
|
1506
|
+
unique: true
|
|
1507
|
+
})),
|
|
1508
|
+
references: optional(list(fieldRef("referenced"), {
|
|
1509
|
+
nonEmpty: true,
|
|
1510
|
+
unique: true
|
|
1511
|
+
})),
|
|
1512
|
+
map: optional(str()),
|
|
1513
|
+
onDelete: optional(oneOf(identifier("NoAction"), identifier("Restrict"), identifier("Cascade"), identifier("SetNull"), identifier("SetDefault"))),
|
|
1514
|
+
onUpdate: optional(oneOf(identifier("NoAction"), identifier("Restrict"), identifier("Cascade"), identifier("SetNull"), identifier("SetDefault"))),
|
|
1515
|
+
/**
|
|
1516
|
+
* Opts a foreign key out of its default backing index
|
|
1517
|
+
* (`index: false`). Omitted (the default) keeps the FK's derived
|
|
1518
|
+
* backing-index expectation; only `false` is meaningful — there is no
|
|
1519
|
+
* `index: true` spelling since that is already the default.
|
|
1520
|
+
*/
|
|
1521
|
+
index: optional(bool())
|
|
1522
|
+
},
|
|
1523
|
+
refine: relationInvariants
|
|
1524
|
+
});
|
|
1525
|
+
function relationAttributeSpan(ctx) {
|
|
1526
|
+
const field = ctx.field;
|
|
1527
|
+
if (field !== void 0) {
|
|
1528
|
+
const node = findFieldAttributeNode(field, "relation");
|
|
1529
|
+
if (node !== void 0) return nodePslSpan(node.syntax, ctx.sourceFile);
|
|
1530
|
+
return field.span;
|
|
1531
|
+
}
|
|
1532
|
+
return ctx.selfModel.span;
|
|
1533
|
+
}
|
|
1534
|
+
function resolveReferencedModel(symbols, field) {
|
|
1535
|
+
const topLevel = symbols.topLevel.models[field.typeName];
|
|
1536
|
+
if (topLevel !== void 0) return topLevel;
|
|
1537
|
+
for (const namespace of Object.values(symbols.topLevel.namespaces)) {
|
|
1538
|
+
const model = namespace.models[field.typeName];
|
|
1539
|
+
if (model !== void 0) return model;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function interpretRelationAttribute(input) {
|
|
1543
|
+
const node = findFieldAttributeNode(input.field, "relation");
|
|
1544
|
+
if (node === void 0) return void 0;
|
|
1545
|
+
return interpretFieldAttribute({
|
|
1546
|
+
node,
|
|
1547
|
+
spec: sqlRelation,
|
|
1548
|
+
model: input.selfModel,
|
|
1549
|
+
field: input.field,
|
|
1550
|
+
sourceFile: input.sourceFile,
|
|
1551
|
+
sourceId: input.sourceId,
|
|
1552
|
+
diagnostics: input.diagnostics,
|
|
1553
|
+
resolveReferencedModel: () => resolveReferencedModel(input.symbols, input.field)
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
function indexFkRelations(input) {
|
|
1557
|
+
const modelRelations = /* @__PURE__ */ new Map();
|
|
1558
|
+
const fkRelationsByPair = /* @__PURE__ */ new Map();
|
|
1559
|
+
const fkRelationsByDeclaringModel = /* @__PURE__ */ new Map();
|
|
1560
|
+
for (const relation of input.fkRelationMetadata) {
|
|
1561
|
+
const declaringFkRelations = fkRelationsByDeclaringModel.get(relation.declaringModelName);
|
|
1562
|
+
if (declaringFkRelations) declaringFkRelations.push(relation);
|
|
1563
|
+
else fkRelationsByDeclaringModel.set(relation.declaringModelName, [relation]);
|
|
1564
|
+
const existing = modelRelations.get(relation.declaringModelName);
|
|
1565
|
+
const current = existing ?? [];
|
|
1566
|
+
if (!existing) modelRelations.set(relation.declaringModelName, current);
|
|
1567
|
+
current.push({
|
|
1568
|
+
fieldName: relation.declaringFieldName,
|
|
1569
|
+
toModel: relation.targetModelName,
|
|
1570
|
+
toTable: relation.targetTableName,
|
|
1571
|
+
...ifDefined("toNamespaceId", relation.targetNamespaceId),
|
|
1572
|
+
cardinality: "N:1",
|
|
1573
|
+
on: {
|
|
1574
|
+
parentTable: relation.declaringTableName,
|
|
1575
|
+
parentColumns: relation.localColumns,
|
|
1576
|
+
childTable: relation.targetTableName,
|
|
1577
|
+
childColumns: relation.referencedColumns
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
const pairKey = fkRelationPairKey(relation.declaringModelName, relation.targetModelName);
|
|
1581
|
+
const pairRelations = fkRelationsByPair.get(pairKey);
|
|
1582
|
+
if (!pairRelations) {
|
|
1583
|
+
fkRelationsByPair.set(pairKey, [relation]);
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
pairRelations.push(relation);
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
modelRelations,
|
|
1590
|
+
fkRelationsByPair,
|
|
1591
|
+
fkRelationsByDeclaringModel
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
function idColumnsAreExactlyFkPair(idColumns, parentColumns, childColumns) {
|
|
1595
|
+
if (idColumns.length !== parentColumns.length + childColumns.length) return false;
|
|
1596
|
+
const fkColumns = /* @__PURE__ */ new Set([...parentColumns, ...childColumns]);
|
|
1597
|
+
if (fkColumns.size !== parentColumns.length + childColumns.length) return false;
|
|
1598
|
+
return idColumns.every((column) => fkColumns.has(column));
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Reorders the child FK's junction columns into the target model's id-column
|
|
1602
|
+
* order. Returns undefined unless the FK references exactly the target's full
|
|
1603
|
+
* id, because downstream consumers pair `through.childColumns` positionally
|
|
1604
|
+
* against the target id columns — an FK referencing anything else (a non-id
|
|
1605
|
+
* unique, a partial id) would produce a silently wrong join.
|
|
1606
|
+
*/
|
|
1607
|
+
function childColumnsInTargetIdOrder(childFk, targetIdColumns) {
|
|
1608
|
+
if (childFk.referencedColumns.length !== targetIdColumns.length) return;
|
|
1609
|
+
const localByReferenced = /* @__PURE__ */ new Map();
|
|
1610
|
+
for (const [index, referencedColumn] of childFk.referencedColumns.entries()) {
|
|
1611
|
+
const localColumn = childFk.localColumns[index];
|
|
1612
|
+
if (localColumn === void 0) return;
|
|
1613
|
+
localByReferenced.set(referencedColumn, localColumn);
|
|
1614
|
+
}
|
|
1615
|
+
if (localByReferenced.size !== targetIdColumns.length) return;
|
|
1616
|
+
const ordered = [];
|
|
1617
|
+
for (const idColumn of targetIdColumns) {
|
|
1618
|
+
const localColumn = localByReferenced.get(idColumn);
|
|
1619
|
+
if (localColumn === void 0) return;
|
|
1620
|
+
ordered.push(localColumn);
|
|
1621
|
+
}
|
|
1622
|
+
return ordered;
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Finds explicit junction models that connect a bare backrelation list field
|
|
1626
|
+
* to its target model: a model whose composite id columns are exactly the FK
|
|
1627
|
+
* columns of one relation back to the candidate's model (the parent side) and
|
|
1628
|
+
* one relation to the candidate's target model (the child side). The child
|
|
1629
|
+
* FK must reference exactly the target model's id columns; its junction
|
|
1630
|
+
* columns are carried in target-id order on the pair. A relation name on the
|
|
1631
|
+
* list field pins the parent-side FK relation, which is how self-referential
|
|
1632
|
+
* many-to-many sides are disambiguated.
|
|
1633
|
+
*
|
|
1634
|
+
* Alongside the recognised pairs, returns junction-shaped near-misses (models
|
|
1635
|
+
* that link both sides but were declined) so the caller can emit a
|
|
1636
|
+
* junction-specific diagnostic instead of the generic orphaned-list message.
|
|
1637
|
+
*/
|
|
1638
|
+
function findJunctionFkPairs(input) {
|
|
1639
|
+
const targetIdColumns = input.modelIdColumns.get(input.candidate.targetModelName);
|
|
1640
|
+
if (!targetIdColumns || targetIdColumns.length === 0) return {
|
|
1641
|
+
pairs: [],
|
|
1642
|
+
nearMisses: []
|
|
1643
|
+
};
|
|
1644
|
+
const pairs = [];
|
|
1645
|
+
const nearMisses = [];
|
|
1646
|
+
for (const [junctionModelName, junctionFks] of input.fkRelationsByDeclaringModel) {
|
|
1647
|
+
const idColumns = input.modelIdColumns.get(junctionModelName);
|
|
1648
|
+
for (const parentFk of junctionFks) {
|
|
1649
|
+
if (parentFk.targetModelName !== input.candidate.modelName) continue;
|
|
1650
|
+
if (input.candidate.relationName !== void 0 && parentFk.relationName !== input.candidate.relationName) continue;
|
|
1651
|
+
for (const childFk of junctionFks) {
|
|
1652
|
+
if (childFk === parentFk || childFk.targetModelName !== input.candidate.targetModelName) continue;
|
|
1653
|
+
if (!idColumns || !idColumnsAreExactlyFkPair(idColumns, parentFk.localColumns, childFk.localColumns)) {
|
|
1654
|
+
nearMisses.push({
|
|
1655
|
+
junctionModelName,
|
|
1656
|
+
reason: "id-not-fk-covering"
|
|
1657
|
+
});
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
const orderedChildColumns = childColumnsInTargetIdOrder(childFk, targetIdColumns);
|
|
1661
|
+
if (!orderedChildColumns) {
|
|
1662
|
+
nearMisses.push({
|
|
1663
|
+
junctionModelName,
|
|
1664
|
+
reason: "target-fk-not-id"
|
|
1665
|
+
});
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
pairs.push({
|
|
1669
|
+
parentFk,
|
|
1670
|
+
childFk,
|
|
1671
|
+
childColumnsInTargetIdOrder: orderedChildColumns
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return {
|
|
1677
|
+
pairs,
|
|
1678
|
+
nearMisses
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
function junctionNearMissDiagnostic(candidate, nearMiss, sourceId) {
|
|
1682
|
+
const listField = `${candidate.modelName}.${candidate.field.name}`;
|
|
1683
|
+
const data = {
|
|
1684
|
+
listField,
|
|
1685
|
+
junctionModel: nearMiss.junctionModelName,
|
|
1686
|
+
targetModel: candidate.targetModelName
|
|
1687
|
+
};
|
|
1688
|
+
if (nearMiss.reason === "target-fk-not-id") return {
|
|
1689
|
+
code: "PSL_JUNCTION_TARGET_FK_NOT_ID",
|
|
1690
|
+
message: `Backrelation list field "${listField}" found junction model "${nearMiss.junctionModelName}", but its foreign key to "${candidate.targetModelName}" does not reference "${candidate.targetModelName}"'s @id. The junction's target-side foreign key must reference "${candidate.targetModelName}"'s full @id columns for many-to-many recognition.`,
|
|
1691
|
+
sourceId,
|
|
1692
|
+
span: candidate.field.span,
|
|
1693
|
+
data
|
|
1694
|
+
};
|
|
1695
|
+
return {
|
|
1696
|
+
code: "PSL_JUNCTION_ID_NOT_FK_COVERING",
|
|
1697
|
+
message: `Backrelation list field "${listField}" found junction-shaped model "${nearMiss.junctionModelName}" linking "${candidate.modelName}" and "${candidate.targetModelName}", but its id does not cover exactly its foreign-key columns. Declare @@id([...]) on "${nearMiss.junctionModelName}" listing exactly the two foreign-key columns for many-to-many recognition.`,
|
|
1698
|
+
sourceId,
|
|
1699
|
+
span: candidate.field.span,
|
|
1700
|
+
data
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
function manyToManyRelationNode(candidate, pair) {
|
|
1704
|
+
return {
|
|
1705
|
+
fieldName: candidate.field.name,
|
|
1706
|
+
toModel: pair.childFk.targetModelName,
|
|
1707
|
+
toTable: pair.childFk.targetTableName,
|
|
1708
|
+
...ifDefined("toNamespaceId", pair.childFk.targetNamespaceId),
|
|
1709
|
+
cardinality: "N:M",
|
|
1710
|
+
on: {
|
|
1711
|
+
parentTable: candidate.tableName,
|
|
1712
|
+
parentColumns: pair.parentFk.referencedColumns,
|
|
1713
|
+
childTable: pair.parentFk.declaringTableName,
|
|
1714
|
+
childColumns: pair.parentFk.localColumns
|
|
1715
|
+
},
|
|
1716
|
+
through: {
|
|
1717
|
+
table: pair.parentFk.declaringTableName,
|
|
1718
|
+
...ifDefined("namespaceId", pair.parentFk.declaringNamespaceId),
|
|
1719
|
+
parentColumns: pair.parentFk.localColumns,
|
|
1720
|
+
childColumns: pair.childColumnsInTargetIdOrder
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function relationsForModel(modelRelations, modelName) {
|
|
1725
|
+
const existing = modelRelations.get(modelName);
|
|
1726
|
+
if (existing) return existing;
|
|
1727
|
+
const created = [];
|
|
1728
|
+
modelRelations.set(modelName, created);
|
|
1729
|
+
return created;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* A set of columns is unique when it exactly matches one of the model's unique
|
|
1733
|
+
* column sets — its primary key or any single- or multi-column `@unique` /
|
|
1734
|
+
* `@@unique` constraint. Set equality (not subset) is required: a singular
|
|
1735
|
+
* back-relation means at most one child per parent, which a unique constraint
|
|
1736
|
+
* covering exactly the FK columns guarantees.
|
|
1737
|
+
*/
|
|
1738
|
+
function fkColumnsAreUnique(localColumns, uniqueColumnSets) {
|
|
1739
|
+
const local = new Set(localColumns);
|
|
1740
|
+
return uniqueColumnSets.some((columns) => columns.length === local.size && columns.every((column) => local.has(column)));
|
|
1741
|
+
}
|
|
1742
|
+
function applyBackrelationCandidates(input) {
|
|
1743
|
+
for (const candidate of input.backrelationCandidates) {
|
|
1744
|
+
const pairKey = fkRelationPairKey(candidate.targetModelName, candidate.modelName);
|
|
1745
|
+
const pairMatches = input.fkRelationsByPair.get(pairKey) ?? [];
|
|
1746
|
+
const matches = candidate.relationName ? pairMatches.filter((relation) => relation.relationName === candidate.relationName) : [...pairMatches];
|
|
1747
|
+
if (matches.length === 0) {
|
|
1748
|
+
if (candidate.isList) {
|
|
1749
|
+
const { pairs: junctionPairs, nearMisses } = findJunctionFkPairs({
|
|
1750
|
+
candidate,
|
|
1751
|
+
fkRelationsByDeclaringModel: input.fkRelationsByDeclaringModel,
|
|
1752
|
+
modelIdColumns: input.modelIdColumns
|
|
1753
|
+
});
|
|
1754
|
+
const junctionPair = junctionPairs[0];
|
|
1755
|
+
if (junctionPairs.length === 1 && junctionPair) {
|
|
1756
|
+
relationsForModel(input.modelRelations, candidate.modelName).push(manyToManyRelationNode(candidate, junctionPair));
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (junctionPairs.length > 1) {
|
|
1760
|
+
input.diagnostics.push({
|
|
1761
|
+
code: "PSL_AMBIGUOUS_BACKRELATION",
|
|
1762
|
+
message: `Backrelation list field "${candidate.modelName}.${candidate.field.name}" matches multiple junction FK pairs for a many-to-many relation. Add @relation(name: "...") (or @relation("...")) to the list field and the junction FK-side relation pointing back at "${candidate.modelName}" to disambiguate.`,
|
|
1763
|
+
sourceId: input.sourceId,
|
|
1764
|
+
span: candidate.field.span
|
|
1765
|
+
});
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
const nearMiss = nearMisses[0];
|
|
1769
|
+
if (nearMiss) {
|
|
1770
|
+
input.diagnostics.push(junctionNearMissDiagnostic(candidate, nearMiss, input.sourceId));
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
input.diagnostics.push({
|
|
1775
|
+
code: "PSL_ORPHANED_BACKRELATION",
|
|
1776
|
+
message: `Backrelation field "${candidate.modelName}.${candidate.field.name}" has no matching FK-side relation on model "${candidate.targetModelName}". Add @relation(fields: [...], references: [...]) on the FK-side relation${candidate.isList ? " or use an explicit join model for many-to-many" : ""}.`,
|
|
1777
|
+
sourceId: input.sourceId,
|
|
1778
|
+
span: candidate.field.span
|
|
1779
|
+
});
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
if (matches.length > 1) {
|
|
1783
|
+
input.diagnostics.push({
|
|
1784
|
+
code: "PSL_AMBIGUOUS_BACKRELATION",
|
|
1785
|
+
message: `Backrelation field "${candidate.modelName}.${candidate.field.name}" matches multiple FK-side relations on model "${candidate.targetModelName}". Add @relation(name: "...") (or @relation("...")) to both sides to disambiguate.`,
|
|
1786
|
+
sourceId: input.sourceId,
|
|
1787
|
+
span: candidate.field.span
|
|
1788
|
+
});
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
invariant(matches.length === 1, "Backrelation matching requires exactly one match");
|
|
1792
|
+
const matched = matches[0];
|
|
1793
|
+
assertDefined(matched, "Backrelation matching requires a defined relation match");
|
|
1794
|
+
if (!candidate.isList) {
|
|
1795
|
+
const uniqueColumnSets = input.modelUniqueColumnSets.get(matched.declaringModelName) ?? [];
|
|
1796
|
+
if (!fkColumnsAreUnique(matched.localColumns, uniqueColumnSets)) {
|
|
1797
|
+
input.diagnostics.push({
|
|
1798
|
+
code: "PSL_NON_UNIQUE_BACKRELATION",
|
|
1799
|
+
message: `Backrelation field "${candidate.modelName}.${candidate.field.name}" is singular, but the matching FK on "${matched.declaringModelName}" (fields ${matched.localColumns.map((column) => `"${column}"`).join(", ")}) is not unique. A singular back-relation implies at most one related row; add @unique (or @@unique([...])) to the FK fields, or make "${candidate.field.name}" a list.`,
|
|
1800
|
+
sourceId: input.sourceId,
|
|
1801
|
+
span: candidate.field.span
|
|
1802
|
+
});
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
relationsForModel(input.modelRelations, candidate.modelName).push({
|
|
1807
|
+
fieldName: candidate.field.name,
|
|
1808
|
+
toModel: matched.declaringModelName,
|
|
1809
|
+
toTable: matched.declaringTableName,
|
|
1810
|
+
...ifDefined("toNamespaceId", matched.declaringNamespaceId),
|
|
1811
|
+
cardinality: candidate.isList ? "1:N" : "1:1",
|
|
1812
|
+
on: {
|
|
1813
|
+
parentTable: candidate.tableName,
|
|
1814
|
+
parentColumns: matched.referencedColumns,
|
|
1815
|
+
childTable: matched.declaringTableName,
|
|
1816
|
+
childColumns: matched.localColumns
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function validateBackrelationFieldAttributes(input) {
|
|
1822
|
+
let valid = true;
|
|
1823
|
+
for (const attribute of input.field.attributes) {
|
|
1824
|
+
if (attribute.name === "relation") continue;
|
|
1825
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1826
|
+
familyId: input.familyId,
|
|
1827
|
+
targetId: input.targetId,
|
|
1828
|
+
authoringContributions: input.authoringContributions
|
|
1829
|
+
});
|
|
1830
|
+
if (uncomposedNamespace) {
|
|
1831
|
+
reportUncomposedNamespace({
|
|
1832
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1833
|
+
namespace: uncomposedNamespace,
|
|
1834
|
+
sourceId: input.sourceId,
|
|
1835
|
+
span: attribute.span,
|
|
1836
|
+
diagnostics: input.diagnostics
|
|
1837
|
+
});
|
|
1838
|
+
valid = false;
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
input.diagnostics.push({
|
|
1842
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1843
|
+
message: `Field "${input.modelName}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
1844
|
+
sourceId: input.sourceId,
|
|
1845
|
+
span: attribute.span
|
|
1846
|
+
});
|
|
1847
|
+
valid = false;
|
|
1848
|
+
}
|
|
1849
|
+
return valid;
|
|
1850
|
+
}
|
|
1851
|
+
function buildComposedExtensionPackRefs(target, extensionIds, extensionPackRefs = []) {
|
|
1852
|
+
if (extensionIds.length === 0) return;
|
|
1853
|
+
const extensionPackRefById = new Map(extensionPackRefs.map((packRef) => [packRef.id, packRef]));
|
|
1854
|
+
return Object.fromEntries(extensionIds.map((extensionId) => [extensionId, extensionPackRefById.get(extensionId) ?? {
|
|
1855
|
+
kind: "extension",
|
|
1856
|
+
id: extensionId,
|
|
1857
|
+
familyId: target.familyId,
|
|
1858
|
+
targetId: target.targetId,
|
|
1859
|
+
version: "0.0.1"
|
|
1860
|
+
}]));
|
|
1861
|
+
}
|
|
1862
|
+
function compareStrings(left, right) {
|
|
1863
|
+
if (left < right) return -1;
|
|
1864
|
+
if (left > right) return 1;
|
|
1865
|
+
return 0;
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Name of the framework-parser synthesised bucket for top-level
|
|
1869
|
+
* declarations. Re-declared here so the per-target dispatch does not
|
|
1870
|
+
* have to import from `@internal/framework-components/psl-ast`
|
|
1871
|
+
* (which would cross a layer that the interpreter does not otherwise
|
|
1872
|
+
* import from). The value is part of the framework parser's contract;
|
|
1873
|
+
* if it changes there, the matching test in this package's
|
|
1874
|
+
* `interpreter.diagnostics.test.ts` flips first.
|
|
1875
|
+
*/
|
|
1876
|
+
const UNSPECIFIED_PSL_NAMESPACE_NAME = "__unspecified__";
|
|
1877
|
+
/**
|
|
1878
|
+
* Per-target namespace-block validation: walk the AST's namespace buckets and
|
|
1879
|
+
* emit diagnostics for syntactic constructs the target does not accept.
|
|
1880
|
+
*
|
|
1881
|
+
* - **SQLite** has no schema concept and rejects every explicit
|
|
1882
|
+
* `namespace { … }` block. The implicit `__unspecified__` bucket
|
|
1883
|
+
* (produced by the parser for top-level declarations outside any
|
|
1884
|
+
* block) is the only namespace SQLite accepts.
|
|
1885
|
+
* - **Postgres** accepts every explicit block — `namespace unbound { … }`
|
|
1886
|
+
* is the late-binding opt-in (lowers to the IR `__unbound__` slot in
|
|
1887
|
+
* a follow-on commit), `namespace public { … }` reopen-merges with
|
|
1888
|
+
* the implicit bucket, and any other name lowers to a named schema.
|
|
1889
|
+
*
|
|
1890
|
+
* Storage-side lowering of these buckets to IR namespace slots is not
|
|
1891
|
+
* yet wired; this helper closes only the diagnostic surface.
|
|
1892
|
+
*/
|
|
1893
|
+
/**
|
|
1894
|
+
* Per-target namespace lowering: map a PSL AST namespace bucket name to the
|
|
1895
|
+
* resolved IR namespace id (the key downstream consumers use against
|
|
1896
|
+
* `SqlStorage.namespaces`).
|
|
1897
|
+
*
|
|
1898
|
+
* - **Postgres**: an explicit `namespace unbound { … }` block lowers
|
|
1899
|
+
* to the framework sentinel `__unbound__` — the slot whose binding
|
|
1900
|
+
* the connection's `search_path` resolves at runtime. Every other
|
|
1901
|
+
* explicit bucket name (e.g. `auth`, `public`) passes through as a
|
|
1902
|
+
* named schema id. The implicit `__unspecified__` bucket — top-level
|
|
1903
|
+
* declarations outside any `namespace { … }` block — leaves the
|
|
1904
|
+
* coordinate unset; downstream consumers treat unset as the
|
|
1905
|
+
* late-bound default, and TS / PSL authoring stay byte-identical
|
|
1906
|
+
* on single-namespace contracts. (A future round will add a
|
|
1907
|
+
* target-default-namespace surface so `__unspecified__` lowers to
|
|
1908
|
+
* `public` consistently on both authoring paths.)
|
|
1909
|
+
* - **SQLite**: SQLite has no schema concept; every namespace
|
|
1910
|
+
* collapses to the late-bound default. The namespace-block
|
|
1911
|
+
* validation step (above) has already rejected any explicit
|
|
1912
|
+
* `namespace { … }` block on SQLite, so the only bucket the
|
|
1913
|
+
* lowering ever sees there is `__unspecified__`.
|
|
1914
|
+
*
|
|
1915
|
+
* Returns `undefined` for targets / bucket names with no explicit
|
|
1916
|
+
* namespaceId to assign — callers leave the model's `namespaceId`
|
|
1917
|
+
* slot empty (which means the late-bound default at the `StorageTable`
|
|
1918
|
+
* layer; emitted JSON omits the field).
|
|
1919
|
+
*/
|
|
1920
|
+
function resolveNamespaceIdForSqlTarget(input) {
|
|
1921
|
+
if (input.targetId !== "postgres") return;
|
|
1922
|
+
if (input.bucketName === UNSPECIFIED_PSL_NAMESPACE_NAME) return "public";
|
|
1923
|
+
if (input.bucketName === "unbound") return UNBOUND_NAMESPACE_ID;
|
|
1924
|
+
return input.bucketName;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* A namespace block is the unbound block when its name RESOLVES to the
|
|
1928
|
+
* unbound id — both the `namespace unbound { }` spelling (mapped by
|
|
1929
|
+
* {@link resolveNamespaceIdForSqlTarget}) and the raw sentinel spelling
|
|
1930
|
+
* `namespace __unbound__ { }` (passed through verbatim) behave identically:
|
|
1931
|
+
* same models-sibling restriction, same block-lowering bucket.
|
|
1932
|
+
*/
|
|
1933
|
+
function isUnboundNamespaceBlock(ns, targetId) {
|
|
1934
|
+
return resolveNamespaceIdForSqlTarget({
|
|
1935
|
+
bucketName: ns.name,
|
|
1936
|
+
targetId
|
|
1937
|
+
}) === UNBOUND_NAMESPACE_ID;
|
|
1938
|
+
}
|
|
1939
|
+
function validateNamespaceBlocksForSqlTarget(input) {
|
|
1940
|
+
if (input.targetId === "sqlite") {
|
|
1941
|
+
for (const namespace of input.namespaces) input.diagnostics.push({
|
|
1942
|
+
code: "PSL_UNSUPPORTED_NAMESPACE_BLOCK",
|
|
1943
|
+
message: `SQLite does not support \`namespace ${namespace.name} { … }\` blocks (SQLite has no schema concept; declare models at the document top level instead).`,
|
|
1944
|
+
sourceId: input.sourceId,
|
|
1945
|
+
span: nodePslSpan(namespace.node.syntax, input.sourceFile)
|
|
1946
|
+
});
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (input.targetId === "postgres") {
|
|
1950
|
+
const unboundBlock = input.namespaces.find((ns) => isUnboundNamespaceBlock(ns, input.targetId) && Object.keys(ns.models).length > 0);
|
|
1951
|
+
const hasSibling = input.namespaces.some((ns) => !isUnboundNamespaceBlock(ns, input.targetId));
|
|
1952
|
+
if (unboundBlock !== void 0 && hasSibling) input.diagnostics.push({
|
|
1953
|
+
code: "PSL_RESERVED_NAMESPACE_NAME",
|
|
1954
|
+
message: "Namespace \"unbound\" is reserved for the late-binding sentinel mapping; a `namespace unbound { … }` containing models cannot appear alongside other named namespace blocks. Use `namespace unbound { … }` alone (no sibling named namespaces) for late-binding multi-tenant contracts.",
|
|
1955
|
+
sourceId: input.sourceId,
|
|
1956
|
+
span: nodePslSpan(unboundBlock.node.syntax, input.sourceFile)
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Walks the flat `entityTypes` namespace tree in `authoringContributions` and
|
|
1962
|
+
* returns a map from discriminator string to the matching descriptor. The
|
|
1963
|
+
* interpreter uses this to dispatch parsed extension blocks to their factory
|
|
1964
|
+
* without naming any specific discriminator value (generic, by-discriminator).
|
|
1965
|
+
*/
|
|
1966
|
+
function buildEntityTypesByDiscriminator(contributions) {
|
|
1967
|
+
const result = /* @__PURE__ */ new Map();
|
|
1968
|
+
const namespace = contributions?.entityTypes;
|
|
1969
|
+
if (namespace === void 0) return result;
|
|
1970
|
+
const walk = (node) => {
|
|
1971
|
+
for (const value of Object.values(node)) if (isAuthoringEntityTypeDescriptor(value)) result.set(value.discriminator, value);
|
|
1972
|
+
else if (typeof value === "object" && value !== null) walk(value);
|
|
1973
|
+
};
|
|
1974
|
+
walk(namespace);
|
|
1975
|
+
return result;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* The `PSL_DUPLICATE_ATTRIBUTE` diagnostic for a model attribute declared
|
|
1979
|
+
* more than once on one model. Shared by the built-in `@@control` path and
|
|
1980
|
+
* the contributed-model-attribute path so the code and wording stay in one
|
|
1981
|
+
* place. `name` is the bare attribute name (`control`, `rls`, …).
|
|
1982
|
+
*/
|
|
1983
|
+
function duplicateModelAttributeDiagnostic(input) {
|
|
1984
|
+
return {
|
|
1985
|
+
code: "PSL_DUPLICATE_ATTRIBUTE",
|
|
1986
|
+
message: `\`@@${input.name}\` declared more than once on model "${input.modelName}".`,
|
|
1987
|
+
sourceId: input.sourceId,
|
|
1988
|
+
...ifDefined("span", input.span)
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Enforces `AuthoringPslBlockDescriptor.requiresModelAttribute` over one
|
|
1993
|
+
* scope (the top level or one namespace): every block whose descriptor
|
|
1994
|
+
* declares the requirement must name a model that carries the required
|
|
1995
|
+
* bare `@@` attribute. Runs on the parsed symbol table, so it is
|
|
1996
|
+
* independent of block/model declaration order and of lowering order. A
|
|
1997
|
+
* missing parameter or an unresolvable model is skipped — the
|
|
1998
|
+
* missing-required-parameter and unresolved-ref diagnostics own those
|
|
1999
|
+
* failure modes.
|
|
2000
|
+
*/
|
|
2001
|
+
function validateBlockModelAttributeRequirements(input) {
|
|
2002
|
+
for (const scope of input.scopes) for (const blockSymbol of Object.values(scope.blocks)) {
|
|
2003
|
+
const requirement = findBlockDescriptor(input.pslBlockDescriptors, blockSymbol.keyword)?.requiresModelAttribute;
|
|
2004
|
+
if (requirement === void 0) continue;
|
|
2005
|
+
const captured = blockSymbol.block.parameters[requirement.parameter];
|
|
2006
|
+
if (captured?.kind !== "ref") continue;
|
|
2007
|
+
const model = scope.models[captured.identifier];
|
|
2008
|
+
if (model === void 0) continue;
|
|
2009
|
+
if (model.attributes.some((attribute) => attribute.name === requirement.attribute)) continue;
|
|
2010
|
+
input.diagnostics.push({
|
|
2011
|
+
code: "PSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE",
|
|
2012
|
+
message: `\`${blockSymbol.keyword}\` block "${blockSymbol.block.name}" targets model "${captured.identifier}", which does not declare \`@@${requirement.attribute}\`. Add \`@@${requirement.attribute}\` to model "${captured.identifier}".`,
|
|
2013
|
+
sourceId: input.sourceId,
|
|
2014
|
+
span: captured.span
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Walks the flat `modelAttributes` namespace tree in `authoringContributions`
|
|
2020
|
+
* and returns a map from bare `@@` attribute name to the matching
|
|
2021
|
+
* descriptor. The model-attribute loop in `buildModelNodeFromPsl` uses this
|
|
2022
|
+
* to dispatch a contributed attribute without naming any specific attribute.
|
|
2023
|
+
*/
|
|
2024
|
+
function buildModelAttributesByName(contributions) {
|
|
2025
|
+
const result = /* @__PURE__ */ new Map();
|
|
2026
|
+
const namespace = contributions?.modelAttributes;
|
|
2027
|
+
if (namespace === void 0) return result;
|
|
2028
|
+
const walk = (node) => {
|
|
2029
|
+
for (const value of Object.values(node)) if (isAuthoringModelAttributeDescriptor(value)) result.set(value.attribute, value);
|
|
2030
|
+
else if (typeof value === "object" && value !== null) walk(value);
|
|
2031
|
+
};
|
|
2032
|
+
walk(namespace);
|
|
2033
|
+
return result;
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* For a single lexical scope (a named PSL namespace, or the document top
|
|
2037
|
+
* level), lowers all extension blocks into IR entities via the registered
|
|
2038
|
+
* factory for each block's discriminator. Groups results by discriminator
|
|
2039
|
+
* (the entries key — one-string rule: discriminator === entries key).
|
|
2040
|
+
*
|
|
2041
|
+
* This pass is intentionally generic: no discriminator value is named here.
|
|
2042
|
+
* The factory (registered by the target pack) owns all block-specific logic.
|
|
2043
|
+
* A descriptor's factory output may also opt into value-set derivation via
|
|
2044
|
+
* the SQL family's `SqlValueSetDerivingEntityTypeOutput.deriveValueSet` hook
|
|
2045
|
+
* (probed by {@link deriveValueSetFromEntity}) — when present, the derived
|
|
2046
|
+
* value-set is folded into the namespace's `valueSet` slot (keyed by block
|
|
2047
|
+
* name), so a value-set-carrying pack entity (e.g. Postgres `native_enum`)
|
|
2048
|
+
* contributes the value-set that drives value-set → codec typing without
|
|
2049
|
+
* this pass inspecting a target-specific shape.
|
|
2050
|
+
*
|
|
2051
|
+
* The `namespaceId` is attached to the block before the factory call so the
|
|
2052
|
+
* factory can record the namespace coordinate without the interpreter
|
|
2053
|
+
* containing any target-specific knowledge about how namespace ids are used.
|
|
2054
|
+
*
|
|
2055
|
+
* Ref conversion is this pass's job, over typed data: each block-descriptor
|
|
2056
|
+
* parameter declared `{ kind: 'ref', refKind: 'model' }` (same-namespace
|
|
2057
|
+
* scope) is resolved to the referenced model's storage table name and
|
|
2058
|
+
* attached to the block as `resolvedModelRefs` ({@link ResolvedPslModelRefs})
|
|
2059
|
+
* before the factory runs — the factory consumes a resolved coordinate and
|
|
2060
|
+
* never looks a model up itself. A required model ref that is missing or
|
|
2061
|
+
* does not resolve is this pass's diagnostic; the factory is skipped.
|
|
2062
|
+
*/
|
|
2063
|
+
function lowerExtensionBlocksForNamespace(blocks, nsId, entityTypesByDiscriminator, entityContext, pslBlockDescriptors, resolveModelTable) {
|
|
2064
|
+
const blockSymbols = Object.values(blocks);
|
|
2065
|
+
if (blockSymbols.length === 0) return {};
|
|
2066
|
+
const result = {};
|
|
2067
|
+
for (const blockSymbol of blockSymbols) {
|
|
2068
|
+
const block = blockSymbol.block;
|
|
2069
|
+
const descriptor = entityTypesByDiscriminator.get(block.kind);
|
|
2070
|
+
if (descriptor === void 0) continue;
|
|
2071
|
+
const blockDescriptor = findBlockDescriptor(pslBlockDescriptors, block.keyword);
|
|
2072
|
+
let unresolvedRef = false;
|
|
2073
|
+
let resolvedModelRefs;
|
|
2074
|
+
for (const [paramName, paramDecl] of Object.entries(blockDescriptor?.parameters ?? {})) {
|
|
2075
|
+
if (paramDecl.kind !== "ref" || paramDecl.refKind !== "model" || paramDecl.scope !== "same-namespace") continue;
|
|
2076
|
+
const captured = block.parameters[paramName];
|
|
2077
|
+
if (captured?.kind !== "ref") {
|
|
2078
|
+
if (paramDecl.required === true) {
|
|
2079
|
+
entityContext.diagnostics?.push({
|
|
2080
|
+
code: "PSL_EXTENSION_MODEL_REF_UNRESOLVED",
|
|
2081
|
+
message: `\`${block.keyword}\` block "${block.name}" is missing the required \`${paramName}\` model reference.`,
|
|
2082
|
+
sourceId: entityContext.sourceId ?? "unknown",
|
|
2083
|
+
span: block.span
|
|
2084
|
+
});
|
|
2085
|
+
unresolvedRef = true;
|
|
2086
|
+
}
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
const tableName = resolveModelTable(captured.identifier);
|
|
2090
|
+
if (tableName === void 0) {
|
|
2091
|
+
entityContext.diagnostics?.push({
|
|
2092
|
+
code: "PSL_EXTENSION_MODEL_REF_UNRESOLVED",
|
|
2093
|
+
message: `\`${block.keyword}\` block "${block.name}" references model "${captured.identifier}" in \`${paramName}\`, which is not declared in the same namespace. Declare the model or fix the reference.`,
|
|
2094
|
+
sourceId: entityContext.sourceId ?? "unknown",
|
|
2095
|
+
span: captured.span
|
|
2096
|
+
});
|
|
2097
|
+
unresolvedRef = true;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
resolvedModelRefs = {
|
|
2101
|
+
...resolvedModelRefs ?? {},
|
|
2102
|
+
[paramName]: { tableName }
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
if (unresolvedRef) continue;
|
|
2106
|
+
const annotatedBlock = {
|
|
2107
|
+
...block,
|
|
2108
|
+
namespaceId: nsId,
|
|
2109
|
+
...resolvedModelRefs !== void 0 ? { resolvedModelRefs } : {}
|
|
2110
|
+
};
|
|
2111
|
+
const entity = instantiateAuthoringEntityType(descriptor.discriminator, descriptor, [annotatedBlock], entityContext);
|
|
2112
|
+
if (entity === void 0) continue;
|
|
2113
|
+
const entriesKey = descriptor.discriminator;
|
|
2114
|
+
const slot = result[entriesKey] ?? {};
|
|
2115
|
+
result[entriesKey] = slot;
|
|
2116
|
+
slot[block.name] = entity;
|
|
2117
|
+
const derivedValueSet = deriveValueSetFromEntity(descriptor.output, entity);
|
|
2118
|
+
if (derivedValueSet !== void 0) {
|
|
2119
|
+
const valueSetSlot = result["valueSet"] ?? {};
|
|
2120
|
+
result["valueSet"] = valueSetSlot;
|
|
2121
|
+
valueSetSlot[block.name] = derivedValueSet;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return result;
|
|
2125
|
+
}
|
|
2126
|
+
function processEnumDeclarations(input) {
|
|
2127
|
+
const enumHandles = {};
|
|
2128
|
+
const enumTypeDescriptors = /* @__PURE__ */ new Map();
|
|
2129
|
+
if (input.enumBlocks.length === 0) return {
|
|
2130
|
+
enumHandles,
|
|
2131
|
+
enumTypeDescriptors
|
|
2132
|
+
};
|
|
2133
|
+
const enumDescriptor = getAuthoringEntity(input.authoringContributions, ["enum"]);
|
|
2134
|
+
if (!enumDescriptor) {
|
|
2135
|
+
for (const decl of input.enumBlocks) input.diagnostics.push({
|
|
2136
|
+
code: "PSL_ENUM_MISSING_FACTORY",
|
|
2137
|
+
message: `enum "${decl.name}" requires an "enum" entityType factory in the active authoring contributions`,
|
|
2138
|
+
sourceId: input.sourceId,
|
|
2139
|
+
span: decl.span
|
|
2140
|
+
});
|
|
2141
|
+
return {
|
|
2142
|
+
enumHandles,
|
|
2143
|
+
enumTypeDescriptors
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
for (const decl of input.enumBlocks) {
|
|
2147
|
+
const handle = instantiateAuthoringEntityType("enum", enumDescriptor, [decl], input.entityContext);
|
|
2148
|
+
if (handle === void 0 || handle === null) continue;
|
|
2149
|
+
enumHandles[decl.name] = handle;
|
|
2150
|
+
enumTypeDescriptors.set(decl.name, {
|
|
2151
|
+
codecId: handle.codecId,
|
|
2152
|
+
nativeType: handle.nativeType
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
return {
|
|
2156
|
+
enumHandles,
|
|
2157
|
+
enumTypeDescriptors
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
/** Generic top-level blocks are supported only when a composed descriptor claims their keyword. */
|
|
2161
|
+
function composedBlockKeywords(authoringContributions) {
|
|
2162
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
2163
|
+
const descriptors = authoringContributions?.pslBlockDescriptors ?? {};
|
|
2164
|
+
for (const [keyword, value] of Object.entries(descriptors)) if (isAuthoringPslBlockDescriptor(value)) keywords.add(keyword);
|
|
2165
|
+
return keywords;
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* The owning side of a relation is the one that declares `fields`/`references` on its
|
|
2169
|
+
* `@relation` attribute — those name the FK columns. A singular model-typed field whose
|
|
2170
|
+
* `@relation` carries only a name (or nothing at all) is the back side: infer prints exactly
|
|
2171
|
+
* that shape for a 1:1 back-relation whenever the FK needs disambiguating (two FKs between the
|
|
2172
|
+
* same table pair, or a self-referencing unique FK). Checking for the attribute's mere presence
|
|
2173
|
+
* would misclassify that back side as the owning side.
|
|
2174
|
+
*/
|
|
2175
|
+
function relationAttributeDeclaresOwningSide(relationAttribute) {
|
|
2176
|
+
return getNamedArgument(relationAttribute, "fields") !== void 0 || getNamedArgument(relationAttribute, "references") !== void 0;
|
|
2177
|
+
}
|
|
2178
|
+
function buildModelNodeFromPsl(input) {
|
|
2179
|
+
const { model, mapping, sourceId, diagnostics } = input;
|
|
2180
|
+
const tableName = mapping.tableName;
|
|
2181
|
+
const modelNamespaceId = input.modelNamespaceIds.get(model.name);
|
|
2182
|
+
const namespaceExtensionEntitiesForModel = modelNamespaceId !== void 0 ? input.namespaceExtensionEntities?.get(modelNamespaceId) : void 0;
|
|
2183
|
+
const resolvedFields = collectResolvedFields({
|
|
2184
|
+
model,
|
|
2185
|
+
mapping,
|
|
2186
|
+
enumTypeDescriptors: input.enumTypeDescriptors,
|
|
2187
|
+
namedTypeDescriptors: input.namedTypeDescriptors,
|
|
2188
|
+
modelNames: input.modelNames,
|
|
2189
|
+
compositeTypeNames: input.compositeTypeNames,
|
|
2190
|
+
composedExtensions: input.composedExtensions,
|
|
2191
|
+
authoringContributions: input.authoringContributions,
|
|
2192
|
+
familyId: input.familyId,
|
|
2193
|
+
targetId: input.targetId,
|
|
2194
|
+
defaultFunctionRegistry: input.defaultFunctionRegistry,
|
|
2195
|
+
generatorDescriptorById: input.generatorDescriptorById,
|
|
2196
|
+
diagnostics,
|
|
2197
|
+
sourceId,
|
|
2198
|
+
sourceFile: input.sourceFile,
|
|
2199
|
+
scalarColumnDescriptors: input.scalarColumnDescriptors,
|
|
2200
|
+
...ifDefined("enumHandles", input.enumHandles),
|
|
2201
|
+
capabilities: input.capabilities,
|
|
2202
|
+
...ifDefined("namespaceId", modelNamespaceId),
|
|
2203
|
+
...ifDefined("namespaceExtensionEntities", namespaceExtensionEntitiesForModel),
|
|
2204
|
+
...ifDefined("codecLookup", input.codecLookup)
|
|
2205
|
+
});
|
|
2206
|
+
const inlineIdFields = resolvedFields.filter((field) => field.isId);
|
|
2207
|
+
if (inlineIdFields.length > 1) diagnostics.push({
|
|
2208
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2209
|
+
message: `Model "${model.name}" cannot declare inline @id on multiple fields; use model-level @@id([...]) for composite identity`,
|
|
2210
|
+
sourceId,
|
|
2211
|
+
span: model.span
|
|
2212
|
+
});
|
|
2213
|
+
const singleInlineIdField = inlineIdFields.length === 1 ? inlineIdFields[0] : void 0;
|
|
2214
|
+
let primaryKey = singleInlineIdField ? {
|
|
2215
|
+
columns: [singleInlineIdField.columnName],
|
|
2216
|
+
...ifDefined("name", singleInlineIdField.idName)
|
|
2217
|
+
} : void 0;
|
|
2218
|
+
const hasInlinePrimaryKey = primaryKey !== void 0;
|
|
2219
|
+
let blockPrimaryKeyDeclared = false;
|
|
2220
|
+
let controlPolicyDeclared = false;
|
|
2221
|
+
let controlPolicy;
|
|
2222
|
+
const declaredContributedModelAttributes = /* @__PURE__ */ new Set();
|
|
2223
|
+
const modelAttributeEntities = {};
|
|
2224
|
+
const resultBackrelationCandidates = [];
|
|
2225
|
+
for (const field of Object.values(model.fields)) {
|
|
2226
|
+
if (!input.modelNames.has(field.typeName)) continue;
|
|
2227
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
2228
|
+
if (!field.list && relationAttribute && relationAttributeDeclaresOwningSide(relationAttribute)) continue;
|
|
2229
|
+
const attributesValid = validateBackrelationFieldAttributes({
|
|
2230
|
+
modelName: model.name,
|
|
2231
|
+
field,
|
|
2232
|
+
sourceId,
|
|
2233
|
+
composedExtensions: input.composedExtensions,
|
|
2234
|
+
authoringContributions: input.authoringContributions,
|
|
2235
|
+
diagnostics,
|
|
2236
|
+
familyId: input.familyId,
|
|
2237
|
+
targetId: input.targetId
|
|
2238
|
+
});
|
|
2239
|
+
let relationName;
|
|
2240
|
+
if (relationAttribute) {
|
|
2241
|
+
const parsedRelation = interpretRelationAttribute({
|
|
2242
|
+
selfModel: model,
|
|
2243
|
+
field,
|
|
2244
|
+
symbols: input.symbolTable,
|
|
2245
|
+
sourceFile: input.sourceFile,
|
|
2246
|
+
sourceId,
|
|
2247
|
+
diagnostics
|
|
2248
|
+
});
|
|
2249
|
+
if (!parsedRelation) continue;
|
|
2250
|
+
if (parsedRelation.fields || parsedRelation.references) {
|
|
2251
|
+
diagnostics.push({
|
|
2252
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2253
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare fields/references; define them on the FK-side relation field`,
|
|
2254
|
+
sourceId,
|
|
2255
|
+
span: relationAttribute.span
|
|
2256
|
+
});
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
if (parsedRelation.onDelete || parsedRelation.onUpdate || parsedRelation.index !== void 0) {
|
|
2260
|
+
diagnostics.push({
|
|
2261
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2262
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare onDelete/onUpdate/index; define them on the FK-side relation field`,
|
|
2263
|
+
sourceId,
|
|
2264
|
+
span: relationAttribute.span
|
|
2265
|
+
});
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
relationName = parsedRelation.name;
|
|
2269
|
+
}
|
|
2270
|
+
if (!attributesValid) continue;
|
|
2271
|
+
resultBackrelationCandidates.push({
|
|
2272
|
+
modelName: model.name,
|
|
2273
|
+
tableName,
|
|
2274
|
+
field,
|
|
2275
|
+
targetModelName: field.typeName,
|
|
2276
|
+
isList: field.list,
|
|
2277
|
+
...ifDefined("relationName", relationName)
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
const relationAttributes = Object.values(model.fields).map((field) => ({
|
|
2281
|
+
field,
|
|
2282
|
+
relation: getAttribute(field.attributes, "relation")
|
|
2283
|
+
})).filter((entry) => Boolean(entry.relation));
|
|
2284
|
+
const uniqueConstraints = resolvedFields.filter((field) => field.isUnique).map((field) => ({
|
|
2285
|
+
columns: [field.columnName],
|
|
2286
|
+
...ifDefined("name", field.uniqueName)
|
|
2287
|
+
}));
|
|
2288
|
+
const indexNodes = [];
|
|
2289
|
+
const foreignKeyNodes = [];
|
|
2290
|
+
const modelAttributeNodes = Array.from(model.node.attributes());
|
|
2291
|
+
for (const [attributeIndex, modelAttribute] of model.attributes.entries()) {
|
|
2292
|
+
if (modelAttribute.name === "map") continue;
|
|
2293
|
+
if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
|
|
2294
|
+
if (modelAttribute.name === "control") {
|
|
2295
|
+
if (controlPolicyDeclared) {
|
|
2296
|
+
diagnostics.push(duplicateModelAttributeDiagnostic({
|
|
2297
|
+
name: "control",
|
|
2298
|
+
modelName: model.name,
|
|
2299
|
+
sourceId,
|
|
2300
|
+
span: modelAttribute.span
|
|
2301
|
+
}));
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
controlPolicyDeclared = true;
|
|
2305
|
+
const node = modelAttributeNodes[attributeIndex];
|
|
2306
|
+
if (node === void 0) continue;
|
|
2307
|
+
const parsed = interpretModelAttribute({
|
|
2308
|
+
node,
|
|
2309
|
+
spec: controlModelSpec,
|
|
2310
|
+
model,
|
|
2311
|
+
sourceFile: input.sourceFile,
|
|
2312
|
+
sourceId,
|
|
2313
|
+
diagnostics
|
|
2314
|
+
});
|
|
2315
|
+
if (parsed !== void 0) controlPolicy = parsed.policy;
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
|
|
2319
|
+
if (modelAttribute.name === "id") {
|
|
2320
|
+
if (blockPrimaryKeyDeclared) {
|
|
2321
|
+
diagnostics.push({
|
|
2322
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2323
|
+
message: `Model "${model.name}" declares @@id more than once`,
|
|
2324
|
+
sourceId,
|
|
2325
|
+
span: modelAttribute.span
|
|
2326
|
+
});
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
if (hasInlinePrimaryKey) {
|
|
2330
|
+
diagnostics.push({
|
|
2331
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2332
|
+
message: `Model "${model.name}" cannot declare both field-level @id and model-level @@id`,
|
|
2333
|
+
sourceId,
|
|
2334
|
+
span: modelAttribute.span
|
|
2335
|
+
});
|
|
2336
|
+
blockPrimaryKeyDeclared = true;
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
const node = modelAttributeNodes[attributeIndex];
|
|
2340
|
+
if (node === void 0) continue;
|
|
2341
|
+
const parsed = interpretModelAttribute({
|
|
2342
|
+
node,
|
|
2343
|
+
spec: idModelSpec,
|
|
2344
|
+
model,
|
|
2345
|
+
sourceFile: input.sourceFile,
|
|
2346
|
+
sourceId,
|
|
2347
|
+
diagnostics
|
|
2348
|
+
});
|
|
2349
|
+
if (parsed === void 0) continue;
|
|
2350
|
+
const fieldNames = parsed.fields;
|
|
2351
|
+
const nullableFieldName = fieldNames.find((name) => model.fields[name]?.optional === true);
|
|
2352
|
+
if (nullableFieldName !== void 0) {
|
|
2353
|
+
diagnostics.push({
|
|
2354
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2355
|
+
message: `${attributeLabel} cannot include optional field "${nullableFieldName}"; primary key columns must be NOT NULL`,
|
|
2356
|
+
sourceId,
|
|
2357
|
+
span: modelAttribute.span
|
|
2358
|
+
});
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
const columnNames = mapFieldNamesToColumns({
|
|
2362
|
+
modelName: model.name,
|
|
2363
|
+
fieldNames,
|
|
2364
|
+
mapping,
|
|
2365
|
+
sourceId,
|
|
2366
|
+
diagnostics,
|
|
2367
|
+
span: modelAttribute.span,
|
|
2368
|
+
entityLabel: attributeLabel
|
|
2369
|
+
});
|
|
2370
|
+
if (!columnNames) continue;
|
|
2371
|
+
primaryKey = {
|
|
2372
|
+
columns: columnNames,
|
|
2373
|
+
...ifDefined("name", parsed.map)
|
|
2374
|
+
};
|
|
2375
|
+
blockPrimaryKeyDeclared = true;
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
if (modelAttribute.name === "unique") {
|
|
2379
|
+
const node = modelAttributeNodes[attributeIndex];
|
|
2380
|
+
if (node === void 0) continue;
|
|
2381
|
+
const parsed = interpretModelAttribute({
|
|
2382
|
+
node,
|
|
2383
|
+
spec: uniqueModelSpec,
|
|
2384
|
+
model,
|
|
2385
|
+
sourceFile: input.sourceFile,
|
|
2386
|
+
sourceId,
|
|
2387
|
+
diagnostics
|
|
2388
|
+
});
|
|
2389
|
+
if (parsed === void 0) continue;
|
|
2390
|
+
const columnNames = mapFieldNamesToColumns({
|
|
2391
|
+
modelName: model.name,
|
|
2392
|
+
fieldNames: parsed.fields,
|
|
2393
|
+
mapping,
|
|
2394
|
+
sourceId,
|
|
2395
|
+
diagnostics,
|
|
2396
|
+
span: modelAttribute.span,
|
|
2397
|
+
entityLabel: attributeLabel
|
|
2398
|
+
});
|
|
2399
|
+
if (!columnNames) continue;
|
|
2400
|
+
uniqueConstraints.push({
|
|
2401
|
+
columns: columnNames,
|
|
2402
|
+
...ifDefined("name", parsed.map)
|
|
2403
|
+
});
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (modelAttribute.name === "index") {
|
|
2407
|
+
const node = modelAttributeNodes[attributeIndex];
|
|
2408
|
+
if (node === void 0) continue;
|
|
2409
|
+
const parsed = interpretModelAttribute({
|
|
2410
|
+
node,
|
|
2411
|
+
spec: indexModelSpec,
|
|
2412
|
+
model,
|
|
2413
|
+
sourceFile: input.sourceFile,
|
|
2414
|
+
sourceId,
|
|
2415
|
+
diagnostics
|
|
2416
|
+
});
|
|
2417
|
+
if (parsed === void 0) continue;
|
|
2418
|
+
let columnNames;
|
|
2419
|
+
if (parsed.fields !== void 0) {
|
|
2420
|
+
const mapped = mapFieldNamesToColumns({
|
|
2421
|
+
modelName: model.name,
|
|
2422
|
+
fieldNames: parsed.fields,
|
|
2423
|
+
mapping,
|
|
2424
|
+
sourceId,
|
|
2425
|
+
diagnostics,
|
|
2426
|
+
span: modelAttribute.span,
|
|
2427
|
+
entityLabel: attributeLabel
|
|
2428
|
+
});
|
|
2429
|
+
if (!mapped) continue;
|
|
2430
|
+
columnNames = mapped;
|
|
2431
|
+
}
|
|
2432
|
+
indexNodes.push(blindCast({
|
|
2433
|
+
...ifDefined("columns", columnNames),
|
|
2434
|
+
...ifDefined("expression", parsed.expression),
|
|
2435
|
+
where: parsed.where,
|
|
2436
|
+
unique: parsed.unique,
|
|
2437
|
+
name: parsed.name,
|
|
2438
|
+
map: parsed.map,
|
|
2439
|
+
type: parsed.type,
|
|
2440
|
+
options: parsed.options
|
|
2441
|
+
}));
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
const contributedModelAttribute = input.modelAttributesByName.get(modelAttribute.name);
|
|
2445
|
+
if (contributedModelAttribute !== void 0) {
|
|
2446
|
+
if (declaredContributedModelAttributes.has(modelAttribute.name)) {
|
|
2447
|
+
diagnostics.push(duplicateModelAttributeDiagnostic({
|
|
2448
|
+
name: modelAttribute.name,
|
|
2449
|
+
modelName: model.name,
|
|
2450
|
+
sourceId,
|
|
2451
|
+
span: modelAttribute.span
|
|
2452
|
+
}));
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
declaredContributedModelAttributes.add(modelAttribute.name);
|
|
2456
|
+
const node = modelAttributeNodes[attributeIndex];
|
|
2457
|
+
if (node === void 0) continue;
|
|
2458
|
+
const parsed = interpretModelAttribute({
|
|
2459
|
+
node,
|
|
2460
|
+
spec: blindCast(contributedModelAttribute.spec),
|
|
2461
|
+
model,
|
|
2462
|
+
sourceFile: input.sourceFile,
|
|
2463
|
+
sourceId,
|
|
2464
|
+
diagnostics
|
|
2465
|
+
});
|
|
2466
|
+
if (parsed === void 0) continue;
|
|
2467
|
+
const lowered = blindCast(contributedModelAttribute.lower)(parsed, {
|
|
2468
|
+
family: input.familyId,
|
|
2469
|
+
target: input.targetId,
|
|
2470
|
+
modelName: model.name,
|
|
2471
|
+
storageName: tableName,
|
|
2472
|
+
namespaceId: modelNamespaceId ?? input.defaultNamespaceId,
|
|
2473
|
+
sourceId,
|
|
2474
|
+
diagnostics: { push: (d) => {
|
|
2475
|
+
diagnostics.push(blindCast(d));
|
|
2476
|
+
} }
|
|
2477
|
+
});
|
|
2478
|
+
if (lowered === void 0) continue;
|
|
2479
|
+
const slot = modelAttributeEntities[contributedModelAttribute.attribute] ?? {};
|
|
2480
|
+
modelAttributeEntities[contributedModelAttribute.attribute] = slot;
|
|
2481
|
+
slot[lowered.key] = lowered.entity;
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
|
|
2485
|
+
familyId: input.familyId,
|
|
2486
|
+
targetId: input.targetId,
|
|
2487
|
+
authoringContributions: input.authoringContributions
|
|
2488
|
+
});
|
|
2489
|
+
if (uncomposedNamespace) {
|
|
2490
|
+
reportUncomposedNamespace({
|
|
2491
|
+
subjectLabel: `Attribute "@@${modelAttribute.name}"`,
|
|
2492
|
+
namespace: uncomposedNamespace,
|
|
2493
|
+
sourceId,
|
|
2494
|
+
span: modelAttribute.span,
|
|
2495
|
+
diagnostics
|
|
2496
|
+
});
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
diagnostics.push({
|
|
2500
|
+
code: "PSL_UNSUPPORTED_MODEL_ATTRIBUTE",
|
|
2501
|
+
message: `Model "${model.name}" uses unsupported attribute "@@${modelAttribute.name}"`,
|
|
2502
|
+
sourceId,
|
|
2503
|
+
span: modelAttribute.span
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
const resultFkRelationMetadata = [];
|
|
2507
|
+
const resultCrossSpaceRelations = [];
|
|
2508
|
+
for (const relationAttribute of relationAttributes) {
|
|
2509
|
+
const { typeName: fieldTypeName, typeNamespaceId: fieldTypeNamespaceId, typeContractSpaceId: fieldTypeContractSpaceId } = relationAttribute.field;
|
|
2510
|
+
if (relationAttribute.field.list) {
|
|
2511
|
+
if (fieldTypeContractSpaceId !== void 0) diagnostics.push({
|
|
2512
|
+
code: "PSL_UNSUPPORTED_CROSS_SPACE_LIST",
|
|
2513
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" is a cross-space list relation (type "${fieldTypeContractSpaceId}:${fieldTypeNamespaceId !== void 0 ? `${fieldTypeNamespaceId}.` : ""}${fieldTypeName}[]"). Cross-space relations must be singular in v0.1 — list cross-space relations are not supported.`,
|
|
2514
|
+
sourceId,
|
|
2515
|
+
span: relationAttribute.field.span
|
|
2516
|
+
});
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
if (!relationAttributeDeclaresOwningSide(relationAttribute.relation)) continue;
|
|
2520
|
+
if (fieldTypeContractSpaceId !== void 0) {
|
|
2521
|
+
const extContractForSpace = input.composedExtensionContracts.get(fieldTypeContractSpaceId);
|
|
2522
|
+
if (extContractForSpace === void 0) {
|
|
2523
|
+
diagnostics.push({
|
|
2524
|
+
code: "PSL_UNKNOWN_CONTRACT_SPACE",
|
|
2525
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references contract space "${fieldTypeContractSpaceId}" which is not declared in extensions. Add "${fieldTypeContractSpaceId}" to extensions in prisma-next.config.ts.`,
|
|
2526
|
+
sourceId,
|
|
2527
|
+
span: relationAttribute.field.span,
|
|
2528
|
+
data: {
|
|
2529
|
+
space: fieldTypeContractSpaceId,
|
|
2530
|
+
suggestedPack: fieldTypeContractSpaceId
|
|
2531
|
+
}
|
|
2532
|
+
});
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
const parsedRelation = interpretRelationAttribute({
|
|
2536
|
+
selfModel: model,
|
|
2537
|
+
field: relationAttribute.field,
|
|
2538
|
+
symbols: input.symbolTable,
|
|
2539
|
+
sourceFile: input.sourceFile,
|
|
2540
|
+
sourceId,
|
|
2541
|
+
diagnostics
|
|
2542
|
+
});
|
|
2543
|
+
if (!parsedRelation) continue;
|
|
2544
|
+
if (!parsedRelation.fields || !parsedRelation.references) {
|
|
2545
|
+
diagnostics.push({
|
|
2546
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2547
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" requires fields and references arguments`,
|
|
2548
|
+
sourceId,
|
|
2549
|
+
span: relationAttribute.relation.span
|
|
2550
|
+
});
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const localColumns = mapFieldNamesToColumns({
|
|
2554
|
+
modelName: model.name,
|
|
2555
|
+
fieldNames: parsedRelation.fields,
|
|
2556
|
+
mapping,
|
|
2557
|
+
sourceId,
|
|
2558
|
+
diagnostics,
|
|
2559
|
+
span: relationAttribute.relation.span,
|
|
2560
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
2561
|
+
});
|
|
2562
|
+
if (!localColumns) continue;
|
|
2563
|
+
const referencedColumns = parsedRelation.references;
|
|
2564
|
+
if (localColumns.length !== referencedColumns.length) {
|
|
2565
|
+
diagnostics.push({
|
|
2566
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2567
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" must provide the same number of fields and references`,
|
|
2568
|
+
sourceId,
|
|
2569
|
+
span: relationAttribute.relation.span
|
|
2570
|
+
});
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction(parsedRelation.onDelete) : void 0;
|
|
2574
|
+
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction(parsedRelation.onUpdate) : void 0;
|
|
2575
|
+
const crossTargetNamespaceId = fieldTypeNamespaceId ?? "__unbound__";
|
|
2576
|
+
const extContract = extContractForSpace;
|
|
2577
|
+
const resolvedTable = extContract.domain.namespaces[crossTargetNamespaceId]?.models[fieldTypeName]?.storage["table"];
|
|
2578
|
+
if (typeof resolvedTable !== "string") {
|
|
2579
|
+
const availableModels = Object.keys(extContract.domain.namespaces[crossTargetNamespaceId]?.models ?? {}).join(", ") || "(none)";
|
|
2580
|
+
diagnostics.push({
|
|
2581
|
+
code: "PSL_UNKNOWN_CROSS_SPACE_TARGET",
|
|
2582
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references model "${fieldTypeName}" in namespace "${crossTargetNamespaceId}" of space "${fieldTypeContractSpaceId}", but that model was not found in the extension contract. Available models: ${availableModels}`,
|
|
2583
|
+
sourceId,
|
|
2584
|
+
span: relationAttribute.field.span,
|
|
2585
|
+
data: {
|
|
2586
|
+
space: fieldTypeContractSpaceId,
|
|
2587
|
+
namespace: crossTargetNamespaceId,
|
|
2588
|
+
model: fieldTypeName
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
continue;
|
|
2592
|
+
}
|
|
2593
|
+
const crossTargetTableName = resolvedTable;
|
|
2594
|
+
foreignKeyNodes.push({
|
|
2595
|
+
columns: localColumns,
|
|
2596
|
+
references: {
|
|
2597
|
+
model: fieldTypeName,
|
|
2598
|
+
table: crossTargetTableName,
|
|
2599
|
+
columns: referencedColumns,
|
|
2600
|
+
namespaceId: crossTargetNamespaceId,
|
|
2601
|
+
spaceId: fieldTypeContractSpaceId
|
|
2602
|
+
},
|
|
2603
|
+
...ifDefined("name", parsedRelation.map),
|
|
2604
|
+
...ifDefined("onDelete", onDelete),
|
|
2605
|
+
...ifDefined("onUpdate", onUpdate),
|
|
2606
|
+
...ifDefined("index", parsedRelation.index)
|
|
2607
|
+
});
|
|
2608
|
+
resultCrossSpaceRelations.push({
|
|
2609
|
+
fieldName: relationAttribute.field.name,
|
|
2610
|
+
toModel: fieldTypeName,
|
|
2611
|
+
toTable: crossTargetTableName,
|
|
2612
|
+
cardinality: "N:1",
|
|
2613
|
+
spaceId: fieldTypeContractSpaceId,
|
|
2614
|
+
namespaceId: crossTargetNamespaceId,
|
|
2615
|
+
on: {
|
|
2616
|
+
parentTable: tableName,
|
|
2617
|
+
parentColumns: localColumns,
|
|
2618
|
+
childTable: crossTargetTableName,
|
|
2619
|
+
childColumns: referencedColumns
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
const qualifiedTypeName = fieldTypeNamespaceId ? `${fieldTypeNamespaceId}.${fieldTypeName}` : fieldTypeName;
|
|
2625
|
+
if (!input.modelNames.has(fieldTypeName)) {
|
|
2626
|
+
diagnostics.push({
|
|
2627
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
2628
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2629
|
+
sourceId,
|
|
2630
|
+
span: relationAttribute.field.span
|
|
2631
|
+
});
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
const normalizedQualifier = fieldTypeNamespaceId === void 0 ? void 0 : fieldTypeNamespaceId === "unbound" ? "__unbound__" : fieldTypeNamespaceId;
|
|
2635
|
+
if (normalizedQualifier !== void 0 && !input.modelMappingsByCoordinate.has(modelCoordinateKey(normalizedQualifier, fieldTypeName))) {
|
|
2636
|
+
diagnostics.push({
|
|
2637
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
2638
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2639
|
+
sourceId,
|
|
2640
|
+
span: relationAttribute.field.span
|
|
2641
|
+
});
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
const parsedRelation = interpretRelationAttribute({
|
|
2645
|
+
selfModel: model,
|
|
2646
|
+
field: relationAttribute.field,
|
|
2647
|
+
symbols: input.symbolTable,
|
|
2648
|
+
sourceFile: input.sourceFile,
|
|
2649
|
+
sourceId,
|
|
2650
|
+
diagnostics
|
|
2651
|
+
});
|
|
2652
|
+
if (!parsedRelation) continue;
|
|
2653
|
+
if (!parsedRelation.fields || !parsedRelation.references) {
|
|
2654
|
+
diagnostics.push({
|
|
2655
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2656
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" requires fields and references arguments`,
|
|
2657
|
+
sourceId,
|
|
2658
|
+
span: relationAttribute.relation.span
|
|
2659
|
+
});
|
|
2660
|
+
continue;
|
|
2661
|
+
}
|
|
2662
|
+
const targetMapping = normalizedQualifier !== void 0 ? input.modelMappingsByCoordinate.get(modelCoordinateKey(normalizedQualifier, fieldTypeName)) : input.modelMappings.get(fieldTypeName);
|
|
2663
|
+
if (!targetMapping) {
|
|
2664
|
+
diagnostics.push({
|
|
2665
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
2666
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2667
|
+
sourceId,
|
|
2668
|
+
span: relationAttribute.field.span
|
|
2669
|
+
});
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
const localColumns = mapFieldNamesToColumns({
|
|
2673
|
+
modelName: model.name,
|
|
2674
|
+
fieldNames: parsedRelation.fields,
|
|
2675
|
+
mapping,
|
|
2676
|
+
sourceId,
|
|
2677
|
+
diagnostics,
|
|
2678
|
+
span: relationAttribute.relation.span,
|
|
2679
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
2680
|
+
});
|
|
2681
|
+
if (!localColumns) continue;
|
|
2682
|
+
const referencedColumns = mapFieldNamesToColumns({
|
|
2683
|
+
modelName: targetMapping.model.name,
|
|
2684
|
+
fieldNames: parsedRelation.references,
|
|
2685
|
+
mapping: targetMapping,
|
|
2686
|
+
sourceId,
|
|
2687
|
+
diagnostics,
|
|
2688
|
+
span: relationAttribute.relation.span,
|
|
2689
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
2690
|
+
});
|
|
2691
|
+
if (!referencedColumns) continue;
|
|
2692
|
+
if (localColumns.length !== referencedColumns.length) {
|
|
2693
|
+
diagnostics.push({
|
|
2694
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2695
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" must provide the same number of fields and references`,
|
|
2696
|
+
sourceId,
|
|
2697
|
+
span: relationAttribute.relation.span
|
|
2698
|
+
});
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction(parsedRelation.onDelete) : void 0;
|
|
2702
|
+
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction(parsedRelation.onUpdate) : void 0;
|
|
2703
|
+
const targetNamespaceId = normalizedQualifier !== void 0 ? normalizedQualifier : input.modelNamespaceIds.get(targetMapping.model.name);
|
|
2704
|
+
foreignKeyNodes.push({
|
|
2705
|
+
columns: localColumns,
|
|
2706
|
+
references: {
|
|
2707
|
+
model: targetMapping.model.name,
|
|
2708
|
+
table: targetMapping.tableName,
|
|
2709
|
+
columns: referencedColumns,
|
|
2710
|
+
...ifDefined("namespaceId", targetNamespaceId)
|
|
2711
|
+
},
|
|
2712
|
+
...ifDefined("name", parsedRelation.map),
|
|
2713
|
+
...ifDefined("onDelete", onDelete),
|
|
2714
|
+
...ifDefined("onUpdate", onUpdate),
|
|
2715
|
+
...ifDefined("index", parsedRelation.index)
|
|
2716
|
+
});
|
|
2717
|
+
resultFkRelationMetadata.push({
|
|
2718
|
+
declaringModelName: model.name,
|
|
2719
|
+
declaringFieldName: relationAttribute.field.name,
|
|
2720
|
+
declaringTableName: tableName,
|
|
2721
|
+
...ifDefined("declaringNamespaceId", input.modelNamespaceIds.get(model.name)),
|
|
2722
|
+
targetModelName: targetMapping.model.name,
|
|
2723
|
+
targetTableName: targetMapping.tableName,
|
|
2724
|
+
...ifDefined("targetNamespaceId", targetNamespaceId),
|
|
2725
|
+
...ifDefined("relationName", parsedRelation.name),
|
|
2726
|
+
localColumns,
|
|
2727
|
+
referencedColumns
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
return {
|
|
2731
|
+
modelNode: {
|
|
2732
|
+
modelName: model.name,
|
|
2733
|
+
tableName,
|
|
2734
|
+
fields: resolvedFields.map((resolvedField) => {
|
|
2735
|
+
const enumHandle = input.enumHandles?.get(resolvedField.field.typeName);
|
|
2736
|
+
return {
|
|
2737
|
+
fieldName: resolvedField.field.name,
|
|
2738
|
+
columnName: resolvedField.columnName,
|
|
2739
|
+
descriptor: resolvedField.descriptor,
|
|
2740
|
+
nullable: resolvedField.nullable,
|
|
2741
|
+
...resolvedField.many && resolvedField.valueObjectTypeName === void 0 ? { many: true } : {},
|
|
2742
|
+
...ifDefined("default", resolvedField.defaultValue),
|
|
2743
|
+
...ifDefined("executionDefaults", resolvedField.executionDefaults),
|
|
2744
|
+
...ifDefined("enumTypeHandle", enumHandle)
|
|
2745
|
+
};
|
|
2746
|
+
}),
|
|
2747
|
+
...ifDefined("id", primaryKey),
|
|
2748
|
+
...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
|
|
2749
|
+
...indexNodes.length > 0 ? { indexes: indexNodes } : {},
|
|
2750
|
+
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {},
|
|
2751
|
+
...ifDefined("control", controlPolicy)
|
|
2752
|
+
},
|
|
2753
|
+
fkRelationMetadata: resultFkRelationMetadata,
|
|
2754
|
+
crossSpaceRelations: resultCrossSpaceRelations,
|
|
2755
|
+
backrelationCandidates: resultBackrelationCandidates,
|
|
2756
|
+
resolvedFields,
|
|
2757
|
+
modelAttributeEntities
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
function buildValueObjects(input) {
|
|
2761
|
+
const { compositeTypes, enumTypeDescriptors, namedTypeDescriptors, scalarColumnDescriptors, composedExtensions, familyId, targetId, authoringContributions, diagnostics, sourceId } = input;
|
|
2762
|
+
const valueObjects = {};
|
|
2763
|
+
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
2764
|
+
for (const compositeType of compositeTypes) {
|
|
2765
|
+
const fields = {};
|
|
2766
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
2767
|
+
if (compositeTypeNames.has(field.typeName)) {
|
|
2768
|
+
const result = {
|
|
2769
|
+
type: {
|
|
2770
|
+
kind: "valueObject",
|
|
2771
|
+
name: field.typeName
|
|
2772
|
+
},
|
|
2773
|
+
nullable: field.optional
|
|
2774
|
+
};
|
|
2775
|
+
fields[field.name] = field.list ? {
|
|
2776
|
+
...result,
|
|
2777
|
+
many: true
|
|
2778
|
+
} : result;
|
|
2779
|
+
continue;
|
|
2780
|
+
}
|
|
2781
|
+
const resolved = resolveFieldTypeDescriptor({
|
|
2782
|
+
field,
|
|
2783
|
+
enumTypeDescriptors,
|
|
2784
|
+
namedTypeDescriptors,
|
|
2785
|
+
scalarColumnDescriptors,
|
|
2786
|
+
authoringContributions,
|
|
2787
|
+
composedExtensions,
|
|
2788
|
+
familyId,
|
|
2789
|
+
targetId,
|
|
2790
|
+
diagnostics,
|
|
2791
|
+
sourceId,
|
|
2792
|
+
entityLabel: `Field "${compositeType.name}.${field.name}"`
|
|
2793
|
+
});
|
|
2794
|
+
if (!resolved.ok) {
|
|
2795
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
2796
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
2797
|
+
message: `Field "${compositeType.name}.${field.name}" type "${field.typeName}" is not supported`,
|
|
2798
|
+
sourceId,
|
|
2799
|
+
span: field.span
|
|
2800
|
+
});
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
const scalarField = {
|
|
2804
|
+
nullable: field.optional,
|
|
2805
|
+
type: {
|
|
2806
|
+
kind: "scalar",
|
|
2807
|
+
codecId: resolved.descriptor.codecId
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
fields[field.name] = field.list ? {
|
|
2811
|
+
...scalarField,
|
|
2812
|
+
many: true
|
|
2813
|
+
} : scalarField;
|
|
2814
|
+
}
|
|
2815
|
+
valueObjects[compositeType.name] = { fields };
|
|
2816
|
+
}
|
|
2817
|
+
return valueObjects;
|
|
2818
|
+
}
|
|
2819
|
+
function patchModelDomainFields(models, modelResolvedFields) {
|
|
2820
|
+
let patched = models;
|
|
2821
|
+
for (const [modelName, resolvedFields] of modelResolvedFields) {
|
|
2822
|
+
const model = patched[modelName];
|
|
2823
|
+
if (!model) continue;
|
|
2824
|
+
let needsPatch = false;
|
|
2825
|
+
const patchedFields = { ...model.fields };
|
|
2826
|
+
for (const rf of resolvedFields) if (rf.valueObjectTypeName) {
|
|
2827
|
+
needsPatch = true;
|
|
2828
|
+
patchedFields[rf.field.name] = {
|
|
2829
|
+
nullable: rf.field.optional,
|
|
2830
|
+
type: {
|
|
2831
|
+
kind: "valueObject",
|
|
2832
|
+
name: rf.valueObjectTypeName
|
|
2833
|
+
},
|
|
2834
|
+
...rf.many ? { many: true } : {}
|
|
2835
|
+
};
|
|
2836
|
+
} else if (rf.many && rf.scalarCodecId) {
|
|
2837
|
+
needsPatch = true;
|
|
2838
|
+
patchedFields[rf.field.name] = {
|
|
2839
|
+
nullable: rf.field.optional,
|
|
2840
|
+
type: {
|
|
2841
|
+
kind: "scalar",
|
|
2842
|
+
codecId: rf.scalarCodecId
|
|
2843
|
+
},
|
|
2844
|
+
many: true
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
if (needsPatch) patched = {
|
|
2848
|
+
...patched,
|
|
2849
|
+
[modelName]: {
|
|
2850
|
+
...model,
|
|
2851
|
+
fields: patchedFields
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
return patched;
|
|
2856
|
+
}
|
|
2857
|
+
function collectPolymorphismDeclarations(models, sourceFile, sourceId, diagnostics) {
|
|
2858
|
+
const discriminatorDeclarations = /* @__PURE__ */ new Map();
|
|
2859
|
+
const baseDeclarations = /* @__PURE__ */ new Map();
|
|
2860
|
+
for (const model of models) {
|
|
2861
|
+
const discriminatorNode = findModelAttributeNode(model, "discriminator");
|
|
2862
|
+
if (discriminatorNode !== void 0) {
|
|
2863
|
+
const parsed = interpretModelAttribute({
|
|
2864
|
+
node: discriminatorNode,
|
|
2865
|
+
spec: discriminatorModelSpec,
|
|
2866
|
+
model,
|
|
2867
|
+
sourceFile,
|
|
2868
|
+
sourceId,
|
|
2869
|
+
diagnostics
|
|
2870
|
+
});
|
|
2871
|
+
if (parsed !== void 0) {
|
|
2872
|
+
const span = nodePslSpan(discriminatorNode.syntax, sourceFile);
|
|
2873
|
+
const discField = model.fields[parsed.field];
|
|
2874
|
+
if (discField && discField.typeName !== "String") diagnostics.push({
|
|
2875
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2876
|
+
message: `Discriminator field "${parsed.field}" on model "${model.name}" must be of type String, but is "${discField.typeName}"`,
|
|
2877
|
+
sourceId,
|
|
2878
|
+
span
|
|
2879
|
+
});
|
|
2880
|
+
else discriminatorDeclarations.set(model.name, {
|
|
2881
|
+
fieldName: parsed.field,
|
|
2882
|
+
span
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
const baseNode = findModelAttributeNode(model, "base");
|
|
2887
|
+
if (baseNode !== void 0) {
|
|
2888
|
+
const parsed = interpretModelAttribute({
|
|
2889
|
+
node: baseNode,
|
|
2890
|
+
spec: baseModelSpec,
|
|
2891
|
+
model,
|
|
2892
|
+
sourceFile,
|
|
2893
|
+
sourceId,
|
|
2894
|
+
diagnostics
|
|
2895
|
+
});
|
|
2896
|
+
if (parsed !== void 0) baseDeclarations.set(model.name, {
|
|
2897
|
+
baseName: parsed.base,
|
|
2898
|
+
value: parsed.value,
|
|
2899
|
+
span: nodePslSpan(baseNode.syntax, sourceFile)
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
return {
|
|
2904
|
+
discriminatorDeclarations,
|
|
2905
|
+
baseDeclarations
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, diagnostics) {
|
|
2909
|
+
let patched = models;
|
|
2910
|
+
const coordinateFor = (modelName) => modelCoordinateKey(modelNamespaceIds.get(modelName) ?? defaultNamespaceId, modelName);
|
|
2911
|
+
for (const [baseName, fieldNames] of stiBaseFieldsByBase) {
|
|
2912
|
+
const baseKey = coordinateFor(baseName);
|
|
2913
|
+
const baseModel = patched[baseKey];
|
|
2914
|
+
if (!baseModel || fieldNames.length === 0) continue;
|
|
2915
|
+
patched = {
|
|
2916
|
+
...patched,
|
|
2917
|
+
[baseKey]: stripStorageOnlyDomainFields(baseModel, fieldNames)
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
for (const [modelName, decl] of discriminatorDeclarations) {
|
|
2921
|
+
if (baseDeclarations.has(modelName)) {
|
|
2922
|
+
diagnostics.push({
|
|
2923
|
+
code: "PSL_DISCRIMINATOR_AND_BASE",
|
|
2924
|
+
message: `Model "${modelName}" cannot have both @@discriminator and @@base`,
|
|
2925
|
+
sourceId,
|
|
2926
|
+
span: decl.span
|
|
2927
|
+
});
|
|
2928
|
+
continue;
|
|
2929
|
+
}
|
|
2930
|
+
const model = patched[coordinateFor(modelName)];
|
|
2931
|
+
if (!model) continue;
|
|
2932
|
+
const variants = {};
|
|
2933
|
+
const seenValues = /* @__PURE__ */ new Map();
|
|
2934
|
+
for (const [variantName, baseDecl] of baseDeclarations) {
|
|
2935
|
+
if (baseDecl.baseName !== modelName) continue;
|
|
2936
|
+
const existingVariant = seenValues.get(baseDecl.value);
|
|
2937
|
+
if (existingVariant) {
|
|
2938
|
+
diagnostics.push({
|
|
2939
|
+
code: "PSL_DUPLICATE_DISCRIMINATOR_VALUE",
|
|
2940
|
+
message: `Discriminator value "${baseDecl.value}" is used by both "${existingVariant}" and "${variantName}" on base model "${modelName}"`,
|
|
2941
|
+
sourceId,
|
|
2942
|
+
span: baseDecl.span
|
|
2943
|
+
});
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
seenValues.set(baseDecl.value, variantName);
|
|
2947
|
+
variants[variantName] = { value: baseDecl.value };
|
|
2948
|
+
}
|
|
2949
|
+
if (Object.keys(variants).length === 0) {
|
|
2950
|
+
diagnostics.push({
|
|
2951
|
+
code: "PSL_ORPHANED_DISCRIMINATOR",
|
|
2952
|
+
message: `Model "${modelName}" has @@discriminator but no variant models declare @@base(${modelName}, ...)`,
|
|
2953
|
+
sourceId,
|
|
2954
|
+
span: decl.span
|
|
2955
|
+
});
|
|
2956
|
+
continue;
|
|
2957
|
+
}
|
|
2958
|
+
patched = {
|
|
2959
|
+
...patched,
|
|
2960
|
+
[coordinateFor(modelName)]: {
|
|
2961
|
+
...model,
|
|
2962
|
+
discriminator: { field: decl.fieldName },
|
|
2963
|
+
variants
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
for (const [variantName, baseDecl] of baseDeclarations) {
|
|
2968
|
+
if (!modelNames.has(baseDecl.baseName)) {
|
|
2969
|
+
diagnostics.push({
|
|
2970
|
+
code: "PSL_BASE_TARGET_NOT_FOUND",
|
|
2971
|
+
message: `Model "${variantName}" @@base references non-existent model "${baseDecl.baseName}"`,
|
|
2972
|
+
sourceId,
|
|
2973
|
+
span: baseDecl.span
|
|
2974
|
+
});
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
if (!discriminatorDeclarations.has(baseDecl.baseName)) {
|
|
2978
|
+
diagnostics.push({
|
|
2979
|
+
code: "PSL_ORPHANED_BASE",
|
|
2980
|
+
message: `Model "${variantName}" declares @@base(${baseDecl.baseName}, ...) but "${baseDecl.baseName}" has no @@discriminator`,
|
|
2981
|
+
sourceId,
|
|
2982
|
+
span: baseDecl.span
|
|
2983
|
+
});
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
if (discriminatorDeclarations.has(variantName)) continue;
|
|
2987
|
+
const variantModel = patched[coordinateFor(variantName)];
|
|
2988
|
+
if (!variantModel) continue;
|
|
2989
|
+
const baseMapping = modelMappings.get(baseDecl.baseName);
|
|
2990
|
+
const variantMapping = modelMappings.get(variantName);
|
|
2991
|
+
const resolvedTable = variantMapping?.model.attributes.some((attr) => attr.name === "map") ?? false ? variantMapping?.tableName : baseMapping?.tableName;
|
|
2992
|
+
const patchedVariant = {
|
|
2993
|
+
...variantModel,
|
|
2994
|
+
base: crossRef(baseDecl.baseName, modelNamespaceIds.get(baseDecl.baseName) ?? defaultNamespaceId),
|
|
2995
|
+
...resolvedTable ? { storage: {
|
|
2996
|
+
...variantModel.storage,
|
|
2997
|
+
table: resolvedTable
|
|
2998
|
+
} } : {}
|
|
2999
|
+
};
|
|
3000
|
+
patched = {
|
|
3001
|
+
...patched,
|
|
3002
|
+
[coordinateFor(variantName)]: stripStorageOnlyDomainFields(patchedVariant, syntheticPkFieldsByVariant.get(variantName) ?? [])
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
return patched;
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Multi-table-inheritance variants (`@@base` + their own `@@map`) live in a
|
|
3009
|
+
* separate table from their base. The ORM joins that table to the base on the
|
|
3010
|
+
* shared primary key (`base.id = variant.id`), so the variant storage table
|
|
3011
|
+
* must carry the base PK column even though the variant domain model declares
|
|
3012
|
+
* only its own fields. This enriches each MTI variant's `ModelNode` with that
|
|
3013
|
+
* link column, a primary key on it, and a FK back to the base table.
|
|
3014
|
+
*
|
|
3015
|
+
* The link column is reported back per variant in `syntheticPkFieldsByVariant`
|
|
3016
|
+
* so the domain-model patch can drop it again — keeping the variant's domain
|
|
3017
|
+
* surface thin (its create/read inputs don't gain a redundant `id`) while the
|
|
3018
|
+
* storage table stays joinable. Single-table-inheritance variants (no own
|
|
3019
|
+
* table) are left untouched.
|
|
3020
|
+
*/
|
|
3021
|
+
function materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames) {
|
|
3022
|
+
const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
|
|
3023
|
+
const syntheticPkFieldsByVariant = /* @__PURE__ */ new Map();
|
|
3024
|
+
return {
|
|
3025
|
+
modelNodes: modelNodes.map((node) => {
|
|
3026
|
+
const baseDecl = baseDeclarations.get(node.modelName);
|
|
3027
|
+
if (!baseDecl) return node;
|
|
3028
|
+
const baseNode = nodeByModel.get(baseDecl.baseName);
|
|
3029
|
+
if (!baseNode) return node;
|
|
3030
|
+
if (stiVariantNames.has(node.modelName)) return node;
|
|
3031
|
+
const basePrimaryKey = baseNode.id;
|
|
3032
|
+
if (!basePrimaryKey || basePrimaryKey.columns.length === 0) return node;
|
|
3033
|
+
const existingColumns = new Set(node.fields.map((field) => field.columnName));
|
|
3034
|
+
const linkFields = [];
|
|
3035
|
+
for (const pkColumn of basePrimaryKey.columns) {
|
|
3036
|
+
if (existingColumns.has(pkColumn)) continue;
|
|
3037
|
+
const baseField = baseNode.fields.find((field) => "descriptor" in field && field.columnName === pkColumn);
|
|
3038
|
+
if (!baseField) continue;
|
|
3039
|
+
linkFields.push({
|
|
3040
|
+
fieldName: baseField.fieldName,
|
|
3041
|
+
columnName: pkColumn,
|
|
3042
|
+
descriptor: baseField.descriptor,
|
|
3043
|
+
nullable: false
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
if (linkFields.length === 0) return node;
|
|
3047
|
+
syntheticPkFieldsByVariant.set(node.modelName, linkFields.map((field) => field.fieldName));
|
|
3048
|
+
const foreignKey = {
|
|
3049
|
+
columns: basePrimaryKey.columns,
|
|
3050
|
+
references: {
|
|
3051
|
+
model: baseNode.modelName,
|
|
3052
|
+
table: baseNode.tableName,
|
|
3053
|
+
columns: basePrimaryKey.columns,
|
|
3054
|
+
...ifDefined("namespaceId", baseNode.namespaceId)
|
|
3055
|
+
},
|
|
3056
|
+
constraint: true,
|
|
3057
|
+
index: false,
|
|
3058
|
+
onDelete: "cascade"
|
|
3059
|
+
};
|
|
3060
|
+
return {
|
|
3061
|
+
...node,
|
|
3062
|
+
fields: [...linkFields, ...node.fields],
|
|
3063
|
+
id: { columns: basePrimaryKey.columns },
|
|
3064
|
+
foreignKeys: [...node.foreignKeys ?? [], foreignKey]
|
|
3065
|
+
};
|
|
3066
|
+
}),
|
|
3067
|
+
syntheticPkFieldsByVariant
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
/**
|
|
3071
|
+
* Single-table-inheritance variants (`@@base` with no own `@@map`) share the
|
|
3072
|
+
* base table: `resolvePolymorphism` points the variant's `storage.table` at the
|
|
3073
|
+
* base, and the ORM reads variant-declared fields straight off the base table.
|
|
3074
|
+
* For that to validate and round-trip, the base storage table must physically
|
|
3075
|
+
* carry every STI variant's declared columns. This enriches the base
|
|
3076
|
+
* `ModelNode` with those columns.
|
|
3077
|
+
*
|
|
3078
|
+
* The materialised columns are always nullable in storage: the base table hosts
|
|
3079
|
+
* every variant's rows, so a column a variant declares as required is still
|
|
3080
|
+
* NULL on sibling-variant rows. The variant's domain field keeps its declared
|
|
3081
|
+
* nullability — required-in-domain / nullable-in-storage is the intended STI
|
|
3082
|
+
* shape.
|
|
3083
|
+
*
|
|
3084
|
+
* Collisions (two variants declaring the same column, or a variant column name
|
|
3085
|
+
* clashing with a base column) are resolved skip-if-exists here, mirroring the
|
|
3086
|
+
* MTI link guard; surfacing them as diagnostics is tracked separately
|
|
3087
|
+
* (TML-2827).
|
|
3088
|
+
*/
|
|
3089
|
+
function materializeStiVariantStorageColumns(modelNodes, baseDeclarations, stiVariantNames) {
|
|
3090
|
+
if (stiVariantNames.size === 0) return {
|
|
3091
|
+
modelNodes: [...modelNodes],
|
|
3092
|
+
stiBaseFieldsByBase: /* @__PURE__ */ new Map()
|
|
3093
|
+
};
|
|
3094
|
+
const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
|
|
3095
|
+
const stiColumnsByBase = /* @__PURE__ */ new Map();
|
|
3096
|
+
for (const variantName of stiVariantNames) {
|
|
3097
|
+
const variantNode = nodeByModel.get(variantName);
|
|
3098
|
+
const baseDecl = baseDeclarations.get(variantName);
|
|
3099
|
+
if (!variantNode || !baseDecl) continue;
|
|
3100
|
+
const baseNode = nodeByModel.get(baseDecl.baseName);
|
|
3101
|
+
if (!baseNode) continue;
|
|
3102
|
+
const baseColumns = new Set(baseNode.fields.map((field) => field.columnName));
|
|
3103
|
+
const claimed = stiColumnsByBase.get(baseDecl.baseName) ?? [];
|
|
3104
|
+
const claimedColumns = new Set(claimed.map((field) => field.columnName));
|
|
3105
|
+
for (const field of variantNode.fields) {
|
|
3106
|
+
if (baseColumns.has(field.columnName) || claimedColumns.has(field.columnName)) continue;
|
|
3107
|
+
claimedColumns.add(field.columnName);
|
|
3108
|
+
claimed.push({
|
|
3109
|
+
...field,
|
|
3110
|
+
nullable: true
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
stiColumnsByBase.set(baseDecl.baseName, claimed);
|
|
3114
|
+
}
|
|
3115
|
+
const stiBaseFieldsByBase = /* @__PURE__ */ new Map();
|
|
3116
|
+
for (const [baseName, columns] of stiColumnsByBase) stiBaseFieldsByBase.set(baseName, columns.map((field) => field.fieldName));
|
|
3117
|
+
return {
|
|
3118
|
+
modelNodes: modelNodes.map((node) => {
|
|
3119
|
+
if (stiVariantNames.has(node.modelName)) return {
|
|
3120
|
+
...node,
|
|
3121
|
+
sharesBaseTable: true
|
|
3122
|
+
};
|
|
3123
|
+
const stiColumns = stiColumnsByBase.get(node.modelName);
|
|
3124
|
+
if (!stiColumns || stiColumns.length === 0) return node;
|
|
3125
|
+
return {
|
|
3126
|
+
...node,
|
|
3127
|
+
fields: [...node.fields, ...stiColumns]
|
|
3128
|
+
};
|
|
3129
|
+
}),
|
|
3130
|
+
stiBaseFieldsByBase
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Drop the storage-only link fields (added by
|
|
3135
|
+
* {@link materializeMtiVariantStorageLinks}) from a variant's domain model, so
|
|
3136
|
+
* the domain surface stays thin while the storage table keeps the link column.
|
|
3137
|
+
*/
|
|
3138
|
+
function stripStorageOnlyDomainFields(model, fieldNames) {
|
|
3139
|
+
if (fieldNames.length === 0) return model;
|
|
3140
|
+
const fields = { ...model.fields };
|
|
3141
|
+
for (const name of fieldNames) delete fields[name];
|
|
3142
|
+
const storage = blindCast(model.storage);
|
|
3143
|
+
const storageFields = { ...storage.fields };
|
|
3144
|
+
for (const name of fieldNames) delete storageFields[name];
|
|
3145
|
+
return {
|
|
3146
|
+
...model,
|
|
3147
|
+
fields,
|
|
3148
|
+
storage: {
|
|
3149
|
+
...storage,
|
|
3150
|
+
fields: storageFields
|
|
3151
|
+
}
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
function interpretPslDocumentToSqlContract(input) {
|
|
3155
|
+
const sourceId = input.sourceId;
|
|
3156
|
+
if (!input.target) return notOk({
|
|
3157
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
3158
|
+
diagnostics: [{
|
|
3159
|
+
code: "PSL_TARGET_CONTEXT_REQUIRED",
|
|
3160
|
+
message: "PSL interpretation requires an explicit target context from composition.",
|
|
3161
|
+
sourceId
|
|
3162
|
+
}]
|
|
3163
|
+
});
|
|
3164
|
+
if (!input.scalarColumnDescriptors) return notOk({
|
|
3165
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
3166
|
+
diagnostics: [{
|
|
3167
|
+
code: "PSL_SCALAR_TYPE_CONTEXT_REQUIRED",
|
|
3168
|
+
message: "PSL interpretation requires composed scalar type descriptors.",
|
|
3169
|
+
sourceId
|
|
3170
|
+
}]
|
|
3171
|
+
});
|
|
3172
|
+
const { topLevel } = input.symbolTable;
|
|
3173
|
+
const sourceFile = input.sourceFile;
|
|
3174
|
+
const namespaceSymbols = Object.values(topLevel.namespaces);
|
|
3175
|
+
const diagnostics = [...input.seedDiagnostics ?? []];
|
|
3176
|
+
validateNamespaceBlocksForSqlTarget({
|
|
3177
|
+
namespaces: namespaceSymbols,
|
|
3178
|
+
targetId: input.target.targetId,
|
|
3179
|
+
sourceId,
|
|
3180
|
+
sourceFile,
|
|
3181
|
+
diagnostics
|
|
3182
|
+
});
|
|
3183
|
+
validateBlockModelAttributeRequirements({
|
|
3184
|
+
scopes: [topLevel, ...namespaceSymbols],
|
|
3185
|
+
pslBlockDescriptors: input.authoringContributions?.pslBlockDescriptors ?? {},
|
|
3186
|
+
sourceId,
|
|
3187
|
+
diagnostics
|
|
3188
|
+
});
|
|
3189
|
+
const models = [];
|
|
3190
|
+
const modelEntries = [];
|
|
3191
|
+
const modelNamespaceIds = /* @__PURE__ */ new Map();
|
|
3192
|
+
const compositeTypes = [];
|
|
3193
|
+
const collectScope = (bucketName, scopeModels, scopeCompositeTypes) => {
|
|
3194
|
+
const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
|
|
3195
|
+
bucketName,
|
|
3196
|
+
targetId: input.target.targetId
|
|
3197
|
+
});
|
|
3198
|
+
for (const model of scopeModels) {
|
|
3199
|
+
models.push(model);
|
|
3200
|
+
modelEntries.push({
|
|
3201
|
+
model,
|
|
3202
|
+
namespaceId: resolvedNamespaceId
|
|
3203
|
+
});
|
|
3204
|
+
if (resolvedNamespaceId !== void 0) modelNamespaceIds.set(model.name, resolvedNamespaceId);
|
|
3205
|
+
}
|
|
3206
|
+
for (const compositeType of scopeCompositeTypes) compositeTypes.push(compositeType);
|
|
3207
|
+
};
|
|
3208
|
+
collectScope(UNSPECIFIED_PSL_NAMESPACE_NAME, Object.values(topLevel.models), Object.values(topLevel.compositeTypes));
|
|
3209
|
+
for (const namespace of namespaceSymbols) collectScope(namespace.name, Object.values(namespace.models), Object.values(namespace.compositeTypes));
|
|
3210
|
+
const defaultNamespaceId = input.target.defaultNamespaceId;
|
|
3211
|
+
const modelNames = new Set(models.map((model) => model.name));
|
|
3212
|
+
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
3213
|
+
const composedExtensions = new Set(input.composedExtensions ?? []);
|
|
3214
|
+
const composedExtensionContracts = input.composedExtensionContracts;
|
|
3215
|
+
const defaultFunctionRegistry = input.controlMutationDefaults?.defaultFunctionRegistry ?? /* @__PURE__ */ new Map();
|
|
3216
|
+
const generatorDescriptors = input.controlMutationDefaults?.generatorDescriptors ?? [];
|
|
3217
|
+
const generatorDescriptorById = /* @__PURE__ */ new Map();
|
|
3218
|
+
for (const descriptor of generatorDescriptors) generatorDescriptorById.set(descriptor.id, descriptor);
|
|
3219
|
+
const isEnumBlock = (block) => block.keyword === "enum";
|
|
3220
|
+
const legitimateBlockKeywords = composedBlockKeywords(input.authoringContributions);
|
|
3221
|
+
const reportUnsupportedTopLevelBlock = (block) => {
|
|
3222
|
+
diagnostics.push({
|
|
3223
|
+
code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
|
|
3224
|
+
message: `Unsupported top-level block "${block.keyword}"`,
|
|
3225
|
+
sourceId,
|
|
3226
|
+
span: keywordPslSpan(block.node.syntax, block.keyword, sourceFile)
|
|
3227
|
+
});
|
|
3228
|
+
};
|
|
3229
|
+
const topLevelEnums = [];
|
|
3230
|
+
const topLevelExtensionBlocks = {};
|
|
3231
|
+
for (const [blockName, block] of Object.entries(topLevel.blocks)) {
|
|
3232
|
+
if (!legitimateBlockKeywords.has(block.keyword)) {
|
|
3233
|
+
reportUnsupportedTopLevelBlock(block);
|
|
3234
|
+
continue;
|
|
3235
|
+
}
|
|
3236
|
+
if (isEnumBlock(block)) topLevelEnums.push(block.block);
|
|
3237
|
+
else topLevelExtensionBlocks[blockName] = block;
|
|
3238
|
+
}
|
|
3239
|
+
for (const namespace of namespaceSymbols) for (const block of Object.values(namespace.blocks)) {
|
|
3240
|
+
if (isEnumBlock(block)) {
|
|
3241
|
+
diagnostics.push({
|
|
3242
|
+
code: "PSL_ENUM_NAMESPACE_NOT_SUPPORTED",
|
|
3243
|
+
message: `enum "${block.name}" inside namespace "${namespace.name}" is not supported; declare enum at the top level`,
|
|
3244
|
+
sourceId,
|
|
3245
|
+
span: nodePslSpan(block.node.syntax, sourceFile)
|
|
3246
|
+
});
|
|
3247
|
+
continue;
|
|
3248
|
+
}
|
|
3249
|
+
if (!legitimateBlockKeywords.has(block.keyword)) reportUnsupportedTopLevelBlock(block);
|
|
3250
|
+
}
|
|
3251
|
+
const enumResult = processEnumDeclarations({
|
|
3252
|
+
enumBlocks: topLevelEnums,
|
|
3253
|
+
sourceId,
|
|
3254
|
+
authoringContributions: input.authoringContributions,
|
|
3255
|
+
entityContext: {
|
|
3256
|
+
family: input.target.familyId,
|
|
3257
|
+
target: input.target.targetId,
|
|
3258
|
+
...ifDefined("codecLookup", input.codecLookup),
|
|
3259
|
+
sourceId,
|
|
3260
|
+
diagnostics: { push: (d) => {
|
|
3261
|
+
diagnostics.push(blindCast(d));
|
|
3262
|
+
} },
|
|
3263
|
+
...ifDefined("enumInferenceCodecs", input.enumInferenceCodecs)
|
|
3264
|
+
},
|
|
3265
|
+
diagnostics
|
|
3266
|
+
});
|
|
3267
|
+
const allEnumTypeDescriptors = new Map(enumResult.enumTypeDescriptors);
|
|
3268
|
+
const validEnumHandles = { ...enumResult.enumHandles };
|
|
3269
|
+
const enumHandlesByName = new Map(Object.entries(validEnumHandles));
|
|
3270
|
+
const entityTypesByDiscriminator = buildEntityTypesByDiscriminator(input.authoringContributions);
|
|
3271
|
+
const modelAttributesByName = buildModelAttributesByName(input.authoringContributions);
|
|
3272
|
+
const authoringWarnings = [];
|
|
3273
|
+
const extensionEntityContext = {
|
|
3274
|
+
family: input.target.familyId,
|
|
3275
|
+
target: input.target.targetId,
|
|
3276
|
+
...ifDefined("enumInferenceCodecs", input.enumInferenceCodecs),
|
|
3277
|
+
...ifDefined("codecLookup", input.codecLookup),
|
|
3278
|
+
sourceId,
|
|
3279
|
+
diagnostics: { push: (d) => {
|
|
3280
|
+
diagnostics.push(blindCast(d));
|
|
3281
|
+
} },
|
|
3282
|
+
warnings: authoringWarnings
|
|
3283
|
+
};
|
|
3284
|
+
const earlyModelMappingsByCoordinate = buildModelMappings(modelEntries, defaultNamespaceId, [], sourceId, sourceFile);
|
|
3285
|
+
const composedPslBlockDescriptors = input.authoringContributions?.pslBlockDescriptors ?? {};
|
|
3286
|
+
const namespaceExtensionEntities = /* @__PURE__ */ new Map();
|
|
3287
|
+
const mergeNamespaceExtensionEntities = (nsId, entities) => {
|
|
3288
|
+
if (Object.keys(entities).length === 0) return;
|
|
3289
|
+
const existing = namespaceExtensionEntities.get(nsId);
|
|
3290
|
+
if (existing === void 0) {
|
|
3291
|
+
namespaceExtensionEntities.set(nsId, entities);
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
const merged = { ...existing };
|
|
3295
|
+
for (const [entriesKey, slot] of Object.entries(entities)) {
|
|
3296
|
+
const existingSlot = existing[entriesKey];
|
|
3297
|
+
if (existingSlot !== void 0) {
|
|
3298
|
+
for (const name of Object.keys(slot)) if (Object.hasOwn(existingSlot, name)) diagnostics.push({
|
|
3299
|
+
code: "PSL_DUPLICATE_EXTENSION_ENTITY",
|
|
3300
|
+
message: `entries slot "${entriesKey}" in namespace "${nsId}": entity "${name}" is declared more than once in the same namespace.`,
|
|
3301
|
+
sourceId
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
merged[entriesKey] = {
|
|
3305
|
+
...existingSlot,
|
|
3306
|
+
...slot
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
namespaceExtensionEntities.set(nsId, merged);
|
|
3310
|
+
};
|
|
3311
|
+
for (const ns of namespaceSymbols) {
|
|
3312
|
+
if (ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME) continue;
|
|
3313
|
+
const nsId = resolveNamespaceIdForSqlTarget({
|
|
3314
|
+
bucketName: ns.name,
|
|
3315
|
+
targetId: input.target.targetId
|
|
3316
|
+
});
|
|
3317
|
+
if (nsId === void 0) continue;
|
|
3318
|
+
mergeNamespaceExtensionEntities(nsId, lowerExtensionBlocksForNamespace(ns.blocks, nsId, entityTypesByDiscriminator, extensionEntityContext, composedPslBlockDescriptors, (modelName) => earlyModelMappingsByCoordinate.get(modelCoordinateKey(nsId, modelName))?.tableName));
|
|
3319
|
+
}
|
|
3320
|
+
if (Object.keys(topLevelExtensionBlocks).length > 0) {
|
|
3321
|
+
const topLevelNsId = resolveNamespaceIdForSqlTarget({
|
|
3322
|
+
bucketName: UNSPECIFIED_PSL_NAMESPACE_NAME,
|
|
3323
|
+
targetId: input.target.targetId
|
|
3324
|
+
}) ?? defaultNamespaceId;
|
|
3325
|
+
mergeNamespaceExtensionEntities(topLevelNsId, lowerExtensionBlocksForNamespace(topLevelExtensionBlocks, topLevelNsId, entityTypesByDiscriminator, extensionEntityContext, composedPslBlockDescriptors, (modelName) => earlyModelMappingsByCoordinate.get(modelCoordinateKey(topLevelNsId, modelName))?.tableName));
|
|
3326
|
+
}
|
|
3327
|
+
const defaultNsExtensionValueSets = namespaceExtensionEntities.get(defaultNamespaceId)?.["valueSet"];
|
|
3328
|
+
if (defaultNsExtensionValueSets !== void 0) {
|
|
3329
|
+
for (const name of Object.keys(defaultNsExtensionValueSets)) if (Object.hasOwn(validEnumHandles, name)) diagnostics.push({
|
|
3330
|
+
code: "PSL_VALUE_SET_NAME_COLLISION",
|
|
3331
|
+
message: `namespace "${defaultNamespaceId}": name "${name}" is declared both as a domain enum and as an extension entity that derives a value-set; rename one`,
|
|
3332
|
+
sourceId
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
const isScalarRefinement = (symbol) => !symbol.isConstructor && symbol.baseType !== void 0 && input.scalarColumnDescriptors.has(symbol.baseType);
|
|
3336
|
+
const allNamedTypes = Object.values(topLevel.namedTypes);
|
|
3337
|
+
const namedTypeResult = resolveNamedTypeDeclarations({
|
|
3338
|
+
declarations: [...allNamedTypes.filter(isScalarRefinement), ...allNamedTypes.filter((symbol) => !isScalarRefinement(symbol))],
|
|
3339
|
+
sourceId,
|
|
3340
|
+
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
3341
|
+
scalarColumnDescriptors: input.scalarColumnDescriptors,
|
|
3342
|
+
composedExtensions,
|
|
3343
|
+
familyId: input.target.familyId,
|
|
3344
|
+
targetId: input.target.targetId,
|
|
3345
|
+
authoringContributions: input.authoringContributions,
|
|
3346
|
+
diagnostics
|
|
3347
|
+
});
|
|
3348
|
+
const storageTypes = { ...namedTypeResult.storageTypes };
|
|
3349
|
+
const modelMappingsByCoordinate = buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourceId, sourceFile);
|
|
3350
|
+
const modelMappings = /* @__PURE__ */ new Map();
|
|
3351
|
+
for (const mapping of modelMappingsByCoordinate.values()) modelMappings.set(mapping.model.name, mapping);
|
|
3352
|
+
const modelNodes = [];
|
|
3353
|
+
const fkRelationMetadata = [];
|
|
3354
|
+
const backrelationCandidates = [];
|
|
3355
|
+
const modelResolvedFields = /* @__PURE__ */ new Map();
|
|
3356
|
+
const crossSpaceRelationsByModel = /* @__PURE__ */ new Map();
|
|
3357
|
+
const modelAttributeEntitiesByNamespace = /* @__PURE__ */ new Map();
|
|
3358
|
+
for (const { model, namespaceId } of modelEntries) {
|
|
3359
|
+
const coordinate = modelCoordinateKey(namespaceId ?? defaultNamespaceId, model.name);
|
|
3360
|
+
const mapping = modelMappingsByCoordinate.get(coordinate);
|
|
3361
|
+
if (!mapping) continue;
|
|
3362
|
+
const result = buildModelNodeFromPsl({
|
|
3363
|
+
model,
|
|
3364
|
+
mapping,
|
|
3365
|
+
modelMappings,
|
|
3366
|
+
modelMappingsByCoordinate,
|
|
3367
|
+
modelNames,
|
|
3368
|
+
compositeTypeNames,
|
|
3369
|
+
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
3370
|
+
namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
|
|
3371
|
+
composedExtensions,
|
|
3372
|
+
composedExtensionContracts,
|
|
3373
|
+
familyId: input.target.familyId,
|
|
3374
|
+
targetId: input.target.targetId,
|
|
3375
|
+
authoringContributions: input.authoringContributions,
|
|
3376
|
+
defaultFunctionRegistry,
|
|
3377
|
+
generatorDescriptorById,
|
|
3378
|
+
scalarColumnDescriptors: input.scalarColumnDescriptors,
|
|
3379
|
+
sourceId,
|
|
3380
|
+
sourceFile,
|
|
3381
|
+
symbolTable: input.symbolTable,
|
|
3382
|
+
diagnostics,
|
|
3383
|
+
modelNamespaceIds,
|
|
3384
|
+
...enumHandlesByName.size > 0 ? { enumHandles: enumHandlesByName } : {},
|
|
3385
|
+
capabilities: input.capabilities,
|
|
3386
|
+
...namespaceExtensionEntities.size > 0 ? { namespaceExtensionEntities } : {},
|
|
3387
|
+
...ifDefined("codecLookup", input.codecLookup),
|
|
3388
|
+
modelAttributesByName,
|
|
3389
|
+
defaultNamespaceId
|
|
3390
|
+
});
|
|
3391
|
+
modelNodes.push(namespaceId !== void 0 ? {
|
|
3392
|
+
...result.modelNode,
|
|
3393
|
+
namespaceId
|
|
3394
|
+
} : result.modelNode);
|
|
3395
|
+
fkRelationMetadata.push(...result.fkRelationMetadata);
|
|
3396
|
+
backrelationCandidates.push(...result.backrelationCandidates);
|
|
3397
|
+
modelResolvedFields.set(coordinate, result.resolvedFields);
|
|
3398
|
+
if (result.crossSpaceRelations.length > 0) {
|
|
3399
|
+
const existing = crossSpaceRelationsByModel.get(model.name) ?? [];
|
|
3400
|
+
crossSpaceRelationsByModel.set(model.name, [...existing, ...result.crossSpaceRelations]);
|
|
3401
|
+
}
|
|
3402
|
+
if (Object.keys(result.modelAttributeEntities).length > 0) {
|
|
3403
|
+
const nsKey = namespaceId ?? defaultNamespaceId;
|
|
3404
|
+
const existingByAttribute = modelAttributeEntitiesByNamespace.get(nsKey) ?? {};
|
|
3405
|
+
for (const [attribute, keyed] of Object.entries(result.modelAttributeEntities)) existingByAttribute[attribute] = {
|
|
3406
|
+
...existingByAttribute[attribute] ?? {},
|
|
3407
|
+
...keyed
|
|
3408
|
+
};
|
|
3409
|
+
modelAttributeEntitiesByNamespace.set(nsKey, existingByAttribute);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
const { modelRelations, fkRelationsByPair, fkRelationsByDeclaringModel } = indexFkRelations({ fkRelationMetadata });
|
|
3413
|
+
const modelIdColumns = /* @__PURE__ */ new Map();
|
|
3414
|
+
const modelUniqueColumnSets = /* @__PURE__ */ new Map();
|
|
3415
|
+
for (const modelNode of modelNodes) {
|
|
3416
|
+
if (modelNode.id) modelIdColumns.set(modelNode.modelName, modelNode.id.columns);
|
|
3417
|
+
const uniqueColumnSets = [];
|
|
3418
|
+
if (modelNode.id) uniqueColumnSets.push(modelNode.id.columns);
|
|
3419
|
+
for (const unique of modelNode.uniques ?? []) uniqueColumnSets.push(unique.columns);
|
|
3420
|
+
modelUniqueColumnSets.set(modelNode.modelName, uniqueColumnSets);
|
|
3421
|
+
}
|
|
3422
|
+
applyBackrelationCandidates({
|
|
3423
|
+
backrelationCandidates,
|
|
3424
|
+
fkRelationsByPair,
|
|
3425
|
+
fkRelationsByDeclaringModel,
|
|
3426
|
+
modelIdColumns,
|
|
3427
|
+
modelUniqueColumnSets,
|
|
3428
|
+
modelRelations,
|
|
3429
|
+
diagnostics,
|
|
3430
|
+
sourceId
|
|
3431
|
+
});
|
|
3432
|
+
for (const [modelName, relations] of crossSpaceRelationsByModel) {
|
|
3433
|
+
const existing = modelRelations.get(modelName);
|
|
3434
|
+
if (existing) existing.push(...relations);
|
|
3435
|
+
else modelRelations.set(modelName, [...relations]);
|
|
3436
|
+
}
|
|
3437
|
+
const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(models, sourceFile, sourceId, diagnostics);
|
|
3438
|
+
const stiVariantNames = /* @__PURE__ */ new Set();
|
|
3439
|
+
for (const variantName of baseDeclarations.keys()) if (!(modelMappings.get(variantName)?.model.attributes.some((attr) => attr.name === "map") ?? false)) stiVariantNames.add(variantName);
|
|
3440
|
+
const { modelNodes: mtiLinkedModelNodes, syntheticPkFieldsByVariant } = materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames);
|
|
3441
|
+
const { modelNodes: stiColumnModelNodes, stiBaseFieldsByBase } = materializeStiVariantStorageColumns(mtiLinkedModelNodes, baseDeclarations, stiVariantNames);
|
|
3442
|
+
const valueObjects = buildValueObjects({
|
|
3443
|
+
compositeTypes,
|
|
3444
|
+
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
3445
|
+
namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
|
|
3446
|
+
scalarColumnDescriptors: input.scalarColumnDescriptors,
|
|
3447
|
+
composedExtensions,
|
|
3448
|
+
familyId: input.target.familyId,
|
|
3449
|
+
targetId: input.target.targetId,
|
|
3450
|
+
authoringContributions: input.authoringContributions,
|
|
3451
|
+
diagnostics,
|
|
3452
|
+
sourceId
|
|
3453
|
+
});
|
|
3454
|
+
if (diagnostics.length > 0) return notOk({
|
|
3455
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
3456
|
+
diagnostics
|
|
3457
|
+
});
|
|
3458
|
+
const { createNamespace } = input;
|
|
3459
|
+
const createNamespaceWithExtensions = (nsInput) => {
|
|
3460
|
+
const entities = namespaceExtensionEntities.get(nsInput.id);
|
|
3461
|
+
const attributeEntities = modelAttributeEntitiesByNamespace.get(nsInput.id);
|
|
3462
|
+
if (entities === void 0 && attributeEntities === void 0) return createNamespace(nsInput);
|
|
3463
|
+
if (attributeEntities !== void 0) {
|
|
3464
|
+
for (const slot of Object.keys(attributeEntities)) if (Object.hasOwn(nsInput.entries, slot) || entities !== void 0 && slot in entities) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `entries slot "${slot}" in namespace "${nsInput.id}" is contributed by both a model attribute and a block/base entry kind. A model-attribute entries key must be unique across the namespace's entry kinds.`, { meta: {
|
|
3465
|
+
slot,
|
|
3466
|
+
namespaceId: nsInput.id
|
|
3467
|
+
} });
|
|
3468
|
+
}
|
|
3469
|
+
const mergedValueSet = {
|
|
3470
|
+
...nsInput.entries["valueSet"],
|
|
3471
|
+
...entities?.["valueSet"]
|
|
3472
|
+
};
|
|
3473
|
+
const extended = {
|
|
3474
|
+
...nsInput,
|
|
3475
|
+
entries: {
|
|
3476
|
+
...nsInput.entries,
|
|
3477
|
+
...entities,
|
|
3478
|
+
...attributeEntities,
|
|
3479
|
+
...Object.keys(mergedValueSet).length > 0 ? { valueSet: mergedValueSet } : {}
|
|
3480
|
+
}
|
|
3481
|
+
};
|
|
3482
|
+
return createNamespace(extended);
|
|
3483
|
+
};
|
|
3484
|
+
const contract = buildSqlContractFromDefinition({
|
|
3485
|
+
target: input.target,
|
|
3486
|
+
warnings: authoringWarnings.length > 0 ? authoringWarnings : void 0,
|
|
3487
|
+
...ifDefined("extensions", buildComposedExtensionPackRefs(input.target, [...composedExtensions].sort(compareStrings), input.composedExtensionPackRefs)),
|
|
3488
|
+
...Object.keys(storageTypes).length > 0 ? { storageTypes } : {},
|
|
3489
|
+
...Object.keys(validEnumHandles).length > 0 ? { enums: validEnumHandles } : {},
|
|
3490
|
+
...namespaceExtensionEntities.size > 0 ? { namespaces: [...namespaceExtensionEntities.keys()] } : {},
|
|
3491
|
+
createNamespace: createNamespaceWithExtensions,
|
|
3492
|
+
models: stiColumnModelNodes.map((model) => ({
|
|
3493
|
+
...model,
|
|
3494
|
+
...modelRelations.has(model.modelName) ? { relations: [...modelRelations.get(model.modelName) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName)) } : {}
|
|
3495
|
+
}))
|
|
3496
|
+
}, input.codecLookup);
|
|
3497
|
+
const modelsForPatch = {};
|
|
3498
|
+
for (const [namespaceId, namespaceSlice] of Object.entries(contract.domain.namespaces)) for (const [modelName, model] of Object.entries(namespaceSlice.models)) {
|
|
3499
|
+
const coordinate = modelCoordinateKey(namespaceId, modelName);
|
|
3500
|
+
invariant(!Object.hasOwn(modelsForPatch, coordinate), `symbol table guarantees coordinate uniqueness; duplicate model "${namespaceId}.${modelName}" reached interpretation`);
|
|
3501
|
+
modelsForPatch[coordinate] = model;
|
|
3502
|
+
}
|
|
3503
|
+
let patchedModels = patchModelDomainFields(modelsForPatch, modelResolvedFields);
|
|
3504
|
+
const polyDiagnostics = [];
|
|
3505
|
+
patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, input.target.defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, polyDiagnostics);
|
|
3506
|
+
if (polyDiagnostics.length > 0) return notOk({
|
|
3507
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
3508
|
+
diagnostics: polyDiagnostics
|
|
3509
|
+
});
|
|
3510
|
+
const variantModelNames = new Set(baseDeclarations.keys());
|
|
3511
|
+
const filteredRoots = Object.fromEntries(Object.entries(contract.roots).filter(([, crossReference]) => !variantModelNames.has(crossReference.model)));
|
|
3512
|
+
return ok({
|
|
3513
|
+
...contract,
|
|
3514
|
+
roots: filteredRoots,
|
|
3515
|
+
domain: { namespaces: Object.fromEntries(Object.entries(contract.domain.namespaces).map(([namespaceId, namespaceSlice]) => [namespaceId, {
|
|
3516
|
+
models: Object.fromEntries(Object.entries(namespaceSlice.models).map(([modelName, model]) => [modelName, patchedModels[modelCoordinateKey(namespaceId, modelName)] ?? model])),
|
|
3517
|
+
...ifDefined("enum", namespaceSlice.enum),
|
|
3518
|
+
...ifDefined("valueObjects", namespaceSlice.valueObjects),
|
|
3519
|
+
...namespaceId === input.target.defaultNamespaceId && Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
|
|
3520
|
+
}])) }
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
//#endregion
|
|
3524
|
+
export { interpretPslDocumentToSqlContract as t };
|
|
3525
|
+
|
|
3526
|
+
//# sourceMappingURL=interpreter-5WgpL-ax-_nqHLUTr.mjs.map
|