zenstack 0.3.9 → 0.3.11
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/bundle/cli/index.js +227 -171
- package/bundle/language-server/main.js +1 -1
- package/bundle/res/stdlib.zmodel +24 -0
- package/package.json +2 -2
- package/src/cli/cli-util.ts +98 -2
- package/src/cli/index.ts +52 -23
- package/src/generator/constants.ts +1 -1
- package/src/generator/prisma/query-guard-generator.ts +3 -3
- package/src/generator/react-hooks/index.ts +4 -2
- package/src/generator/service/index.ts +2 -2
- package/src/language-server/constants.ts +2 -1
- package/src/res/stdlib.zmodel +24 -0
- package/src/telemetry.ts +9 -0
- package/src/utils/pkg-utils.ts +63 -0
|
@@ -6205,4 +6205,4 @@ See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return r}
|
|
|
6205
6205
|
"imports": [],
|
|
6206
6206
|
"interfaces": [],
|
|
6207
6207
|
"usedGrammars": []
|
|
6208
|
-
}`);var QL={languageId:"zmodel",fileExtensions:[".zmodel"],caseInsensitive:!1},NE={AstReflection:()=>new ms},DE={Grammar:()=>SE(),LanguageMetaData:()=>QL,parser:{}};var an=St(Dn()),IE=St(fn());function bi(r,e){let t=r.reduce((n,i)=>{var o;return n[i.name]=(o=n[i.name])!=null?o:[],n[i.name].push(i),n},{});for(let[n,i]of Object.entries(t))i.length>1&&e("error",`Duplicated declaration name "${n}"`,{node:i[1]})}function Ol(r){if(Dl(r)&&typeof r.value=="string")return r.value}function eF(r,e){switch(r){case"Any":return!0;case"Float":return e==="Any"||e==="Int"||e==="Float";default:return e==="Any"||e===r}}function ny(r){switch(r){case"Any":case"Boolean":case"String":case"DateTime":case"Int":case"Float":case"Null":return r;case"BigInt":return"Int";case"Decimal":return"Float";case"Json":case"Bytes":return"Any"}}function wE(r,e,t){var s,u;let n=r.$resolvedType;if(!n)return!1;let i=e.type.type,o=e.type.array,a=e.type.reference;if(i){if(typeof(n==null?void 0:n.decl)!="string")return!1;if(i==="FieldReference")return o?RE(r.value)&&!r.value.items.find(c=>!ey(c)||!ea(c.target.ref)):ey(r.value)&&ea(r.value.target.ref);if(i==="ContextType")if(ea(t.$container)){if(!((u=(s=t.$container)==null?void 0:s.type)!=null&&u.type))return!1;i=ny(t.$container.type.type)}else i="Any";return eF(i,n.decl)&&o===n.array}else return(a==null?void 0:a.ref)===n.decl&&o===n.array}var $l=class extends an.DefaultLinker{constructor(t){super(t);this.descriptions=t.workspace.AstNodeDescriptionProvider}link(i){return no(this,arguments,function*(t,n=IE.CancellationToken.None){var o,a;if(!(((o=t.parseResult.lexerErrors)==null?void 0:o.length)>0||((a=t.parseResult.parserErrors)==null?void 0:a.length)>0)){for(let s of(0,an.streamContents)(t.parseResult.value))yield(0,an.interruptAndCheck)(n),this.resolve(s,t);t.state=an.DocumentState.Linked}})}linkReference(t,n,i,o){if(!this.resolveFromScopeProviders(t,n,i,o)){let a=t[n];this.doLink({reference:a,container:t,property:n},i)}}resolveFromScopeProviders(t,n,i,o){let a=t[n];for(let s of o){let u=s(a.$refText);if(u)return a._ref=u,a._nodeDescription=this.descriptions.createDescription(u,u.name,i),u}return null}resolve(t,n,i=[]){if(!t.$resolvedType)switch(t.$type){case Nl:this.resolveLiteral(t);break;case Sl:this.resolveInvocation(t,n,i);break;case kl:this.resolveArray(t,n,i);break;case wl:this.resolveReference(t,n,i);break;case Zh:this.resolveMemberAccess(t,n,i);break;case ry:this.resolveUnary(t,n,i);break;case Jh:this.resolveBinary(t,n,i);break;case ty:this.resolveThis(t,n,i);break;case Qh:this.resolveNull(t,n,i);break;case AE:this.resolveAttributeArg(t,n,i);break;default:this.resolveDefault(t,n,i);break}}resolveBinary(t,n,i){switch(t.operator){case">":case">=":case"<":case"<=":case"==":case"!=":case"&&":case"||":this.resolve(t.left,n,i),this.resolve(t.right,n,i),this.resolveToBuiltinTypeOrDecl(t,"Boolean");break;case"?":case"!":case"^":this.resolveCollectionPredicate(t,n,i);break;default:throw Error(`Unsupported binary operator: ${t.operator}`)}}resolveUnary(t,n,i){this.resolve(t.operand,n,i),t.$resolvedType=t.operand.$resolvedType}resolveReference(t,n,i){this.linkReference(t,"target",n,i),t.args.forEach(o=>this.resolve(o,n,i)),t.target.ref&&(t.target.ref.$type===xl?this.resolveToBuiltinTypeOrDecl(t,t.target.ref.$container):this.resolveToDeclaredType(t,t.target.ref.type))}resolveArray(t,n,i){t.items.forEach(a=>this.resolve(a,n,i));let o=t.items[0].$resolvedType;o!=null&&o.decl&&this.resolveToBuiltinTypeOrDecl(t,o.decl,!0)}resolveInvocation(t,n,i){if(this.linkReference(t,"function",n,i),t.args.forEach(o=>this.resolve(o,n,i)),t.function.ref){let o=t.function.ref;this.resolveToDeclaredType(t,o.returnType)}}resolveLiteral(t){let n=typeof t.value=="string"?"String":typeof t.value=="boolean"?"Boolean":typeof t.value=="number"?"Int":void 0;n&&this.resolveToBuiltinTypeOrDecl(t,n)}resolveMemberAccess(t,n,i){this.resolve(t.operand,n,i);let o=t.operand.$resolvedType;if(o&&!o.array&&Vi(o.decl)){let a=o.decl;i=[u=>a.fields.find(c=>c.name===u),...i]}this.linkReference(t,"member",n,i),t.member.ref&&this.resolveToDeclaredType(t,t.member.ref.type)}resolveCollectionPredicate(t,n,i){this.resolve(t.left,n,i);let o=t.left.$resolvedType;if(o&&Vi(o.decl)&&o.array){let a=o.decl;i=[u=>a.fields.find(c=>c.name===u),...i],this.resolve(t.right,n,i),this.resolveToBuiltinTypeOrDecl(t,"Boolean")}else console.warn("Unresolved collection predicate")}resolveThis(t,n,i){let o=t.$container;for(;o&&!Vi(o);)o=o.$container;o&&this.resolveToBuiltinTypeOrDecl(t,o)}resolveNull(t,n,i){this.resolveToBuiltinTypeOrDecl(t,"Null")}resolveAttributeArg(t,n,i){this.resolve(t.value,n,i),t.$resolvedType=t.value.$resolvedType}resolveDefault(t,n,i){for(let[o,a]of Object.entries(t))o.startsWith("$")||(0,an.isReference)(a)&&this.linkReference(t,o,n,i);for(let o of(0,an.streamContents)(t))this.resolve(o,n,i)}resolveToDeclaredType(t,n){if(n.type){let i=ny(n.type);t.$resolvedType={decl:i,array:n.array}}else n.reference&&(t.$resolvedType={decl:n.reference.ref,array:n.array})}resolveToBuiltinTypeOrDecl(t,n,i=!1){t.$resolvedType={decl:n,array:i}}};var ra=St(Dn());var ta=class extends ra.DefaultScopeComputation{constructor(t){super(t);this.services=t}computeExports(t,n){return no(this,null,function*(){let i=yield As(ta.prototype,this,"computeExports").call(this,t,n);for(let o of(0,ra.streamAllContents)(t.parseResult.value))if(n&&(yield(0,ra.interruptAndCheck)(n)),PE(o)){let a=this.services.workspace.AstNodeDescriptionProvider.createDescription(o,o.name,t);i.push(a)}return i})}};var LE=St(Dn());var iy=["postgresql","mysql","sqlite","sqlserver"],OE=["String","Int","Float","Decimal","BigInt","Boolean","Bytes","DateTime"],Ml="stdlib.zmodel";var hs=class{validate(e,t){var n;bi(e.declarations,t),(n=e.$document)!=null&&n.uri.path.endsWith(Ml)||this.validateDataSources(e,t)}validateDataSources(e,t){let n=e.declarations.filter(i=>CE(i));n.length===0?t("error","Model must define a datasource",{node:e}):n.length>1&&t("error","Multiple datasource declarations are not allowed",{node:n[1]})}};var tF=["provider","url","shadowDatabaseUrl","relationMode"],ys=class{validate(e,t){bi(e.fields,t),this.validateFields(e,t),this.validateProvider(e,t),this.validateUrl(e,t),this.validateRelationMode(e,t)}validateFields(e,t){e.fields.forEach(n=>{tF.includes(n.name)||t("error",`Unexpected field "${n.name}"`,{node:n})})}validateProvider(e,t){let n=e.fields.find(o=>o.name==="provider");if(!n){t("error",'datasource must include a "provider" field',{node:e});return}let i=Ol(n.value);i?iy.includes(i)||t("error",`Provider "${i}" is not supported. Choose from ${iy.map(o=>'"'+o+'"').join(" | ")}.`,{node:n.value}):t("error",'"provider" must be set to a string literal',{node:n.value})}validateUrl(e,t){var i;e.fields.find(o=>o.name==="url")||t("error",'datasource must include a "url" field',{node:e});for(let o of["url","shadowDatabaseUrl"]){let a=e.fields.find(u=>u.name===o);if(!a)continue;!Ol(a.value)&&!(kE(a.value)&&((i=a.value.function.ref)==null?void 0:i.name)==="env")&&t("error",`"${o}" must be set to a string literal or an invocation of "env" function`,{node:a.value})}}validateRelationMode(e,t){let n=e.fields.find(i=>i.name==="relationMode");if(n){let i=Ol(n.value);(!i||!["foreignKeys","prisma"].includes(i))&&t("error",'"relationMode" must be set to "foreignKeys" or "prisma"',{node:n.value})}}};var ME=St($E()),gs=class{validate(e,t){bi(e.fields,t),this.validateFields(e,t),this.validateAttributes(e,t)}validateFields(e,t){let n=e.fields.filter(i=>i.attributes.find(o=>{var a;return((a=o.decl.ref)==null?void 0:a.name)==="@id"}));n.length===0?t("error","Model must include a field with @id attribute",{node:e}):n.length>1?t("error","Model can include at most one field with @id attribute",{node:e}):(n[0].type.optional&&t("error","Field with @id attribute must not be optional",{node:n[0]}),(n[0].type.array||!n[0].type.type||!OE.includes(n[0].type.type))&&t("error","Field with @id attribute must be of scalar type",{node:n[0]})),e.fields.forEach(i=>this.validateField(i,t))}validateField(e,t){var n;e.type.array&&e.type.optional&&t("error","Optional lists are not supported. Use either `Type[]` or `Type?`",{node:e.type}),e.attributes.forEach(i=>this.validateAttributeApplication(i,t)),Vi((n=e.type.reference)==null?void 0:n.ref)&&this.validateRelationField(e,t)}validateAttributes(e,t){e.attributes.forEach(n=>{this.validateAttributeApplication(n,t)})}validateAttributeApplication(e,t){let n=e.decl.ref;if(!n)return;let i=e.$container;if(n.name==="@@@targetField"&&!_E(i)){t("error",`attribute "${n.name}" can only be used on attribute declarations`,{node:e});return}ea(i)&&!this.isValidAttributeTarget(n,i)&&t("error",`attribute "${n.name}" cannot be used on this type of field`,{node:e});let o=new Set;for(let s of e.args){let u;if(s.name){if(u=n.params.find(c=>c.name===s.name),!u)return t("error",`Attribute "${n.name}" doesn't have a parameter named "${s.name}"`,{node:s}),!1}else if(u=n.params.find(c=>c.default&&!o.has(c)),!u)return t("error","Unexpected unnamed argument",{node:s}),!1;if(!wE(s,u,e))return t("error","Value is not assignable to parameter",{node:s}),!1;if(o.has(u))return t("error",`Parameter "${u.name}" is already provided`,{node:s}),!1;o.add(u),s.$resolvedParam=u}let a=n.params.filter(s=>!s.type.optional&&!o.has(s));return a.length>0?(t("error",`Required ${(0,ME.default)("parameter",a.length)} not provided: ${a.map(s=>s.name).join(", ")}`,{node:e}),!1):!0}isValidAttributeTarget(e,t){var a;let n=e.attributes.find(s=>{var u;return((u=s.decl.ref)==null?void 0:u.name)==="@@@targetField"});if(!n)return!0;let i=n.args[0].value.items.map(s=>{var u;return(u=s.target.ref)==null?void 0:u.name}),o=!1;for(let s of i){switch(s){case"StringField":o=o||t.type.type==="String";break;case"IntField":o=o||t.type.type==="Int";break;case"FloatField":o=o||t.type.type==="Float";break;case"DecimalField":o=o||t.type.type==="Decimal";break;case"BooleanField":o=o||t.type.type==="Boolean";break;case"DateTimeField":o=o||t.type.type==="DateTime";break;case"JsonField":o=o||t.type.type==="Json";break;case"BytesField":o=o||t.type.type==="Bytes";break;case"ModelField":o=o||Vi((a=t.type.reference)==null?void 0:a.ref);break;default:break}if(o)break}return o}parseRelation(e,t){let n=e.attributes.find(u=>{var c;return((c=u.decl.ref)==null?void 0:c.name)==="@relation"}),i,o,a,s=!0;if(!n)return{attr:n,name:i,fields:o,references:a,valid:!0};for(let u of n.args)!u.name||u.name==="name"?Dl(u.value)&&(i=u.value.value):u.name==="fields"?(o=u.value.items,o.length===0&&(t&&t("error",'"fields" value cannot be emtpy',{node:u}),s=!1)):u.name==="references"&&(a=u.value.items,a.length===0&&(t&&t("error",'"references" value cannot be emtpy',{node:u}),s=!1));return{attr:n,name:i,fields:o,references:a,valid:s}}validateRelationField(e,t){var c,l,d,g;let n=this.parseRelation(e,t);if(!n.valid)return;let i=e.type.reference.ref,o=i.fields.filter(h=>{var p;return((p=h.type.reference)==null?void 0:p.ref)===e.$container});if(o=o.filter(h=>{let p=this.parseRelation(h);return p.valid&&p.name===n.name}),o.length===0){t("error",`The relation field "${e.name}" on model "${e.$container.name}" is missing an opposite relation field on model "${i.name}"`,{node:e});return}else if(o.length>1){o.forEach(h=>t("error",`Fields ${o.map(p=>'"'+p.name+'"').join(", ")} on model "${i.name}" refer to the same relation to model "${e.$container.name}"`,{node:h}));return}let a=o[0],s=this.parseRelation(a),u;if(((c=n==null?void 0:n.references)==null?void 0:c.length)&&((l=n.fields)==null?void 0:l.length))if((s==null?void 0:s.references)||(s==null?void 0:s.fields)){t("error",'"fields" and "references" must be provided only on one side of relation field',{node:a});return}else u=a;else if(((d=s==null?void 0:s.references)==null?void 0:d.length)&&((g=s.fields)==null?void 0:g.length))if((n==null?void 0:n.references)||(n==null?void 0:n.fields)){t("error",'"fields" and "references" must be provided only on one side of relation field',{node:e});return}else u=e;else{[e,a].forEach(h=>t("error",'Field for one side of relation must carry @relation attribute with both "fields" and "references" fields',{node:h}));return}if(!u.type.array&&!u.type.optional){t("error","Relation field needs to be list or optional",{node:u});return}}};var vs=class{validate(e,t){}};var Ts=class{validate(e,t){bi(e.fields,t)}};var Ll=class extends LE.ValidationRegistry{constructor(e){super(e);let t=e.validation.ZModelValidator,n={Model:t.checkModel,DataSource:t.checkDataSource,DataModel:t.checkDataModel,Enum:t.checkEnum,Attribute:t.checkAttribute};this.register(n,t)}},Fl=class{shouldCheck(e){let t,n=e;for(;n;){if(n.$document){t=n.$document;break}n=n.$container}return(t==null?void 0:t.parseResult.lexerErrors.length)===0&&(t==null?void 0:t.parseResult.parserErrors.length)===0}checkModel(e,t){this.shouldCheck(e)&&new hs().validate(e,t)}checkDataSource(e,t){this.shouldCheck(e)&&new ys().validate(e,t)}checkDataModel(e,t){this.shouldCheck(e)&&new gs().validate(e,t)}checkEnum(e,t){this.shouldCheck(e)&&new Ts().validate(e,t)}checkAttribute(e,t){this.shouldCheck(e)&&new vs().validate(e,t)}};var UE=St(Ne()),HE=St(yu());var FE=St(Dn()),qE=St(require("path")),jE=St(gn());var Yi=class extends FE.DefaultWorkspaceManager{loadAdditionalDocuments(e,t){return no(this,null,function*(){yield As(Yi.prototype,this,"loadAdditionalDocuments").call(this,e,t);let n=jE.URI.file(qE.default.join(__dirname,"../res",Ml));console.log(`Adding stdlib document from ${n}`);let i=this.langiumDocuments.getOrCreateDocument(n);t(i)})}};var ql=St(Dn()),GE=St(Ne()),Rs=class{constructor(e){this.nameProvider=e.references.NameProvider,this.references=e.references.References,this.grammarConfig=e.parser.GrammarConfig}getDefinition(e,t){let n=e.parseResult.value;if(n.$cstNode){let i=n.$cstNode,o=(0,ql.findDeclarationNodeAtOffset)(i,e.textDocument.offsetAt(t.position),this.grammarConfig.nameRegexp);if(o)return this.collectLocationLinks(o,t)}}collectLocationLinks(e,t){var i;let n=this.findLink(e);if(n&&!n.targetDocument.textDocument.uri.endsWith("stdlib.zmodel"))return[GE.LocationLink.create(n.targetDocument.textDocument.uri,((i=n.target.element.$cstNode)!=null?i:n.target).range,n.target.range,n.source.range)]}findLink(e){let t=this.references.findDeclarationNode(e);if(t!=null&&t.element){let n=(0,ql.getDocument)(t.element);if(t&&n)return{source:e,target:t,targetDocument:n}}}};var rF={references:{ScopeComputation:r=>new ta(r),Linker:r=>new $l(r)},validation:{ValidationRegistry:r=>new Ll(r),ZModelValidator:()=>new Fl},lsp:{DefinitionProvider:r=>new Rs(r)}};function nF(r){return{ServiceRegistry:()=>new st.DefaultServiceRegistry,lsp:{Connection:()=>r.connection,LanguageServer:e=>new st.DefaultLanguageServer(e)},workspace:{LangiumDocuments:e=>new st.DefaultLangiumDocuments(e),LangiumDocumentFactory:e=>new st.DefaultLangiumDocumentFactory(e),DocumentBuilder:e=>new st.DefaultDocumentBuilder(e),TextDocuments:()=>new UE.TextDocuments(HE.TextDocument),TextDocumentFactory:e=>new st.DefaultTextDocumentFactory(e),IndexManager:e=>new st.DefaultIndexManager(e),WorkspaceManager:e=>new Yi(e),FileSystemProvider:e=>r.fileSystemProvider(e),MutexLock:()=>new st.MutexLock,ConfigurationProvider:e=>new st.DefaultConfigurationProvider(e)}}}function WE(r){let e=(0,st.inject)(nF(r),NE),t=(0,st.inject)((0,st.createDefaultModule)({shared:e}),DE,rF);return e.ServiceRegistry.register(t),{shared:e,ZModel:t}}var iF=(0,jl.createConnection)(jl.ProposedFeatures.all),{shared:oF}=WE(fy({connection:iF},BE.NodeFileSystem));(0,KE.startLanguageServer)(oF);
|
|
6208
|
+
}`);var QL={languageId:"zmodel",fileExtensions:[".zmodel"],caseInsensitive:!1},NE={AstReflection:()=>new ms},DE={Grammar:()=>SE(),LanguageMetaData:()=>QL,parser:{}};var an=St(Dn()),IE=St(fn());function bi(r,e){let t=r.reduce((n,i)=>{var o;return n[i.name]=(o=n[i.name])!=null?o:[],n[i.name].push(i),n},{});for(let[n,i]of Object.entries(t))i.length>1&&e("error",`Duplicated declaration name "${n}"`,{node:i[1]})}function Ol(r){if(Dl(r)&&typeof r.value=="string")return r.value}function eF(r,e){switch(r){case"Any":return!0;case"Float":return e==="Any"||e==="Int"||e==="Float";default:return e==="Any"||e===r}}function ny(r){switch(r){case"Any":case"Boolean":case"String":case"DateTime":case"Int":case"Float":case"Null":return r;case"BigInt":return"Int";case"Decimal":return"Float";case"Json":case"Bytes":return"Any"}}function wE(r,e,t){var s,u;let n=r.$resolvedType;if(!n)return!1;let i=e.type.type,o=e.type.array,a=e.type.reference;if(i){if(typeof(n==null?void 0:n.decl)!="string")return!1;if(i==="FieldReference")return o?RE(r.value)&&!r.value.items.find(c=>!ey(c)||!ea(c.target.ref)):ey(r.value)&&ea(r.value.target.ref);if(i==="ContextType")if(ea(t.$container)){if(!((u=(s=t.$container)==null?void 0:s.type)!=null&&u.type))return!1;i=ny(t.$container.type.type)}else i="Any";return eF(i,n.decl)&&o===n.array}else return(a==null?void 0:a.ref)===n.decl&&o===n.array}var $l=class extends an.DefaultLinker{constructor(t){super(t);this.descriptions=t.workspace.AstNodeDescriptionProvider}link(i){return no(this,arguments,function*(t,n=IE.CancellationToken.None){var o,a;if(!(((o=t.parseResult.lexerErrors)==null?void 0:o.length)>0||((a=t.parseResult.parserErrors)==null?void 0:a.length)>0)){for(let s of(0,an.streamContents)(t.parseResult.value))yield(0,an.interruptAndCheck)(n),this.resolve(s,t);t.state=an.DocumentState.Linked}})}linkReference(t,n,i,o){if(!this.resolveFromScopeProviders(t,n,i,o)){let a=t[n];this.doLink({reference:a,container:t,property:n},i)}}resolveFromScopeProviders(t,n,i,o){let a=t[n];for(let s of o){let u=s(a.$refText);if(u)return a._ref=u,a._nodeDescription=this.descriptions.createDescription(u,u.name,i),u}return null}resolve(t,n,i=[]){if(!t.$resolvedType)switch(t.$type){case Nl:this.resolveLiteral(t);break;case Sl:this.resolveInvocation(t,n,i);break;case kl:this.resolveArray(t,n,i);break;case wl:this.resolveReference(t,n,i);break;case Zh:this.resolveMemberAccess(t,n,i);break;case ry:this.resolveUnary(t,n,i);break;case Jh:this.resolveBinary(t,n,i);break;case ty:this.resolveThis(t,n,i);break;case Qh:this.resolveNull(t,n,i);break;case AE:this.resolveAttributeArg(t,n,i);break;default:this.resolveDefault(t,n,i);break}}resolveBinary(t,n,i){switch(t.operator){case">":case">=":case"<":case"<=":case"==":case"!=":case"&&":case"||":this.resolve(t.left,n,i),this.resolve(t.right,n,i),this.resolveToBuiltinTypeOrDecl(t,"Boolean");break;case"?":case"!":case"^":this.resolveCollectionPredicate(t,n,i);break;default:throw Error(`Unsupported binary operator: ${t.operator}`)}}resolveUnary(t,n,i){this.resolve(t.operand,n,i),t.$resolvedType=t.operand.$resolvedType}resolveReference(t,n,i){this.linkReference(t,"target",n,i),t.args.forEach(o=>this.resolve(o,n,i)),t.target.ref&&(t.target.ref.$type===xl?this.resolveToBuiltinTypeOrDecl(t,t.target.ref.$container):this.resolveToDeclaredType(t,t.target.ref.type))}resolveArray(t,n,i){t.items.forEach(a=>this.resolve(a,n,i));let o=t.items[0].$resolvedType;o!=null&&o.decl&&this.resolveToBuiltinTypeOrDecl(t,o.decl,!0)}resolveInvocation(t,n,i){if(this.linkReference(t,"function",n,i),t.args.forEach(o=>this.resolve(o,n,i)),t.function.ref){let o=t.function.ref;this.resolveToDeclaredType(t,o.returnType)}}resolveLiteral(t){let n=typeof t.value=="string"?"String":typeof t.value=="boolean"?"Boolean":typeof t.value=="number"?"Int":void 0;n&&this.resolveToBuiltinTypeOrDecl(t,n)}resolveMemberAccess(t,n,i){this.resolve(t.operand,n,i);let o=t.operand.$resolvedType;if(o&&!o.array&&Vi(o.decl)){let a=o.decl;i=[u=>a.fields.find(c=>c.name===u),...i]}this.linkReference(t,"member",n,i),t.member.ref&&this.resolveToDeclaredType(t,t.member.ref.type)}resolveCollectionPredicate(t,n,i){this.resolve(t.left,n,i);let o=t.left.$resolvedType;if(o&&Vi(o.decl)&&o.array){let a=o.decl;i=[u=>a.fields.find(c=>c.name===u),...i],this.resolve(t.right,n,i),this.resolveToBuiltinTypeOrDecl(t,"Boolean")}else console.warn("Unresolved collection predicate")}resolveThis(t,n,i){let o=t.$container;for(;o&&!Vi(o);)o=o.$container;o&&this.resolveToBuiltinTypeOrDecl(t,o)}resolveNull(t,n,i){this.resolveToBuiltinTypeOrDecl(t,"Null")}resolveAttributeArg(t,n,i){this.resolve(t.value,n,i),t.$resolvedType=t.value.$resolvedType}resolveDefault(t,n,i){for(let[o,a]of Object.entries(t))o.startsWith("$")||(0,an.isReference)(a)&&this.linkReference(t,o,n,i);for(let o of(0,an.streamContents)(t))this.resolve(o,n,i)}resolveToDeclaredType(t,n){if(n.type){let i=ny(n.type);t.$resolvedType={decl:i,array:n.array}}else n.reference&&(t.$resolvedType={decl:n.reference.ref,array:n.array})}resolveToBuiltinTypeOrDecl(t,n,i=!1){t.$resolvedType={decl:n,array:i}}};var ra=St(Dn());var ta=class extends ra.DefaultScopeComputation{constructor(t){super(t);this.services=t}computeExports(t,n){return no(this,null,function*(){let i=yield As(ta.prototype,this,"computeExports").call(this,t,n);for(let o of(0,ra.streamAllContents)(t.parseResult.value))if(n&&(yield(0,ra.interruptAndCheck)(n)),PE(o)){let a=this.services.workspace.AstNodeDescriptionProvider.createDescription(o,o.name,t);i.push(a)}return i})}};var LE=St(Dn());var iy=["sqlite","postgresql","mysql","sqlserver","cockroachdb"],OE=["String","Int","Float","Decimal","BigInt","Boolean","Bytes","DateTime"],Ml="stdlib.zmodel";var hs=class{validate(e,t){var n;bi(e.declarations,t),(n=e.$document)!=null&&n.uri.path.endsWith(Ml)||this.validateDataSources(e,t)}validateDataSources(e,t){let n=e.declarations.filter(i=>CE(i));n.length===0?t("error","Model must define a datasource",{node:e}):n.length>1&&t("error","Multiple datasource declarations are not allowed",{node:n[1]})}};var tF=["provider","url","shadowDatabaseUrl","relationMode"],ys=class{validate(e,t){bi(e.fields,t),this.validateFields(e,t),this.validateProvider(e,t),this.validateUrl(e,t),this.validateRelationMode(e,t)}validateFields(e,t){e.fields.forEach(n=>{tF.includes(n.name)||t("error",`Unexpected field "${n.name}"`,{node:n})})}validateProvider(e,t){let n=e.fields.find(o=>o.name==="provider");if(!n){t("error",'datasource must include a "provider" field',{node:e});return}let i=Ol(n.value);i?iy.includes(i)||t("error",`Provider "${i}" is not supported. Choose from ${iy.map(o=>'"'+o+'"').join(" | ")}.`,{node:n.value}):t("error",'"provider" must be set to a string literal',{node:n.value})}validateUrl(e,t){var i;e.fields.find(o=>o.name==="url")||t("error",'datasource must include a "url" field',{node:e});for(let o of["url","shadowDatabaseUrl"]){let a=e.fields.find(u=>u.name===o);if(!a)continue;!Ol(a.value)&&!(kE(a.value)&&((i=a.value.function.ref)==null?void 0:i.name)==="env")&&t("error",`"${o}" must be set to a string literal or an invocation of "env" function`,{node:a.value})}}validateRelationMode(e,t){let n=e.fields.find(i=>i.name==="relationMode");if(n){let i=Ol(n.value);(!i||!["foreignKeys","prisma"].includes(i))&&t("error",'"relationMode" must be set to "foreignKeys" or "prisma"',{node:n.value})}}};var ME=St($E()),gs=class{validate(e,t){bi(e.fields,t),this.validateFields(e,t),this.validateAttributes(e,t)}validateFields(e,t){let n=e.fields.filter(i=>i.attributes.find(o=>{var a;return((a=o.decl.ref)==null?void 0:a.name)==="@id"}));n.length===0?t("error","Model must include a field with @id attribute",{node:e}):n.length>1?t("error","Model can include at most one field with @id attribute",{node:e}):(n[0].type.optional&&t("error","Field with @id attribute must not be optional",{node:n[0]}),(n[0].type.array||!n[0].type.type||!OE.includes(n[0].type.type))&&t("error","Field with @id attribute must be of scalar type",{node:n[0]})),e.fields.forEach(i=>this.validateField(i,t))}validateField(e,t){var n;e.type.array&&e.type.optional&&t("error","Optional lists are not supported. Use either `Type[]` or `Type?`",{node:e.type}),e.attributes.forEach(i=>this.validateAttributeApplication(i,t)),Vi((n=e.type.reference)==null?void 0:n.ref)&&this.validateRelationField(e,t)}validateAttributes(e,t){e.attributes.forEach(n=>{this.validateAttributeApplication(n,t)})}validateAttributeApplication(e,t){let n=e.decl.ref;if(!n)return;let i=e.$container;if(n.name==="@@@targetField"&&!_E(i)){t("error",`attribute "${n.name}" can only be used on attribute declarations`,{node:e});return}ea(i)&&!this.isValidAttributeTarget(n,i)&&t("error",`attribute "${n.name}" cannot be used on this type of field`,{node:e});let o=new Set;for(let s of e.args){let u;if(s.name){if(u=n.params.find(c=>c.name===s.name),!u)return t("error",`Attribute "${n.name}" doesn't have a parameter named "${s.name}"`,{node:s}),!1}else if(u=n.params.find(c=>c.default&&!o.has(c)),!u)return t("error","Unexpected unnamed argument",{node:s}),!1;if(!wE(s,u,e))return t("error","Value is not assignable to parameter",{node:s}),!1;if(o.has(u))return t("error",`Parameter "${u.name}" is already provided`,{node:s}),!1;o.add(u),s.$resolvedParam=u}let a=n.params.filter(s=>!s.type.optional&&!o.has(s));return a.length>0?(t("error",`Required ${(0,ME.default)("parameter",a.length)} not provided: ${a.map(s=>s.name).join(", ")}`,{node:e}),!1):!0}isValidAttributeTarget(e,t){var a;let n=e.attributes.find(s=>{var u;return((u=s.decl.ref)==null?void 0:u.name)==="@@@targetField"});if(!n)return!0;let i=n.args[0].value.items.map(s=>{var u;return(u=s.target.ref)==null?void 0:u.name}),o=!1;for(let s of i){switch(s){case"StringField":o=o||t.type.type==="String";break;case"IntField":o=o||t.type.type==="Int";break;case"FloatField":o=o||t.type.type==="Float";break;case"DecimalField":o=o||t.type.type==="Decimal";break;case"BooleanField":o=o||t.type.type==="Boolean";break;case"DateTimeField":o=o||t.type.type==="DateTime";break;case"JsonField":o=o||t.type.type==="Json";break;case"BytesField":o=o||t.type.type==="Bytes";break;case"ModelField":o=o||Vi((a=t.type.reference)==null?void 0:a.ref);break;default:break}if(o)break}return o}parseRelation(e,t){let n=e.attributes.find(u=>{var c;return((c=u.decl.ref)==null?void 0:c.name)==="@relation"}),i,o,a,s=!0;if(!n)return{attr:n,name:i,fields:o,references:a,valid:!0};for(let u of n.args)!u.name||u.name==="name"?Dl(u.value)&&(i=u.value.value):u.name==="fields"?(o=u.value.items,o.length===0&&(t&&t("error",'"fields" value cannot be emtpy',{node:u}),s=!1)):u.name==="references"&&(a=u.value.items,a.length===0&&(t&&t("error",'"references" value cannot be emtpy',{node:u}),s=!1));return{attr:n,name:i,fields:o,references:a,valid:s}}validateRelationField(e,t){var c,l,d,g;let n=this.parseRelation(e,t);if(!n.valid)return;let i=e.type.reference.ref,o=i.fields.filter(h=>{var p;return((p=h.type.reference)==null?void 0:p.ref)===e.$container});if(o=o.filter(h=>{let p=this.parseRelation(h);return p.valid&&p.name===n.name}),o.length===0){t("error",`The relation field "${e.name}" on model "${e.$container.name}" is missing an opposite relation field on model "${i.name}"`,{node:e});return}else if(o.length>1){o.forEach(h=>t("error",`Fields ${o.map(p=>'"'+p.name+'"').join(", ")} on model "${i.name}" refer to the same relation to model "${e.$container.name}"`,{node:h}));return}let a=o[0],s=this.parseRelation(a),u;if(((c=n==null?void 0:n.references)==null?void 0:c.length)&&((l=n.fields)==null?void 0:l.length))if((s==null?void 0:s.references)||(s==null?void 0:s.fields)){t("error",'"fields" and "references" must be provided only on one side of relation field',{node:a});return}else u=a;else if(((d=s==null?void 0:s.references)==null?void 0:d.length)&&((g=s.fields)==null?void 0:g.length))if((n==null?void 0:n.references)||(n==null?void 0:n.fields)){t("error",'"fields" and "references" must be provided only on one side of relation field',{node:e});return}else u=e;else{[e,a].forEach(h=>t("error",'Field for one side of relation must carry @relation attribute with both "fields" and "references" fields',{node:h}));return}if(!u.type.array&&!u.type.optional){t("error","Relation field needs to be list or optional",{node:u});return}}};var vs=class{validate(e,t){}};var Ts=class{validate(e,t){bi(e.fields,t)}};var Ll=class extends LE.ValidationRegistry{constructor(e){super(e);let t=e.validation.ZModelValidator,n={Model:t.checkModel,DataSource:t.checkDataSource,DataModel:t.checkDataModel,Enum:t.checkEnum,Attribute:t.checkAttribute};this.register(n,t)}},Fl=class{shouldCheck(e){let t,n=e;for(;n;){if(n.$document){t=n.$document;break}n=n.$container}return(t==null?void 0:t.parseResult.lexerErrors.length)===0&&(t==null?void 0:t.parseResult.parserErrors.length)===0}checkModel(e,t){this.shouldCheck(e)&&new hs().validate(e,t)}checkDataSource(e,t){this.shouldCheck(e)&&new ys().validate(e,t)}checkDataModel(e,t){this.shouldCheck(e)&&new gs().validate(e,t)}checkEnum(e,t){this.shouldCheck(e)&&new Ts().validate(e,t)}checkAttribute(e,t){this.shouldCheck(e)&&new vs().validate(e,t)}};var UE=St(Ne()),HE=St(yu());var FE=St(Dn()),qE=St(require("path")),jE=St(gn());var Yi=class extends FE.DefaultWorkspaceManager{loadAdditionalDocuments(e,t){return no(this,null,function*(){yield As(Yi.prototype,this,"loadAdditionalDocuments").call(this,e,t);let n=jE.URI.file(qE.default.join(__dirname,"../res",Ml));console.log(`Adding stdlib document from ${n}`);let i=this.langiumDocuments.getOrCreateDocument(n);t(i)})}};var ql=St(Dn()),GE=St(Ne()),Rs=class{constructor(e){this.nameProvider=e.references.NameProvider,this.references=e.references.References,this.grammarConfig=e.parser.GrammarConfig}getDefinition(e,t){let n=e.parseResult.value;if(n.$cstNode){let i=n.$cstNode,o=(0,ql.findDeclarationNodeAtOffset)(i,e.textDocument.offsetAt(t.position),this.grammarConfig.nameRegexp);if(o)return this.collectLocationLinks(o,t)}}collectLocationLinks(e,t){var i;let n=this.findLink(e);if(n&&!n.targetDocument.textDocument.uri.endsWith("stdlib.zmodel"))return[GE.LocationLink.create(n.targetDocument.textDocument.uri,((i=n.target.element.$cstNode)!=null?i:n.target).range,n.target.range,n.source.range)]}findLink(e){let t=this.references.findDeclarationNode(e);if(t!=null&&t.element){let n=(0,ql.getDocument)(t.element);if(t&&n)return{source:e,target:t,targetDocument:n}}}};var rF={references:{ScopeComputation:r=>new ta(r),Linker:r=>new $l(r)},validation:{ValidationRegistry:r=>new Ll(r),ZModelValidator:()=>new Fl},lsp:{DefinitionProvider:r=>new Rs(r)}};function nF(r){return{ServiceRegistry:()=>new st.DefaultServiceRegistry,lsp:{Connection:()=>r.connection,LanguageServer:e=>new st.DefaultLanguageServer(e)},workspace:{LangiumDocuments:e=>new st.DefaultLangiumDocuments(e),LangiumDocumentFactory:e=>new st.DefaultLangiumDocumentFactory(e),DocumentBuilder:e=>new st.DefaultDocumentBuilder(e),TextDocuments:()=>new UE.TextDocuments(HE.TextDocument),TextDocumentFactory:e=>new st.DefaultTextDocumentFactory(e),IndexManager:e=>new st.DefaultIndexManager(e),WorkspaceManager:e=>new Yi(e),FileSystemProvider:e=>r.fileSystemProvider(e),MutexLock:()=>new st.MutexLock,ConfigurationProvider:e=>new st.DefaultConfigurationProvider(e)}}}function WE(r){let e=(0,st.inject)(nF(r),NE),t=(0,st.inject)((0,st.createDefaultModule)({shared:e}),DE,rF);return e.ServiceRegistry.register(t),{shared:e,ZModel:t}}var iF=(0,jl.createConnection)(jl.ProposedFeatures.all),{shared:oF}=WE(fy({connection:iF},BE.NodeFileSystem));(0,KE.startLanguageServer)(oF);
|
package/bundle/res/stdlib.zmodel
CHANGED
|
@@ -7,6 +7,30 @@ enum ReferentialAction {
|
|
|
7
7
|
* Used with "onUpdate": updates the relation scalar fields if the referenced scalar fields of the dependent record are updated.
|
|
8
8
|
*/
|
|
9
9
|
Cascade
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Used with "onDelete": prevents the deletion if any referencing records exist.
|
|
13
|
+
* Used with "onUpdate": prevents the identifier of a referenced record from being changed.
|
|
14
|
+
*/
|
|
15
|
+
Restrict
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Similar to 'Restrict', the difference between the two is dependent on the database being used.
|
|
19
|
+
* See details: https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions#noaction
|
|
20
|
+
*/
|
|
21
|
+
NoAction
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Used with "onDelete": the scalar field of the referencing object will be set to NULL.
|
|
25
|
+
* Used with "onUpdate": when updating the identifier of a referenced object, the scalar fields of the referencing objects will be set to NULL.
|
|
26
|
+
*/
|
|
27
|
+
SetNull
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Used with "onDelete": the scalar field of the referencing object will be set to the fields default value.
|
|
31
|
+
* Used with "onUpdate": the scalar field of the referencing object will be set to the fields default value.
|
|
32
|
+
*/
|
|
33
|
+
SetDefault
|
|
10
34
|
}
|
|
11
35
|
|
|
12
36
|
/*
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publisher": "zenstack",
|
|
4
4
|
"displayName": "ZenStack Language Tools",
|
|
5
5
|
"description": "A toolkit for modeling data and access policies in full-stack development with Next.js and Typescript",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.11",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ZenStack Team"
|
|
9
9
|
},
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"main": "./bundle/extension.js",
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@zenstackhq/
|
|
68
|
+
"@zenstackhq/runtime": "0.3.11",
|
|
69
69
|
"async-exit-hook": "^2.0.1",
|
|
70
70
|
"change-case": "^4.1.2",
|
|
71
71
|
"chevrotain": "^9.1.0",
|
package/src/cli/cli-util.ts
CHANGED
|
@@ -6,12 +6,108 @@ import fs from 'fs';
|
|
|
6
6
|
import { LangiumServices } from 'langium';
|
|
7
7
|
import { NodeFileSystem } from 'langium/node';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
import {
|
|
9
|
+
import { installPackage } from 'src/utils/pkg-utils';
|
|
10
10
|
import { URI } from 'vscode-uri';
|
|
11
|
+
import { ZenStackGenerator } from '../generator';
|
|
11
12
|
import { GENERATED_CODE_PATH } from '../generator/constants';
|
|
12
13
|
import { Context, GeneratorError } from '../generator/types';
|
|
13
14
|
import { CliError } from './cli-error';
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Initializes an existing project for ZenStack
|
|
18
|
+
*/
|
|
19
|
+
export async function initProject(projectPath: string) {
|
|
20
|
+
const schema = path.join(projectPath, 'zenstack', 'schema.zmodel');
|
|
21
|
+
let schemaGenerated = false;
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(schema)) {
|
|
24
|
+
console.warn(colors.yellow(`Model already exists: ${schema}`));
|
|
25
|
+
} else {
|
|
26
|
+
// create a default model
|
|
27
|
+
if (!fs.existsSync(path.join(projectPath, 'zenstack'))) {
|
|
28
|
+
fs.mkdirSync(path.join(projectPath, 'zenstack'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(
|
|
32
|
+
schema,
|
|
33
|
+
`// This is a sample model to get you started.
|
|
34
|
+
// Learn how to model you app: https://zenstack.dev/#/modeling-your-app.
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* A sample data source using local sqlite db.
|
|
38
|
+
* See how to use a different db: https://zenstack.dev/#/zmodel-data-source.
|
|
39
|
+
*/
|
|
40
|
+
datasource db {
|
|
41
|
+
provider = 'sqlite'
|
|
42
|
+
url = 'file:./todo.db'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* User model
|
|
47
|
+
*/
|
|
48
|
+
model User {
|
|
49
|
+
id String @id @default(cuid())
|
|
50
|
+
email String @unique @email
|
|
51
|
+
password String @password @omit @length(8, 16)
|
|
52
|
+
posts Post[]
|
|
53
|
+
|
|
54
|
+
// everybody can signup
|
|
55
|
+
@@allow('create', true)
|
|
56
|
+
|
|
57
|
+
// full access by self
|
|
58
|
+
@@allow('all', auth() == this)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* Post model
|
|
63
|
+
*/
|
|
64
|
+
model Post {
|
|
65
|
+
id String @id @default(cuid())
|
|
66
|
+
createdAt DateTime @default(now())
|
|
67
|
+
updatedAt DateTime @updatedAt
|
|
68
|
+
title String @length(1, 256)
|
|
69
|
+
content String
|
|
70
|
+
published Boolean @default(false)
|
|
71
|
+
author User? @relation(fields: [authorId], references: [id])
|
|
72
|
+
authorId String?
|
|
73
|
+
|
|
74
|
+
// allow read for all signin users
|
|
75
|
+
@@allow('read', auth() != null && published)
|
|
76
|
+
|
|
77
|
+
// full access by author
|
|
78
|
+
@@allow('all', author == auth())
|
|
79
|
+
}
|
|
80
|
+
`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// add zenstack/schema.prisma to .gitignore
|
|
84
|
+
const gitIgnorePath = path.join(projectPath, '.gitignore');
|
|
85
|
+
let gitIgnoreContent = '';
|
|
86
|
+
if (fs.existsSync(gitIgnorePath)) {
|
|
87
|
+
gitIgnoreContent =
|
|
88
|
+
fs.readFileSync(gitIgnorePath, { encoding: 'utf-8' }) + '\n';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!gitIgnoreContent.includes('zenstack/schema.prisma')) {
|
|
92
|
+
gitIgnoreContent += 'zenstack/schema.prisma\n';
|
|
93
|
+
fs.writeFileSync(gitIgnorePath, gitIgnoreContent);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
schemaGenerated = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
installPackage('zenstack', true, undefined, projectPath);
|
|
100
|
+
installPackage('@zenstackhq/runtime', false, undefined, projectPath);
|
|
101
|
+
|
|
102
|
+
if (schemaGenerated) {
|
|
103
|
+
console.log(`Sample model generated at: ${colors.green(schema)}
|
|
104
|
+
|
|
105
|
+
Please check the following guide on how to model your app:
|
|
106
|
+
https://zenstack.dev/#/modeling-your-app.
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
15
111
|
/**
|
|
16
112
|
* Loads a zmodel document from a file.
|
|
17
113
|
* @param fileName File name
|
|
@@ -77,7 +173,7 @@ export async function loadDocument(
|
|
|
77
173
|
}
|
|
78
174
|
|
|
79
175
|
export async function runGenerator(
|
|
80
|
-
options: { schema: string },
|
|
176
|
+
options: { schema: string; packageManager: string },
|
|
81
177
|
includedGenerators?: string[],
|
|
82
178
|
clearOutput = true
|
|
83
179
|
) {
|
package/src/cli/index.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { Command, Option } from 'commander';
|
|
3
|
-
import { ZModelLanguageMetaData } from '../language-server/generated/module';
|
|
4
|
-
import colors from 'colors';
|
|
5
|
-
import { execSync } from '../utils/exec-utils';
|
|
6
2
|
import { paramCase } from 'change-case';
|
|
3
|
+
import colors from 'colors';
|
|
4
|
+
import { Command, Option } from 'commander';
|
|
7
5
|
import path from 'path';
|
|
8
|
-
import {
|
|
6
|
+
import { ZModelLanguageMetaData } from '../language-server/generated/module';
|
|
9
7
|
import telemetry from '../telemetry';
|
|
8
|
+
import { execSync } from '../utils/exec-utils';
|
|
10
9
|
import { CliError } from './cli-error';
|
|
10
|
+
import { initProject, runGenerator } from './cli-util';
|
|
11
|
+
|
|
12
|
+
export const initAction = async (projectPath: string): Promise<void> => {
|
|
13
|
+
await telemetry.trackSpan(
|
|
14
|
+
'cli:command:start',
|
|
15
|
+
'cli:command:complete',
|
|
16
|
+
'cli:command:error',
|
|
17
|
+
{ command: 'init' },
|
|
18
|
+
() => initProject(projectPath)
|
|
19
|
+
);
|
|
20
|
+
};
|
|
11
21
|
|
|
12
22
|
export const generateAction = async (options: {
|
|
13
23
|
schema: string;
|
|
24
|
+
packageManager: string;
|
|
14
25
|
}): Promise<void> => {
|
|
15
26
|
await telemetry.trackSpan(
|
|
16
27
|
'cli:command:start',
|
|
@@ -74,7 +85,6 @@ function prismaAction(prismaCmd: string): (...args: any[]) => Promise<void> {
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
export default async function (): Promise<void> {
|
|
77
|
-
// try {
|
|
78
88
|
await telemetry.trackSpan(
|
|
79
89
|
'cli:start',
|
|
80
90
|
'cli:complete',
|
|
@@ -97,7 +107,7 @@ export default async function (): Promise<void> {
|
|
|
97
107
|
.description(
|
|
98
108
|
`${colors.bold.blue(
|
|
99
109
|
'ζ'
|
|
100
|
-
)} ZenStack
|
|
110
|
+
)} ZenStack is a toolkit for building secure CRUD apps with Next.js + Typescript.\n\nDocumentation: https://go.zenstack.dev/doc.`
|
|
101
111
|
)
|
|
102
112
|
.showHelpAfterError()
|
|
103
113
|
.showSuggestionAfterError();
|
|
@@ -107,28 +117,43 @@ export default async function (): Promise<void> {
|
|
|
107
117
|
`schema file (with extension ${schemaExtensions})`
|
|
108
118
|
).default('./zenstack/schema.zmodel');
|
|
109
119
|
|
|
120
|
+
const pmOption = new Option(
|
|
121
|
+
'--package-manager, -p',
|
|
122
|
+
'package manager to use: "npm", "yarn" or "pnpm"'
|
|
123
|
+
).default('auto detect');
|
|
124
|
+
|
|
110
125
|
//#region wraps Prisma commands
|
|
111
126
|
|
|
127
|
+
program
|
|
128
|
+
.command('init')
|
|
129
|
+
.description('Set up a new ZenStack project.')
|
|
130
|
+
.addOption(pmOption)
|
|
131
|
+
.argument('<path>', 'project path')
|
|
132
|
+
.action(initAction);
|
|
133
|
+
|
|
112
134
|
program
|
|
113
135
|
.command('generate')
|
|
114
136
|
.description(
|
|
115
|
-
'
|
|
137
|
+
'Generates RESTful API and Typescript client for your data model.'
|
|
116
138
|
)
|
|
117
139
|
.addOption(schemaOption)
|
|
140
|
+
.addOption(pmOption)
|
|
118
141
|
.action(generateAction);
|
|
119
142
|
|
|
120
143
|
const migrate = program
|
|
121
144
|
.command('migrate')
|
|
122
145
|
.description(
|
|
123
|
-
`
|
|
146
|
+
`Updates the database schema with migrations\nAlias for ${colors.cyan(
|
|
147
|
+
'prisma migrate'
|
|
148
|
+
)}.`
|
|
124
149
|
);
|
|
125
150
|
|
|
126
151
|
migrate
|
|
127
152
|
.command('dev')
|
|
128
153
|
.description(
|
|
129
|
-
`
|
|
154
|
+
`Creates a migration, apply it to the database, generate db client\nAlias for ${colors.cyan(
|
|
130
155
|
'prisma migrate dev'
|
|
131
|
-
)}
|
|
156
|
+
)}.`
|
|
132
157
|
)
|
|
133
158
|
.addOption(schemaOption)
|
|
134
159
|
.option(
|
|
@@ -142,9 +167,9 @@ export default async function (): Promise<void> {
|
|
|
142
167
|
migrate
|
|
143
168
|
.command('reset')
|
|
144
169
|
.description(
|
|
145
|
-
`
|
|
170
|
+
`Resets your database and apply all migrations\nAlias for ${colors.cyan(
|
|
146
171
|
'prisma migrate reset'
|
|
147
|
-
)}
|
|
172
|
+
)}.`
|
|
148
173
|
)
|
|
149
174
|
.addOption(schemaOption)
|
|
150
175
|
.option('--force', 'Skip the confirmation prompt')
|
|
@@ -153,9 +178,9 @@ export default async function (): Promise<void> {
|
|
|
153
178
|
migrate
|
|
154
179
|
.command('deploy')
|
|
155
180
|
.description(
|
|
156
|
-
`
|
|
181
|
+
`Applies pending migrations to the database in production/staging\nAlias for ${colors.cyan(
|
|
157
182
|
'prisma migrate deploy'
|
|
158
|
-
)}
|
|
183
|
+
)}.`
|
|
159
184
|
)
|
|
160
185
|
.addOption(schemaOption)
|
|
161
186
|
.action(prismaAction('migrate'));
|
|
@@ -163,22 +188,26 @@ export default async function (): Promise<void> {
|
|
|
163
188
|
migrate
|
|
164
189
|
.command('status')
|
|
165
190
|
.description(
|
|
166
|
-
`
|
|
191
|
+
`Checks the status of migrations in the production/staging database\nAlias for ${colors.cyan(
|
|
167
192
|
'prisma migrate status'
|
|
168
|
-
)}
|
|
193
|
+
)}.`
|
|
169
194
|
)
|
|
170
195
|
.addOption(schemaOption)
|
|
171
196
|
.action(prismaAction('migrate'));
|
|
172
197
|
|
|
173
198
|
const db = program
|
|
174
199
|
.command('db')
|
|
175
|
-
.description(
|
|
200
|
+
.description(
|
|
201
|
+
`Manages your database schema and lifecycle during development\nAlias for ${colors.cyan(
|
|
202
|
+
'prisma db'
|
|
203
|
+
)}.`
|
|
204
|
+
);
|
|
176
205
|
|
|
177
206
|
db.command('push')
|
|
178
207
|
.description(
|
|
179
|
-
`
|
|
208
|
+
`Pushes the Prisma schema state to the database\nAlias for ${colors.cyan(
|
|
180
209
|
'prisma db push'
|
|
181
|
-
)}
|
|
210
|
+
)}.`
|
|
182
211
|
)
|
|
183
212
|
.addOption(schemaOption)
|
|
184
213
|
.option('--accept-data-loss', 'Ignore data loss warnings')
|
|
@@ -187,9 +216,9 @@ export default async function (): Promise<void> {
|
|
|
187
216
|
program
|
|
188
217
|
.command('studio')
|
|
189
218
|
.description(
|
|
190
|
-
`
|
|
191
|
-
'studio'
|
|
192
|
-
)}
|
|
219
|
+
`Browses your data with Prisma Studio\nAlias for ${colors.cyan(
|
|
220
|
+
'prisma studio'
|
|
221
|
+
)}.`
|
|
193
222
|
)
|
|
194
223
|
.addOption(schemaOption)
|
|
195
224
|
.option('-p --port <port>', 'Port to start Studio in')
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
PolicyKind,
|
|
11
11
|
PolicyOperationKind,
|
|
12
12
|
RuntimeAttribute,
|
|
13
|
-
} from '@zenstackhq/
|
|
13
|
+
} from '@zenstackhq/runtime/server';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph';
|
|
16
16
|
import {
|
|
17
17
|
GUARD_FIELD_NAME,
|
|
18
|
-
|
|
18
|
+
RUNTIME_PACKAGE,
|
|
19
19
|
UNKNOWN_USER_ID,
|
|
20
20
|
} from '../constants';
|
|
21
21
|
import { Context } from '../types';
|
|
@@ -38,7 +38,7 @@ export default class QueryGuardGenerator {
|
|
|
38
38
|
|
|
39
39
|
sf.addImportDeclaration({
|
|
40
40
|
namedImports: [{ name: 'QueryContext' }],
|
|
41
|
-
moduleSpecifier:
|
|
41
|
+
moduleSpecifier: `${RUNTIME_PACKAGE}/server`,
|
|
42
42
|
isTypeOnly: true,
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -5,7 +5,7 @@ import { paramCase } from 'change-case';
|
|
|
5
5
|
import { DataModel } from '@lang/generated/ast';
|
|
6
6
|
import colors from 'colors';
|
|
7
7
|
import { extractDataModelsWithAllowRules } from '../ast-utils';
|
|
8
|
-
import { API_ROUTE_NAME } from '../constants';
|
|
8
|
+
import { API_ROUTE_NAME, RUNTIME_PACKAGE } from '../constants';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Generate react data query hooks code
|
|
@@ -51,7 +51,9 @@ export default class ReactHooksGenerator implements Generator {
|
|
|
51
51
|
moduleSpecifier: '../../.prisma',
|
|
52
52
|
});
|
|
53
53
|
sf.addStatements([
|
|
54
|
-
`import
|
|
54
|
+
`import * as request from '${RUNTIME_PACKAGE}/lib/request';`,
|
|
55
|
+
`import { ServerErrorCode, RequestOptions } from '${RUNTIME_PACKAGE}/lib/types';`,
|
|
56
|
+
`import { validate } from '${RUNTIME_PACKAGE}/lib/validation';`,
|
|
55
57
|
`import { type SWRResponse } from 'swr';`,
|
|
56
58
|
`import { ${this.getValidator(
|
|
57
59
|
model,
|
|
@@ -2,7 +2,7 @@ import { Context, Generator } from '../types';
|
|
|
2
2
|
import { Project } from 'ts-morph';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import colors from 'colors';
|
|
5
|
-
import {
|
|
5
|
+
import { RUNTIME_PACKAGE } from '../constants';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Generates ZenStack service code
|
|
@@ -22,7 +22,7 @@ export default class ServiceGenerator implements Generator {
|
|
|
22
22
|
|
|
23
23
|
sf.addStatements([
|
|
24
24
|
`import { PrismaClient } from "../.prisma";`,
|
|
25
|
-
`import { DefaultService } from "${
|
|
25
|
+
`import { DefaultService } from "${RUNTIME_PACKAGE}/lib/service";`,
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
28
|
const cls = sf.addClass({
|
package/src/res/stdlib.zmodel
CHANGED
|
@@ -7,6 +7,30 @@ enum ReferentialAction {
|
|
|
7
7
|
* Used with "onUpdate": updates the relation scalar fields if the referenced scalar fields of the dependent record are updated.
|
|
8
8
|
*/
|
|
9
9
|
Cascade
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Used with "onDelete": prevents the deletion if any referencing records exist.
|
|
13
|
+
* Used with "onUpdate": prevents the identifier of a referenced record from being changed.
|
|
14
|
+
*/
|
|
15
|
+
Restrict
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Similar to 'Restrict', the difference between the two is dependent on the database being used.
|
|
19
|
+
* See details: https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions#noaction
|
|
20
|
+
*/
|
|
21
|
+
NoAction
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Used with "onDelete": the scalar field of the referencing object will be set to NULL.
|
|
25
|
+
* Used with "onUpdate": when updating the identifier of a referenced object, the scalar fields of the referencing objects will be set to NULL.
|
|
26
|
+
*/
|
|
27
|
+
SetNull
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Used with "onDelete": the scalar field of the referencing object will be set to the fields default value.
|
|
31
|
+
* Used with "onUpdate": the scalar field of the referencing object will be set to the fields default value.
|
|
32
|
+
*/
|
|
33
|
+
SetDefault
|
|
10
34
|
}
|
|
11
35
|
|
|
12
36
|
/*
|
package/src/telemetry.ts
CHANGED
|
@@ -5,6 +5,8 @@ import cuid from 'cuid';
|
|
|
5
5
|
import * as os from 'os';
|
|
6
6
|
import sleep from 'sleep-promise';
|
|
7
7
|
import exitHook from 'async-exit-hook';
|
|
8
|
+
import { CliError } from './cli/cli-error';
|
|
9
|
+
import { CommanderError } from 'commander';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Telemetry events
|
|
@@ -57,6 +59,13 @@ export class Telemetry {
|
|
|
57
59
|
// a small delay to ensure telemetry is sent
|
|
58
60
|
await sleep(this.exitWait);
|
|
59
61
|
}
|
|
62
|
+
|
|
63
|
+
if (err instanceof CliError || err instanceof CommanderError) {
|
|
64
|
+
// error already handled
|
|
65
|
+
} else {
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
process.exit(1);
|
|
61
70
|
});
|
|
62
71
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from './exec-utils';
|
|
4
|
+
|
|
5
|
+
type PackageManagers = 'npm' | 'yarn' | 'pnpm';
|
|
6
|
+
|
|
7
|
+
function getPackageManager(projectPath = '.'): PackageManagers {
|
|
8
|
+
if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) {
|
|
9
|
+
return 'yarn';
|
|
10
|
+
} else if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) {
|
|
11
|
+
return 'pnpm';
|
|
12
|
+
} else {
|
|
13
|
+
return 'npm';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function installPackage(
|
|
18
|
+
pkg: string,
|
|
19
|
+
dev: boolean,
|
|
20
|
+
pkgManager: PackageManagers | undefined = undefined,
|
|
21
|
+
projectPath = '.'
|
|
22
|
+
) {
|
|
23
|
+
const manager = pkgManager ?? getPackageManager(projectPath);
|
|
24
|
+
console.log(`Installing package "${pkg}" with ${manager}`);
|
|
25
|
+
switch (manager) {
|
|
26
|
+
case 'yarn':
|
|
27
|
+
execSync(
|
|
28
|
+
`yarn add --cwd "${projectPath}" ${
|
|
29
|
+
dev ? ' --save-dev' : ''
|
|
30
|
+
} ${pkg}`
|
|
31
|
+
);
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
case 'pnpm':
|
|
35
|
+
execSync(
|
|
36
|
+
`pnpm add -C "${projectPath}" ${
|
|
37
|
+
dev ? ' --save-dev' : ''
|
|
38
|
+
} ${pkg}`
|
|
39
|
+
);
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
default:
|
|
43
|
+
execSync(
|
|
44
|
+
`npm install --prefix "${projectPath}" ${
|
|
45
|
+
dev ? ' --save-dev' : ''
|
|
46
|
+
} ${pkg}`
|
|
47
|
+
);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ensurePackage(
|
|
53
|
+
pkg: string,
|
|
54
|
+
dev: boolean,
|
|
55
|
+
pkgManager: PackageManagers | undefined = undefined,
|
|
56
|
+
projectPath = '.'
|
|
57
|
+
) {
|
|
58
|
+
try {
|
|
59
|
+
require(pkg);
|
|
60
|
+
} catch {
|
|
61
|
+
installPackage(pkg, dev, pkgManager, projectPath);
|
|
62
|
+
}
|
|
63
|
+
}
|