#realtime context is wonky; no redis entries

1 messages · Page 1 of 1 (latest)

red oracle
#

I implemented a subscription for notifications. Even though it works, it doesn't seem to write the published notifications into my redis db. I fear I have an issue with the GlobalContext and how pubSub is used. There might have been an issue with the realtime plugin setup, so I just want to confirm and see what I'm doing wrong. Any help would be appreciated as I've been stuck trying to figure this out and the AI is of no help either.

realtime.ts

import Redis from 'ioredis'
import { RedwoodRealtimeOptions } from '@redwoodjs/realtime'
import subscriptions from 'src/subscriptions/**/*.{js,ts}'

const publishClient = new Redis(process.env.REDIS_URL, {
  maxRetriesPerRequest: 2,
  db: 1,
})
const subscribeClient = new Redis(process.env.REDIS_URL, {
  maxRetriesPerRequest: 2,
  db: 1,
})

// This works so I know the connection is set up
publishClient.set("test", "test)

export const realtime: RedwoodRealtimeOptions = {
  subscriptions: {
    subscriptions,
    store: { redis: { publishClient, subscribeClient } },
  },
  /*
  liveQueries: {
    //store: 'in-memory',
    store: { redis: { publishClient, subscribeClient } },
  },
  */
  // To enable defer and streaming, set to true.
  enableDeferStream: true,
}
#

graphql.ts

import { useZenStack } from '@zenstackhq/redwood'

import { createAuthDecoder } from '@redwoodjs/auth-dbauth-api'
import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { cookieName, getCurrentUser } from 'src/lib/auth'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'
import { realtime } from 'src/lib/realtime'
import { store } from 'src/lib/trustedDocumentsStore'

const authDecoder = createAuthDecoder(cookieName)

export const handler = createGraphQLHandler({
  authDecoder,
  getCurrentUser,
  loggerConfig: { logger, options: {} },
  directives,
  sdls,
  services,
  realtime,

  onException: () => {
    // Disconnect from your database with an unhandled exception.
    db.$disconnect()
  },

  trustedDocuments: {
    store,
  },

  extraPlugins: [useZenStack(db)],
})

And then I extend the global context for Zenstack with:
api/src/zenstack.d.ts

import type { PrismaClient } from '@prisma/client'

import type {
  InferredCurrentUser,
  Overwrite,
  UndefinedRoles,
} from '../../.redwood/types/includes/all-currentUser'

declare module '@redwoodjs/context' {
  interface GlobalContext {
    db: PrismaClient
    currentUser?: Overwrite<UndefinedRoles, InferredCurrentUser>
  }
}
#

api/src/subscriptions/newNotification.ts

import gql from 'graphql-tag'
import type { Notification } from 'types/graphql'
import type { GlobalContext } from '@redwoodjs/context'
import type { PubSub } from '@redwoodjs/realtime'
import { logger } from 'src/lib/logger'

export const schema = gql`
  type Notification {
    type: String!
    message: String!
    createdAt: DateTime!
  }

  type Subscription {
    newNotification(userId: Int!): Notification!
      @requireAuth
  }
`

export type NewNotificationChannel = {
  newNotification: [userId: number, payload: Notification]
}

export type NewNotificationChannelType = PubSub<NewNotificationChannel>

// I have to do it this way, otherwise the context on the resolver is overwritten and I lose access to the currentUser, see below
export interface PubSubContext extends GlobalContext {
  pubSub: NewNotificationChannelType
}

const resolvers = {
  newNotification: {
    subscribe: (
      _,
      { userId },
      { pubSub }: { pubSub: NewNotificationChannelType }
    ) => {
      // Ensure the user can only subscribe to their own notifications
      if (context.currentUser.id !== userId) {
        throw new Error('Unauthorized')
      }
// when I initialize my subscription on the client I see they're listening
      logger.warn({ userId }, 'begun subscribing')
      return pubSub.subscribe('newNotification', userId)
    },
    resolve: (payload) => {
// this properly happens and is consumed on the client component
      logger.warn({ payload }, 'newNotification subscription resolve')

      return payload
    },
  },
}

export default resolvers
#

A resolver that uses it, for example /points.ts

// If I set the context with pubSub and channel type I lose currentUser in my context for this resolver
export const createPoints: MutationResolvers['createPoints'] =
  async ({ input }, { context }: { context: PubSubContext }) => {
    // Get the recipient's points
    const recipient = await db.user.findUnique({
      where: { id: input.recipientId },
      select: { points: true },
    })
... calculate new points and insert to db, then ...

    // Publish a notification to the recipient (This works)
    context.pubSub.publish('newNotification', input.recipientId, {
      type: 'points',
      message: `You received ${input.points} points from ${context.currentUser.firstName} ${context.currentUser.lastName}`, <-- I lose this currentUser if I don't extend the GlobalContext like I do above
      createdAt: new Date(),
    })

    // Publish a notification to the awardedBy (This works)
    context.pubSub.publish('newNotification', context.currentUser.id, {
      type: 'points',
      message: `You awarded ${input.points} points to ${input.recipientId}`,
      createdAt: new Date(),
    })

    return points // After calculation and insert, don't worry this works too
  }

What I expect is to see the published notification in my redis db and not have a typescript error on my resolver. I believe something is wrong with how I'm using the pubSub with context, so it can't properly use ioredis client to write the entries.

#

My current error on the "createPoints" resolver says:

Type '({ input }: RequireFields<MutationcreatePointsTransactionArgs, "input">, { context }: { context: PubSubContext; }) => Promise<...>' is not assignable to type 'Resolver<ResolverTypeWrapper<{ ... 17 more ...; updatedAt: string | Date; }[]; ... 16 more ...; updatedAt: string | Date; ...'.
  Types of parameters '__1' and 'obj' are incompatible.
    Type '{ root: {}; context: RedwoodGraphQLContext; info: GraphQLResolveInfo; }' is not assignable to type '{ context: PubSubContext; }'.
      Types of property 'context' are incompatible.
        Type 'RedwoodGraphQLContext' is missing the following properties from type 'PubSubContext': pubSub, db
ts(2322)

This bit is concerning and I'm not sure what's wrong?

Types of parameters '__1' and 'obj' are incompatible.
    Type '{ root: {}; context: RedwoodGraphQLContext; info: GraphQLResolveInfo; }' is not assignable to type '{ context: PubSubContext; }'.
      Types of property 'context' are incompatible.
        Type 'RedwoodGraphQLContext' is missing the following properties from type 'PubSubContext': pubSub, db
red oracle
#

I noticed that the resolver automatically assumes RedwoodGraphQLContext with:

async ({ input }, { context }) => {

So instead of extending GlobalContext I tried extending the RedwoodGraphQLContext with my pubSub channel and sadly I still get that typescript error message 😦 When I declare my resolver like this without settings PubSubContext for context, intellisense shows me I have .currentUser on the context object but no pubSub or db. 🤔

#

If I'm reading the error correct my RedwoodGraphQLContext is missing pubSub? How do I get it? 😅

#

Okay so I seem to have resolved the context issue even though it feels wrong.. my .publish() still doesn't write it in my redis db though.

subscription file changed GlobalContext extension to:

import { PrismaClient } from '@prisma/client'
import { RedwoodGraphQLContext } from '@redwoodjs/graphql-server'
import type {
  InferredCurrentUser,
  Overwrite,
  UndefinedRoles,
} from '../../../.redwood/types/includes/all-currentUser'

export type PubSubContext = RedwoodGraphQLContext & {
  pubSub: NewNotificationChannelType
  currentUser?: Overwrite<UndefinedRoles, InferredCurrentUser>
  db: PrismaClient
}

And in the same file:

subscribe: (_, { userId }, { pubSub, currentUser }: PubSubContext) => {...}

Then in my create resolver I do:

async ({ input }, { context }) => {
    const pubSubContext = context as PubSubContext
...

But now I have to use pubSubContext everywhere, like

pubSubContext.currentUser.id
// or
pubSubContext.db

I guess that makes sense and the typescript error is now gone. However, I'm still not getting any of my published subscriptions into my redis db 😢

#

I feel like I've now created an extended RedwoodGraphQLContext that is not linked to the pubSub declared in realtime.ts so it's not using the store there, but I can't figure out how to make this work 🤯

Even though in my subscription file I grab the type from there to create my extended context:

import type { PubSub } from '@redwoodjs/realtime'
#

The reason I believe the issue is that I'm not using the realtime PubSub is because if I add:

// Test Redis connection
publishClient.on('connect', () => {
  logger.warn('Redis publish client connected')
})
publishClient.on('error', (err) => {
  logger.warn('Redis publish client error', err)
})
publishClient.on('message', (msg) => {
  logger.warn('Redis publish message', msg)
})

To my realtime.ts file I see the client connected, but I don't see an error or message show up at all, leading me to believe it's not being used?

The only workaround I can think of is to publish them myself with redis, like

// Store the notification in Redis
    await redis.lpush(`notifications:${input.recipientId}`, JSON.stringify(notification))

But then I've essentially circumvented the plugin, so I hope I can get some guidance on what I'm doing wrong here in regards to context and building the PubSub type 🙏

hot axle
#

This is a lot for me to ingest. Can you give me a tldr?

red oracle
# hot axle This is a lot for me to ingest. Can you give me a tldr?

I'm sorry about the length, I tried to be thorough. Thank you for taking the time to look at this.

Essentially I've set up subscriptions as you can see in the files above (resolver and subscription). I can't seem to get the subscriptions to be posted into redis because there's a context issue. By default the context on the resolver is of type RedwoodGraphQLContext, but when I tried to pass it as you show in your example:

  { context }: { context: { pubSub: NewNotificationChannelType } }

It didn't work for me, I was getting that typescript error you see a few posts above yours. It also overwrote the whole context so I didn't have access to context.currentUser or context.db anymore. Hence why I decided to extend it in my subscription and use it like:

async ({ input }, { context }) => {
    const pubSubContext = context as PubSubContext
#

So with my current implementation and changes the typescript error goes away (as I'm extending RedwoodGraphQLContext) but I don't get anything in redis.

#

I think I'm not using the store from the pubsub type I import and so nothing shows up.

hot axle
#

“It overwrote the context” hmm so ZenStack puts db in context and auth puts currentUser there too

#

You’re saying that adding the pubSub removed those or just the types

red oracle
#

Yeah so if I do that,

  { context }: { context: { pubSub: NewNotificationChannelType } }

I get context.pubSub but nothing else; no db no currentUser and the typescript error message above.

#

That's why I ended up with the "workaround"

hot axle
#

Just the types or can you actually still get the currentUser?

red oracle
#

No I can't, like there's no .currentUser or .db

hot axle
#

I’m trying to understand if this is a types issue or that the context is purged which is odd

red oracle
hot axle
#

I think we’ll need an issue written up with a small reproduction so we can dig into it

red oracle
#

That's all I have if I do it that way

#

Alright, I'll try to get that done. I realize it's probably esoteric to support through it like this 😅

#

Maybe in doing so I can figure out if it's zenstack that's somehow interferring

hot axle
# red oracle

I’d prefer if the repo didn’t use ZenStack to eliminate that interaction

red oracle
#

But I don't think it is because with ZenStack I declare the module for GlobalContext whereas this seems to be related to RedwoodGraphqlContext

red oracle
hot axle
#

It’s something we’ll have to step through to see what’s going on.

red oracle
#

When I set up realtime

#

Am I supposed to import PubSub from <@&951246524061483072>/realtime?

#

From what I read it seems I shouldn't have to touch context or import PubSub at all, it should just be accessible on context. right?

hot axle
#

That was my intention yes on the context

red oracle
#

So when I set up the channel type like

export type NewNotificationChannel = {
  newNotification: [userId: number, payload: Notification]
}

export type NewNotificationChannelType = PubSub<NewNotificationChannel>

I should still import PubSub from import { PubSub } from '<@&951246524061483072>/realtime'
correct?

hot axle
red oracle
#

And that's where it kinda falls apart for me because on the resolver end I can't do

async ({ input }, { context }: { context: { pubSub: NewNotificationChannel } }) => {
red oracle
#

Yeah so maybe it's just how I set up Zenstack, that could be it

#

The zenstack.d.ts file looks like

import type { PrismaClient } from '@prisma/client'

import type {
  InferredCurrentUser,
  Overwrite,
  UndefinedRoles,
} from '../../.redwood/types/includes/all-currentUser'

declare module '@redwoodjs/context' {
  interface GlobalContext {
    db: PrismaClient
    currentUser?: Overwrite<UndefinedRoles, InferredCurrentUser>
  }
}
#

But that essentially redeclares the context, there's no pubSub here

#

So I think that might be the issue.

hot axle
#

Should be able to use the type

red oracle
#

Right yeah I can't as you can see I get that typescript error 🤔 the red squiggly lines in my screenshot above

red oracle
#

Maybe I need to add it to the zenstack file?

    pubSub: PubSub<any>
hot axle
#

Hmm I dunno about that ZenStack

red oracle
#

Yeah i'll try it on a fresh repo

#

and see if that's the issue

#

Thank you for trying 🙂

hot axle
#

It’d be better to use the extendContext within the GraphQL lifecycle

#

Orrr

red oracle
# hot axle https://github.com/redwoodjs/redwood/blob/594470bf8249ba24c88bfe20aa41eea274ab81...

Mine looks similar I just had to use extending the context to avoid the typescript error:

export type NewNotificationChannel = {
  newNotification: [userId: number, payload: Notification]
}

export type NewNotificationChannelType = PubSub<NewNotificationChannel>

export type PubSubContext = RedwoodGraphQLContext & {
  pubSub: PubSub<NewNotificationChannel>
  currentUser?: Overwrite<UndefinedRoles, InferredCurrentUser>
  db: PrismaClient
}
hot axle
red oracle
#

This shows it as best I can, let me check out those links

#

^ But I lose the redis config

#

That PubSub is not the same

#

as what realtime.ts configures

red oracle
hot axle
#

Should be able to use that setContext yup

red oracle
#

Interesting, I'll try that out. I know Zenstack didn't really follow up with implementing it properly with RedwoodJS

#

that zenstack.d.ts file is just how I figured how to declare the GlobalContext to use the zenstack db instance. It could be the thing that's interferring and reseting the context so pubSub isn't available

hot axle
#

The plugin is great for graphql and makes most sense for me to set the db there using thy i guess

#

Honestly I don’t know anyone who uses ZS in production and we haven’t seen them in the community in some time

red oracle
#

😅 I'm trying to be the first but I hear ya

#

I'll take that one to heart

hot axle
#

Prisma may have have some rough points but there’s a big team and company behind it

red oracle
#

True there's safety in that

hot axle
#

You can do multi file schemas now

#

And I personally don’t agree with the permissions implementation on the dm model like they do

#

I mean how do you secure a non-database operation?

red oracle
hot axle
#

What if I have some business logic that calls a third party api?

red oracle
#

Oh yeah, that's true

#

I personally don't

hot axle
#

So now I need two approaches to secure my api

red oracle
#

I'd just use a permission directive like I showed in that thread long time ago, but to protect graphql operations I feel pretty good with the ACL wrappers, they seem powerful enough where I declare exactly what rules I want for what scenarios and it's made it easy for me to implement multi-tenancy on 1 db.

hot axle
#

And I also get the need for polymorphism coming from Rails but really haven’t missed it like I thought I would

red oracle
#

I do 😅 In my use case, I have users and they could be teachers, students, parents etc.

#

I want to separate login user details and app specific details to those models

#

So it's just a pity I can't do

model User {
Type Parent | Student | Teacher
}

hot axle
#

Here’s the thing

red oracle
#

Now I have to add a separate table, right:

model User {
type UserType
}

model UserType {
staff?
parent?
teacher?
}

with potentially empty columns for rows

hot axle
#

Your sdl types don’t have to be your database model types 1-1

red oracle
hot axle
#

That’s how most people model their data. There is a user table

#

And then sdl types for teachers students parents that extend the user model

red oracle
#

But I don't understand; where do you save information pertaining to teachers specifically, to students specifically. etc.

hot axle
#

You have two options

#

Relations

#

Or optional fields on user that have validation on the sdl type

#

Large long flexible database tables with specific sdl types that enforce rules per the flavor of that model

#

Have seen the latter more often

#

Since often the fields are more reused than first expected

red oracle
#

Wouldn't I also need like a type field to be able to identify if that user has a teacher or student relation when querying/mutating

#

Do you have by chance anything I can read up on for the second sdl approach?

#

I'd like to explore that

hot axle
#

Maybe. It’s hard for me to do that in my head

#

Yeah it’s later here on east coast but I know I’ve had some discussions about this

#

I’ll try to find or find some example code

red oracle
#

I know we can use unions in the sdl defintions so what I'm picturing is on the sdl something like

union UserType = Teacher | Student | Parent

and then when I run queries I'd have to spread out the return and likely use __typename

#

Like

...Teacher {
fields
}
...Student {
fields
}
etc

#

At least that's what I've seen but yeah if you have anything I can read up on that'd be amazing ❤️

#

I'll try to reproduce the original issue here on a fresh repo and open an issue if it persists (without Zenstack). Thank you for your time DT and the valuable points!

#

I'm east coast as well 🙂

hot axle
#

Also can just ask ChatGPT

#

enum UserType {
TEACHER
STUDENT
PARENT
}

model User {
id Int @id @default(autoincrement())
name String
email String @unique
type UserType
department String? // Specific to Teacher
grade String? // Specific to Student
emergencyPhone String? // Specific to Parent
}

#

interface User {
id: ID!
name: String!
email: String!
type: UserType!
}

type Teacher implements User {
id: ID!
name: String!
email: String!
type: UserType!
department: String!
}

type Student implements User {
id: ID!
name: String!
email: String!
type: UserType!
grade: String!
}

type Parent implements User {
id: ID!
name: String!
email: String!
type: UserType!
emergencyPhone: String!
}

enum UserType {
TEACHER
STUDENT
PARENT
}

type Query {
users: [User!]!
}

#

interface User {
id: ID!
name: String!
email: String!
type: UserType!
}

type Teacher implements User {
id: ID!
name: String!
email: String!
type: UserType!
department: String!
}

type Student implements User {
id: ID!
name: String!
email: String!
type: UserType!
grade: String!
}

type Parent implements User {
id: ID!
name: String!
email: String!
type: UserType!
emergencyPhone: String!
}

enum UserType {
TEACHER
STUDENT
PARENT
}

type Query {
users: [User!]!
teachers: [Teacher!]!
students: [Student!]!
parents: [Parent!]!
teacher(id: ID!): Teacher
student(id: ID!): Student
parent(id: ID!): Parent
}

#

This isn’t a service but the idea is there const resolvers = {
Query: {
users: async (parent, args, context) => {
return context.prisma.user.findMany();
},
teachers: async (parent, args, context) => {
return context.prisma.user.findMany({
where: {
type: 'TEACHER',
},
});
},
students: async (parent, args, context) => {
return context.prisma.user.findMany({
where: {
type: 'STUDENT',
},
});
},
parents: async (parent, args, context) => {
return context.prisma.user.findMany({
where: {
type: 'PARENT',
},
});
},
teacher: async (parent, { id }, context) => {
return context.prisma.user.findUnique({
where: { id: parseInt(id) },
});
},
student: async (parent, { id }, context) => {
return context.prisma.user.findUnique({
where: { id: parseInt(id) },
});
},
parent: async (parent, { id }, context) => {
return context.prisma.user.findUnique({
where: { id: parseInt(id) },
});
},
},
User: {
__resolveType(user) {
switch (user.type) {
case 'TEACHER':
return 'Teacher';
case 'STUDENT':
return 'Student';
case 'PARENT':
return 'Parent';
default:
return null;
}
},
},
};

#

This setup allows you to have both general and specific queries for your polymorphic user types, making your GraphQL API more flexible and intuitive for clients consuming the data.

red oracle
#

I haven't seen "implements" before

#

Didn't know that existed to be frank

#

I see what you're saying now

hot axle
#

I think ZenStack is kinda doing what GraphQL schema is accomplishing

#

ZS either makes more models or allows filtering a model into discrete ones

#

Here your db is clean and way to migrate and report on

#

And your sdl is business centric

red oracle
#

Yeah I see the benefit if I'm reading the code correct. Interesting.

hot axle
#

And if you need a new type of user like grandparent or principal it’s super easy

#

An enum and some sdl

red oracle
#

Right, that's very easy.

hot axle
#

And if you had user reports by type you don’t have to rewrite it to add grandpa

red oracle
#

Hmm, I'll have to experiment with this.

#

Thank you!

#

Leverage the power of sdl 😎

hot axle
#

Yeah the biggest a-ha moment I had with graphql is that it should not always map 1-1 with your data model

#

It’s the shape of your business api logic

red oracle
#

Take on me! 🙂

hot axle
#

And my strongly typing the parent etc the resolver can never return something that it isn’t

#

A parent can never return a grade

red oracle
#

Right that's important and I see how the type with implements handles that

#

I like it.

hot axle
#

And if you still want database validation you can do something like

#

ALTER TABLE "User"
ADD CONSTRAINT "grade_not_null_if_student"
CHECK (
("type" != 'STUDENT' AND "grade" IS NULL) OR
("type" = 'STUDENT' AND "grade" IS NOT NULL)
);

#

So a student needs a grade

red oracle
#

I'm definitely looking more into this way of doing it compared to relations, it's a good starting point for me to work through this with my AI

#

As a teacher it feels good to be a student sometimes 🙂

hot axle
#

You can define something that gets parents from the user table and enforce permissions

#

And pass in the context in service to that custom client method to check current user or roles or whatnot

red oracle
#

Yeah I tried extensions for something else actually

#

Computed fields

#

But it seems the needs clause can't handle relations, seems to be an open issue at Prisma since 2019

#

I definitely think mastering extension would leverage a lot of capabilities Zenstack offers

#

But there are some limitations

#

I think I'll experiment with the sdl approach and try more client extensions, I think you're right that they can definitely improve ABAC

#

might be even cleaner than putting directives on every resolver

#

I think that'd be cool to see from Redwood in the future maybe more examples on how to use extensions to do exactly that? Instead of using context.currentUser.id in every mutation resolver, maybe just have it once in an extension

#

It's definitely a topic I'm still learning about, the syntax for them somehow gives me trouble 😅

#

I have the conceptual ideas, but I can't make it work as an extension

red oracle
#

@hot axle Yeah I hate to be that guy but either I'm doing something wrong with how I'm setting up realtime or it's actually a bug? I just made a new redwood app and added a subscription. Same issue, no zenstack involved.

I created an issue here:
https://github.com/redwoodjs/redwood/issues/11316

hot axle
#

Wait just want to make sure of something

#

Are you looking in the redis client keys and values?

#

For pub sub there is a different … thing I use Métis on osx

red oracle
#

I check my db 1 since that's the one I'm specifically using; I have a logger that should alert you when the publishClient connects, errors or sends a message

hot axle
#

Pub sub is something one connects to to see the traffic

red oracle
#

I have an actual redis instance I'm testing this against

red oracle
#

My understanding was that you can set up the redis client in realtime.ts and any time you publish something on a channel that's persisted in redis as key/value

hot axle
red oracle
#

Right I see, I connect to my redis server and use redis-cli

#

and just check if there's anything in db 1 and there isn't

#

But either way, the typescript error is in that repo

#

So something with context is definitely messed up or I'm declaring it incorrectly, but I followed your demo example

hot axle
#

Ok I’ll try to look tomorrow or Wednesday as have bunch of calls tomorrow

red oracle
#

No worries

#

Take your time, I appreciate you looking at it ❤️

hot axle
#

No it’s not a key value

#

There is a special pubSub stream view that shows the messages

#

Medis lets you see those with the little ))) icon

red oracle
#

So you're saying that the realtime implementation doesn't actually write anything into redis? Oh wow I misunderstood that completely then

hot axle
#

You will not see keys and values but published messages to channels

red oracle
#

I see, I'll look into how I can see that with redis-cli

hot axle
#

The channel being “things:id”

red oracle
#

And confirm

#

Yeah I thought it'd make the actual values

#

so they persist on the redis instance

#

So.. do those pubSub channels on redis persist? Like can I read from them?

#

If I refresh my page with inmemory all my notifications are gone, I figured I'd be able to write a resolver and use it in a cell to pull the key/values from redis and return those (instead of prisma)

#

But I guess since they don't exist, I'll have to use this pubSub channel?

#

Interesting, that wasn't clear to me from the docs 😅 I'll look into that with the redis cli tool

#

The ioredis package should be able to read from there though right?

hot axle
#

No they are not persisted like keys

red oracle
#

Oh snap okay.. well that defeats the purpose of what I had in mind

#

I guess I'll have to handle that part myself

#

insert points record into db -> publish using the subscription -> persist in redis

hot axle
#

If you want that would store the key as a key value set and then also publish a message

red oracle
#

Yeah

hot axle
#

Or that yes

red oracle
#

Heh, that explains the redis part I suppose

#

My thinking was I'd keep notifications as key/values and then expire them with redis

#

But I'm reading the redis docs now

#

That's completely different. In that case, I guess if there's no difference in persistence from inMemory store to redis, what is the actual.. difference?

hot axle
#

Global store

#

If you have your app deployed on multiple instances they can all talk to the same Redis

#

And memory store is gone on restart

red oracle
#

Got'cha

hot axle
#

This is important even on serverless as each function could talk to its own memory store

red oracle
#

Makes sense.

Redis Pub/Sub: Redis does not persist messages for disconnected subscribers. Messages are not stored, and disconnected subscribers may miss messages published during their offline period.

#

That is why I'd want to use a persistent store for these notifications

#

so if you log back in you'd see what you've missed

#

But okay yeah that's something I can take care of

#

It's still weird that context gets overwritten. Thank you again for everything.

#

I'll have to treat you to some coffee or steak 😂

hot axle
#

And tracks read states

red oracle
#

Yeah I'm trying to handle everything in-house as much as possible

red oracle
#

There's just all these hoops I have to jump over when creating education software

#

I can't wait to show the team what I'm building once it's all done and I'm serving several districts 😅

hot axle
#

Yeah for this you want to store notifications in your db and readAt state

#

You can just publish a subscription “new messages”

#

And then fetch unread notifications

#

In a notifications widget list

red oracle
#

Yeah that's what I had in mind and I was just going to use a cell where the resolver pulls from the redis db instead of prisma/postgres

hot axle
#

You can still do that

red oracle
#

I've done all the code for it, it's all ready I just hit a roadblock with my misunderstanding of the pub/sub

hot axle
#

The benefit of db is you can enforce user access more

red oracle
#

The idea is to have a cell fetch all notifications (with pagination and orderBy unread), then add to the context with subscriptions

red oracle
#

So nobody should ever be able to fetch notifications that aren't theirs

hot axle
#

That sounds overkill. Most time a bell with an animation on a new messages here is good

#

And that can be set by both the publish on some event

#

Or if the notifications isn’t empty

#

I would not publish a message per notification

#

When fetching

#

Kinda redundant

red oracle
#

Yeah that's true 🤔 lol

hot axle
#

Notifications are great and everyone wants them until they get them and then they get too many and want to turn off

#

I’ve spent days implementing them only to have people turn them off

red oracle
#

Right, that's a good point--I figured I'd add a profile settings matrix where users can opt-in for notifications on events like assignments due, grades in, etc.

#

But you're right I don't want to overkill on the notification system

#

I just thought it'd be a good use case for graphql subscriptions

hot axle
#

It absolutely is. And you can check that matrix when publishing to the channel