diff --git a/package.json b/package.json index 531ff97..ca2eee5 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,8 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.29.0", - "@swc/core": "^1.12.5", "@types/node": "^20.17.24", "eslint": "~9.29.0", - "npm-run-all": "^4.1.5", "prettier": "^3.5.3", "tsup": "^8.5.0", "tsx": "^4.20.3", diff --git a/packages/cli/package.json b/packages/cli/package.json index e93188a..bc60529 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,7 +31,6 @@ "@zenstackhq/language": "workspace:*", "@zenstackhq/sdk": "workspace:*", "@zenstackhq/common-helpers": "workspace:*", - "async-exit-hook": "^2.0.1", "colors": "1.4.0", "commander": "^8.3.0", "langium": "catalog:", @@ -43,15 +42,11 @@ "prisma": "catalog:" }, "devDependencies": { - "@types/async-exit-hook": "^2.0.0", "@types/better-sqlite3": "^7.6.13", - "@types/semver": "^7.3.13", - "@types/tmp": "^0.2.6", + "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/testtools": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*", - "better-sqlite3": "^11.8.1", - "tmp": "^0.2.3" + "better-sqlite3": "^11.8.1" } } diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 9c19d96..a78d9b0 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -40,7 +40,7 @@ import { ZenStackClient } from '@zenstackhq/runtime'; import { schema } from '${outputPath}/schema'; const client = new ZenStackClient(schema, { - dialectConfig: { ... } + dialectConfig: { ... } }); \`\`\` `); diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index a2a54f6..97e262b 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -35,7 +35,7 @@ "ora": "^5.4.1" }, "devDependencies": { - "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "@zenstackhq/eslint-config": "workspace:*", + "@zenstackhq/typescript-config": "workspace:*" } } diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index 4bfd341..39d36e8 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -30,15 +30,15 @@ "license": "MIT", "packageManager": "pnpm@10.12.1", "dependencies": { + "@zenstackhq/language": "workspace:*", "langium": "catalog:", "vscode-languageclient": "^9.0.1", - "vscode-languageserver": "^9.0.1", - "@zenstackhq/language": "workspace:*" + "vscode-languageserver": "^9.0.1" }, "devDependencies": { "@types/vscode": "^1.63.0", - "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "@zenstackhq/eslint-config": "workspace:*", + "@zenstackhq/typescript-config": "workspace:*" }, "files": [ "dist", diff --git a/packages/language/package.json b/packages/language/package.json index 1eee56e..e8b5f35 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/pluralize": "^0.0.33", - "langium-cli": "~3.3.0", + "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "langium-cli": "~3.3.0" }, "volta": { "node": "18.19.1", diff --git a/packages/language/res/stdlib.zmodel b/packages/language/res/stdlib.zmodel index 363d818..ec144c9 100644 --- a/packages/language/res/stdlib.zmodel +++ b/packages/language/res/stdlib.zmodel @@ -25,7 +25,7 @@ enum ReferentialAction { * Used with "onUpdate": when updating the identifier of a referenced object, the scalar fields of the referencing objects will be set to NULL. */ SetNull - + /** * Used with "onDelete": the scalar field of the referencing object will be set to the fields default value. * Used with "onUpdate": the scalar field of the referencing object will be set to the fields default value. @@ -104,7 +104,7 @@ function ulid(): String { } @@@expressionContext([DefaultValue]) /** - * Creates a sequence of integers in the underlying database and assign the incremented + * Creates a sequence of integers in the underlying database and assign the incremented * values to the ID values of the created records based on the sequence. */ function autoincrement(): Int { @@ -174,9 +174,9 @@ function isEmpty(field: Any[]): Boolean { /** * The name of the model for which the policy rule is defined. If the rule is * inherited to a sub model, this function returns the name of the sub model. - * + * * @param optional parameter to control the casing of the returned value. Valid - * values are "original", "upper", "lower", "capitalize", "uncapitalize". Defaults + * values are "original", "upper", "lower", "capitalize", "uncapitalize". Defaults * to "original". */ function currentModel(casing: String?): String { @@ -186,7 +186,7 @@ function currentModel(casing: String?): String { * The operation for which the policy rule is defined for. Note that a rule with * "all" operation is expanded to "create", "read", "update", and "delete" rules, * and the function returns corresponding value for each expanded version. - * + * * @param optional parameter to control the casing of the returned value. Valid * values are "original", "upper", "lower", "capitalize", "uncapitalize". Defaults * to "original". @@ -523,13 +523,13 @@ attribute @@schema(_ name: String) @@@prisma /** * Indicates that the field is a password field and needs to be hashed before persistence. - * + * * ZenStack uses `bcryptjs` library to hash password. You can use the `saltLength` parameter * to configure the cost of hashing, or use `salt` parameter to provide an explicit salt. * By default, salt length of 12 is used. * * @see https://www.npmjs.com/package/bcryptjs for details - * + * * @param saltLength: length of salt to use (cost factor for the hash function) * @param salt: salt to use (a pregenerated valid salt) */ @@ -538,8 +538,8 @@ attribute @password(saltLength: Int?, salt: String?) @@@targetField([StringField /** * Indicates that the field is encrypted when storing in the DB and should be decrypted when read - * - * ZenStack uses the Web Crypto API to encrypt and decrypt the field. + * + * ZenStack uses the Web Crypto API to encrypt and decrypt the field. */ attribute @encrypted() @@@targetField([StringField]) @@ -665,9 +665,9 @@ function url(field: String): Boolean { /** * Checks if the current user can perform the given operation on the given field. - * + * * @param field: The field to check access for - * @param operation: The operation to check access for. Can be "read", "create", "update", or "delete". If the operation is not provided, + * @param operation: The operation to check access for. Can be "read", "create", "update", or "delete". If the operation is not provided, * it defaults the operation of the containing policy rule. */ function check(field: Any, operation: String?): Boolean { diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index be6b3c6..c1e6ada 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -332,7 +332,6 @@ export function getObjectLiteral(expr: Expression | ConfigExpr | undefined): } export function getLiteralArray< - T extends string | number | boolean | any = any, >(expr: Expression | ConfigExpr | undefined): T[] | undefined { const arr = getArray(expr); diff --git a/packages/language/src/validators/datamodel-validator.ts b/packages/language/src/validators/datamodel-validator.ts index 877f92e..fd23c29 100644 --- a/packages/language/src/validators/datamodel-validator.ts +++ b/packages/language/src/validators/datamodel-validator.ts @@ -259,7 +259,6 @@ export default class DataModelValidator implements AstValidator { return; } - const oppositeModel = field.type.reference!.ref! as DataModel; // Use name because the current document might be updated diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 0d7fa8e..5f2bca5 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -164,7 +164,7 @@ Argument: DataModel: (comments+=TRIPLE_SLASH_COMMENT)* ( - ((isAbstract?='abstract')? 'model' name=RegularID + ((isAbstract?='abstract')? 'model' name=RegularID ('extends' superTypes+=[DataModel] (',' superTypes+=[DataModel])*)?) | ((isView?='view') name=RegularID) ) @@ -193,7 +193,7 @@ TypeDef: type TypeDefFieldTypes = TypeDef | Enum; TypeDefField: - (comments+=TRIPLE_SLASH_COMMENT)* + (comments+=TRIPLE_SLASH_COMMENT)* name=RegularIDWithTypeNames type=TypeDefFieldType (attributes+=DataModelFieldAttribute)*; TypeDefFieldType: @@ -208,7 +208,7 @@ Enum: 'enum' name=RegularID '{' ( fields+=EnumField | attributes+=DataModelAttribute - )+ + )+ '}'; EnumField: diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 3143bb8..3a64c3c 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -73,7 +73,6 @@ "nanoid": "^5.0.9", "ts-pattern": "catalog:", "ulid": "^3.0.0", - "utility-types": "^3.11.0", "uuid": "^11.0.5" }, "peerDependencies": { @@ -92,13 +91,11 @@ "devDependencies": { "@types/better-sqlite3": "^7.0.0", "@types/pg": "^8.0.0", - "@types/tmp": "^0.2.6", + "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/language": "workspace:*", "@zenstackhq/sdk": "workspace:*", "@zenstackhq/testtools": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*", - "tmp": "^0.2.3", "tsx": "^4.19.2" } } diff --git a/packages/runtime/src/client/crud-types.ts b/packages/runtime/src/client/crud-types.ts index d7c7a3e..ada7554 100644 --- a/packages/runtime/src/client/crud-types.ts +++ b/packages/runtime/src/client/crud-types.ts @@ -1,5 +1,4 @@ import type { ExpressionBuilder, OperandExpression, SqlBool } from 'kysely'; -import type { Optional } from 'utility-types'; import type { BuiltinType, FieldDef, @@ -29,6 +28,7 @@ import type { MapBaseType, NonEmptyArray, NullableIf, + Optional, OrArray, ValueOfPotentialTuple, WrapType, diff --git a/packages/runtime/src/client/executor/zenstack-query-executor.ts b/packages/runtime/src/client/executor/zenstack-query-executor.ts index 3803f3f..381ec9a 100644 --- a/packages/runtime/src/client/executor/zenstack-query-executor.ts +++ b/packages/runtime/src/client/executor/zenstack-query-executor.ts @@ -22,7 +22,6 @@ import { } from 'kysely'; import { nanoid } from 'nanoid'; import { match } from 'ts-pattern'; -import type { PromiseType } from 'utility-types'; import type { GetModels, SchemaDef } from '../../schema'; import type { ClientImpl } from '../client-impl'; import type { ClientContract } from '../contract'; @@ -58,7 +57,7 @@ export class ZenStackQueryExecutor extends DefaultQuer override async executeQuery(compiledQuery: CompiledQuery, queryId: QueryId) { let queryNode = compiledQuery.query; - let mutationInterceptionInfo: PromiseType>; + let mutationInterceptionInfo: Awaited>; if (this.isMutationNode(queryNode) && this.hasMutationHooks) { mutationInterceptionInfo = await this.callMutationInterceptionFilters(queryNode); } @@ -291,7 +290,7 @@ export class ZenStackQueryExecutor extends DefaultQuer private callBeforeMutationHooks( queryNode: OperationNode, - mutationInterceptionInfo: PromiseType>, + mutationInterceptionInfo: Awaited>, ) { if (!mutationInterceptionInfo?.intercept) { return; @@ -315,7 +314,7 @@ export class ZenStackQueryExecutor extends DefaultQuer private async callAfterQueryInterceptionFilters( queryResult: QueryResult, queryNode: OperationNode, - mutationInterceptionInfo: PromiseType>, + mutationInterceptionInfo: Awaited>, ) { if (!mutationInterceptionInfo?.intercept) { return; diff --git a/packages/runtime/src/client/functions.ts b/packages/runtime/src/client/functions.ts index 1b24603..781372a 100644 --- a/packages/runtime/src/client/functions.ts +++ b/packages/runtime/src/client/functions.ts @@ -1,4 +1,4 @@ -import { invariant } from '@zenstackhq/common-helpers'; +import { invariant, capitalize, uncapitalize } from '@zenstackhq/common-helpers'; import { sql, ValueNode, type Expression, type ExpressionBuilder } from 'kysely'; import { match } from 'ts-pattern'; import type { ZModelFunction, ZModelFunctionContext } from './options'; @@ -132,8 +132,10 @@ function processCasing(casing: Expression, result: string, model: string) { .with('original', () => model) .with('upper', () => result.toUpperCase()) .with('lower', () => result.toLowerCase()) - .with('capitalize', () => `${result.charAt(0).toUpperCase() + result.slice(1)}`) - .with('uncapitalize', () => `${result.charAt(0).toLowerCase() + result.slice(1)}`) + .with(' + + ', () => capitalize(result)) + .with('uncapitalize', () => uncapitalize(result)) .otherwise(() => { throw new Error( `Invalid casing value: ${opNode.value}. Must be "original", "upper", "lower", "capitalize", or "uncapitalize".`, diff --git a/packages/runtime/src/client/options.ts b/packages/runtime/src/client/options.ts index be4ba98..150c8e1 100644 --- a/packages/runtime/src/client/options.ts +++ b/packages/runtime/src/client/options.ts @@ -1,7 +1,6 @@ import type { Expression, ExpressionBuilder, KyselyConfig, PostgresDialectConfig, SqliteDialectConfig } from 'kysely'; -import type { Optional } from 'utility-types'; import type { DataSourceProvider, GetModel, GetModels, ProcedureDef, SchemaDef } from '../schema'; -import type { PrependParameter } from '../utils/type-utils'; +import type { Optional, PrependParameter } from '../utils/type-utils'; import type { ClientContract, CRUD, ProcedureFunc } from './contract'; import type { BaseCrudDialect } from './crud/dialects/base'; import type { RuntimePlugin } from './plugin'; diff --git a/packages/runtime/src/plugins/policy/expression-transformer.ts b/packages/runtime/src/plugins/policy/expression-transformer.ts index cf37879..1ffc321 100644 --- a/packages/runtime/src/plugins/policy/expression-transformer.ts +++ b/packages/runtime/src/plugins/policy/expression-transformer.ts @@ -17,6 +17,7 @@ import { ValueNode, WhereNode, type ExpressionBuilder, + type OperandExpression, type OperationNode, } from 'kysely'; import { match } from 'ts-pattern'; diff --git a/packages/runtime/src/plugins/policy/plugin.zmodel b/packages/runtime/src/plugins/policy/plugin.zmodel index 3ff7d9c..ecb3932 100644 --- a/packages/runtime/src/plugins/policy/plugin.zmodel +++ b/packages/runtime/src/plugins/policy/plugin.zmodel @@ -1,6 +1,6 @@ /** * Defines an access policy that allows a set of operations when the given condition is true. - * + * * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. * @param condition: a boolean expression that controls if the operation should be allowed. */ @@ -8,8 +8,8 @@ attribute @@allow(_ operation: String @@@completionHint(["'create'", "'read'", " /** * Defines an access policy that allows the annotated field to be read or updated. - * You can pass a third argument as `true` to make it override the model-level policies. - * + * You can pass a third argument as `true` to make it override the model-level policies. + * * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. * @param condition: a boolean expression that controls if the operation should be allowed. * @param override: a boolean value that controls if the field-level policy should override the model-level policy. @@ -18,7 +18,7 @@ attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "' /** * Defines an access policy that denies a set of operations when the given condition is true. - * + * * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. * @param condition: a boolean expression that controls if the operation should be denied. */ diff --git a/packages/runtime/src/utils/type-utils.ts b/packages/runtime/src/utils/type-utils.ts index 0046b42..c1bd0d0 100644 --- a/packages/runtime/src/utils/type-utils.ts +++ b/packages/runtime/src/utils/type-utils.ts @@ -1,5 +1,7 @@ import type Decimal from 'decimal.js'; +export type Optional = Omit & Partial>; + export type NullableIf = Condition extends true ? T | null : T; export type PartialRecord = Partial>; diff --git a/packages/runtime/test/client-api/type-coverage.test.ts b/packages/runtime/test/client-api/type-coverage.test.ts index 3574d49..f525026 100644 --- a/packages/runtime/test/client-api/type-coverage.test.ts +++ b/packages/runtime/test/client-api/type-coverage.test.ts @@ -8,7 +8,7 @@ describe('zmodel type coverage tests', () => { ` model Foo { id String @id @default(cuid()) - + String String Int Int BigInt BigInt @@ -17,7 +17,7 @@ describe('zmodel type coverage tests', () => { Decimal Decimal Boolean Boolean Bytes Bytes - + @@allow('all', true) } `, diff --git a/packages/runtime/test/policy/connect-disconnect.test.ts b/packages/runtime/test/policy/connect-disconnect.test.ts index 48e45da..d6e3012 100644 --- a/packages/runtime/test/policy/connect-disconnect.test.ts +++ b/packages/runtime/test/policy/connect-disconnect.test.ts @@ -7,11 +7,11 @@ describe('connect and disconnect tests', () => { id String @id @default(uuid()) m2 M2[] value Int @default(0) - + @@deny('read', value < 0) @@allow('all', true) } - + model M2 { id String @id @default(uuid()) value Int @@ -19,7 +19,7 @@ describe('connect and disconnect tests', () => { m1 M1? @relation(fields: [m1Id], references:[id]) m1Id String? m3 M3[] - + @@allow('read,create', true) @@allow('update', !deleted) } @@ -183,17 +183,17 @@ describe('connect and disconnect tests', () => { model M1 { id String @id @default(uuid()) m2 M2? - + @@allow('all', true) } - + model M2 { id String @id @default(uuid()) value Int deleted Boolean @default(false) m1 M1? @relation(fields: [m1Id], references:[id]) m1Id String? @unique - + @@allow('read,create', true) @@allow('update', !deleted) } @@ -285,17 +285,17 @@ describe('connect and disconnect tests', () => { id String @id @default(uuid()) value Int @default(0) m2 M2[] - + @@deny('read', value < 0) @@allow('all', true) } - + model M2 { id String @id @default(uuid()) value Int deleted Boolean @default(false) m1 M1[] - + @@deny('read', value < 0) @@allow('read,create', true) @@allow('update', !deleted) @@ -325,16 +325,16 @@ describe('connect and disconnect tests', () => { id String @id @default(uuid()) value Int @default(0) m2 M1OnM2[] - + @@allow('all', true) } - + model M2 { id String @id @default(uuid()) value Int deleted Boolean @default(false) m1 M1OnM2[] - + @@allow('read,create', true) } diff --git a/packages/runtime/test/policy/cross-model-field-comparison.test.ts b/packages/runtime/test/policy/cross-model-field-comparison.test.ts index 53a29ba..146992a 100644 --- a/packages/runtime/test/policy/cross-model-field-comparison.test.ts +++ b/packages/runtime/test/policy/cross-model-field-comparison.test.ts @@ -10,16 +10,16 @@ describe('cross-model field comparison tests', () => { profile Profile @relation(fields: [profileId], references: [id]) profileId Int @unique age Int - + @@allow('all', age == profile.age) @@deny('update', age > 100) } - + model Profile { id Int @id age Int user User? - + @@allow('all', true) } `, diff --git a/packages/runtime/test/policy/current-model.test.ts b/packages/runtime/test/policy/current-model.test.ts index 8ad743b..024e658 100644 --- a/packages/runtime/test/policy/current-model.test.ts +++ b/packages/runtime/test/policy/current-model.test.ts @@ -10,7 +10,7 @@ describe('currentModel tests', () => { @@allow('read', true) @@allow('create', currentModel() == 'User') } - + model Post { id Int @id @@allow('read', true) @@ -31,7 +31,7 @@ describe('currentModel tests', () => { @@allow('read', true) @@allow('create', currentModel('upper') == 'USER') } - + model Post { id Int @id @@allow('read', true) @@ -52,7 +52,7 @@ describe('currentModel tests', () => { @@allow('read', true) @@allow('create', currentModel('lower') == 'user') } - + model Post { id Int @id @@allow('read', true) @@ -73,7 +73,7 @@ describe('currentModel tests', () => { @@allow('read', true) @@allow('create', currentModel('capitalize') == 'User') } - + model post { id Int @id @@allow('read', true) @@ -94,7 +94,7 @@ describe('currentModel tests', () => { @@allow('read', true) @@allow('create', currentModel('uncapitalize') == 'uSER') } - + model POST { id Int @id @@allow('read', true) @@ -119,7 +119,7 @@ describe('currentModel tests', () => { model User extends Base { } - + model Post extends Base { } `, @@ -144,7 +144,7 @@ describe('currentModel tests', () => { model User extends Base { } - + model Post extends Base { } `, diff --git a/packages/runtime/test/policy/empty-policy.test.ts b/packages/runtime/test/policy/empty-policy.test.ts index 0199122..432454c 100644 --- a/packages/runtime/test/policy/empty-policy.test.ts +++ b/packages/runtime/test/policy/empty-policy.test.ts @@ -52,10 +52,10 @@ describe('empty policy tests', () => { model M1 { id String @id @default(uuid()) m2 M2[] - + @@allow('all', true) } - + model M2 { id String @id @default(uuid()) m1 M1 @relation(fields: [m1Id], references:[id]) @@ -81,10 +81,10 @@ describe('empty policy tests', () => { model M1 { id String @id @default(uuid()) m2 M2? - + @@allow('all', true) } - + model M2 { id String @id @default(uuid()) m1 M1 @relation(fields: [m1Id], references:[id]) diff --git a/packages/runtime/test/policy/field-comparison.test.ts b/packages/runtime/test/policy/field-comparison.test.ts index 7d597d0..1d8e3cd 100644 --- a/packages/runtime/test/policy/field-comparison.test.ts +++ b/packages/runtime/test/policy/field-comparison.test.ts @@ -23,14 +23,14 @@ describe('field comparison tests', () => { it('works with "in" operator', async () => { const db = await createPolicyTestClient( ` - model Model { - id String @id @default(uuid()) - x String - y String[] - @@allow('create', x in y) - @@allow('read', x in y) - } - `, + model Model { + id String @id @default(uuid()) + x String + y String[] + @@allow('create', x in y) + @@allow('read', x in y) + } + `, { provider: 'postgresql', dbName: 'field-comparison-tests-operator', diff --git a/packages/runtime/test/schemas/todo.zmodel b/packages/runtime/test/schemas/todo.zmodel index ef9453f..f462d06 100644 --- a/packages/runtime/test/schemas/todo.zmodel +++ b/packages/runtime/test/schemas/todo.zmodel @@ -109,7 +109,7 @@ model List { // require login @@deny('all', auth() == null) - // can be read by owner or space members (only if not private) + // can be read by owner or space members (only if not private) @@allow('read', ownerId == auth().id || (space.members?[userId == auth().id] && !private)) // when create, owner must be set to current user, and user must be in the space @@ -117,7 +117,7 @@ model List { // when create, owner must be set to current user, and user must be in the space // update is not allowed to change owner - @@allow('update', ownerId == auth().id && space.members?[userId == auth().id] + @@allow('update', ownerId == auth().id && space.members?[userId == auth().id] // TODO: future() support // && future().ownerId == ownerId ) diff --git a/packages/runtime/test/utils.ts b/packages/runtime/test/utils.ts index 88aa389..21cd1f9 100644 --- a/packages/runtime/test/utils.ts +++ b/packages/runtime/test/utils.ts @@ -1,6 +1,7 @@ import { invariant } from '@zenstackhq/common-helpers'; import { loadDocument } from '@zenstackhq/language'; import { PrismaSchemaGenerator } from '@zenstackhq/sdk'; +import { invariant } from '@zenstackhq/sdk/local-helpers'; import { generateTsSchema } from '@zenstackhq/testtools'; import SQLite from 'better-sqlite3'; import { execSync } from 'node:child_process'; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 3cf45b0..074a3d3 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -35,21 +35,29 @@ "types": "./dist/schema.d.cts", "default": "./dist/schema.cjs" } + }, + "./local-helpers": { + "import": { + "types": "./dist/local-helpers.d.ts", + "default": "./dist/local-helpers.js" + }, + "require": { + "types": "./dist/local-helpers.d.cts", + "default": "./dist/local-helpers.cjs" + } } }, "dependencies": { "@zenstackhq/language": "workspace:*", "@zenstackhq/common-helpers": "workspace:*", "langium": "catalog:", - "tmp": "^0.2.3", "ts-pattern": "catalog:", "typescript": "catalog:" }, "devDependencies": { - "@types/tmp": "^0.2.6", - "decimal.js": "^10.4.3", - "kysely": "catalog:", + "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "decimal.js": "^10.4.3", + "kysely": "catalog:" } } diff --git a/packages/sdk/src/local-helpers/index.ts b/packages/sdk/src/local-helpers/index.ts new file mode 100644 index 0000000..2404c25 --- /dev/null +++ b/packages/sdk/src/local-helpers/index.ts @@ -0,0 +1,4 @@ +export * from './is-plain-object'; +export * from './lower-case-first'; +export * from './upper-case-first'; +export * from './tiny-invariant'; diff --git a/packages/sdk/src/local-helpers/is-plain-object.ts b/packages/sdk/src/local-helpers/is-plain-object.ts new file mode 100644 index 0000000..f5c13d6 --- /dev/null +++ b/packages/sdk/src/local-helpers/is-plain-object.ts @@ -0,0 +1,23 @@ +function isObject(o: unknown) { + return Object.prototype.toString.call(o) === '[object Object]'; +} + +export function isPlainObject(o: unknown) { + if (isObject(o) === false) return false; + + // If has modified constructor + const ctor = (o as { constructor: unknown }).constructor; + if (ctor === undefined) return true; + + // If has modified prototype + const prot = (ctor as { prototype: unknown }).prototype; + if (isObject(prot) === false) return false; + + // If constructor does not have an Object-specific method + if (Object.prototype.hasOwnProperty.call(prot, 'isPrototypeOf') === false) { + return false; + } + + // Most likely a plain Object + return true; +} diff --git a/packages/sdk/src/local-helpers/lower-case-first.ts b/packages/sdk/src/local-helpers/lower-case-first.ts new file mode 100644 index 0000000..f915f17 --- /dev/null +++ b/packages/sdk/src/local-helpers/lower-case-first.ts @@ -0,0 +1,5 @@ +export function lowerCaseFirst(input: string) { + return input.charAt(0).toLowerCase() + input.slice(1); +} + +export { lowerCaseFirst as uncapitalize }; diff --git a/packages/sdk/src/local-helpers/tiny-invariant.ts b/packages/sdk/src/local-helpers/tiny-invariant.ts new file mode 100644 index 0000000..1e6638e --- /dev/null +++ b/packages/sdk/src/local-helpers/tiny-invariant.ts @@ -0,0 +1,17 @@ +const isProduction = process.env['NODE_ENV'] === 'production'; +const prefix = 'Invariant failed'; + +export function invariant( + condition: unknown, + message?: string, +): asserts condition { + if (condition) { + return; + } + + if (isProduction) { + throw new Error(prefix); + } + + throw new Error(message ? `${prefix}: ${message}` : prefix); +} diff --git a/packages/sdk/src/local-helpers/upper-case-first.ts b/packages/sdk/src/local-helpers/upper-case-first.ts new file mode 100644 index 0000000..f3719e6 --- /dev/null +++ b/packages/sdk/src/local-helpers/upper-case-first.ts @@ -0,0 +1,5 @@ +export function upperCaseFirst(input: string) { + return input.charAt(0).toUpperCase() + input.slice(1); +} + +export { upperCaseFirst as capitalize }; diff --git a/packages/sdk/src/prisma/prisma-builder.ts b/packages/sdk/src/prisma/prisma-builder.ts index f0cf5d3..a118a64 100644 --- a/packages/sdk/src/prisma/prisma-builder.ts +++ b/packages/sdk/src/prisma/prisma-builder.ts @@ -145,7 +145,6 @@ export class Model extends ContainerDeclaration { } override toString(): string { - const result: any[] = [...this.fields]; if (this.attributes.length > 0) { diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index c6e22a2..140feb3 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -39,6 +39,7 @@ import path from 'node:path'; import { match } from 'ts-pattern'; import * as ts from 'typescript'; import { ModelUtils } from '.'; +import { invariant } from './local-helpers'; import { getAttribute, getAuthDecl, hasAttribute, isIdField, isUniqueField } from './model-utils'; export class TsSchemaGenerator { diff --git a/packages/sdk/tsup.config.ts b/packages/sdk/tsup.config.ts index 4c9aca3..9a917ce 100644 --- a/packages/sdk/tsup.config.ts +++ b/packages/sdk/tsup.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ entry: { index: 'src/index.ts', schema: 'src/schema/index.ts', + 'local-helpers': 'src/local-helpers/index.ts', }, outDir: 'dist', splitting: false, diff --git a/packages/tanstack-query/package.json b/packages/tanstack-query/package.json index 88a13e7..0671cb1 100644 --- a/packages/tanstack-query/package.json +++ b/packages/tanstack-query/package.json @@ -28,8 +28,8 @@ "@zenstackhq/runtime": "workspace:*" }, "devDependencies": { - "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "@zenstackhq/eslint-config": "workspace:*", + "@zenstackhq/typescript-config": "workspace:*" }, "peerDependencies": { "@tanstack/react-query": "^5.0.0" diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 10e7cfe..b3fd12c 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "@types/tmp": "^0.2.6", - "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "@zenstackhq/eslint-config": "workspace:*", + "@zenstackhq/typescript-config": "workspace:*" } } diff --git a/packages/zod/package.json b/packages/zod/package.json index e025e0e..98852b0 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -29,8 +29,8 @@ "ts-pattern": "catalog:" }, "devDependencies": { - "@zenstackhq/typescript-config": "workspace:*", - "@zenstackhq/eslint-config": "workspace:*" + "@zenstackhq/eslint-config": "workspace:*", + "@zenstackhq/typescript-config": "workspace:*" }, "peerDependencies": { "zod": "catalog:"