accel-record
Advanced tools
Comparing version 0.1.2 to 0.2.0
{ | ||
"name": "accel-record", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "", | ||
@@ -22,3 +22,5 @@ "type": "module", | ||
"accella", | ||
"accel-record" | ||
"accel-record", | ||
"activerecord", | ||
"orm" | ||
], | ||
@@ -33,6 +35,6 @@ "files": [ | ||
"dependencies": { | ||
"accel-record-core": "^0.1.1", | ||
"accel-record-core": "^0.2.0", | ||
"accel-record-factory": "^0.1.1", | ||
"prisma-generator-accel-record": "^0.1.1" | ||
"prisma-generator-accel-record": "^0.2.0" | ||
} | ||
} |
288
README.md
@@ -34,3 +34,2 @@ # Accel Record | ||
```ts | ||
// src/index.ts | ||
import { User } from "./models/index.js"; | ||
@@ -65,3 +64,3 @@ | ||
class UserModel extends ApplicationRecord { | ||
export class UserModel extends ApplicationRecord { | ||
// Define a method to get the full name | ||
@@ -75,3 +74,2 @@ get fullName(): string { | ||
```ts | ||
// src/index.ts | ||
import { User } from "./models/index.js"; | ||
@@ -169,1 +167,285 @@ | ||
``` | ||
## Examples | ||
### Creating and Saving Data | ||
```ts | ||
import { NewUser, User } from "./models/index.js"; | ||
// Create a user | ||
const user: User = User.create({ | ||
firstName: "John", | ||
lastName: "Doe", | ||
}); | ||
console.log(user.id); // => 1 | ||
// You can also write it like this | ||
const user: NewUser = User.build({}); | ||
user.firstName = "Alice"; | ||
user.lastName = "Smith"; | ||
user.save(); | ||
console.log(user.id); // => 2 | ||
``` | ||
### Retrieving Data | ||
```ts | ||
import { User } from "./models/index.js"; | ||
const allUsers = User.all(); | ||
console.log(`IDs of all users: ${allUsers.map((u) => u.id).join(", ")}`); | ||
const firstUser = User.first(); | ||
console.log(`Name of the first user: ${firstUser?.firstName}`); | ||
const john = User.findBy({ firstName: "John" }); | ||
console.log(`ID of the user with the name John: ${john?.id}`); | ||
const does = User.where({ lastName: "Doe" }); | ||
console.log(`Number of users with the last name Doe: ${does.count()}`); | ||
``` | ||
### Updating Data | ||
```ts | ||
import { User } from "./models/index.js"; | ||
const user = User.first()!; | ||
user.update({ age: 26 }); | ||
// You can also write it like this | ||
user.age = 26; | ||
user.save(); | ||
``` | ||
### Deleting Data | ||
```ts | ||
import { User } from "./models/index.js"; | ||
const user = User.first()!; | ||
// Delete a record | ||
user.delete(); | ||
// Alternatively, delete with associations | ||
user.destroy(); | ||
``` | ||
## Model Types | ||
### NewModel and PersistedModel | ||
Accel Record provides two types, `NewModel` and `PersistedModel`, to distinguish between newly created and saved models. \ | ||
Depending on the schema definition, some properties in `NewModel` allow `undefined`, while `PersistedModel` does not. \ | ||
This allows you to handle both pre-save and post-save models in a type-safe manner. | ||
```ts | ||
import { User, NewUser } from "./models/index.js"; | ||
/* | ||
Example of NewModel: | ||
NewUser represents a pre-save model and has the following type: | ||
interface NewUser { | ||
id: number | undefined; | ||
firstName: string | undefined; | ||
lastName: string | undefined; | ||
age: number | undefined; | ||
} | ||
*/ | ||
const newUser: NewUser = User.build({}); | ||
/* | ||
Example of PersistedModel: | ||
User represents a saved model and has the following type: | ||
interface User { | ||
id: number; | ||
firstName: string; | ||
lastName: string; | ||
age: number | undefined; | ||
} | ||
*/ | ||
const persistedUser: User = User.first()!; | ||
``` | ||
### BaseModel | ||
The above `NewModel` and `PersistedModel` inherit from `BaseModel`. \ | ||
Methods defined in `BaseModel` can be used by both `NewModel` and `PersistedModel`. | ||
```ts | ||
// src/models/user.ts | ||
import { ApplicationRecord } from "./applicationRecord.js"; | ||
/* | ||
Example of BaseModel: | ||
UserModel corresponds to `NewUser` and `User` in `BaseModel`. | ||
*/ | ||
export class UserModel extends ApplicationRecord { | ||
// Methods defined here can be used by both `NewUser` and `User`. | ||
get fullName(): string | undefined { | ||
if (!this.firstName || !this.lastName) { | ||
// For `NewUser`, we need to consider the possibility of `firstName` and `lastName` being `undefined`. | ||
return undefined; | ||
} | ||
return `${this.firstName} ${this.lastName}`; | ||
} | ||
} | ||
``` | ||
```ts | ||
import { User, NewUser } from "./models/index.js"; | ||
const newUser: NewUser = User.build({}); | ||
console.log(newUser.fullName); // => undefined | ||
const user: User = User.first()!; | ||
console.log(user.fullName); // => "John Doe" | ||
``` | ||
You can also define methods that are type-safe and can only be used by `PersistedModel` by specifying the `this` type for the method. (In this case, the `get` keyword cannot be used due to TypeScript specifications) | ||
```ts | ||
// src/models/user.ts | ||
import { ApplicationRecord } from "./applicationRecord.js"; | ||
import { User } from "./index.js"; | ||
export class UserModel extends ApplicationRecord { | ||
// This method can only be used by `User` and is type-safe. Using it with `NewUser` will result in a type error. | ||
fullName(this: User): string { | ||
return `${this.firstName} ${this.lastName}`; | ||
} | ||
} | ||
``` | ||
```ts | ||
import { User, NewUser } from "./models/index.js"; | ||
const newUser: NewUser = User.build({}); | ||
// @ts-expect-error | ||
newUser.fullName(); | ||
// => The 'this' context of type 'NewUser' is not assignable to method's 'this' of type 'User'. | ||
const user: User = User.first()!; | ||
console.log(user.fullName()); // => "John Doe" | ||
``` | ||
### Converting from NewModel to PersistedModel | ||
By using methods like `save()` and `isPersisted()`, you can convert a `NewModel` type to a `PersistedModel` type. | ||
```ts | ||
import { User, NewUser } from "./models/index.js"; | ||
// Prepare a user of NewModel type | ||
const user: NewUser = User.build({ | ||
firstName: "John", | ||
lastName: "Doe", | ||
}); | ||
if (user.save()) { | ||
// If save is successful, the NewModel is converted to PersistedModel. | ||
// In this block, the user can be treated as User type. | ||
console.log(user.id); // user.id is of type number | ||
} else { | ||
// If save fails, the NewModel remains the same type. | ||
// In this block, the user remains as NewUser type. | ||
console.log(user.id); // user.id is of type number | undefined | ||
} | ||
const someFunc = (user: NewUser | User) => { | ||
if (user.isPersisted()) { | ||
// If isPersisted() is true, the NewModel is converted to PersistedModel. | ||
// In this block, the user can be treated as User type. | ||
console.log(user.id); // user.id is of type number | ||
} else { | ||
// If isPersisted() is false, the NewModel remains the same type. | ||
// In this block, the user remains as NewUser type. | ||
console.log(user.id); // user.id is of type number | undefined | ||
} | ||
}; | ||
``` | ||
## Prisma Schema and Field Types | ||
Accel Record uses Prisma for schema definition, and the support status for each feature is as follows: | ||
| Feature | Notation | Support | | ||
| ------------------------------- | ----------- | ------- | | ||
| ID | @id | ✅ | | ||
| Multi-field ID (Composite ID) | @@id | - | | ||
| Table name mapping | @@map | ✅ | | ||
| Column name mapping | @map | ✅ | | ||
| Default value | @default | ✅ | | ||
| Updated at | @updatedAt | ✅ | | ||
| List | [] | ✅ | | ||
| Optional | ? | ✅ | | ||
| Relation field | | ✅ | | ||
| Implicit many-to-many relations | | ✅ | | ||
| Enums | enum | ✅ | | ||
| Unsupported type | Unsupported | - | | ||
The types of NewModel and PersistedModel differ depending on whether the field type is required or optional. | ||
| Type | NewModel | PersistedModel | | ||
| -------------- | -------- | --------------- | | ||
| Required Field | Nullable | **NonNullable** | | ||
| Optional Field | Nullable | Nullable | | ||
In addition, the types of NewModel and PersistedModel differ depending on how the default value is specified. | ||
| Argument | NewModel | PersistedModel | | ||
| --------------- | --------------- | --------------- | | ||
| Static value | **NonNullable** | **NonNullable** | | ||
| autoincrement() | Nullable | **NonNullable** | | ||
| now() | Nullable | **NonNullable** | | ||
| dbgenerated() | Nullable | **NonNullable** | | ||
| uuid() | **NonNullable** | **NonNullable** | | ||
| cuid() | **NonNullable** | **NonNullable** | | ||
Here are examples of model definitions and their corresponding NewModel and PersistedModel: | ||
```ts | ||
// prisma/schema.prisma | ||
model Sample { | ||
id Int @id @default(autoincrement()) | ||
required Int | ||
optional String? | ||
hasDefault Boolean @default(false) | ||
createdAt DateTime @default(now()) | ||
updatedAt DateTime @updatedAt | ||
uuid String @default(uuid()) | ||
cuid String @default(cuid()) | ||
} | ||
``` | ||
```ts | ||
// NewModel | ||
interface NewSample { | ||
id: number | undefined; | ||
required: number | undefined; | ||
optional: string | undefined; | ||
hasDefault: boolean; | ||
createdAt: Date | undefined; | ||
updatedAt: Date | undefined; | ||
uuid: string; | ||
cuid: string; | ||
} | ||
// PersistedModel | ||
interface Sample { | ||
id: number; | ||
required: number; | ||
optional: string | undefined; | ||
hasDefault: boolean; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
uuid: string; | ||
cuid: string; | ||
} | ||
``` |
12060
448
+ Addedaccel-record-core@0.2.0(transitive)
+ Addedprisma-generator-accel-record@0.2.0(transitive)
- Removedprisma-generator-accel-record@0.1.1(transitive)
Updatedaccel-record-core@^0.2.0