Skip to content
This repository has been archived by the owner on Oct 21, 2020. It is now read-only.

WIP New Prisma Client JS API draft #356

Closed
6 of 59 tasks
schickling opened this issue Dec 11, 2019 · 6 comments
Closed
6 of 59 tasks

WIP New Prisma Client JS API draft #356

schickling opened this issue Dec 11, 2019 · 6 comments
Assignees
Labels

Comments

@schickling
Copy link
Member

schickling commented Dec 11, 2019

Prisma Schema

The example code below assumes the following Prisma schema:

// datasource config ...

type ID = String @id @default(cuid())

model Post {
  id        ID
  title     String
  body      String
  published Boolean
  comments  Comment[]
  author    User
}

model Comment {
  id     ID
  text   String
  post   Post
  media  Media[]
  author User
}

embed Media {
  url      String
  uploaded Boolean
}

model User {
  id           ID
  firstName    String
  lastName     String
  email        String
  posts        Post[]
  comments     Comment[]
  friends      User[]    @relation("friends")
  profile      Profile
  bestFriend   User?     @relation("bestFriend") @unique
  version      Int
}

embed Profile {
  imageUrl  String
  imageSize String
}

Types

// NOTE the following types are auto-generated
type Post = {
  id: string
  title: string
  body: string
  published: boolean
}

type Comment = {
  id: string
  text: string
  media: Media[]
}

type Media = {
  url: string
  uploaded: boolean
}

type User = {
  id: string
  firstName: string
  lastName: string
  email: string
  profile: Profile
  version: number
}

type Profile = {
  imageUrl: string
  imageSize: number
}

Terminology

  • To be defined
    • Query
    • Operation
    • Write
    • Read
    • Selection set
    • Query builder
    • Terminating/chainable operation
    • Graph selection: Travesal vs operate
    • ...

Constructor

export type LogLevel = 'info' | 'warn' | 'query' 

export type LogOption = LogLevel | {
  level: LogLevel
  /**
   * @default 'stdout'
   */
  emit?: 'event' | 'stdout'
}

export interface PrismaClientOptions {
  datasources?: Datasources

  /**
   * @default false
   */
  log?: boolean | LogOption[]

  /**
   * You probably don't want to use this. \`__internal\` is used by internal tooling.
   */
  __internal?: {
    debug?: boolean
    hooks?: Hooks
    engine?: {
      cwd?: string
      binaryPath?: string
    }
  }
}

Logging & Debugging

Logging

All engine related logs can be configured with the log property.
These are examples how to specify different log levels:

Just providing the log levels, stdout as default

const prisma = new PrismaClient({
  log: ['info', 'query'],
})

Changing on a per log level, where the logs end up: As an event or in stdout.

const prisma = new PrismaClient({
  log: [
    {
      level: 'info',
      emit: 'stdout',
    },
    {
      level: 'query',
      emit: 'event',
    },
    'WARN',
  ],
})

prisma.on('query', e => {
  console.log(e.timestamp, e.query, e.args)
})

Log level names get mapped to the event name for the event emitter.

const prisma = new PrismaClient({
  log: [
    {
      level: 'info',
      emit: 'event',
    },
    {
      level: 'query',
      emit: 'event',
    },
    {
      level: 'warn',
      emit: 'event',
    },
  ],
})

prisma.on('query', e => {
  e.timestamp
  e.query
  e.args
  console.log(e)
})

prisma.on('info', e => {
  e.timestamp
  e.message
  console.log(e)
})

prisma.on('warn', e => {
  e.timestamp
  e.message
  console.log(e)
})

Debugging

The debug property includes debugging logs of the TypeScript implementation; whereas, the log property addresses the logs coming from the query engine.
As the TypeScript debugging logs are mostly used by maintainers and not users, it was moved to __internal to not confuse users of the PrismaClient.js library.

const prisma = new PrismaClient({
  __internal: {
    debug: true,
  },
})

Basic Queries

// Find single record by @id field. Returns nullable result
const alice: User | null = await prisma.user.find('user-id')

// Find single record by other unique field
const alice: User | null = await prisma.user.find({
  email: 'alice@prisma.io',
})

// Find using composite/multi-field unique indexes
// Note: This example is not compatible with the example schema above.
const john: User | null = await prisma.user.find({
  firstName_lastName: {
    firstName: 'John',
    lastName: 'Doe',
  },
})

// Find using named unique relation
const bob: User | null = await prisma.user.find({
  bestFriend: { email: 'alice@prisma.io' },
})

// Get many nodes
const allUsers: User[] = await prisma.user.findMany()
const first100Users: User[] = await prisma.user.findMany({ first: 100 })

// Find by (non-)unique field and return first found record
const firstDoe: User | null = await prisma.user
  .findMany({ where: { lastName: 'Doe' } })
  .first()

// Alternative
const firstDoe: User | null = await prisma.user.findFirst({
  where: { lastName: 'Doe' },
})

// Ordering
const usersByEmail: User[] = await prisma.user.findMany({
  orderBy: { email: 'ASC' },
})
const usersByEmailAndName: User[] = await prisma.user.findMany({
  orderBy: { email: 'ASC', name: 'DESC' },
})
const usersByProfile: User[] = await prisma.user.findMany({
  orderBy: { profile: { imageSize: 'ASC' } },
})

// Where / filtering
await prisma.user.findMany({ where: { email: 'alice@gmail.com' } })
await prisma.user.findMany({ where: { email: { contains: '@gmail.com' } } })
await prisma.user.findMany({
  where: { email: { containsInsensitive: '@gmail.com' } },
})

// Exists
const userFound: boolean = await prisma.user.find('bobs-id').exists()
const foundAtLeastOneUser: boolean = await prisma.user
  .findMany({ email: { containsInsensitive: '@gmail.com' } })
  .exists()

// Simple aggregation short
// TODO more examples
const deletedCount: number = await prisma.user.delete().count()

Field-level Primary Key constraint

Different unique constraints will change Prisma Client's where blocks:

model User {
    id String @id
    firstName String
    lastName String
    email String
}
prisma.user.find({ id: 10 })

Field-level unique constraint

model User {
    id String @id
    firstName String
    lastName String
    email String  @unique
}
// allowed
prisma.user.find({ id: 10 })
prisma.user.find({ email: 'alice@prisma.io' })
// compiler error
prisma.user.find({ id: 10, email: 'alice@prisma.io' })

Model-level composite constraint (unnamed)

model User {
    id String @id
    firstName String
    lastName String
    email String @unique
    @@unique([ firstName, lastName ])
}
// allowed
prisma.user.find({ id: 10 })
prisma.user.find({ email: 'alice@prisma.io' })
prisma.user.find({
  firstName_lastName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})
// compiler error
prisma.user.find({
  email: 'alice@prisma.io',
  firstName_lastName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})
prisma.user.find({
  id: 10,
  firstName_lastName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})

Naming the composite constraint

model User {
    id String @id
    firstName String
    lastName String
    email String @unique
    @@unique([ firstName, lastName ], alias: "fullName")
}
// allowed
prisma.user.find({ id: 10 })
prisma.user.find({ email: 'alice@prisma.io' })
prisma.user.find({
  fullName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})
// compiler error
prisma.user.find({
  email: 'alice@prisma.io',
  fullName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})
prisma.user.find({
  id: 10,
  fullName: {
    firstName: 'Alice',
    lastName: 'Prisma',
  },
})

Writing Data

Write operations

Nested Write API

Operation Touches Record Touches Link
link No Yes
unlink No Yes
??? resetAndLink No Yes
linkOrCreate Yes Yes
create Yes Yes
update Yes No
replace Yes No
delete Yes Yes

Fluent Write API

  • create
  • orCreate
  • update
  • replace
  • delete
// Returns Promise<void>
await prisma.user.create({
  firstName: 'Alice',
  lastName: 'Doe',
  email: 'alice@prisma.io',
  profile: { imageUrl: 'http://...', imageSize: 100 },
})

// Returns Promise<void>
await prisma.user.find('bobs-id').update({ firstName: 'Alice' })

// Returns Promise<void>
await prisma.user
  .find({ email: 'bob@prisma.io' })
  .update({ firstName: 'Alice' })

// Like `update` but replaces entire record. Requires all required fields like `create`.
// Resets all connections.
// Returns Promise<void>
await prisma.user.find({ email: 'bob@prisma.io' }).update(
  {
    firstName: 'Alice',
    lastName: 'Doe',
    email: 'alice@prisma.io',
    profile: { imageUrl: 'http://...', imageSize: 100 },
  },
  { replace: true },
)

// Returns Promise<void>
await prisma.user.find('alice-id').orCreate({
  firstName: 'Alice',
  lastName: 'Doe',
  email: 'alice@prisma.io',
  profile: { imageUrl: 'http://...', imageSize: 100 },
})

// Returns Promise<void>
await prisma.user
  .find('alice-id')
  .update({ firstName: 'Alice' })
  .orCreate({
    firstName: 'Alice',
    lastName: 'Doe',
    email: 'alice@prisma.io',
    profile: { imageUrl: 'http://...', imageSize: 100 },
  })

// Note: Delete operation sends query BEFORE record is deleted -> no .load() possible
await prisma.user.find('bobs-id').delete()

// Write operations can be chained with the `.load()` API
const updatedUser: User = await prisma.user
  .find('bobs-id')
  .update({ firstName: 'Alice' })
  .load()

Many operations

await prisma.user
  .findMany({ where: { email: { endsWith: '@gmail.com' } } })
  .update({ lastName: 'Doe' })

await prisma.user
  .findMany({ where: { email: { endsWith: '@gmail.com' } } })
  .delete()

Nested writes

// Nested create
await prisma.user.create({
  firstName: 'Alice',
  posts: {
    create: { title: 'New post', body: 'Hello world', published: true },
  },
})

// TODO: How to return data from nested writes
// - how many records were affected (e.g. nested update many)
// await prisma.user
//   .create({
//     firstName: 'Alice',
//     posts: {
//       create: { title: 'New post', body: 'Hello world', published: true },
//     },
//   })
//   .load({ select: { posts: { newOnly: true } } })

// Nested write with connect
await prisma.user.create({
  firstName: 'Alice',
  lastName: 'Doe',
  email: 'alice@prisma.io',
  profile: { imageUrl: 'http://...', imageSize: 100 },
  posts: { connect: 'post-id' },
})

// Create a post and connect it to the author with a unique constraint
await prisma.post.create({
  title: 'My cool blog post',
  author: {
    connect: {
      firstName_lastName: {
        firstName: 'John',
        lastName: 'Doe',
      },
    },
  },
})

// How to get newly created posts?
await prisma.user.find('bobs-id').update({
  posts: {
    create: { title: 'New post', body: 'Hello world', published: true },
  },
})

//////////////

await prisma.user.create({
  firstName: 'Alice',
  lastName: 'Doe',
  email: 'alice@prisma.io',
  profile: { imageUrl: 'http://...', imageSize: 100 },
  posts: p => p.connect('post-id'),
})

await prisma.user.find('bobs-id').update({
  posts: e => {
    e.create({ title: 'New post', body: 'Hello world', published: true })
    e.create([{ title: 'New post', body: 'Hello world', published: true }])
    e.update({ title: 'New post', body: 'Hello world', published: true })
    e.create({
      title: 'New post',
      body: 'Hello world',
      published: true,
      comments: c => c.create({ text: 'This is a comment' }),
    })

    e.operation(/* AST: */[
      {
        type: 'create',
        data: {
          title: 'New post',
          body: 'Hello world',
          published: true,
          comments:
        },
      },
    ])
  },
})

//////////////

Load: Select / Include API

  • Implicit load: Read operations
  • Explicit load:
    • After write operations
    • Custom selection set via include/select
// Select API
type DynamicResult1 = {
  posts: { comments: Comment[] }[]
  friends: User[]
}[]

const userWithPostsAndFriends: DynamicResult1 = await prisma.user
  .find('bobs-id')
  .load({ select: { posts: { select: { comments: true } }, friends: true } })

type DynamicResult2 = (User & {
  posts: (Post & { comments: Comment[] })[]
  friends: User[]
})[]

const userWithPostsAndFriends: DynamicResult2 = await prisma.user
  .find('bobs-id')
  .load({ include: { posts: { include: { comments: true } }, friends: true } })

Default selection set

  • TODO
    • Scalars
    • Relations
    • ID
    • Embeds

Advanced Fluent API

  • TODO: Spec out difference between chainable vs terminating
    • Chainable: schema-based fields (e.g. relations), find, update, upsert, create,
    • Terminating: select, include, delete, count, scalar field, exists, aggregates?
  • TODO: Spec out return behavior
    • read traversal
    • write operation: single vs multi-record operation
    • Alternative: explicit fetch/query
  • TODO: tradeoff multi-record-operations -> return count by default but can be adjusted to return actual data -> how?
  • TODO: Document transactional behavior
const bobsPosts: Post[] = await prisma.user.find('bobs-id').post({ first: 50 })

// Nested arrays are flat-mapped
const comments: Comment[] = await.prisma.user
  .find('bobs-id')
  .post()
  .comments()

type DynamicResult3 = (Post & { comments: Comment[] })[]

const bobsPosts: DynamicResult3 = await prisma.user
  .find('bobs-id')
  .post({ first: 50 })
  .load({ include: { comments: true } })

const media: Media[] = await prisma.post
  .find('id')
  .comments({ where: { text: { startsWith: 'Hello' } } })
  .media({ where: { url: 'exact-url' }, first: 100 })
  .update({ uploaded: true })
  .load()

// Supports chaining multiple write operations
await prisma.user
  .find('user-id')
  .update({ email: 'new@email.com' })
  .post({ where: { published: true } })
  .update({ comments: { connect: 'comment-id' } })

Null narrowing

  • Single-record find queries return null by default
  • update and delete as well as field traversal queries will fail if record is null

Expressing the same query using fluent API syntax and nested writes

  • TODO
  • Add to spec: Control execution order of nested writes
// Declarative
await prisma.user.find('bob-id').update({
  email: 'new@email.com',
  posts: {
    update: {
      where: { published: true },
      data: { comments: { connect: 'comment-id' } },
    },
  },
})

// Fluent
await prisma.user
  .find('user-id')
  .update({ email: 'new@email.com' })
  .post({ where: { published: true } })
  .update({ comments: { connect: 'comment-id' } })

// await prisma.user
//   .find('user-id')
//   .update({ email: 'new@email.com' })
//   .post({ where: { published: true } })
//   .comments()
//   .connect('comment-id')

Mental model: Graph traversal

  • Idea: Select one or multiple records. Then read and/or write them.

Working with types

  • Use cases
    • Constructing arguments
    • Return types (e.g. select/include results -> typeof ?)
    • Fluent API

Expressions

  • Kinds
    • Criteria filters (where)
    • Order by
    • Write operations
    • Select (aggregations)
    • Group by

Criteria Filters

// Find one
await prisma.user.find(u => u.email.eq('alice@prisma.io'))

// Find many
await prisma.user.findMany({
  where: u => u.firstName.subString(1, 4).eq('arl'),
})
await prisma.user.findMany({ where: u => u.email.contains('@gmail.com') })
await prisma.user.findMany({
  where: u => u.email.insensitive.contains('@gmail.com'),
})
await prisma.user.findMany({
  where: u => u.email.toLowerCase().contains('@gmail.com'),
})

Order By

The query findMany allows the user to supply the orderBy argument which controls the sorting of the returned array of records:

await prisma.user.findMany({ orderBy: u => u.email.asc() })
await prisma.user.findMany({
  orderBy: (u, e) => e.and(u.email.asc(), u.firstName.desc()),
})
await prisma.user.findMany({ orderBy: u => u.profile.imageSize.asc() })

If a query uses pagination parameters but no orderBy parameter is supplied an implicit orderBy for the id field of the model is added. The following two queries are semantically equivalent:

await prisma.user.findMany({ first: 100 })
await prisma.user.findMany({ first: 100, orderBy: u => u.id.asc() })

Write Operations (Update/Atomic)

Set:

await prisma.users
  .findMany()
  .update({ email: u => u.email.set('bob@gmail.com') })

Type specific:

See:

// String
await prisma.users.findMany().update({ email: u => u.email.concat('-v2') })
await prisma.users.findMany().update({ email: u => u.email.toLowerCase() })
await prisma.users.findMany().update({ email: u => u.email.subString(1, 4) })
// ...

// Numbers: Int, Float, ...
await prisma.users.findMany().update({ version: u => u.version.inc(5) })
await prisma.users.findMany().update({ version: u => u.version.dec(5) })
await prisma.users.findMany().update({ version: u => u.version.mul(5) })
await prisma.users.findMany().update({ version: u => u.version.div(5) })
await prisma.users.findMany().update({ version: u => u.version.mod(5) })
await prisma.users.findMany().update({ version: u => u.version.pow(5, 10) })
await prisma.users.findMany().update({ version: u => u.version.min(5, 10) })
await prisma.users.findMany().update({ version: u => u.version.max(5, 10) })

// Boolean

// Arrays

Top level callback

await prisma.users.findMany().update(u => ({
  firstName: u.firstName.toLowerCase(),
  lastName: u.lastName.toLowerCase(),
}))

Aggregations

  • count
  • sum
  • avg
  • median
  • max
  • min
await prisma.users.findMany({ where: { version: 5 } }).load({
  include: { aggr: u => ({ postCount: u.posts.count() }) },
})

await prisma.users
  .findMany({ where: { version: 5 } })
  .load({ include: { postCount: u => u.posts.count() } })

await prisma.users.findMany({ where: { version: 5 } }).load({
  select: {
    id: true,
    postCount: u => u.posts({ where: { p => p.comments().count().gt(10) } }).count(),
  },
})

Group by

type DynamicResult4 = {
  key: string
  records: User[]
}
const groupByResult: DynamicResult4 = await prisma.user
  .findMany({
    where: { isActive: true },
    orderBy: { lastName: 'ASC' },
    first: 100,
  })
  .group({
    by: 'lastName',
    having: g => g.age.avg.gt(10),
    first: 10,
  })
  .load({ include: { avgAge: g => g.age.avg() } })

await prisma.user
  .findMany({ where: u => u.age.lt(90) })
  .group({ by: u => u.version.div(10) })
  .load({ include: { versionSum: g => g.version.sum() } })

await prisma.user.findMany().group({ by: u => u.email.toLowerCase() })

Distinct

// TODO: Do we really need this API?
const values: string[] = await prisma.post.findMany().title({ distinct: true })

const values: string[] = await prisma.post
  .findMany()
  .distinct({ title: true })
  .title()

type SubSet = { published: boolean; title: string }
const values: SubSet[] = await prisma.post
  .findMany()
  .distinct({ published: true, title: true })

const distinctCount: number = await prisma.post
  .findMany()
  .distinct({ published: true, title: true })
  .count()

// TODO count distinctly grouped value -> see aggregations

Experimental: Meta response

Note: This is a early draft for this feature and won't be implemented in the near future

// PageInfo
const bobsPostsWithPageInfo: PageInfo<Post> = await prisma.user
  .find('bobs-id')
  .post({ first: 50 })
  .loadWithPageInfo()

type PageInfo<Data> = {
  data: Data[]
  hasNext: boolean
  hasPrev: boolean
}

const [bobsPosts, meta]: [Post[], Meta] = await prisma.user
  .find('bobs-id')
  .post({ first: 50 })
  .loadWithMeta({ pageInfo: true })
  • Meta

    • pageinfo
    • traces/performance
    • which records have been touched during a (nested) write
  • Strategies

    • By extending load API + return object
    • Return extra meta object
  • Can be applied to every paginable list and stream

Optimistic Concurrency Control / Optimistic Offline Lock

  • TODO Needs to be specced out for nested Graph API

Supported operations

  • update
  • replace
  • delete
  • update
  • delete
await prisma.user
  .find('alice-id')
  .update({ firstName: 'Alice' }, { if: { version: 12 } })

await prisma.user
  .find('alice-id')
  .update({ firstName: 'Alice' }, { if: { version: 12 } })
  .orCreate({
    /* ... */
  })

await prisma.user.find('bobs-id').delete({ if: { version: 12 } })

// Global condition
await prisma.user
  .find('bobs-id')
  .delete()
  .if([{ model: 'User', where: 'bobs-id', if: { name: 'Bob' } }])

// both can be combined
await prisma.user
  .find('bobs-id')
  .delete({ if: { version: 12 } })
  .if([{ model: 'User', where: 'alices-id', if: { name: 'Alice' } }])

Batching

Note: Combined with OCC (i.e. if) also known as "unit of work"

// Batching, don't get the results with $noData
const m1 = prisma.user.create({ firstName: 'Alice' }).load()
const m2 = prisma.post.create({ title: 'Hello world' })
const [u1, p1]: [User, void] = await prisma.batch([m1, m2])

// TODO
// - `if` API
// - error handling: throw on first error or batch errors

// Batching with transaction
await prisma.batch([m1, m2], { transaction: true })

Criteria API

Design decisions

  • Choose boolean-based nested object syntax instead of array
  • language native DSL (Linq) vs string-based DSL (GraphQL)

Constructor

Connection management

  • Lazy connect by default
const prisma = new PrismaClient()
await prisma.connect()

await prisma.disconnect()

Error Handling

Error Character Encoding

Prisma Client generates pretty error messages with ANSI characters for features like color coding errors and warnings in queries and newlines that are very useful for
development as they usually pin point the issue.

However, when this data is serialized it contains a lot of unicode characters.

Serialized Prisma Client error


Error: ^[[31mInvalid ^[[1m`const data = await prisma.users.findMany()`^[[22m invocation in ^[[4m/Users/divyendusingh/Documents/prisma/p2-studio/index.js:8:37^[[24m^[[39m ^[[2m ^[[90m 4 ^[[39m^[[36mconst^[[39m prisma = ^[[36mnew^[[39m PrismaClient^[[38;2;107;139;140m(^[[39m^[[38;2;107;139;140m)^[[39m^[[22m ^[[2m ^[[90m 5 ^[[39m^[[22m ^[[2m ^[[90m 6 ^[[39m^[[36masync^[[39m ^[[36mfunction^[[39m ^[[36mmain^[[39m^[[38;2;107;139;140m(^[[39m^[[38;2;107;139;140m)^[[39m ^[[38;2;107;139;140m{^[[39m^[[22m ^[[2m ^[[90m 7 ^[[39m ^[[36mtry^[[39m ^[[38;2;107;139;140m{^[[39m^[[22m ^[[31m^[[1m→^[[22m^[[39m ^[[90m 8 ^[[39m ^[[36mconst^[[39m data = ^[[36mawait^[[39m prisma^[[38;2;107;139;140m.^[[39musers^[[38;2;107;139;140m.^[[39m^[[36mfindMany^[[39m^[[38;2;107;139;140m(^[[39m{ ^[[91munknown^[[39m: ^[[2m'1'^[[22m^[[2m^[[22m ^[[91m~~~~~~~^[[39m ^[[2m^[[22m}^[[2m)^[[22m Unknown arg ^[[91m`unknown`^[[39m in ^[[1munknown^[[22m for type ^[[1mUser^[[22m. Did you mean `^[[92mskip^[[39m`? ^[[2mAvailable args:^[[22m ^[[2mtype^[[22m ^[[1m^[[2mfindManyUser^[[1m^[[22m ^[[2m{^[[22m ^[[2m^[[32mwhere^[[39m?: ^[[37mUserWhereInput^[[39m^[[22m ^[[2m^[[32morderBy^[[39m?: ^[[37mUserOrderByInput^[[39m^[[22m ^[[2m^[[32mskip^[[39m?: ^[[37mInt^[[39m^[[22m ^[[2m^[[32mafter^[[39m?: ^[[37mString^[[39m^[[22m ^[[2m^[[32mbefore^[[39m?: ^[[37mString^[[39m^[[22m ^[[2m^[[32mfirst^[[39m?: ^[[37mInt^[[39m^[[22m ^[[2m^[[32mlast^[[39m?: ^[[37mInt^[[39m^[[22m ^[[2m}^[[22m

There are two prominent use cases amongst others for disabling/better structuring the error logs:

  1. In Production logs, one might want to read the error messages thrown by Prisma Client.

  2. In tools like Studio, it currently strips the ANSI characters (like this) and displays the output as
    the error message.

To solve these two use case, Prisma Client can do the following:

  1. Use NODE_ENV. When NODE_ENV is set to development or is unset, Prisma Client can emit logs with ANSI characters. However, when NODE_ENV is set to
    production Prisma Client can omit ANSI characters and any additional newline characters from the logs.

  2. PrismaClient can additionally offer prettyLogs as a constructor argument (defaults to true) to switch off the pretty error logging.

Unresolved questions

  • distinct
    • select/include

Figured out but needs spec

  • Error handling
  • OCC (also for nested operations)
  • Implicit back relations
  • Name clashes / $ pre-fixing
  • Pluralization
  • Criteria API
  • File layout of generated node package
  • Type-mapping (default + custom)
  • Generated type-names for implemenation (what's exported vs internal)

Bigger todos

Small & self-contained

  • Decouple engine connect API from Prisma Client instance (solves: https://github.com/prisma/prismajs/issues/153)
  • Should we load data by default for create operations?
  • "Dataloader"
  • Query engine logging
  • Enable .first() type narrowing for include / select
  • Distinct
  • Tracing
  • find vs findUnique vs get ...
  • Terminology: link vs connect (https://github.com/prisma/prismajs/issues/227 and Photon Terminology #140 (comment))
  • Cascading deletes
  • Force indexes
  • PrismaClient constructor API
  • Generator/binary versioning
  • Options argument
  • Exec
  • Rename where to Criteria (filter/unique criteria)
  • Connection handling/config
  • Composite models: field grouping for efficient look ups

Ugly parts

  • Select/Include API: Chainable .select() vs nested { select: { } } API
  • Upsert, findOrCreate, ...
  • Line between main arg vs options arg

Related

  • OCC needs triggers

Future topics

  • Non-CRUD API operations
  • Silent mutations prisma/prisma#4075
  • Real-time API (subscriptions/live queries)
  • Dependent batch writes (see GraphQL export directive Feature request: batchOperations graphql/graphql-js#462)
  • Usage in browser (depends on WASM)
  • Prisma Client usage with Prisma server/cluster
  • Meta responses
    • How to query records that were "touched" during nested writes
    • (Nested) page info
  • Union queries
@schickling schickling self-assigned this Dec 11, 2019
@tvvignesh
Copy link

@schickling This draft is insanely amazing. Infact, you can use this itself as docs once its implemented.

Great work 👍 Just hoping that you will get there soon! Btw, is this planned before GA?

@schickling
Copy link
Member Author

Glad you like it @tvvignesh. We will most likely not be able to implement all of the suggested API changes above but this will be the direction we're going to. Would be great if you could add more thoughts in a further comment what exactly you like about the changes above. Any kind of feedback is helpful!

@schickling schickling pinned this issue Jan 4, 2020
@ctrlplusb
Copy link

ctrlplusb commented Jan 9, 2020

@schickling - wow, lots of good stuff here. some great API improvements that will for sure provide a more intuitive mechanism by which to construct queries/mutations.

QQ: Would this deprecate the current API or rather be an alternative/extension?

@tvvignesh
Copy link

Glad you like it @tvvignesh. We will most likely not be able to implement all of the suggested API changes above but this will be the direction we're going to. Would be great if you could add more thoughts in a further comment what exactly you like about the changes above. Any kind of feedback is helpful!

@schickling Sorry for the late reply.

More than any feature in specific, its the documentation which I really liked. Also the fact that you have thought out lots of possible use cases.

The main thing more than documentation in any project is a sample implementation of the same which I see a lot here. Typically people just say this is the function, this is the parameter and leave it there. This takes it to the next level.

@Jolg42 Jolg42 changed the title WIP New Photon.js API draft WIP New Prisma Client JS API draft Feb 14, 2020
@janpio janpio unpinned this issue Feb 16, 2020
@schickling schickling pinned this issue Mar 11, 2020
@flybayer
Copy link

There's some good stuff in here! Will any of this make it into v1?

@schickling
Copy link
Member Author

@flybayer we've started breaking up the API above into separate smaller feature requests which we'll start to ship one by one for now.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants