a tool for shared writing and social publishing
0

Configure Feed

Select the types of activity you want to include in your feed.

update inngest to v4

Jared Pereira (Apr 24, 2026, 4:42 PM EDT) 7c043465 c55c9264

+253 -156
+25 -2
actions/publications/subscribeEmail.tsx
··· 16 16 getSuppression, 17 17 deleteSuppression, 18 18 } from "src/utils/postmarkSuppressions"; 19 - import { unsubscribeToPublication } from "app/lish/subscribeToPublication"; 19 + import { 20 + publishAtprotoSubscriptionForDid, 21 + unsubscribeToPublication, 22 + } from "app/lish/subscribeToPublication"; 20 23 21 24 type RequestError = 22 25 | "invalid_email" ··· 127 130 return Err("database_error"); 128 131 } 129 132 130 - if (verifiedIdentity) return Ok({ confirmed: true }); 133 + if (verifiedIdentity) { 134 + if (verifiedIdentity.atp_did) { 135 + await publishAtprotoSubscriptionForDid( 136 + verifiedIdentity.atp_did, 137 + publicationUri, 138 + ); 139 + } 140 + return Ok({ confirmed: true }); 141 + } 131 142 132 143 const sent = await sendConfirmationEmail({ 133 144 to: email, ··· 189 200 updateError ?? eventError, 190 201 ); 191 202 return Err("database_error"); 203 + } 204 + 205 + const { data: confirmedIdentity } = await supabaseServerClient 206 + .from("identities") 207 + .select("atp_did") 208 + .eq("id", identityId) 209 + .maybeSingle(); 210 + if (confirmedIdentity?.atp_did) { 211 + await publishAtprotoSubscriptionForDid( 212 + confirmedIdentity.atp_did, 213 + publicationUri, 214 + ); 192 215 } 193 216 194 217 return Ok(null);
+82 -91
app/api/inngest/client.ts
··· 1 - import { Inngest } from "inngest"; 2 - 3 - import { EventSchemas } from "inngest"; 1 + import { Inngest, eventType, staticSchema } from "inngest"; 4 2 5 - export type Events = { 6 - "feeds/index-follows": { 7 - data: { 8 - did: string; 9 - }; 10 - }; 11 - "appview/profile-update": { 12 - data: { 13 - record: any; 14 - did: string; 15 - }; 16 - }; 17 - "appview/index-bsky-post-mention": { 18 - data: { 19 - post_uri: string; 20 - document_link: string; 21 - }; 22 - }; 23 - "appview/come-online": { data: {} }; 24 - "user/migrate-to-standard": { 25 - data: { 26 - did: string; 27 - }; 28 - }; 29 - "user/cleanup-expired-oauth-sessions": { 30 - data: {}; 31 - }; 32 - "user/check-oauth-session": { 33 - data: { 3 + // Event type definitions. In v4, the client no longer has centralized 4 + // schemas — each event is its own EventType, usable both as a trigger and 5 + // as an argument to inngest.send() via event.create(). 6 + export const events = { 7 + feedsIndexFollows: eventType("feeds/index-follows", { 8 + schema: staticSchema<{ did: string }>(), 9 + }), 10 + appviewProfileUpdate: eventType("appview/profile-update", { 11 + schema: staticSchema<{ record: any; did: string }>(), 12 + }), 13 + appviewIndexBskyPostMention: eventType("appview/index-bsky-post-mention", { 14 + schema: staticSchema<{ post_uri: string; document_link: string }>(), 15 + }), 16 + appviewComeOnline: eventType("appview/come-online", { 17 + schema: staticSchema<Record<string, never>>(), 18 + }), 19 + userMigrateToStandard: eventType("user/migrate-to-standard", { 20 + schema: staticSchema<{ did: string }>(), 21 + }), 22 + userCleanupExpiredOauthSessions: eventType( 23 + "user/cleanup-expired-oauth-sessions", 24 + { schema: staticSchema<Record<string, never>>() }, 25 + ), 26 + userCheckOauthSession: eventType("user/check-oauth-session", { 27 + schema: staticSchema<{ 34 28 identityId: string; 35 29 did: string; 36 30 tokenCount: number; 37 - }; 38 - }; 39 - "documents/fix-publication-references": { 40 - data: { 41 - documentUris: string[]; 42 - }; 43 - }; 44 - "documents/fix-incorrect-site-values": { 45 - data: { 46 - did: string; 47 - }; 48 - }; 49 - "documents/fix-postref": { 50 - data: { 51 - documentUris?: string[]; 52 - }; 53 - }; 54 - "appview/sync-document-metadata": { 55 - data: { 56 - document_uri: string; 57 - bsky_post_uri?: string; 58 - }; 59 - }; 60 - "user/write-records-to-pds": { 61 - data: { 31 + }>(), 32 + }), 33 + documentsFixPublicationReferences: eventType( 34 + "documents/fix-publication-references", 35 + { schema: staticSchema<{ documentUris: string[] }>() }, 36 + ), 37 + documentsFixIncorrectSiteValues: eventType( 38 + "documents/fix-incorrect-site-values", 39 + { schema: staticSchema<{ did: string }>() }, 40 + ), 41 + documentsFixPostref: eventType("documents/fix-postref", { 42 + schema: staticSchema<{ documentUris?: string[] }>(), 43 + }), 44 + appviewSyncDocumentMetadata: eventType("appview/sync-document-metadata", { 45 + schema: staticSchema<{ document_uri: string; bsky_post_uri?: string }>(), 46 + }), 47 + userWriteRecordsToPds: eventType("user/write-records-to-pds", { 48 + schema: staticSchema<{ 62 49 did: string; 63 50 records: Array<{ 64 51 collection: string; 65 52 rkey: string; 66 53 record: unknown; 67 54 }>; 68 - }; 69 - }; 70 - "stripe/checkout.session.completed": { 71 - data: { 72 - sessionId: string; 73 - }; 74 - }; 75 - "stripe/customer.subscription.updated": { 76 - data: { 77 - subscriptionId: string; 78 - }; 79 - }; 80 - "stripe/customer.subscription.deleted": { 81 - data: { 82 - subscriptionId: string; 83 - }; 84 - }; 85 - "stripe/invoice.payment.succeeded": { 86 - data: { 87 - invoiceId: string; 88 - subscriptionId: string; 89 - customerId: string; 90 - }; 91 - }; 92 - "stripe/invoice.payment.failed": { 93 - data: { 55 + }>(), 56 + }), 57 + stripeCheckoutSessionCompleted: eventType( 58 + "stripe/checkout.session.completed", 59 + { schema: staticSchema<{ sessionId: string }>() }, 60 + ), 61 + stripeCustomerSubscriptionUpdated: eventType( 62 + "stripe/customer.subscription.updated", 63 + { schema: staticSchema<{ subscriptionId: string }>() }, 64 + ), 65 + stripeCustomerSubscriptionDeleted: eventType( 66 + "stripe/customer.subscription.deleted", 67 + { schema: staticSchema<{ subscriptionId: string }>() }, 68 + ), 69 + stripeInvoicePaymentSucceeded: eventType( 70 + "stripe/invoice.payment.succeeded", 71 + { 72 + schema: staticSchema<{ 73 + invoiceId: string; 74 + subscriptionId: string; 75 + customerId: string; 76 + }>(), 77 + }, 78 + ), 79 + stripeInvoicePaymentFailed: eventType("stripe/invoice.payment.failed", { 80 + schema: staticSchema<{ 94 81 invoiceId: string; 95 82 subscriptionId: string; 96 83 customerId: string; 97 - }; 98 - }; 99 - "newsletter/post.send.requested": { 100 - data: { 84 + }>(), 85 + }), 86 + newsletterPostSendRequested: eventType("newsletter/post.send.requested", { 87 + schema: staticSchema<{ 101 88 publication_uri: string; 102 89 document_uri: string; 103 90 root_entity: string; 104 - }; 105 - }; 91 + }>(), 92 + }), 106 93 }; 107 94 108 - // Create a client to send and receive events 95 + // Create a client to send and receive events. 96 + // v4 defaults to cloud mode; opt into dev mode in local development so 97 + // the Inngest dev server can auto-connect without a signing key. 109 98 export const inngest = new Inngest({ 110 99 id: "leaflet", 111 - schemas: new EventSchemas().fromRecord<Events>(), 100 + isDev: 101 + process.env.INNGEST_DEV === "1" || 102 + process.env.NODE_ENV === "development", 112 103 });
+2 -2
app/api/inngest/functions/batched_update_profiles.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 4 4 export const batched_update_profiles = inngest.createFunction( 5 5 { ··· 8 8 maxSize: 100, 9 9 timeout: "10s", 10 10 }, 11 + triggers: [events.appviewProfileUpdate], 11 12 }, 12 - { event: "appview/profile-update" }, 13 13 async ({ events, step }) => { 14 14 let existingProfiles = await supabaseServerClient 15 15 .from("bsky_profiles")
+9 -5
app/api/inngest/functions/cleanup_expired_oauth_sessions.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { restoreOAuthSession } from "src/atproto-oauth"; 4 4 5 5 // Main function that fetches identities and publishes events for each one 6 6 export const cleanup_expired_oauth_sessions = inngest.createFunction( 7 - { id: "cleanup_expired_oauth_sessions" }, 8 - { event: "user/cleanup-expired-oauth-sessions" }, 7 + { 8 + id: "cleanup_expired_oauth_sessions", 9 + triggers: [events.userCleanupExpiredOauthSessions], 10 + }, 9 11 async ({ step }) => { 10 12 // Get all identities with an atp_did (OAuth users) that have at least one auth token 11 13 const identities = await step.run("fetch-oauth-identities", async () => { ··· 70 72 71 73 // Function that checks a single identity's OAuth session and cleans up if expired 72 74 export const check_oauth_session = inngest.createFunction( 73 - { id: "check_oauth_session" }, 74 - { event: "user/check-oauth-session" }, 75 + { 76 + id: "check_oauth_session", 77 + triggers: [events.userCheckOauthSession], 78 + }, 75 79 async ({ event, step }) => { 76 80 const { identityId, did, tokenCount } = event.data; 77 81
+5 -3
app/api/inngest/functions/come_online.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { AtpAgent, AtUri } from "@atproto/api"; 4 4 import { Json } from "supabase/database.types"; 5 5 import { ids } from "lexicons/api/lexicons"; 6 6 7 7 export const come_online = inngest.createFunction( 8 - { id: "come_online" }, 9 - { event: "appview/come-online" }, 8 + { 9 + id: "come_online", 10 + triggers: [events.appviewComeOnline], 11 + }, 10 12 async ({ event, step }) => { 11 13 return { online: true }; 12 14 },
+5 -3
app/api/inngest/functions/fix_incorrect_site_values.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { restoreOAuthSession } from "src/atproto-oauth"; 4 4 import { AtpBaseClient, SiteStandardDocument } from "lexicons/api"; 5 5 import { AtUri } from "@atproto/syntax"; ··· 52 52 * Takes a DID as input and processes publications owned by that identity. 53 53 */ 54 54 export const fix_incorrect_site_values = inngest.createFunction( 55 - { id: "fix_incorrect_site_values" }, 56 - { event: "documents/fix-incorrect-site-values" }, 55 + { 56 + id: "fix_incorrect_site_values", 57 + triggers: [events.documentsFixIncorrectSiteValues], 58 + }, 57 59 async ({ event, step }) => { 58 60 const { did } = event.data; 59 61
+5 -3
app/api/inngest/functions/fix_standard_document_postref.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { restoreOAuthSession } from "src/atproto-oauth"; 4 4 import { 5 5 AtpBaseClient, ··· 29 29 * if no URIs are provided. 30 30 */ 31 31 export const fix_standard_document_postref = inngest.createFunction( 32 - { id: "fix_standard_document_postref" }, 33 - { event: "documents/fix-postref" }, 32 + { 33 + id: "fix_standard_document_postref", 34 + triggers: [events.documentsFixPostref], 35 + }, 34 36 async ({ event, step }) => { 35 37 const { documentUris: providedUris } = event.data as { 36 38 documentUris?: string[];
+5 -3
app/api/inngest/functions/fix_standard_document_publications.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { restoreOAuthSession } from "src/atproto-oauth"; 4 4 import { AtpBaseClient, SiteStandardDocument } from "lexicons/api"; 5 5 import { AtUri } from "@atproto/syntax"; ··· 21 21 * references in their site field. Updates both the PDS record and database. 22 22 */ 23 23 export const fix_standard_document_publications = inngest.createFunction( 24 - { id: "fix_standard_document_publications" }, 25 - { event: "documents/fix-publication-references" }, 24 + { 25 + id: "fix_standard_document_publications", 26 + triggers: [events.documentsFixPublicationReferences], 27 + }, 26 28 async ({ event, step }) => { 27 29 const { documentUris } = event.data as { documentUris: string[] }; 28 30
+2 -2
app/api/inngest/functions/index_follows.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 2 import { AtpAgent, AtUri } from "@atproto/api"; 3 - import { inngest } from "../client"; 3 + import { inngest, events } from "../client"; 4 4 5 5 export const index_follows = inngest.createFunction( 6 6 { ··· 10 10 period: "5m", 11 11 key: "event.data.did", 12 12 }, 13 + triggers: [events.feedsIndexFollows], 13 14 }, 14 - { event: "feeds/index-follows" }, 15 15 async ({ event, step }) => { 16 16 let follows: string[] = []; 17 17 let cursor: null | string = null;
+5 -3
app/api/inngest/functions/index_post_mention.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { AtpAgent, AtUri } from "@atproto/api"; 4 4 import { Json } from "supabase/database.types"; 5 5 import { ids } from "lexicons/api/lexicons"; ··· 12 12 import { documentUriFilter } from "src/utils/uriHelpers"; 13 13 14 14 export const index_post_mention = inngest.createFunction( 15 - { id: "index_post_mention" }, 16 - { event: "appview/index-bsky-post-mention" }, 15 + { 16 + id: "index_post_mention", 17 + triggers: [events.appviewIndexBskyPostMention], 18 + }, 17 19 async ({ event, step }) => { 18 20 let url = new URL(event.data.document_link); 19 21 let path = url.pathname.split("/").filter(Boolean);
+5 -3
app/api/inngest/functions/migrate_user_to_standard.ts
··· 1 1 import { supabaseServerClient } from "supabase/serverClient"; 2 - import { inngest } from "../client"; 2 + import { inngest, events } from "../client"; 3 3 import { restoreOAuthSession } from "src/atproto-oauth"; 4 4 import { 5 5 AtpBaseClient, ··· 30 30 } 31 31 32 32 export const migrate_user_to_standard = inngest.createFunction( 33 - { id: "migrate_user_to_standard" }, 34 - { event: "user/migrate-to-standard" }, 33 + { 34 + id: "migrate_user_to_standard", 35 + triggers: [events.userMigrateToStandard], 36 + }, 35 37 async ({ event, step }) => { 36 38 const { did } = event.data; 37 39
+2 -2
app/api/inngest/functions/send_post_broadcast.ts
··· 1 1 import { render } from "@react-email/render"; 2 2 import { AtUri } from "@atproto/syntax"; 3 - import { inngest } from "../client"; 3 + import { inngest, events } from "../client"; 4 4 import { supabaseServerClient } from "supabase/serverClient"; 5 5 import { PostEmail } from "emails/post"; 6 6 import { emailPropsFromPublication } from "emails/fromPublication"; ··· 35 35 .eq("publication", publication_uri) 36 36 .eq("document", document_uri); 37 37 }, 38 + triggers: [events.newsletterPostSendRequested], 38 39 }, 39 - { event: "newsletter/post.send.requested" }, 40 40 async ({ event, step }) => { 41 41 const { publication_uri, document_uri } = event.data; 42 42
+2 -2
app/api/inngest/functions/sync_document_metadata.ts
··· 1 - import { inngest } from "../client"; 1 + import { inngest, events } from "../client"; 2 2 import { supabaseServerClient } from "supabase/serverClient"; 3 3 import { AtpAgent, AtUri } from "@atproto/api"; 4 4 import { idResolver } from "app/(home-pages)/reader/idResolver"; ··· 17 17 timeout: "3m", 18 18 }, 19 19 concurrency: [{ key: "event.data.document_uri", limit: 1 }], 20 + triggers: [events.appviewSyncDocumentMetadata], 20 21 }, 21 - { event: "appview/sync-document-metadata" }, 22 22 async ({ event, step }) => { 23 23 const { document_uri, bsky_post_uri } = event.data; 24 24
+5 -3
app/api/inngest/functions/write_records_to_pds.ts
··· 1 - import { inngest } from "../client"; 1 + import { inngest, events } from "../client"; 2 2 import { restoreOAuthSession } from "src/atproto-oauth"; 3 3 import { AtpBaseClient } from "lexicons/api"; 4 4 ··· 16 16 } 17 17 18 18 export const write_records_to_pds = inngest.createFunction( 19 - { id: "write-records-to-pds" }, 20 - { event: "user/write-records-to-pds" }, 19 + { 20 + id: "write-records-to-pds", 21 + triggers: [events.userWriteRecordsToPds], 22 + }, 21 23 async ({ event, step }) => { 22 24 const { did, records } = event.data; 23 25
+81
app/lish/subscribeToPublication.ts
··· 111 111 }; 112 112 } 113 113 114 + // Best-effort publish of an AT Protocol subscription record for a known DID. 115 + // Used by the email-subscribe flow when the subscribing email is linked to an 116 + // atp_did: we want the user's PDS record to match their email subscription. 117 + // Swallows all failures — the email subscription is the source of truth and 118 + // must succeed regardless of whether the atproto write goes through. 119 + export async function publishAtprotoSubscriptionForDid( 120 + atp_did: string, 121 + publication: string, 122 + ): Promise<void> { 123 + try { 124 + let { data: existingSubscription } = await supabaseServerClient 125 + .from("publication_subscriptions") 126 + .select("uri") 127 + .eq("identity", atp_did) 128 + .eq("publication", publication) 129 + .maybeSingle(); 130 + if (existingSubscription) return; 131 + 132 + const sessionResult = await restoreOAuthSession(atp_did); 133 + if (!sessionResult.ok) return; 134 + let credentialSession = sessionResult.value; 135 + let agent = new AtpBaseClient( 136 + credentialSession.fetchHandler.bind(credentialSession), 137 + ); 138 + 139 + let record = await agent.site.standard.graph.subscription.create( 140 + { repo: atp_did, rkey: TID.nextStr() }, 141 + { publication }, 142 + ); 143 + await supabaseServerClient.from("publication_subscriptions").insert({ 144 + uri: record.uri, 145 + record, 146 + publication, 147 + identity: atp_did, 148 + }); 149 + 150 + let publicationOwner = new AtUri(publication).host; 151 + if (publicationOwner !== atp_did) { 152 + let notification: Notification = { 153 + id: v7(), 154 + recipient: publicationOwner, 155 + data: { 156 + type: "subscribe", 157 + subscription_uri: record.uri, 158 + }, 159 + }; 160 + await supabaseServerClient.from("notifications").insert(notification); 161 + await pingIdentityToUpdateNotification(publicationOwner); 162 + } 163 + 164 + let { data: existingProfile } = await supabaseServerClient 165 + .from("bsky_profiles") 166 + .select("did") 167 + .eq("did", atp_did) 168 + .maybeSingle(); 169 + if (!existingProfile) { 170 + let bsky = new BskyAgent(credentialSession); 171 + let [profile, resolveDid] = await Promise.all([ 172 + bsky.app.bsky.actor.profile 173 + .get({ repo: atp_did, rkey: "self" }) 174 + .catch(() => null), 175 + idResolver.did.resolve(atp_did).catch(() => null), 176 + ]); 177 + if (profile?.value) { 178 + await supabaseServerClient.from("bsky_profiles").insert({ 179 + did: atp_did, 180 + record: profile.value as Json, 181 + handle: resolveDid?.alsoKnownAs?.[0]?.slice(5), 182 + }); 183 + } 184 + } 185 + } catch (e) { 186 + console.error( 187 + "[publishAtprotoSubscriptionForDid] failed:", 188 + atp_did, 189 + publication, 190 + e, 191 + ); 192 + } 193 + } 194 + 114 195 type UnsubscribeResult = 115 196 | { success: true } 116 197 | { success: false; error: OAuthSessionError };
+11 -27
package-lock.json
··· 52 52 "hls.js": "^1.6.15", 53 53 "hono": "^4.7.11", 54 54 "immer": "^10.2.0", 55 - "inngest": "^3.52.6", 55 + "inngest": "^4.2.4", 56 56 "ioredis": "^5.6.1", 57 57 "katex": "^0.16.22", 58 58 "l": "^0.6.0", ··· 11306 11306 "version": "4.1.2", 11307 11307 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 11308 11308 "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 11309 + "dev": true, 11309 11310 "dependencies": { 11310 11311 "ansi-styles": "^4.1.0", 11311 11312 "supports-color": "^7.1.0" ··· 14599 14600 "version": "4.0.0", 14600 14601 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 14601 14602 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 14603 + "devOptional": true, 14602 14604 "engines": { 14603 14605 "node": ">=8" 14604 14606 } ··· 15187 15189 "license": "MIT" 15188 15190 }, 15189 15191 "node_modules/inngest": { 15190 - "version": "3.52.6", 15191 - "resolved": "https://registry.npmjs.org/inngest/-/inngest-3.52.6.tgz", 15192 - "integrity": "sha512-wDxA1I5CIL7anqyX3Vr0T/5kOHkWN8W5oeqbHhFKFbsFrM+M/J3OEFiRRgcdiGyhNHxPvWaRdQb4N2mfgwc7PQ==", 15192 + "version": "4.2.4", 15193 + "resolved": "https://registry.npmjs.org/inngest/-/inngest-4.2.4.tgz", 15194 + "integrity": "sha512-MBFcRhhQ+dcGHLYCbIcMYxiAZCxzfAx0+3b/euYQKc7MK3rOnNHaNHuF2e0WsT13oWJiGzjuC3T4Nocggmh5WQ==", 15193 15195 "license": "Apache-2.0", 15194 15196 "dependencies": { 15195 15197 "@bufbuild/protobuf": "^2.2.3", ··· 15207 15209 "@types/debug": "^4.1.12", 15208 15210 "@types/ms": "~2.1.0", 15209 15211 "canonicalize": "^1.0.8", 15210 - "chalk": "^4.1.2", 15211 15212 "cross-fetch": "^4.0.0", 15212 15213 "debug": "^4.3.4", 15213 15214 "hash.js": "^1.1.7", 15214 15215 "json-stringify-safe": "^5.0.1", 15215 15216 "ms": "^2.1.3", 15216 15217 "serialize-error-cjs": "^0.1.3", 15217 - "strip-ansi": "^5.2.0", 15218 15218 "temporal-polyfill": "^0.2.5", 15219 15219 "ulid": "^2.3.0", 15220 15220 "zod": "^3.25.0" ··· 15232 15232 "hono": ">=4.2.7", 15233 15233 "koa": ">=2.14.2", 15234 15234 "next": ">=12.0.0", 15235 + "react": ">=18.0.0", 15235 15236 "typescript": ">=5.8.0", 15236 15237 "zod": "^3.25.0 || ^4.0.0" 15237 15238 }, ··· 15263 15264 "next": { 15264 15265 "optional": true 15265 15266 }, 15267 + "react": { 15268 + "optional": true 15269 + }, 15266 15270 "typescript": { 15267 15271 "optional": true 15268 15272 } 15269 - } 15270 - }, 15271 - "node_modules/inngest/node_modules/ansi-regex": { 15272 - "version": "4.1.1", 15273 - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 15274 - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", 15275 - "license": "MIT", 15276 - "engines": { 15277 - "node": ">=6" 15278 - } 15279 - }, 15280 - "node_modules/inngest/node_modules/strip-ansi": { 15281 - "version": "5.2.0", 15282 - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 15283 - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 15284 - "license": "MIT", 15285 - "dependencies": { 15286 - "ansi-regex": "^4.1.0" 15287 - }, 15288 - "engines": { 15289 - "node": ">=6" 15290 15273 } 15291 15274 }, 15292 15275 "node_modules/internal-slot": { ··· 22292 22275 "version": "7.2.0", 22293 22276 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 22294 22277 "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 22278 + "dev": true, 22295 22279 "dependencies": { 22296 22280 "has-flag": "^4.0.0" 22297 22281 },
+1 -1
package.json
··· 64 64 "hls.js": "^1.6.15", 65 65 "hono": "^4.7.11", 66 66 "immer": "^10.2.0", 67 - "inngest": "^3.52.6", 67 + "inngest": "^4.2.4", 68 68 "ioredis": "^5.6.1", 69 69 "katex": "^0.16.22", 70 70 "l": "^0.6.0",
+1 -1
tsconfig.json
··· 18 18 "module": "esnext", 19 19 "downlevelIteration": true, 20 20 "esModuleInterop": true, 21 - "moduleResolution": "node", 21 + "moduleResolution": "bundler", 22 22 "resolveJsonModule": true, 23 23 "isolatedModules": true, 24 24 "jsx": "react-jsx",