@opengeni/db 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -153,6 +153,7 @@ import {
153
153
  asc,
154
154
  desc,
155
155
  eq,
156
+ getTableColumns,
156
157
  gt,
157
158
  gte,
158
159
  inArray,
@@ -3381,10 +3382,31 @@ export type CreateSocialConnectionInput = {
3381
3382
  status: SocialConnectionStatus;
3382
3383
  scopes?: string[];
3383
3384
  credentialRef?: string | null;
3385
+ credentialEncrypted?: string | null;
3384
3386
  tokenMetadata?: Record<string, unknown>;
3385
3387
  metadata?: Record<string, unknown>;
3386
3388
  };
3387
3389
 
3390
+ export type UpsertSocialOAuthConnectionInput = {
3391
+ accountId: string;
3392
+ workspaceId: string;
3393
+ provider: SocialProvider;
3394
+ accountHandle: string;
3395
+ accountName?: string | null;
3396
+ externalAccountId?: string | null;
3397
+ scopes: string[];
3398
+ credentialEncrypted: string;
3399
+ tokenMetadata?: Record<string, unknown>;
3400
+ };
3401
+
3402
+ export type UpdateSocialConnectionCredentialInput = {
3403
+ workspaceId: string;
3404
+ connectionId: string;
3405
+ credentialEncrypted?: string | null;
3406
+ status?: SocialConnectionStatus;
3407
+ tokenMetadata?: Record<string, unknown>;
3408
+ };
3409
+
3388
3410
  export type CreateSocialPostInput = {
3389
3411
  accountId: string;
3390
3412
  workspaceId: string;
@@ -5085,6 +5107,21 @@ function connectionExactSubject(subjectId?: string | null): SQL {
5085
5107
  : isNull(schema.connections.subjectId);
5086
5108
  }
5087
5109
 
5110
+ function personalSlackCanonicalConnectionOrder(): SQL[] {
5111
+ return [
5112
+ sql`case ${schema.connections.status}
5113
+ when 'active' then 0
5114
+ when 'needs_reauth' then 1
5115
+ when 'error' then 2
5116
+ when 'revoked' then 3
5117
+ else 4
5118
+ end`,
5119
+ desc(schema.connections.updatedAt),
5120
+ desc(schema.connections.createdAt),
5121
+ desc(schema.connections.id),
5122
+ ];
5123
+ }
5124
+
5088
5125
  async function withConnectionSubjectRls<T>(
5089
5126
  db: Database,
5090
5127
  workspaceId: string,
@@ -5345,6 +5382,7 @@ export type SlackInteractionInboxEntry = {
5345
5382
  claimHolderId: string | null;
5346
5383
  claimExpiresAt: Date | null;
5347
5384
  attemptCount: number;
5385
+ retryAt: Date | null;
5348
5386
  lastErrorCode: string | null;
5349
5387
  processedAt: Date | null;
5350
5388
  createdAt: Date;
@@ -5368,6 +5406,9 @@ export type SlackInteraction = {
5368
5406
  lastDeliveredSessionEventSequence: number;
5369
5407
  deliveryClaimHolderId: string | null;
5370
5408
  deliveryClaimExpiresAt: Date | null;
5409
+ deliveryAttemptCount: number;
5410
+ deliveryRetryAt: Date | null;
5411
+ deliveryLastErrorCode: string | null;
5371
5412
  ackSlackMessageTs: string | null;
5372
5413
  progressCount: number;
5373
5414
  terminalDeliveryState: "open" | "completed" | "failed" | "cancelled" | "blocked";
@@ -5486,6 +5527,7 @@ export async function enqueueSlackInteractionInbox(
5486
5527
  | "claimHolderId"
5487
5528
  | "claimExpiresAt"
5488
5529
  | "attemptCount"
5530
+ | "retryAt"
5489
5531
  | "lastErrorCode"
5490
5532
  | "processedAt"
5491
5533
  | "createdAt"
@@ -5548,6 +5590,7 @@ export async function settleSlackInteractionInbox(
5548
5590
  status: input.outcome,
5549
5591
  claimHolderId: null,
5550
5592
  claimExpiresAt: null,
5593
+ retryAt: null,
5551
5594
  processedAt: sql`now()`,
5552
5595
  lastErrorCode: input.errorCode ?? null,
5553
5596
  updatedAt: sql`now()`,
@@ -5570,6 +5613,7 @@ export async function releaseSlackInteractionInbox(
5570
5613
  entry: Pick<SlackInteractionInboxEntry, "id" | "accountId" | "workspaceId">;
5571
5614
  claimHolderId: string;
5572
5615
  errorCode: string;
5616
+ retryAt: Date;
5573
5617
  },
5574
5618
  ): Promise<boolean> {
5575
5619
  return await withRlsContext(db, input.entry, async (scopedDb) => {
@@ -5579,6 +5623,7 @@ export async function releaseSlackInteractionInbox(
5579
5623
  status: "pending",
5580
5624
  claimHolderId: null,
5581
5625
  claimExpiresAt: null,
5626
+ retryAt: input.retryAt,
5582
5627
  lastErrorCode: input.errorCode,
5583
5628
  updatedAt: sql`now()`,
5584
5629
  })
@@ -5604,6 +5649,9 @@ export async function getOrCreateSlackInteraction(
5604
5649
  | "lastDeliveredSessionEventSequence"
5605
5650
  | "deliveryClaimHolderId"
5606
5651
  | "deliveryClaimExpiresAt"
5652
+ | "deliveryAttemptCount"
5653
+ | "deliveryRetryAt"
5654
+ | "deliveryLastErrorCode"
5607
5655
  | "ackSlackMessageTs"
5608
5656
  | "progressCount"
5609
5657
  | "terminalDeliveryState"
@@ -5943,7 +5991,13 @@ export async function reopenSlackInteractionDelivery(
5943
5991
  return await withRlsContext(db, input, async (scopedDb) => {
5944
5992
  const rows = await scopedDb
5945
5993
  .update(schema.slackInteractions)
5946
- .set({ terminalDeliveryState: "open", updatedAt: sql`now()` })
5994
+ .set({
5995
+ terminalDeliveryState: "open",
5996
+ deliveryAttemptCount: 0,
5997
+ deliveryRetryAt: null,
5998
+ deliveryLastErrorCode: null,
5999
+ updatedAt: sql`now()`,
6000
+ })
5947
6001
  .where(eq(schema.slackInteractions.id, input.id))
5948
6002
  .returning({ id: schema.slackInteractions.id });
5949
6003
  return rows.length === 1;
@@ -5966,6 +6020,9 @@ export async function advanceSlackInteractionDelivery(
5966
6020
  ...(input.ackSlackMessageTs !== undefined
5967
6021
  ? { ackSlackMessageTs: input.ackSlackMessageTs }
5968
6022
  : {}),
6023
+ deliveryAttemptCount: 0,
6024
+ deliveryRetryAt: null,
6025
+ deliveryLastErrorCode: null,
5969
6026
  updatedAt: sql`now()`,
5970
6027
  })
5971
6028
  .where(
@@ -6005,12 +6062,42 @@ export async function releaseSlackInteractionDelivery(
6005
6062
  });
6006
6063
  }
6007
6064
 
6065
+ export async function deferSlackInteractionDelivery(
6066
+ db: Database,
6067
+ input: Pick<SlackInteraction, "id" | "accountId" | "workspaceId"> & {
6068
+ claimHolderId: string;
6069
+ retryAt: Date;
6070
+ errorCode: string;
6071
+ },
6072
+ ): Promise<boolean> {
6073
+ return await withRlsContext(db, input, async (scopedDb) => {
6074
+ const rows = await scopedDb
6075
+ .update(schema.slackInteractions)
6076
+ .set({
6077
+ deliveryClaimHolderId: null,
6078
+ deliveryClaimExpiresAt: null,
6079
+ deliveryRetryAt: input.retryAt,
6080
+ deliveryLastErrorCode: input.errorCode.slice(0, 128),
6081
+ updatedAt: sql`now()`,
6082
+ })
6083
+ .where(
6084
+ and(
6085
+ eq(schema.slackInteractions.id, input.id),
6086
+ eq(schema.slackInteractions.deliveryClaimHolderId, input.claimHolderId),
6087
+ ),
6088
+ )
6089
+ .returning({ id: schema.slackInteractions.id });
6090
+ return rows.length === 1;
6091
+ });
6092
+ }
6093
+
6008
6094
  export async function closeSlackInteractionDelivery(
6009
6095
  db: Database,
6010
6096
  input: Pick<SlackInteraction, "id" | "accountId" | "workspaceId"> & {
6011
6097
  claimHolderId: string;
6012
6098
  sequence: number;
6013
6099
  state: Exclude<SlackInteraction["terminalDeliveryState"], "open">;
6100
+ errorCode?: string | null;
6014
6101
  },
6015
6102
  ): Promise<boolean> {
6016
6103
  return await withRlsContext(db, input, async (scopedDb) => {
@@ -6021,6 +6108,8 @@ export async function closeSlackInteractionDelivery(
6021
6108
  terminalDeliveryState: input.state,
6022
6109
  deliveryClaimHolderId: null,
6023
6110
  deliveryClaimExpiresAt: null,
6111
+ deliveryRetryAt: null,
6112
+ deliveryLastErrorCode: input.errorCode?.slice(0, 128) ?? null,
6024
6113
  updatedAt: sql`now()`,
6025
6114
  })
6026
6115
  .where(
@@ -6059,6 +6148,7 @@ function mapSlackInteractionInbox(
6059
6148
  claimHolderId: slackRowNullableString(row, "claimHolderId", "claim_holder_id"),
6060
6149
  claimExpiresAt: slackRowNullableDate(row, "claimExpiresAt", "claim_expires_at"),
6061
6150
  attemptCount: slackRowNumber(row, "attemptCount", "attempt_count"),
6151
+ retryAt: slackRowNullableDate(row, "retryAt", "retry_at"),
6062
6152
  lastErrorCode: slackRowNullableString(row, "lastErrorCode", "last_error_code"),
6063
6153
  processedAt: slackRowNullableDate(row, "processedAt", "processed_at"),
6064
6154
  createdAt: slackRowDate(row, "createdAt", "created_at"),
@@ -6102,6 +6192,13 @@ function mapSlackInteraction(
6102
6192
  "deliveryClaimExpiresAt",
6103
6193
  "delivery_claim_expires_at",
6104
6194
  ),
6195
+ deliveryAttemptCount: slackRowNumber(row, "deliveryAttemptCount", "delivery_attempt_count"),
6196
+ deliveryRetryAt: slackRowNullableDate(row, "deliveryRetryAt", "delivery_retry_at"),
6197
+ deliveryLastErrorCode: slackRowNullableString(
6198
+ row,
6199
+ "deliveryLastErrorCode",
6200
+ "delivery_last_error_code",
6201
+ ),
6105
6202
  ackSlackMessageTs: slackRowNullableString(row, "ackSlackMessageTs", "ack_slack_message_ts"),
6106
6203
  progressCount: slackRowNumber(row, "progressCount", "progress_count"),
6107
6204
  terminalDeliveryState: slackRowString(
@@ -7058,13 +7155,25 @@ export async function loadConnectionCredentialForBroker(
7058
7155
  async (scopedDb) => {
7059
7156
  // Prefer active rows: a revoke bumps updatedAt, so recency alone would let a
7060
7157
  // freshly revoked connection shadow an active replacement for the provider.
7158
+ // UUID-free Personal Slack lookup additionally mirrors the UI/reconnect
7159
+ // canonical rule through createdAt and immutable UUID tie-breakers. Migration
7160
+ // 0132 can stamp legacy duplicates with the same updatedAt in one transaction.
7161
+ const personalSlackSubjectLookup =
7162
+ !input.connectionId &&
7163
+ input.allowSubjectOwned === true &&
7164
+ input.providerDomain === "slack.com" &&
7165
+ input.kind === "oauth2";
7061
7166
  const [row] = await scopedDb
7062
7167
  .select()
7063
7168
  .from(schema.connections)
7064
7169
  .where(and(...conditions))
7065
7170
  .orderBy(
7066
- desc(sql`(${schema.connections.status} = 'active')`),
7067
- desc(schema.connections.updatedAt),
7171
+ ...(personalSlackSubjectLookup
7172
+ ? personalSlackCanonicalConnectionOrder()
7173
+ : [
7174
+ desc(sql`(${schema.connections.status} = 'active')`),
7175
+ desc(schema.connections.updatedAt),
7176
+ ]),
7068
7177
  )
7069
7178
  .limit(1);
7070
7179
  if (!row) {
@@ -8594,6 +8703,7 @@ export async function createSocialConnection(
8594
8703
  status: input.status,
8595
8704
  scopes: input.scopes ?? [],
8596
8705
  credentialRef: input.credentialRef ?? null,
8706
+ credentialEncrypted: input.credentialEncrypted ?? null,
8597
8707
  tokenMetadata: input.tokenMetadata ?? {},
8598
8708
  metadata: input.metadata ?? {},
8599
8709
  })
@@ -8606,6 +8716,118 @@ export async function createSocialConnection(
8606
8716
  );
8607
8717
  }
8608
8718
 
8719
+ /**
8720
+ * OAuth-callback persistence: reconnecting the same provider account replaces
8721
+ * the stored credential and revives the connection instead of failing on the
8722
+ * (workspace, provider, handle) unique index.
8723
+ */
8724
+ export async function upsertSocialOAuthConnection(
8725
+ db: Database,
8726
+ input: UpsertSocialOAuthConnectionInput,
8727
+ ): Promise<SocialConnection> {
8728
+ return await withRlsContext(
8729
+ db,
8730
+ { accountId: input.accountId, workspaceId: input.workspaceId },
8731
+ async (scopedDb) => {
8732
+ const [row] = await scopedDb
8733
+ .insert(schema.socialConnections)
8734
+ .values({
8735
+ accountId: input.accountId,
8736
+ workspaceId: input.workspaceId,
8737
+ provider: input.provider,
8738
+ accountHandle: input.accountHandle,
8739
+ accountName: input.accountName ?? null,
8740
+ externalAccountId: input.externalAccountId ?? null,
8741
+ status: "connected",
8742
+ scopes: input.scopes,
8743
+ credentialEncrypted: input.credentialEncrypted,
8744
+ tokenMetadata: input.tokenMetadata ?? {},
8745
+ })
8746
+ .onConflictDoUpdate({
8747
+ target: [
8748
+ schema.socialConnections.workspaceId,
8749
+ schema.socialConnections.provider,
8750
+ schema.socialConnections.accountHandle,
8751
+ ],
8752
+ set: {
8753
+ accountName: input.accountName ?? null,
8754
+ externalAccountId: input.externalAccountId ?? null,
8755
+ status: "connected",
8756
+ scopes: input.scopes,
8757
+ credentialEncrypted: input.credentialEncrypted,
8758
+ tokenMetadata: input.tokenMetadata ?? {},
8759
+ updatedAt: new Date(),
8760
+ },
8761
+ })
8762
+ .returning();
8763
+ if (!row) {
8764
+ throw new Error("Failed to upsert social connection");
8765
+ }
8766
+ return mapSocialConnection(row);
8767
+ },
8768
+ );
8769
+ }
8770
+
8771
+ /**
8772
+ * Host-side credential read for the social API client. Deliberately not part
8773
+ * of mapSocialConnection so the encrypted bundle never rides along on list or
8774
+ * MCP responses.
8775
+ */
8776
+ export async function loadSocialConnectionCredential(
8777
+ db: Database,
8778
+ workspaceId: string,
8779
+ connectionId: string,
8780
+ ): Promise<{ connection: SocialConnection; credentialEncrypted: string | null } | null> {
8781
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8782
+ const [row] = await scopedDb
8783
+ .select()
8784
+ .from(schema.socialConnections)
8785
+ .where(
8786
+ and(
8787
+ eq(schema.socialConnections.workspaceId, workspaceId),
8788
+ eq(schema.socialConnections.id, connectionId),
8789
+ ),
8790
+ )
8791
+ .limit(1);
8792
+ if (!row) {
8793
+ return null;
8794
+ }
8795
+ return { connection: mapSocialConnection(row), credentialEncrypted: row.credentialEncrypted };
8796
+ });
8797
+ }
8798
+
8799
+ export async function updateSocialConnectionCredential(
8800
+ db: Database,
8801
+ input: UpdateSocialConnectionCredentialInput,
8802
+ ): Promise<SocialConnection | null> {
8803
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
8804
+ const [row] = await scopedDb
8805
+ .update(schema.socialConnections)
8806
+ .set({
8807
+ ...(input.credentialEncrypted !== undefined
8808
+ ? { credentialEncrypted: input.credentialEncrypted }
8809
+ : {}),
8810
+ ...(input.status !== undefined ? { status: input.status } : {}),
8811
+ ...(input.tokenMetadata !== undefined ? { tokenMetadata: input.tokenMetadata } : {}),
8812
+ updatedAt: new Date(),
8813
+ })
8814
+ .where(
8815
+ and(
8816
+ eq(schema.socialConnections.workspaceId, input.workspaceId),
8817
+ eq(schema.socialConnections.id, input.connectionId),
8818
+ ),
8819
+ )
8820
+ .returning();
8821
+ return row ? mapSocialConnection(row) : null;
8822
+ });
8823
+ }
8824
+
8825
+ // List/get never select credential_encrypted (same posture as the broker
8826
+ // `connections` helpers): the ciphertext must not ride into API-process memory
8827
+ // on every list, where one added debug log or row spread would expose it.
8828
+ const { credentialEncrypted: _socialCredentialColumn, ...socialConnectionPublicColumns } =
8829
+ getTableColumns(schema.socialConnections);
8830
+
8609
8831
  export async function listSocialConnections(
8610
8832
  db: Database,
8611
8833
  workspaceId: string,
@@ -8613,7 +8835,7 @@ export async function listSocialConnections(
8613
8835
  ): Promise<SocialConnection[]> {
8614
8836
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8615
8837
  const rows = await scopedDb
8616
- .select()
8838
+ .select(socialConnectionPublicColumns)
8617
8839
  .from(schema.socialConnections)
8618
8840
  .where(eq(schema.socialConnections.workspaceId, workspaceId))
8619
8841
  .orderBy(desc(schema.socialConnections.createdAt))
@@ -8629,7 +8851,7 @@ export async function getSocialConnection(
8629
8851
  ): Promise<SocialConnection | null> {
8630
8852
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8631
8853
  const [row] = await scopedDb
8632
- .select()
8854
+ .select(socialConnectionPublicColumns)
8633
8855
  .from(schema.socialConnections)
8634
8856
  .where(
8635
8857
  and(
@@ -8691,6 +8913,70 @@ export async function createSocialPost(
8691
8913
  );
8692
8914
  }
8693
8915
 
8916
+ /**
8917
+ * Idempotent bulk ingest for provider sync: rows already present under the
8918
+ * (workspace, connection, external post id) unique index are skipped, so a
8919
+ * scheduled re-sync never duplicates or fails.
8920
+ */
8921
+ export async function recordSyncedSocialPosts(
8922
+ db: Database,
8923
+ input: {
8924
+ accountId: string;
8925
+ workspaceId: string;
8926
+ connectionId: string;
8927
+ posts: Array<{
8928
+ externalPostId: string;
8929
+ url?: string | null;
8930
+ authorHandle?: string | null;
8931
+ text: string;
8932
+ publishedAt: Date;
8933
+ metrics?: Record<string, number>;
8934
+ raw?: Record<string, unknown>;
8935
+ }>;
8936
+ },
8937
+ ): Promise<{ inserted: number; skipped: number }> {
8938
+ if (input.posts.length === 0) {
8939
+ return { inserted: 0, skipped: 0 };
8940
+ }
8941
+ return await withRlsContext(
8942
+ db,
8943
+ { accountId: input.accountId, workspaceId: input.workspaceId },
8944
+ async (scopedDb) => {
8945
+ const connection = await requireSocialConnection(
8946
+ scopedDb,
8947
+ input.workspaceId,
8948
+ input.connectionId,
8949
+ );
8950
+ const rows = await scopedDb
8951
+ .insert(schema.socialPosts)
8952
+ .values(
8953
+ input.posts.map((post) => ({
8954
+ accountId: input.accountId,
8955
+ workspaceId: input.workspaceId,
8956
+ connectionId: input.connectionId,
8957
+ provider: connection.provider,
8958
+ externalPostId: post.externalPostId,
8959
+ url: post.url ?? null,
8960
+ authorHandle: post.authorHandle ?? connection.accountHandle,
8961
+ text: post.text,
8962
+ publishedAt: post.publishedAt,
8963
+ metrics: post.metrics ?? {},
8964
+ raw: post.raw ?? {},
8965
+ })),
8966
+ )
8967
+ .onConflictDoNothing({
8968
+ target: [
8969
+ schema.socialPosts.workspaceId,
8970
+ schema.socialPosts.connectionId,
8971
+ schema.socialPosts.externalPostId,
8972
+ ],
8973
+ })
8974
+ .returning({ id: schema.socialPosts.id });
8975
+ return { inserted: rows.length, skipped: input.posts.length - rows.length };
8976
+ },
8977
+ );
8978
+ }
8979
+
8694
8980
  export async function listSocialPosts(
8695
8981
  db: Database,
8696
8982
  options: {
@@ -42139,7 +42425,9 @@ function mapKnowledgeMemory(row: typeof schema.knowledgeMemories.$inferSelect):
42139
42425
  };
42140
42426
  }
42141
42427
 
42142
- function mapSocialConnection(row: typeof schema.socialConnections.$inferSelect): SocialConnection {
42428
+ function mapSocialConnection(
42429
+ row: Omit<typeof schema.socialConnections.$inferSelect, "credentialEncrypted">,
42430
+ ): SocialConnection {
42143
42431
  return {
42144
42432
  id: row.id,
42145
42433
  accountId: row.accountId,
package/src/schema.ts CHANGED
@@ -776,6 +776,7 @@ export const slackInteractionInbox = pgTable(
776
776
  claimHolderId: uuid("claim_holder_id"),
777
777
  claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
778
778
  attemptCount: integer("attempt_count").notNull().default(0),
779
+ retryAt: timestamp("retry_at", { withTimezone: true }),
779
780
  lastErrorCode: text("last_error_code"),
780
781
  processedAt: timestamp("processed_at", { withTimezone: true }),
781
782
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -792,6 +793,7 @@ export const slackInteractionInbox = pgTable(
792
793
  ),
793
794
  pending: index("slack_interaction_inbox_pending_idx").on(
794
795
  table.status,
796
+ table.retryAt,
795
797
  table.createdAt,
796
798
  table.id,
797
799
  ),
@@ -829,6 +831,9 @@ export const slackInteractions = pgTable(
829
831
  deliveryClaimExpiresAt: timestamp("delivery_claim_expires_at", {
830
832
  withTimezone: true,
831
833
  }),
834
+ deliveryAttemptCount: integer("delivery_attempt_count").notNull().default(0),
835
+ deliveryRetryAt: timestamp("delivery_retry_at", { withTimezone: true }),
836
+ deliveryLastErrorCode: text("delivery_last_error_code"),
832
837
  ackSlackMessageTs: text("ack_slack_message_ts"),
833
838
  progressCount: integer("progress_count").notNull().default(0),
834
839
  terminalDeliveryState: text("terminal_delivery_state")
@@ -854,6 +859,7 @@ export const slackInteractions = pgTable(
854
859
  .where(sql`${table.sessionId} is not null`),
855
860
  delivery: index("slack_interactions_delivery_idx").on(
856
861
  table.terminalDeliveryState,
862
+ table.deliveryRetryAt,
857
863
  table.updatedAt,
858
864
  table.id,
859
865
  ),
@@ -5119,6 +5125,10 @@ export const socialConnections = pgTable(
5119
5125
  status: text("status").notNull().default("connected"),
5120
5126
  scopes: jsonb("scopes").$type<string[]>().notNull().default([]),
5121
5127
  credentialRef: text("credential_ref"),
5128
+ // AES-256-GCM envelope (environment-crypto v1 format) holding the OAuth
5129
+ // token bundle. Never exposed through contracts or MCP tools; only the
5130
+ // host-side social API client decrypts it.
5131
+ credentialEncrypted: text("credential_encrypted"),
5122
5132
  tokenMetadata: jsonb("token_metadata").$type<Record<string, unknown>>().notNull().default({}),
5123
5133
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
5124
5134
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),