@sixb/pg 0.1.7 → 0.1.9
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/agents/rows.d.ts +0 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7596 -6416
- package/dist/index.js.map +25 -19
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/index.d.ts +2 -0
- package/dist/objects/index.d.ts.map +1 -0
- package/dist/{pg-object-query-compiler.d.ts → objects/query-compiler.d.ts} +25 -4
- package/dist/objects/query-compiler.d.ts.map +1 -0
- package/dist/objects/read-scope.d.ts +17 -0
- package/dist/objects/read-scope.d.ts.map +1 -0
- package/dist/objects/reader.d.ts +50 -0
- package/dist/objects/reader.d.ts.map +1 -0
- package/dist/objects/rows.d.ts +47 -0
- package/dist/objects/rows.d.ts.map +1 -0
- package/dist/objects/selected-reader.d.ts +8 -0
- package/dist/objects/selected-reader.d.ts.map +1 -0
- package/dist/{pg-object-storage.d.ts → objects/storage.d.ts} +12 -20
- package/dist/objects/storage.d.ts.map +1 -0
- package/dist/pg-action-run-storage.d.ts.map +1 -1
- package/dist/pg-ai-cost-storage.d.ts.map +1 -1
- package/dist/pg-ai-usage-storage.d.ts.map +1 -1
- package/dist/pg-execution-storage.d.ts +3 -2
- package/dist/pg-execution-storage.d.ts.map +1 -1
- package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
- package/dist/pg-sync-run-storage.d.ts.map +1 -1
- package/dist/pg-workflow-run-storage.d.ts.map +1 -1
- package/dist/share-grant-storage.d.ts +11 -0
- package/dist/share-grant-storage.d.ts.map +1 -0
- package/dist/share-session-storage.d.ts +11 -0
- package/dist/share-session-storage.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +2 -0
- package/dist/storage-errors.d.ts.map +1 -1
- package/dist/transactions.d.ts +9 -2
- package/dist/transactions.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/agents/rows.ts +0 -5
- package/src/agents/runs.ts +1 -6
- package/src/ai-cost-groups.ts +3 -0
- package/src/index.ts +20 -5
- package/src/migrations/035-share-grants.sql +57 -0
- package/src/migrations/036-share-sessions.sql +173 -0
- package/src/migrations/037-execution-requester-groups.sql +21 -0
- package/src/migrations.ts +9 -0
- package/src/objects/index.ts +1 -0
- package/src/{pg-object-query-compiler.ts → objects/query-compiler.ts} +185 -100
- package/src/objects/read-scope.ts +324 -0
- package/src/objects/reader.ts +604 -0
- package/src/objects/rows.ts +146 -0
- package/src/objects/selected-reader.ts +118 -0
- package/src/objects/storage.ts +305 -0
- package/src/pg-action-run-storage.ts +6 -7
- package/src/pg-ai-cost-storage.ts +11 -2
- package/src/pg-ai-usage-storage.ts +1 -2
- package/src/pg-execution-storage.ts +27 -6
- package/src/pg-pipeline-run-storage.ts +2 -5
- package/src/pg-sync-run-storage.ts +2 -5
- package/src/pg-workflow-run-storage.ts +0 -5
- package/src/share-grant-storage.ts +236 -0
- package/src/share-session-storage.ts +127 -0
- package/src/storage-errors.ts +6 -0
- package/src/transactions.ts +46 -5
- package/dist/pg-object-query-compiler.d.ts.map +0 -1
- package/dist/pg-object-storage.d.ts.map +0 -1
- package/src/pg-object-storage.ts +0 -734
|
@@ -2,10 +2,14 @@ import {
|
|
|
2
2
|
type ExecutionStorageRow,
|
|
3
3
|
executionRecordFromStorageRow,
|
|
4
4
|
executionRecordToStorageRow,
|
|
5
|
-
|
|
6
|
-
validateExecutionRecordReferences,
|
|
5
|
+
prepareExecutionRecord,
|
|
7
6
|
} from "@sixb/core/internal/execution-storage"
|
|
8
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
CreateExecutionInput,
|
|
9
|
+
ExecutionRecord,
|
|
10
|
+
ExecutionStorage,
|
|
11
|
+
ShareSessionStorage,
|
|
12
|
+
} from "@sixb/core/storage"
|
|
9
13
|
import { ExecutionStorageError } from "@sixb/core/storage"
|
|
10
14
|
import type { PgAuthStorage } from "./auth-storage"
|
|
11
15
|
import { isUniqueViolation } from "./storage-errors"
|
|
@@ -14,14 +18,15 @@ import type { PgStoreClient } from "./transactions"
|
|
|
14
18
|
export class PgExecutionStorage implements ExecutionStorage {
|
|
15
19
|
constructor(
|
|
16
20
|
private readonly sql: PgStoreClient,
|
|
17
|
-
private readonly auth: PgAuthStorage
|
|
21
|
+
private readonly auth: PgAuthStorage,
|
|
22
|
+
private readonly shareSessions: Pick<ShareSessionStorage, "getById">
|
|
18
23
|
) {}
|
|
19
24
|
|
|
20
25
|
async create(input: CreateExecutionInput): Promise<ExecutionRecord> {
|
|
21
|
-
const record =
|
|
22
|
-
await validateExecutionRecordReferences(record, {
|
|
26
|
+
const record = await prepareExecutionRecord(input, {
|
|
23
27
|
auth: this.auth,
|
|
24
28
|
getExecution: (params) => this.getById(params),
|
|
29
|
+
getShareSession: (params) => this.shareSessions.getById(params),
|
|
25
30
|
})
|
|
26
31
|
const row = executionRecordToStorageRow(record)
|
|
27
32
|
|
|
@@ -36,6 +41,7 @@ export class PgExecutionStorage implements ExecutionStorage {
|
|
|
36
41
|
source_id,
|
|
37
42
|
requested_by_user_id,
|
|
38
43
|
requested_by_service_account_id,
|
|
44
|
+
requester_group_ids,
|
|
39
45
|
correlation_id,
|
|
40
46
|
parent_execution_id,
|
|
41
47
|
authority_kind,
|
|
@@ -46,6 +52,9 @@ export class PgExecutionStorage implements ExecutionStorage {
|
|
|
46
52
|
authority_primitive_kind,
|
|
47
53
|
authority_primitive_id,
|
|
48
54
|
authority_kernel_operation,
|
|
55
|
+
authority_delegation_kind,
|
|
56
|
+
authority_delegation_id,
|
|
57
|
+
authority_delegation_session_id,
|
|
49
58
|
created_at
|
|
50
59
|
) VALUES (
|
|
51
60
|
${row.projectId},
|
|
@@ -56,6 +65,7 @@ export class PgExecutionStorage implements ExecutionStorage {
|
|
|
56
65
|
${row.sourceId},
|
|
57
66
|
${row.requestedByUserId},
|
|
58
67
|
${row.requestedByServiceAccountId},
|
|
68
|
+
${JSON.stringify(row.requesterGroupIds)}::text::jsonb,
|
|
59
69
|
${row.correlationId},
|
|
60
70
|
${row.parentExecutionId},
|
|
61
71
|
${row.authorityKind},
|
|
@@ -66,6 +76,9 @@ export class PgExecutionStorage implements ExecutionStorage {
|
|
|
66
76
|
${row.authorityPrimitiveKind},
|
|
67
77
|
${row.authorityPrimitiveId},
|
|
68
78
|
${row.authorityKernelOperation},
|
|
79
|
+
${row.authorityDelegationKind},
|
|
80
|
+
${row.authorityDelegationId},
|
|
81
|
+
${row.authorityDelegationSessionId},
|
|
69
82
|
${row.createdAt}
|
|
70
83
|
)
|
|
71
84
|
RETURNING *
|
|
@@ -110,6 +123,7 @@ function toStorageRow(row: PgExecutionRow): ExecutionStorageRow {
|
|
|
110
123
|
sourceId: row.source_id,
|
|
111
124
|
requestedByUserId: row.requested_by_user_id,
|
|
112
125
|
requestedByServiceAccountId: row.requested_by_service_account_id,
|
|
126
|
+
requesterGroupIds: row.requester_group_ids,
|
|
113
127
|
correlationId: row.correlation_id,
|
|
114
128
|
parentExecutionId: row.parent_execution_id,
|
|
115
129
|
authorityKind: row.authority_kind,
|
|
@@ -120,6 +134,9 @@ function toStorageRow(row: PgExecutionRow): ExecutionStorageRow {
|
|
|
120
134
|
authorityPrimitiveKind: row.authority_primitive_kind,
|
|
121
135
|
authorityPrimitiveId: row.authority_primitive_id,
|
|
122
136
|
authorityKernelOperation: row.authority_kernel_operation,
|
|
137
|
+
authorityDelegationKind: row.authority_delegation_kind,
|
|
138
|
+
authorityDelegationId: row.authority_delegation_id,
|
|
139
|
+
authorityDelegationSessionId: row.authority_delegation_session_id,
|
|
123
140
|
createdAt: new Date(row.created_at),
|
|
124
141
|
}
|
|
125
142
|
}
|
|
@@ -133,6 +150,7 @@ interface PgExecutionRow {
|
|
|
133
150
|
readonly source_id: string
|
|
134
151
|
readonly requested_by_user_id: string | null
|
|
135
152
|
readonly requested_by_service_account_id: string | null
|
|
153
|
+
readonly requester_group_ids: string[]
|
|
136
154
|
readonly correlation_id: string
|
|
137
155
|
readonly parent_execution_id: string | null
|
|
138
156
|
readonly authority_kind: ExecutionStorageRow["authorityKind"]
|
|
@@ -143,5 +161,8 @@ interface PgExecutionRow {
|
|
|
143
161
|
readonly authority_primitive_kind: ExecutionStorageRow["authorityPrimitiveKind"]
|
|
144
162
|
readonly authority_primitive_id: string | null
|
|
145
163
|
readonly authority_kernel_operation: ExecutionStorageRow["authorityKernelOperation"]
|
|
164
|
+
readonly authority_delegation_kind: ExecutionStorageRow["authorityDelegationKind"]
|
|
165
|
+
readonly authority_delegation_id: string | null
|
|
166
|
+
readonly authority_delegation_session_id: string | null
|
|
146
167
|
readonly created_at: Date | string
|
|
147
168
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JsonValue } from "@sixb/core"
|
|
2
2
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
3
3
|
import { assertPipelineRunExecution } from "@sixb/core/internal/pipeline-run-storage-provider"
|
|
4
|
+
import { canRequeuePipelineRunAfterEnqueueFailure } from "@sixb/core/internal/storage"
|
|
4
5
|
import type { DatasetVersionRef } from "@sixb/core/lake-storage"
|
|
5
6
|
import type {
|
|
6
7
|
ExecutionStorage,
|
|
@@ -19,11 +20,7 @@ import type {
|
|
|
19
20
|
StartPipelineRunInput,
|
|
20
21
|
StartPipelineStepRunInput,
|
|
21
22
|
} from "@sixb/core/storage"
|
|
22
|
-
import {
|
|
23
|
-
canRequeuePipelineRunAfterEnqueueFailure,
|
|
24
|
-
PIPELINE_RUN_FAILURE_CODES,
|
|
25
|
-
PipelineRunError,
|
|
26
|
-
} from "@sixb/core/storage"
|
|
23
|
+
import { PIPELINE_RUN_FAILURE_CODES, PipelineRunError } from "@sixb/core/storage"
|
|
27
24
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
28
25
|
import type { SqlParameter } from "./pg-client"
|
|
29
26
|
import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JsonValue } from "@sixb/core"
|
|
2
2
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
3
|
+
import { canRequeueSyncRunAfterEnqueueFailure } from "@sixb/core/internal/storage"
|
|
3
4
|
import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider"
|
|
4
5
|
import type {
|
|
5
6
|
ExecutionStorage,
|
|
@@ -13,11 +14,7 @@ import type {
|
|
|
13
14
|
SyncRunRecord,
|
|
14
15
|
SyncRunStorage,
|
|
15
16
|
} from "@sixb/core/storage"
|
|
16
|
-
import {
|
|
17
|
-
canRequeueSyncRunAfterEnqueueFailure,
|
|
18
|
-
SYNC_RUN_FAILURE_CODES,
|
|
19
|
-
SyncRunError,
|
|
20
|
-
} from "@sixb/core/storage"
|
|
17
|
+
import { SYNC_RUN_FAILURE_CODES, SyncRunError } from "@sixb/core/storage"
|
|
21
18
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
22
19
|
import type { SqlParameter } from "./pg-client"
|
|
23
20
|
import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { JsonValue } from "@sixb/core"
|
|
2
|
-
import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
|
|
3
2
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
4
3
|
import {
|
|
5
4
|
assertWorkflowAgentNodeRunExecution,
|
|
@@ -83,7 +82,6 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
83
82
|
input,
|
|
84
83
|
queued_at,
|
|
85
84
|
started_at,
|
|
86
|
-
requester_group_ids,
|
|
87
85
|
attempt
|
|
88
86
|
) VALUES (
|
|
89
87
|
${input.projectId},
|
|
@@ -94,7 +92,6 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
94
92
|
${serializeRecord(input.input)}::text::jsonb,
|
|
95
93
|
${queuedAt},
|
|
96
94
|
${queuedAt},
|
|
97
|
-
${JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds))}::text::jsonb,
|
|
98
95
|
${0}
|
|
99
96
|
)
|
|
100
97
|
RETURNING *
|
|
@@ -822,7 +819,6 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
|
|
|
822
819
|
startedAt: new Date(row.started_at),
|
|
823
820
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
824
821
|
error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
|
|
825
|
-
requesterGroupIds: parseJson(row.requester_group_ids),
|
|
826
822
|
attempt: Number(row.attempt),
|
|
827
823
|
...(row.execution_token && row.execution_queue_lease_expires_at
|
|
828
824
|
? {
|
|
@@ -883,7 +879,6 @@ interface WorkflowRunDatabaseRow {
|
|
|
883
879
|
started_at: Date | string
|
|
884
880
|
finished_at: Date | string | null
|
|
885
881
|
error: JsonValue | null
|
|
886
|
-
requester_group_ids: string[] | string
|
|
887
882
|
attempt: number | string
|
|
888
883
|
execution_token: string | null
|
|
889
884
|
execution_queue_lease_expires_at: Date | string | null
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeGetShareGrantByIdInput,
|
|
3
|
+
normalizeListShareGrantsInput,
|
|
4
|
+
normalizeRevokeShareGrantInput,
|
|
5
|
+
normalizeShareGrantCreate,
|
|
6
|
+
parseShareGrantRecord,
|
|
7
|
+
} from "@sixb/core/internal/share-grant-storage-provider"
|
|
8
|
+
import type {
|
|
9
|
+
CreateShareGrantInput,
|
|
10
|
+
GetShareGrantByIdInput,
|
|
11
|
+
ListShareGrantsInput,
|
|
12
|
+
ListShareGrantsResult,
|
|
13
|
+
RevokeShareGrantInput,
|
|
14
|
+
ShareGrantRecord,
|
|
15
|
+
ShareGrantStorage,
|
|
16
|
+
} from "@sixb/core/storage"
|
|
17
|
+
import { ShareGrantStorageError } from "@sixb/core/storage"
|
|
18
|
+
import type { SqlParameter } from "./pg-client"
|
|
19
|
+
import { isUniqueViolation } from "./storage-errors"
|
|
20
|
+
import type { PgStoreClient } from "./transactions"
|
|
21
|
+
|
|
22
|
+
export class PgShareGrantStorage implements ShareGrantStorage {
|
|
23
|
+
constructor(private readonly sql: PgStoreClient) {}
|
|
24
|
+
|
|
25
|
+
async create(input: CreateShareGrantInput): Promise<ShareGrantRecord> {
|
|
26
|
+
const record = normalizeShareGrantCreate(input)
|
|
27
|
+
try {
|
|
28
|
+
const [row] = await this.sql<PgShareGrantRow[]>`
|
|
29
|
+
INSERT INTO share_grants (
|
|
30
|
+
project_id,
|
|
31
|
+
id,
|
|
32
|
+
definition_id,
|
|
33
|
+
target_object_type_id,
|
|
34
|
+
target_primary_id,
|
|
35
|
+
issued_by_type,
|
|
36
|
+
issued_by_id,
|
|
37
|
+
authority_version,
|
|
38
|
+
authority_snapshot,
|
|
39
|
+
authority_digest,
|
|
40
|
+
token_hash,
|
|
41
|
+
destination_path,
|
|
42
|
+
created_at,
|
|
43
|
+
expires_at
|
|
44
|
+
) VALUES (
|
|
45
|
+
${record.projectId},
|
|
46
|
+
${record.id},
|
|
47
|
+
${record.definitionId},
|
|
48
|
+
${record.target.objectTypeId},
|
|
49
|
+
${record.target.primaryId},
|
|
50
|
+
${record.issuedBy.type},
|
|
51
|
+
${record.issuedBy.id},
|
|
52
|
+
${record.authoritySnapshot.version},
|
|
53
|
+
${JSON.stringify(record.authoritySnapshot)}::text::jsonb,
|
|
54
|
+
${record.authorityDigest},
|
|
55
|
+
${record.tokenHash},
|
|
56
|
+
${record.destinationPath},
|
|
57
|
+
${record.createdAt},
|
|
58
|
+
${record.expiresAt}
|
|
59
|
+
)
|
|
60
|
+
RETURNING *
|
|
61
|
+
`
|
|
62
|
+
if (!row) {
|
|
63
|
+
throw invalidRecord(`Share grant '${record.id}' disappeared after create.`)
|
|
64
|
+
}
|
|
65
|
+
return recordFromRow(row)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (isUniqueViolation(error)) {
|
|
68
|
+
throw new ShareGrantStorageError(
|
|
69
|
+
"duplicate",
|
|
70
|
+
`[SixbPg] Share grant '${record.id}' conflicts with an existing record.`,
|
|
71
|
+
{ cause: error }
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getById(input: GetShareGrantByIdInput): Promise<ShareGrantRecord | null> {
|
|
79
|
+
const normalized = normalizeGetShareGrantByIdInput(input)
|
|
80
|
+
const [row] = await this.sql<PgShareGrantRow[]>`
|
|
81
|
+
SELECT *
|
|
82
|
+
FROM share_grants
|
|
83
|
+
WHERE project_id = ${normalized.projectId} AND id = ${normalized.id}
|
|
84
|
+
`
|
|
85
|
+
return row ? recordFromRow(row) : null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async list(input: ListShareGrantsInput): Promise<ListShareGrantsResult> {
|
|
89
|
+
const normalized = normalizeListShareGrantsInput(input)
|
|
90
|
+
const where = ["project_id = $1"]
|
|
91
|
+
const params: SqlParameter[] = [normalized.projectId]
|
|
92
|
+
let parameter = 2
|
|
93
|
+
|
|
94
|
+
if (normalized.definitionId !== undefined) {
|
|
95
|
+
where.push(`definition_id = $${parameter++}`)
|
|
96
|
+
params.push(normalized.definitionId)
|
|
97
|
+
}
|
|
98
|
+
if (normalized.target !== undefined) {
|
|
99
|
+
where.push(`target_object_type_id = $${parameter++}`, `target_primary_id = $${parameter++}`)
|
|
100
|
+
params.push(normalized.target.objectTypeId, normalized.target.primaryId)
|
|
101
|
+
}
|
|
102
|
+
if (!normalized.includeRevoked) where.push("revoked_at IS NULL")
|
|
103
|
+
if (!normalized.includeExpired) {
|
|
104
|
+
where.push(`expires_at > $${parameter++}`)
|
|
105
|
+
params.push(normalized.now)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const predicate = where.join(" AND ")
|
|
109
|
+
const [totalRow] = await this.sql.unsafe<PgShareGrantCountRow[]>(
|
|
110
|
+
`SELECT COUNT(*)::bigint AS total FROM share_grants WHERE ${predicate}`,
|
|
111
|
+
params
|
|
112
|
+
)
|
|
113
|
+
const total = parseCount(totalRow?.total)
|
|
114
|
+
|
|
115
|
+
const listParams = [...params, normalized.limit, normalized.offset]
|
|
116
|
+
const rows = await this.sql.unsafe<PgShareGrantRow[]>(
|
|
117
|
+
`
|
|
118
|
+
SELECT *
|
|
119
|
+
FROM share_grants
|
|
120
|
+
WHERE ${predicate}
|
|
121
|
+
ORDER BY created_at DESC, id DESC
|
|
122
|
+
LIMIT $${parameter++} OFFSET $${parameter}
|
|
123
|
+
`,
|
|
124
|
+
listParams
|
|
125
|
+
)
|
|
126
|
+
const grants = rows.map(recordFromRow)
|
|
127
|
+
return {
|
|
128
|
+
grants,
|
|
129
|
+
total,
|
|
130
|
+
hasMore: normalized.offset + grants.length < total,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async revoke(input: RevokeShareGrantInput): Promise<ShareGrantRecord | null> {
|
|
135
|
+
const normalized = normalizeRevokeShareGrantInput(input)
|
|
136
|
+
const current = await this.getById({ projectId: normalized.projectId, id: normalized.id })
|
|
137
|
+
if (!current) return null
|
|
138
|
+
if (current.revokedAt) return current
|
|
139
|
+
|
|
140
|
+
const revocation = normalizeRevokeShareGrantInput(normalized, current.createdAt)
|
|
141
|
+
const [updated] = await this.sql<PgShareGrantRow[]>`
|
|
142
|
+
UPDATE share_grants
|
|
143
|
+
SET revoked_at = ${revocation.revokedAt},
|
|
144
|
+
revoked_by_type = ${revocation.revokedBy.type},
|
|
145
|
+
revoked_by_id = ${revocation.revokedBy.id}
|
|
146
|
+
WHERE project_id = ${revocation.projectId}
|
|
147
|
+
AND id = ${revocation.id}
|
|
148
|
+
AND revoked_at IS NULL
|
|
149
|
+
RETURNING *
|
|
150
|
+
`
|
|
151
|
+
if (updated) return recordFromRow(updated)
|
|
152
|
+
|
|
153
|
+
return this.getById({ projectId: revocation.projectId, id: revocation.id })
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface PgShareGrantRow {
|
|
158
|
+
readonly project_id: unknown
|
|
159
|
+
readonly id: unknown
|
|
160
|
+
readonly definition_id: unknown
|
|
161
|
+
readonly target_object_type_id: unknown
|
|
162
|
+
readonly target_primary_id: unknown
|
|
163
|
+
readonly issued_by_type: unknown
|
|
164
|
+
readonly issued_by_id: unknown
|
|
165
|
+
readonly authority_version: unknown
|
|
166
|
+
readonly authority_snapshot: unknown
|
|
167
|
+
readonly authority_digest: unknown
|
|
168
|
+
readonly token_hash: unknown
|
|
169
|
+
readonly destination_path: unknown
|
|
170
|
+
readonly created_at: unknown
|
|
171
|
+
readonly expires_at: unknown
|
|
172
|
+
readonly revoked_at: unknown
|
|
173
|
+
readonly revoked_by_type: unknown
|
|
174
|
+
readonly revoked_by_id: unknown
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface PgShareGrantCountRow {
|
|
178
|
+
readonly total: unknown
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function recordFromRow(row: PgShareGrantRow): ShareGrantRecord {
|
|
182
|
+
const authoritySnapshot = parseJsonColumn(row.authority_snapshot)
|
|
183
|
+
if (
|
|
184
|
+
!isRecord(authoritySnapshot) ||
|
|
185
|
+
!Number.isSafeInteger(row.authority_version) ||
|
|
186
|
+
authoritySnapshot.version !== row.authority_version
|
|
187
|
+
) {
|
|
188
|
+
throw invalidRecord("Share authority version does not match its snapshot.")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return parseShareGrantRecord({
|
|
192
|
+
id: row.id,
|
|
193
|
+
projectId: row.project_id,
|
|
194
|
+
definitionId: row.definition_id,
|
|
195
|
+
target: {
|
|
196
|
+
objectTypeId: row.target_object_type_id,
|
|
197
|
+
primaryId: row.target_primary_id,
|
|
198
|
+
},
|
|
199
|
+
issuedBy: { type: row.issued_by_type, id: row.issued_by_id },
|
|
200
|
+
authoritySnapshot,
|
|
201
|
+
authorityDigest: row.authority_digest,
|
|
202
|
+
tokenHash: row.token_hash,
|
|
203
|
+
destinationPath: row.destination_path,
|
|
204
|
+
createdAt: row.created_at,
|
|
205
|
+
expiresAt: row.expires_at,
|
|
206
|
+
...(row.revoked_at === null ? {} : { revokedAt: row.revoked_at }),
|
|
207
|
+
...(row.revoked_by_type === null && row.revoked_by_id === null
|
|
208
|
+
? {}
|
|
209
|
+
: { revokedBy: { type: row.revoked_by_type, id: row.revoked_by_id } }),
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseJsonColumn(value: unknown): unknown {
|
|
214
|
+
if (typeof value !== "string") return value
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(value)
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw invalidRecord("Share authority snapshot is not valid JSON.", error)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseCount(value: unknown): number {
|
|
223
|
+
const count = Number(value)
|
|
224
|
+
if (!Number.isSafeInteger(count) || count < 0) {
|
|
225
|
+
throw invalidRecord("Share grant count is not a non-negative safe integer.")
|
|
226
|
+
}
|
|
227
|
+
return count
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
231
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function invalidRecord(message: string, cause?: unknown): ShareGrantStorageError {
|
|
235
|
+
return new ShareGrantStorageError("invalid_record", `[SixbPg] ${message}`, { cause })
|
|
236
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeGetShareSessionByIdInput,
|
|
3
|
+
normalizeRenewShareSessionIfValidInput,
|
|
4
|
+
normalizeRevokeShareSessionInput,
|
|
5
|
+
normalizeShareSessionCreate,
|
|
6
|
+
parseShareSessionRecord,
|
|
7
|
+
} from "@sixb/core/internal/share-session-storage-provider"
|
|
8
|
+
import type {
|
|
9
|
+
CreateShareSessionInput,
|
|
10
|
+
GetShareSessionByIdInput,
|
|
11
|
+
RenewShareSessionIfValidInput,
|
|
12
|
+
RevokeShareSessionInput,
|
|
13
|
+
ShareSessionRecord,
|
|
14
|
+
ShareSessionStorage,
|
|
15
|
+
} from "@sixb/core/storage"
|
|
16
|
+
import { ShareSessionStorageError } from "@sixb/core/storage"
|
|
17
|
+
import { isForeignKeyViolation, isUniqueViolation } from "./storage-errors"
|
|
18
|
+
import type { PgStoreClient } from "./transactions"
|
|
19
|
+
|
|
20
|
+
export class PgShareSessionStorage implements ShareSessionStorage {
|
|
21
|
+
constructor(private readonly sql: PgStoreClient) {}
|
|
22
|
+
|
|
23
|
+
async create(input: CreateShareSessionInput): Promise<ShareSessionRecord> {
|
|
24
|
+
const record = normalizeShareSessionCreate(input)
|
|
25
|
+
try {
|
|
26
|
+
const [row] = await this.sql<PgShareSessionRow[]>`
|
|
27
|
+
INSERT INTO share_sessions (
|
|
28
|
+
project_id, id, grant_id, token_hash, created_at, expires_at, absolute_expires_at
|
|
29
|
+
) VALUES (
|
|
30
|
+
${record.projectId}, ${record.id}, ${record.grantId}, ${record.tokenHash},
|
|
31
|
+
${record.createdAt}, ${record.expiresAt}, ${record.absoluteExpiresAt}
|
|
32
|
+
)
|
|
33
|
+
RETURNING *
|
|
34
|
+
`
|
|
35
|
+
if (!row) throw invalidRecord(`Share session '${record.id}' disappeared after create.`)
|
|
36
|
+
return recordFromRow(row)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (isUniqueViolation(error)) {
|
|
39
|
+
throw new ShareSessionStorageError(
|
|
40
|
+
"duplicate",
|
|
41
|
+
`[SixbPg] Share session '${record.id}' conflicts with an existing record.`,
|
|
42
|
+
{ cause: error }
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
if (isForeignKeyViolation(error)) {
|
|
46
|
+
throw new ShareSessionStorageError(
|
|
47
|
+
"invalid_input",
|
|
48
|
+
`[SixbPg] Share grant '${record.grantId}' does not exist in project '${record.projectId}'.`,
|
|
49
|
+
{ cause: error }
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getById(input: GetShareSessionByIdInput): Promise<ShareSessionRecord | null> {
|
|
57
|
+
const normalized = normalizeGetShareSessionByIdInput(input)
|
|
58
|
+
const [row] = await this.sql<PgShareSessionRow[]>`
|
|
59
|
+
SELECT * FROM share_sessions
|
|
60
|
+
WHERE project_id = ${normalized.projectId} AND id = ${normalized.id}
|
|
61
|
+
`
|
|
62
|
+
return row ? recordFromRow(row) : null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async renewIfValid(input: RenewShareSessionIfValidInput): Promise<ShareSessionRecord | null> {
|
|
66
|
+
const normalized = normalizeRenewShareSessionIfValidInput(input)
|
|
67
|
+
const [row] = await this.sql<PgShareSessionRow[]>`
|
|
68
|
+
UPDATE share_sessions
|
|
69
|
+
SET expires_at = LEAST(absolute_expires_at, GREATEST(expires_at, ${normalized.expiresAt}))
|
|
70
|
+
WHERE project_id = ${normalized.projectId}
|
|
71
|
+
AND id = ${normalized.id}
|
|
72
|
+
AND grant_id = ${normalized.grantId}
|
|
73
|
+
AND token_hash = ${normalized.tokenHash}
|
|
74
|
+
AND revoked_at IS NULL
|
|
75
|
+
AND created_at <= ${normalized.now}
|
|
76
|
+
AND expires_at > ${normalized.now}
|
|
77
|
+
AND absolute_expires_at > ${normalized.now}
|
|
78
|
+
RETURNING *
|
|
79
|
+
`
|
|
80
|
+
return row ? recordFromRow(row) : null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async revoke(input: RevokeShareSessionInput): Promise<ShareSessionRecord | null> {
|
|
84
|
+
const normalized = normalizeRevokeShareSessionInput(input)
|
|
85
|
+
const current = await this.getById(normalized)
|
|
86
|
+
if (!current || current.revokedAt) return current
|
|
87
|
+
const revocation = normalizeRevokeShareSessionInput(normalized, current.createdAt)
|
|
88
|
+
const [updated] = await this.sql<PgShareSessionRow[]>`
|
|
89
|
+
UPDATE share_sessions
|
|
90
|
+
SET revoked_at = ${revocation.revokedAt}
|
|
91
|
+
WHERE project_id = ${revocation.projectId}
|
|
92
|
+
AND id = ${revocation.id}
|
|
93
|
+
AND revoked_at IS NULL
|
|
94
|
+
RETURNING *
|
|
95
|
+
`
|
|
96
|
+
if (updated) return recordFromRow(updated)
|
|
97
|
+
return this.getById({ projectId: revocation.projectId, id: revocation.id })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface PgShareSessionRow {
|
|
102
|
+
readonly project_id: unknown
|
|
103
|
+
readonly id: unknown
|
|
104
|
+
readonly grant_id: unknown
|
|
105
|
+
readonly token_hash: unknown
|
|
106
|
+
readonly created_at: unknown
|
|
107
|
+
readonly expires_at: unknown
|
|
108
|
+
readonly absolute_expires_at: unknown
|
|
109
|
+
readonly revoked_at: unknown
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function recordFromRow(row: PgShareSessionRow): ShareSessionRecord {
|
|
113
|
+
return parseShareSessionRecord({
|
|
114
|
+
projectId: row.project_id,
|
|
115
|
+
id: row.id,
|
|
116
|
+
grantId: row.grant_id,
|
|
117
|
+
tokenHash: row.token_hash,
|
|
118
|
+
createdAt: row.created_at,
|
|
119
|
+
expiresAt: row.expires_at,
|
|
120
|
+
absoluteExpiresAt: row.absolute_expires_at,
|
|
121
|
+
...(row.revoked_at === null ? {} : { revokedAt: row.revoked_at }),
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function invalidRecord(message: string, cause?: unknown): ShareSessionStorageError {
|
|
126
|
+
return new ShareSessionStorageError("invalid_record", `[SixbPg] ${message}`, { cause })
|
|
127
|
+
}
|
package/src/storage-errors.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
/** PostgreSQL SQLSTATE for a unique-constraint violation. */
|
|
7
7
|
const UNIQUE_VIOLATION = "23505"
|
|
8
|
+
const FOREIGN_KEY_VIOLATION = "23503"
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* PostgreSQL SQLSTATEs in class 40 (transaction rollback) that are transient and safe to retry by
|
|
@@ -46,6 +47,11 @@ export function isUniqueViolation(error: unknown): boolean {
|
|
|
46
47
|
return pgErrorCode(error) === UNIQUE_VIOLATION
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/** True when the error is a PostgreSQL foreign-key violation (SQLSTATE 23503). */
|
|
51
|
+
export function isForeignKeyViolation(error: unknown): boolean {
|
|
52
|
+
return pgErrorCode(error) === FOREIGN_KEY_VIOLATION
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
/**
|
|
50
56
|
* True when the error is a transient transaction-rollback conflict that is safe to retry by
|
|
51
57
|
* replaying the transaction — a serialization failure (SQLSTATE 40001) or a deadlock (40P01).
|
package/src/transactions.ts
CHANGED
|
@@ -4,10 +4,14 @@ import type { SQL, SQLClient } from "./pg-client"
|
|
|
4
4
|
export type PgStoreClient = SQL | SQLClient
|
|
5
5
|
|
|
6
6
|
export interface RunPgTransactionOptions {
|
|
7
|
-
/** Open the transaction at
|
|
8
|
-
readonly isolation?: "serializable"
|
|
7
|
+
/** Open the transaction at an explicit isolation; omit for the server default. */
|
|
8
|
+
readonly isolation?: "repeatableRead" | "serializable"
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
type PgTransactionIsolation = "unverifiedDefault" | "repeatableRead" | "serializable"
|
|
12
|
+
|
|
13
|
+
const activePgTransactions = new WeakMap<PgStoreClient, PgTransactionIsolation>()
|
|
14
|
+
|
|
11
15
|
export async function runPgTransaction<T>(
|
|
12
16
|
sql: PgStoreClient,
|
|
13
17
|
run: (tx: SQLClient) => Promise<T>,
|
|
@@ -24,10 +28,47 @@ export async function runPgTransaction<T>(
|
|
|
24
28
|
// The isolation level is folded into `BEGIN` (`BEGIN ISOLATION LEVEL SERIALIZABLE`) rather than
|
|
25
29
|
// a separate `SET TRANSACTION` statement: one round-trip instead of two, and the level is
|
|
26
30
|
// guaranteed to take effect before any data statement of the transaction runs.
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
const isolation = options.isolation ?? "unverifiedDefault"
|
|
32
|
+
const trackedRun = async (tx: SQLClient): Promise<T> => {
|
|
33
|
+
activePgTransactions.set(tx, isolation)
|
|
34
|
+
try {
|
|
35
|
+
return await run(tx)
|
|
36
|
+
} finally {
|
|
37
|
+
activePgTransactions.delete(tx)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isolation === "serializable") {
|
|
42
|
+
return sql.begin("isolation level serializable", trackedRun) as Promise<T>
|
|
43
|
+
}
|
|
44
|
+
if (isolation === "repeatableRead") {
|
|
45
|
+
return sql.begin("isolation level repeatable read", trackedRun) as Promise<T>
|
|
46
|
+
}
|
|
47
|
+
return sql.begin(trackedRun) as Promise<T>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Run a multi-statement read against one proven PostgreSQL snapshot.
|
|
52
|
+
*
|
|
53
|
+
* A provider-owned repeatable-read or serializable transaction may be reused. An external
|
|
54
|
+
* transaction client is rejected because postgres.js does not expose its current isolation.
|
|
55
|
+
*/
|
|
56
|
+
export async function runPgRepeatableReadTransaction<T>(
|
|
57
|
+
sql: PgStoreClient,
|
|
58
|
+
run: (tx: SQLClient) => Promise<T>
|
|
59
|
+
): Promise<T> {
|
|
60
|
+
if (canStartPgTransaction(sql)) {
|
|
61
|
+
return runPgTransaction(sql, run, { isolation: "repeatableRead" })
|
|
29
62
|
}
|
|
30
|
-
|
|
63
|
+
|
|
64
|
+
const isolation = activePgTransactions.get(sql)
|
|
65
|
+
if (isolation === "repeatableRead" || isolation === "serializable") {
|
|
66
|
+
return run(sql)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(
|
|
70
|
+
'[SixbPg] Selected object reads cannot join an unverified PostgreSQL transaction. Use storage.transaction(..., { isolation: "serializable" }) when reading through tx.objects.'
|
|
71
|
+
)
|
|
31
72
|
}
|
|
32
73
|
|
|
33
74
|
export function authLockKey(kind: string, ...parts: readonly string[]): string {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pg-object-query-compiler.d.ts","sourceRoot":"","sources":["../src/pg-object-query-compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAKhB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,EAAE,CAAA;IACpB,KAAK,EAAE,aAAa,CAAA;IACpB,YAAY,CAAC,EAAE,oBAAoB,CAAA;IACnC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAClD,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAAA;IAC7C,aAAa,CAAC,IAAI,EAAE,SAAS,oBAAoB,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAC3F;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;CAChB;AAED,UAAU,oBAAoB;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACnC;AAgBD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACrC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,KAAK,kBAAkB,GACnB;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,eAAe,CAAA;CAC7B,GACD;IACE,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,gBAAgB,GAAG,YAAY,CAAA;IACvC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAC1B,CAAA;AAmBL,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,qBAAqB,CAevB;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,GACjB,qBAAqB,CASvB;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,GACjB,qBAAqB,CAUvB;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,oBAAoB,CAkBtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pg-object-storage.d.ts","sourceRoot":"","sources":["../src/pg-object-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EAGnB,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,uBAAuB,EACvB,SAAS,EAET,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,oBAAoB,CAAA;AAU3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AA4GnD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAgB,YAAW,aAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAE/C,iBAAiB,IAAI,uBAAuB;IAItC,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8BpE,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IASpE,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAMvE,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBpE,cAAc,CAAC,MAAM,EAAE;QAC3B,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;KAClB,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAWvB,SAAS,CAAC,MAAM,EAAE;QACtB,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,SAAS,CAAC,EAAE,aAAa,CAAA;KAC1B,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAmB/B,mBAAmB,CAAC,MAAM,EAAE;QAChC,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,SAAS;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC9D,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAkBrC,cAAc,CAAC,MAAM,EAAE;QAC3B,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,SAAS;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC7E,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,CAAC,CAAC;IAqBzC,UAAU,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA6E1E,sBAAsB,CAAC,MAAM,EAAE;QACnC,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,SAAS;YAAE,YAAY,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC7D,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAkC/B,mBAAmB,CAAC,MAAM,EAAE;QAChC,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,EAAE,MAAM,CAAA;KACd,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAmBhE,IAAI,CAAC,MAAM,EAAE;QACjB,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;QACzC,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,YAAY,CAAC,EAAE,IAAI,CAAA;QACnB,aAAa,CAAC,EAAE,IAAI,CAAA;QACpB,YAAY,CAAC,EAAE,IAAI,CAAA;QACnB,aAAa,CAAC,EAAE,IAAI,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,WAAW,GAAG,WAAW,GAAG,WAAW,CAAA;QACjD,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KACvB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CA6EhF"}
|