@opengeni/db 0.17.1 → 0.19.0
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/dist/{chunk-TLAC622R.js → chunk-SZP6KRLC.js} +2128 -1927
- package/dist/chunk-SZP6KRLC.js.map +1 -0
- package/dist/{chunk-T6RSJT6C.js → chunk-XOXEQ7HG.js} +29 -1
- package/dist/chunk-XOXEQ7HG.js.map +1 -0
- package/dist/index.d.ts +71 -2
- package/dist/index.js +1194 -562
- package/dist/index.js.map +1 -1
- package/dist/memory-domain.d.ts +110 -1
- package/dist/memory-governance-schema.d.ts +562 -0
- package/dist/memory-governance.d.ts +41 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +286 -2
- package/dist/schema.js +5 -1
- package/drizzle/0141_social_connection_credentials.sql +8 -0
- package/drizzle/0151_slack_delivery_backoff.sql +134 -0
- package/drizzle/0152_hierarchical_memory_foundation.sql +1589 -0
- package/package.json +3 -3
- package/src/index.ts +304 -11
- package/src/memory-domain.ts +360 -1
- package/src/memory-governance-schema.ts +162 -0
- package/src/memory-governance.ts +317 -0
- package/src/provision-roles.ts +24 -0
- package/src/runtime-posture.ts +4 -0
- package/src/schema.ts +50 -2
- package/dist/chunk-T6RSJT6C.js.map +0 -1
- package/dist/chunk-TLAC622R.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@opengeni/codex": "^0.2.9",
|
|
54
|
-
"@opengeni/config": "^0.
|
|
55
|
-
"@opengeni/contracts": "^0.
|
|
54
|
+
"@opengeni/config": "^0.9.1",
|
|
55
|
+
"@opengeni/contracts": "^0.28.1",
|
|
56
56
|
"@opengeni/network": "^0.1.1",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
58
|
"postgres": "^3.4.7"
|
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,
|
|
@@ -237,6 +238,7 @@ export * from "./session-queue-commands";
|
|
|
237
238
|
export * from "./new-session-drafts";
|
|
238
239
|
export * from "./workspace-instruction-policies";
|
|
239
240
|
export * from "./preference-registry";
|
|
241
|
+
export * from "./memory-governance";
|
|
240
242
|
export { interruptedToolCallResult } from "./session-tool-call-settlement";
|
|
241
243
|
export { decryptEnvironmentValue, encryptEnvironmentValue } from "./environment-crypto";
|
|
242
244
|
export {
|
|
@@ -3381,10 +3383,31 @@ export type CreateSocialConnectionInput = {
|
|
|
3381
3383
|
status: SocialConnectionStatus;
|
|
3382
3384
|
scopes?: string[];
|
|
3383
3385
|
credentialRef?: string | null;
|
|
3386
|
+
credentialEncrypted?: string | null;
|
|
3384
3387
|
tokenMetadata?: Record<string, unknown>;
|
|
3385
3388
|
metadata?: Record<string, unknown>;
|
|
3386
3389
|
};
|
|
3387
3390
|
|
|
3391
|
+
export type UpsertSocialOAuthConnectionInput = {
|
|
3392
|
+
accountId: string;
|
|
3393
|
+
workspaceId: string;
|
|
3394
|
+
provider: SocialProvider;
|
|
3395
|
+
accountHandle: string;
|
|
3396
|
+
accountName?: string | null;
|
|
3397
|
+
externalAccountId?: string | null;
|
|
3398
|
+
scopes: string[];
|
|
3399
|
+
credentialEncrypted: string;
|
|
3400
|
+
tokenMetadata?: Record<string, unknown>;
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3403
|
+
export type UpdateSocialConnectionCredentialInput = {
|
|
3404
|
+
workspaceId: string;
|
|
3405
|
+
connectionId: string;
|
|
3406
|
+
credentialEncrypted?: string | null;
|
|
3407
|
+
status?: SocialConnectionStatus;
|
|
3408
|
+
tokenMetadata?: Record<string, unknown>;
|
|
3409
|
+
};
|
|
3410
|
+
|
|
3388
3411
|
export type CreateSocialPostInput = {
|
|
3389
3412
|
accountId: string;
|
|
3390
3413
|
workspaceId: string;
|
|
@@ -5085,6 +5108,21 @@ function connectionExactSubject(subjectId?: string | null): SQL {
|
|
|
5085
5108
|
: isNull(schema.connections.subjectId);
|
|
5086
5109
|
}
|
|
5087
5110
|
|
|
5111
|
+
function personalSlackCanonicalConnectionOrder(): SQL[] {
|
|
5112
|
+
return [
|
|
5113
|
+
sql`case ${schema.connections.status}
|
|
5114
|
+
when 'active' then 0
|
|
5115
|
+
when 'needs_reauth' then 1
|
|
5116
|
+
when 'error' then 2
|
|
5117
|
+
when 'revoked' then 3
|
|
5118
|
+
else 4
|
|
5119
|
+
end`,
|
|
5120
|
+
desc(schema.connections.updatedAt),
|
|
5121
|
+
desc(schema.connections.createdAt),
|
|
5122
|
+
desc(schema.connections.id),
|
|
5123
|
+
];
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5088
5126
|
async function withConnectionSubjectRls<T>(
|
|
5089
5127
|
db: Database,
|
|
5090
5128
|
workspaceId: string,
|
|
@@ -5345,6 +5383,7 @@ export type SlackInteractionInboxEntry = {
|
|
|
5345
5383
|
claimHolderId: string | null;
|
|
5346
5384
|
claimExpiresAt: Date | null;
|
|
5347
5385
|
attemptCount: number;
|
|
5386
|
+
retryAt: Date | null;
|
|
5348
5387
|
lastErrorCode: string | null;
|
|
5349
5388
|
processedAt: Date | null;
|
|
5350
5389
|
createdAt: Date;
|
|
@@ -5368,6 +5407,9 @@ export type SlackInteraction = {
|
|
|
5368
5407
|
lastDeliveredSessionEventSequence: number;
|
|
5369
5408
|
deliveryClaimHolderId: string | null;
|
|
5370
5409
|
deliveryClaimExpiresAt: Date | null;
|
|
5410
|
+
deliveryAttemptCount: number;
|
|
5411
|
+
deliveryRetryAt: Date | null;
|
|
5412
|
+
deliveryLastErrorCode: string | null;
|
|
5371
5413
|
ackSlackMessageTs: string | null;
|
|
5372
5414
|
progressCount: number;
|
|
5373
5415
|
terminalDeliveryState: "open" | "completed" | "failed" | "cancelled" | "blocked";
|
|
@@ -5486,6 +5528,7 @@ export async function enqueueSlackInteractionInbox(
|
|
|
5486
5528
|
| "claimHolderId"
|
|
5487
5529
|
| "claimExpiresAt"
|
|
5488
5530
|
| "attemptCount"
|
|
5531
|
+
| "retryAt"
|
|
5489
5532
|
| "lastErrorCode"
|
|
5490
5533
|
| "processedAt"
|
|
5491
5534
|
| "createdAt"
|
|
@@ -5548,6 +5591,7 @@ export async function settleSlackInteractionInbox(
|
|
|
5548
5591
|
status: input.outcome,
|
|
5549
5592
|
claimHolderId: null,
|
|
5550
5593
|
claimExpiresAt: null,
|
|
5594
|
+
retryAt: null,
|
|
5551
5595
|
processedAt: sql`now()`,
|
|
5552
5596
|
lastErrorCode: input.errorCode ?? null,
|
|
5553
5597
|
updatedAt: sql`now()`,
|
|
@@ -5570,6 +5614,7 @@ export async function releaseSlackInteractionInbox(
|
|
|
5570
5614
|
entry: Pick<SlackInteractionInboxEntry, "id" | "accountId" | "workspaceId">;
|
|
5571
5615
|
claimHolderId: string;
|
|
5572
5616
|
errorCode: string;
|
|
5617
|
+
retryAt: Date;
|
|
5573
5618
|
},
|
|
5574
5619
|
): Promise<boolean> {
|
|
5575
5620
|
return await withRlsContext(db, input.entry, async (scopedDb) => {
|
|
@@ -5579,6 +5624,7 @@ export async function releaseSlackInteractionInbox(
|
|
|
5579
5624
|
status: "pending",
|
|
5580
5625
|
claimHolderId: null,
|
|
5581
5626
|
claimExpiresAt: null,
|
|
5627
|
+
retryAt: input.retryAt,
|
|
5582
5628
|
lastErrorCode: input.errorCode,
|
|
5583
5629
|
updatedAt: sql`now()`,
|
|
5584
5630
|
})
|
|
@@ -5604,6 +5650,9 @@ export async function getOrCreateSlackInteraction(
|
|
|
5604
5650
|
| "lastDeliveredSessionEventSequence"
|
|
5605
5651
|
| "deliveryClaimHolderId"
|
|
5606
5652
|
| "deliveryClaimExpiresAt"
|
|
5653
|
+
| "deliveryAttemptCount"
|
|
5654
|
+
| "deliveryRetryAt"
|
|
5655
|
+
| "deliveryLastErrorCode"
|
|
5607
5656
|
| "ackSlackMessageTs"
|
|
5608
5657
|
| "progressCount"
|
|
5609
5658
|
| "terminalDeliveryState"
|
|
@@ -5943,7 +5992,13 @@ export async function reopenSlackInteractionDelivery(
|
|
|
5943
5992
|
return await withRlsContext(db, input, async (scopedDb) => {
|
|
5944
5993
|
const rows = await scopedDb
|
|
5945
5994
|
.update(schema.slackInteractions)
|
|
5946
|
-
.set({
|
|
5995
|
+
.set({
|
|
5996
|
+
terminalDeliveryState: "open",
|
|
5997
|
+
deliveryAttemptCount: 0,
|
|
5998
|
+
deliveryRetryAt: null,
|
|
5999
|
+
deliveryLastErrorCode: null,
|
|
6000
|
+
updatedAt: sql`now()`,
|
|
6001
|
+
})
|
|
5947
6002
|
.where(eq(schema.slackInteractions.id, input.id))
|
|
5948
6003
|
.returning({ id: schema.slackInteractions.id });
|
|
5949
6004
|
return rows.length === 1;
|
|
@@ -5966,6 +6021,9 @@ export async function advanceSlackInteractionDelivery(
|
|
|
5966
6021
|
...(input.ackSlackMessageTs !== undefined
|
|
5967
6022
|
? { ackSlackMessageTs: input.ackSlackMessageTs }
|
|
5968
6023
|
: {}),
|
|
6024
|
+
deliveryAttemptCount: 0,
|
|
6025
|
+
deliveryRetryAt: null,
|
|
6026
|
+
deliveryLastErrorCode: null,
|
|
5969
6027
|
updatedAt: sql`now()`,
|
|
5970
6028
|
})
|
|
5971
6029
|
.where(
|
|
@@ -6005,12 +6063,42 @@ export async function releaseSlackInteractionDelivery(
|
|
|
6005
6063
|
});
|
|
6006
6064
|
}
|
|
6007
6065
|
|
|
6066
|
+
export async function deferSlackInteractionDelivery(
|
|
6067
|
+
db: Database,
|
|
6068
|
+
input: Pick<SlackInteraction, "id" | "accountId" | "workspaceId"> & {
|
|
6069
|
+
claimHolderId: string;
|
|
6070
|
+
retryAt: Date;
|
|
6071
|
+
errorCode: string;
|
|
6072
|
+
},
|
|
6073
|
+
): Promise<boolean> {
|
|
6074
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
6075
|
+
const rows = await scopedDb
|
|
6076
|
+
.update(schema.slackInteractions)
|
|
6077
|
+
.set({
|
|
6078
|
+
deliveryClaimHolderId: null,
|
|
6079
|
+
deliveryClaimExpiresAt: null,
|
|
6080
|
+
deliveryRetryAt: input.retryAt,
|
|
6081
|
+
deliveryLastErrorCode: input.errorCode.slice(0, 128),
|
|
6082
|
+
updatedAt: sql`now()`,
|
|
6083
|
+
})
|
|
6084
|
+
.where(
|
|
6085
|
+
and(
|
|
6086
|
+
eq(schema.slackInteractions.id, input.id),
|
|
6087
|
+
eq(schema.slackInteractions.deliveryClaimHolderId, input.claimHolderId),
|
|
6088
|
+
),
|
|
6089
|
+
)
|
|
6090
|
+
.returning({ id: schema.slackInteractions.id });
|
|
6091
|
+
return rows.length === 1;
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6008
6095
|
export async function closeSlackInteractionDelivery(
|
|
6009
6096
|
db: Database,
|
|
6010
6097
|
input: Pick<SlackInteraction, "id" | "accountId" | "workspaceId"> & {
|
|
6011
6098
|
claimHolderId: string;
|
|
6012
6099
|
sequence: number;
|
|
6013
6100
|
state: Exclude<SlackInteraction["terminalDeliveryState"], "open">;
|
|
6101
|
+
errorCode?: string | null;
|
|
6014
6102
|
},
|
|
6015
6103
|
): Promise<boolean> {
|
|
6016
6104
|
return await withRlsContext(db, input, async (scopedDb) => {
|
|
@@ -6021,6 +6109,8 @@ export async function closeSlackInteractionDelivery(
|
|
|
6021
6109
|
terminalDeliveryState: input.state,
|
|
6022
6110
|
deliveryClaimHolderId: null,
|
|
6023
6111
|
deliveryClaimExpiresAt: null,
|
|
6112
|
+
deliveryRetryAt: null,
|
|
6113
|
+
deliveryLastErrorCode: input.errorCode?.slice(0, 128) ?? null,
|
|
6024
6114
|
updatedAt: sql`now()`,
|
|
6025
6115
|
})
|
|
6026
6116
|
.where(
|
|
@@ -6059,6 +6149,7 @@ function mapSlackInteractionInbox(
|
|
|
6059
6149
|
claimHolderId: slackRowNullableString(row, "claimHolderId", "claim_holder_id"),
|
|
6060
6150
|
claimExpiresAt: slackRowNullableDate(row, "claimExpiresAt", "claim_expires_at"),
|
|
6061
6151
|
attemptCount: slackRowNumber(row, "attemptCount", "attempt_count"),
|
|
6152
|
+
retryAt: slackRowNullableDate(row, "retryAt", "retry_at"),
|
|
6062
6153
|
lastErrorCode: slackRowNullableString(row, "lastErrorCode", "last_error_code"),
|
|
6063
6154
|
processedAt: slackRowNullableDate(row, "processedAt", "processed_at"),
|
|
6064
6155
|
createdAt: slackRowDate(row, "createdAt", "created_at"),
|
|
@@ -6102,6 +6193,13 @@ function mapSlackInteraction(
|
|
|
6102
6193
|
"deliveryClaimExpiresAt",
|
|
6103
6194
|
"delivery_claim_expires_at",
|
|
6104
6195
|
),
|
|
6196
|
+
deliveryAttemptCount: slackRowNumber(row, "deliveryAttemptCount", "delivery_attempt_count"),
|
|
6197
|
+
deliveryRetryAt: slackRowNullableDate(row, "deliveryRetryAt", "delivery_retry_at"),
|
|
6198
|
+
deliveryLastErrorCode: slackRowNullableString(
|
|
6199
|
+
row,
|
|
6200
|
+
"deliveryLastErrorCode",
|
|
6201
|
+
"delivery_last_error_code",
|
|
6202
|
+
),
|
|
6105
6203
|
ackSlackMessageTs: slackRowNullableString(row, "ackSlackMessageTs", "ack_slack_message_ts"),
|
|
6106
6204
|
progressCount: slackRowNumber(row, "progressCount", "progress_count"),
|
|
6107
6205
|
terminalDeliveryState: slackRowString(
|
|
@@ -7058,13 +7156,25 @@ export async function loadConnectionCredentialForBroker(
|
|
|
7058
7156
|
async (scopedDb) => {
|
|
7059
7157
|
// Prefer active rows: a revoke bumps updatedAt, so recency alone would let a
|
|
7060
7158
|
// freshly revoked connection shadow an active replacement for the provider.
|
|
7159
|
+
// UUID-free Personal Slack lookup additionally mirrors the UI/reconnect
|
|
7160
|
+
// canonical rule through createdAt and immutable UUID tie-breakers. Migration
|
|
7161
|
+
// 0132 can stamp legacy duplicates with the same updatedAt in one transaction.
|
|
7162
|
+
const personalSlackSubjectLookup =
|
|
7163
|
+
!input.connectionId &&
|
|
7164
|
+
input.allowSubjectOwned === true &&
|
|
7165
|
+
input.providerDomain === "slack.com" &&
|
|
7166
|
+
input.kind === "oauth2";
|
|
7061
7167
|
const [row] = await scopedDb
|
|
7062
7168
|
.select()
|
|
7063
7169
|
.from(schema.connections)
|
|
7064
7170
|
.where(and(...conditions))
|
|
7065
7171
|
.orderBy(
|
|
7066
|
-
|
|
7067
|
-
|
|
7172
|
+
...(personalSlackSubjectLookup
|
|
7173
|
+
? personalSlackCanonicalConnectionOrder()
|
|
7174
|
+
: [
|
|
7175
|
+
desc(sql`(${schema.connections.status} = 'active')`),
|
|
7176
|
+
desc(schema.connections.updatedAt),
|
|
7177
|
+
]),
|
|
7068
7178
|
)
|
|
7069
7179
|
.limit(1);
|
|
7070
7180
|
if (!row) {
|
|
@@ -7779,7 +7889,10 @@ function memoryVectorLiteral(values: number[]): string {
|
|
|
7779
7889
|
}
|
|
7780
7890
|
|
|
7781
7891
|
const agentVisibleMemoryStatuses = [...AGENT_VISIBLE_MEMORY_STATUSES];
|
|
7782
|
-
const
|
|
7892
|
+
const visibleTextHashUniqueIndexNames = new Set([
|
|
7893
|
+
"knowledge_memories_workspace_visible_text_hash_uq",
|
|
7894
|
+
"knowledge_memories_scope_visible_text_hash_uq",
|
|
7895
|
+
]);
|
|
7783
7896
|
|
|
7784
7897
|
function isVisibleTextHashUniqueViolation(error: unknown): boolean {
|
|
7785
7898
|
const candidate = error as {
|
|
@@ -7793,14 +7906,15 @@ function isVisibleTextHashUniqueViolation(error: unknown): boolean {
|
|
|
7793
7906
|
return false;
|
|
7794
7907
|
}
|
|
7795
7908
|
const constraint = candidate.constraint ?? candidate.constraint_name;
|
|
7796
|
-
if (candidate.code === "23505" && constraint
|
|
7909
|
+
if (candidate.code === "23505" && visibleTextHashUniqueIndexNames.has(String(constraint))) {
|
|
7797
7910
|
return true;
|
|
7798
7911
|
}
|
|
7912
|
+
const message = typeof candidate.message === "string" ? candidate.message : null;
|
|
7799
7913
|
if (
|
|
7800
|
-
|
|
7801
|
-
|
|
7914
|
+
message !== null &&
|
|
7915
|
+
[...visibleTextHashUniqueIndexNames].some((name) => message.includes(name)) &&
|
|
7802
7916
|
(candidate.code === "23505" ||
|
|
7803
|
-
|
|
7917
|
+
message.includes("duplicate key value violates unique constraint"))
|
|
7804
7918
|
) {
|
|
7805
7919
|
return true;
|
|
7806
7920
|
}
|
|
@@ -8594,6 +8708,7 @@ export async function createSocialConnection(
|
|
|
8594
8708
|
status: input.status,
|
|
8595
8709
|
scopes: input.scopes ?? [],
|
|
8596
8710
|
credentialRef: input.credentialRef ?? null,
|
|
8711
|
+
credentialEncrypted: input.credentialEncrypted ?? null,
|
|
8597
8712
|
tokenMetadata: input.tokenMetadata ?? {},
|
|
8598
8713
|
metadata: input.metadata ?? {},
|
|
8599
8714
|
})
|
|
@@ -8606,6 +8721,118 @@ export async function createSocialConnection(
|
|
|
8606
8721
|
);
|
|
8607
8722
|
}
|
|
8608
8723
|
|
|
8724
|
+
/**
|
|
8725
|
+
* OAuth-callback persistence: reconnecting the same provider account replaces
|
|
8726
|
+
* the stored credential and revives the connection instead of failing on the
|
|
8727
|
+
* (workspace, provider, handle) unique index.
|
|
8728
|
+
*/
|
|
8729
|
+
export async function upsertSocialOAuthConnection(
|
|
8730
|
+
db: Database,
|
|
8731
|
+
input: UpsertSocialOAuthConnectionInput,
|
|
8732
|
+
): Promise<SocialConnection> {
|
|
8733
|
+
return await withRlsContext(
|
|
8734
|
+
db,
|
|
8735
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
8736
|
+
async (scopedDb) => {
|
|
8737
|
+
const [row] = await scopedDb
|
|
8738
|
+
.insert(schema.socialConnections)
|
|
8739
|
+
.values({
|
|
8740
|
+
accountId: input.accountId,
|
|
8741
|
+
workspaceId: input.workspaceId,
|
|
8742
|
+
provider: input.provider,
|
|
8743
|
+
accountHandle: input.accountHandle,
|
|
8744
|
+
accountName: input.accountName ?? null,
|
|
8745
|
+
externalAccountId: input.externalAccountId ?? null,
|
|
8746
|
+
status: "connected",
|
|
8747
|
+
scopes: input.scopes,
|
|
8748
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
8749
|
+
tokenMetadata: input.tokenMetadata ?? {},
|
|
8750
|
+
})
|
|
8751
|
+
.onConflictDoUpdate({
|
|
8752
|
+
target: [
|
|
8753
|
+
schema.socialConnections.workspaceId,
|
|
8754
|
+
schema.socialConnections.provider,
|
|
8755
|
+
schema.socialConnections.accountHandle,
|
|
8756
|
+
],
|
|
8757
|
+
set: {
|
|
8758
|
+
accountName: input.accountName ?? null,
|
|
8759
|
+
externalAccountId: input.externalAccountId ?? null,
|
|
8760
|
+
status: "connected",
|
|
8761
|
+
scopes: input.scopes,
|
|
8762
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
8763
|
+
tokenMetadata: input.tokenMetadata ?? {},
|
|
8764
|
+
updatedAt: new Date(),
|
|
8765
|
+
},
|
|
8766
|
+
})
|
|
8767
|
+
.returning();
|
|
8768
|
+
if (!row) {
|
|
8769
|
+
throw new Error("Failed to upsert social connection");
|
|
8770
|
+
}
|
|
8771
|
+
return mapSocialConnection(row);
|
|
8772
|
+
},
|
|
8773
|
+
);
|
|
8774
|
+
}
|
|
8775
|
+
|
|
8776
|
+
/**
|
|
8777
|
+
* Host-side credential read for the social API client. Deliberately not part
|
|
8778
|
+
* of mapSocialConnection so the encrypted bundle never rides along on list or
|
|
8779
|
+
* MCP responses.
|
|
8780
|
+
*/
|
|
8781
|
+
export async function loadSocialConnectionCredential(
|
|
8782
|
+
db: Database,
|
|
8783
|
+
workspaceId: string,
|
|
8784
|
+
connectionId: string,
|
|
8785
|
+
): Promise<{ connection: SocialConnection; credentialEncrypted: string | null } | null> {
|
|
8786
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
8787
|
+
const [row] = await scopedDb
|
|
8788
|
+
.select()
|
|
8789
|
+
.from(schema.socialConnections)
|
|
8790
|
+
.where(
|
|
8791
|
+
and(
|
|
8792
|
+
eq(schema.socialConnections.workspaceId, workspaceId),
|
|
8793
|
+
eq(schema.socialConnections.id, connectionId),
|
|
8794
|
+
),
|
|
8795
|
+
)
|
|
8796
|
+
.limit(1);
|
|
8797
|
+
if (!row) {
|
|
8798
|
+
return null;
|
|
8799
|
+
}
|
|
8800
|
+
return { connection: mapSocialConnection(row), credentialEncrypted: row.credentialEncrypted };
|
|
8801
|
+
});
|
|
8802
|
+
}
|
|
8803
|
+
|
|
8804
|
+
export async function updateSocialConnectionCredential(
|
|
8805
|
+
db: Database,
|
|
8806
|
+
input: UpdateSocialConnectionCredentialInput,
|
|
8807
|
+
): Promise<SocialConnection | null> {
|
|
8808
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
8809
|
+
const [row] = await scopedDb
|
|
8810
|
+
.update(schema.socialConnections)
|
|
8811
|
+
.set({
|
|
8812
|
+
...(input.credentialEncrypted !== undefined
|
|
8813
|
+
? { credentialEncrypted: input.credentialEncrypted }
|
|
8814
|
+
: {}),
|
|
8815
|
+
...(input.status !== undefined ? { status: input.status } : {}),
|
|
8816
|
+
...(input.tokenMetadata !== undefined ? { tokenMetadata: input.tokenMetadata } : {}),
|
|
8817
|
+
updatedAt: new Date(),
|
|
8818
|
+
})
|
|
8819
|
+
.where(
|
|
8820
|
+
and(
|
|
8821
|
+
eq(schema.socialConnections.workspaceId, input.workspaceId),
|
|
8822
|
+
eq(schema.socialConnections.id, input.connectionId),
|
|
8823
|
+
),
|
|
8824
|
+
)
|
|
8825
|
+
.returning();
|
|
8826
|
+
return row ? mapSocialConnection(row) : null;
|
|
8827
|
+
});
|
|
8828
|
+
}
|
|
8829
|
+
|
|
8830
|
+
// List/get never select credential_encrypted (same posture as the broker
|
|
8831
|
+
// `connections` helpers): the ciphertext must not ride into API-process memory
|
|
8832
|
+
// on every list, where one added debug log or row spread would expose it.
|
|
8833
|
+
const { credentialEncrypted: _socialCredentialColumn, ...socialConnectionPublicColumns } =
|
|
8834
|
+
getTableColumns(schema.socialConnections);
|
|
8835
|
+
|
|
8609
8836
|
export async function listSocialConnections(
|
|
8610
8837
|
db: Database,
|
|
8611
8838
|
workspaceId: string,
|
|
@@ -8613,7 +8840,7 @@ export async function listSocialConnections(
|
|
|
8613
8840
|
): Promise<SocialConnection[]> {
|
|
8614
8841
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
8615
8842
|
const rows = await scopedDb
|
|
8616
|
-
.select()
|
|
8843
|
+
.select(socialConnectionPublicColumns)
|
|
8617
8844
|
.from(schema.socialConnections)
|
|
8618
8845
|
.where(eq(schema.socialConnections.workspaceId, workspaceId))
|
|
8619
8846
|
.orderBy(desc(schema.socialConnections.createdAt))
|
|
@@ -8629,7 +8856,7 @@ export async function getSocialConnection(
|
|
|
8629
8856
|
): Promise<SocialConnection | null> {
|
|
8630
8857
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
8631
8858
|
const [row] = await scopedDb
|
|
8632
|
-
.select()
|
|
8859
|
+
.select(socialConnectionPublicColumns)
|
|
8633
8860
|
.from(schema.socialConnections)
|
|
8634
8861
|
.where(
|
|
8635
8862
|
and(
|
|
@@ -8691,6 +8918,70 @@ export async function createSocialPost(
|
|
|
8691
8918
|
);
|
|
8692
8919
|
}
|
|
8693
8920
|
|
|
8921
|
+
/**
|
|
8922
|
+
* Idempotent bulk ingest for provider sync: rows already present under the
|
|
8923
|
+
* (workspace, connection, external post id) unique index are skipped, so a
|
|
8924
|
+
* scheduled re-sync never duplicates or fails.
|
|
8925
|
+
*/
|
|
8926
|
+
export async function recordSyncedSocialPosts(
|
|
8927
|
+
db: Database,
|
|
8928
|
+
input: {
|
|
8929
|
+
accountId: string;
|
|
8930
|
+
workspaceId: string;
|
|
8931
|
+
connectionId: string;
|
|
8932
|
+
posts: Array<{
|
|
8933
|
+
externalPostId: string;
|
|
8934
|
+
url?: string | null;
|
|
8935
|
+
authorHandle?: string | null;
|
|
8936
|
+
text: string;
|
|
8937
|
+
publishedAt: Date;
|
|
8938
|
+
metrics?: Record<string, number>;
|
|
8939
|
+
raw?: Record<string, unknown>;
|
|
8940
|
+
}>;
|
|
8941
|
+
},
|
|
8942
|
+
): Promise<{ inserted: number; skipped: number }> {
|
|
8943
|
+
if (input.posts.length === 0) {
|
|
8944
|
+
return { inserted: 0, skipped: 0 };
|
|
8945
|
+
}
|
|
8946
|
+
return await withRlsContext(
|
|
8947
|
+
db,
|
|
8948
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
8949
|
+
async (scopedDb) => {
|
|
8950
|
+
const connection = await requireSocialConnection(
|
|
8951
|
+
scopedDb,
|
|
8952
|
+
input.workspaceId,
|
|
8953
|
+
input.connectionId,
|
|
8954
|
+
);
|
|
8955
|
+
const rows = await scopedDb
|
|
8956
|
+
.insert(schema.socialPosts)
|
|
8957
|
+
.values(
|
|
8958
|
+
input.posts.map((post) => ({
|
|
8959
|
+
accountId: input.accountId,
|
|
8960
|
+
workspaceId: input.workspaceId,
|
|
8961
|
+
connectionId: input.connectionId,
|
|
8962
|
+
provider: connection.provider,
|
|
8963
|
+
externalPostId: post.externalPostId,
|
|
8964
|
+
url: post.url ?? null,
|
|
8965
|
+
authorHandle: post.authorHandle ?? connection.accountHandle,
|
|
8966
|
+
text: post.text,
|
|
8967
|
+
publishedAt: post.publishedAt,
|
|
8968
|
+
metrics: post.metrics ?? {},
|
|
8969
|
+
raw: post.raw ?? {},
|
|
8970
|
+
})),
|
|
8971
|
+
)
|
|
8972
|
+
.onConflictDoNothing({
|
|
8973
|
+
target: [
|
|
8974
|
+
schema.socialPosts.workspaceId,
|
|
8975
|
+
schema.socialPosts.connectionId,
|
|
8976
|
+
schema.socialPosts.externalPostId,
|
|
8977
|
+
],
|
|
8978
|
+
})
|
|
8979
|
+
.returning({ id: schema.socialPosts.id });
|
|
8980
|
+
return { inserted: rows.length, skipped: input.posts.length - rows.length };
|
|
8981
|
+
},
|
|
8982
|
+
);
|
|
8983
|
+
}
|
|
8984
|
+
|
|
8694
8985
|
export async function listSocialPosts(
|
|
8695
8986
|
db: Database,
|
|
8696
8987
|
options: {
|
|
@@ -42139,7 +42430,9 @@ function mapKnowledgeMemory(row: typeof schema.knowledgeMemories.$inferSelect):
|
|
|
42139
42430
|
};
|
|
42140
42431
|
}
|
|
42141
42432
|
|
|
42142
|
-
function mapSocialConnection(
|
|
42433
|
+
function mapSocialConnection(
|
|
42434
|
+
row: Omit<typeof schema.socialConnections.$inferSelect, "credentialEncrypted">,
|
|
42435
|
+
): SocialConnection {
|
|
42143
42436
|
return {
|
|
42144
42437
|
id: row.id,
|
|
42145
42438
|
accountId: row.accountId,
|