@electric-ax/agents-server 0.4.14 → 0.4.16

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.
@@ -3,6 +3,7 @@ import {
3
3
  assertTags,
4
4
  buildTagsIndex,
5
5
  getEntitiesStreamPath,
6
+ hashString,
6
7
  normalizeTags,
7
8
  sourceRefForTags,
8
9
  } from '@electric-ax/agents-runtime'
@@ -13,7 +14,9 @@ import {
13
14
  } from '@electric-sql/client'
14
15
  import { serverLog } from './utils/log.js'
15
16
  import { electricUrlWithPath } from './utils/electric-url.js'
17
+ import { buildReadableEntitiesWhere } from './utils/server-utils.js'
16
18
  import { DEFAULT_TENANT_ID } from './tenant.js'
19
+ import { isBuiltInSystemPrincipalUrl } from './principal.js'
17
20
  import type { EntityBridgeRow, PostgresRegistry } from './entity-registry.js'
18
21
  import type { StreamClient } from './stream-client.js'
19
22
  import type {
@@ -30,7 +33,11 @@ import type {
30
33
  export interface EntityBridgeCoordinator {
31
34
  start(): Promise<void>
32
35
  stop(): Promise<void>
33
- register(tagsInput: unknown): Promise<{
36
+ register(
37
+ tagsInput: unknown,
38
+ principalUrl: string,
39
+ principalKind: string
40
+ ): Promise<{
34
41
  sourceRef: string
35
42
  streamUrl: string
36
43
  }>
@@ -115,19 +122,44 @@ function sqlStringLiteral(value: string): string {
115
122
 
116
123
  function buildTenantTagsWhereClause(
117
124
  tenantId: string,
118
- tags: EntityTags
125
+ tags: EntityTags,
126
+ principalUrl?: string,
127
+ principalKind?: string,
128
+ permissionBypass?: boolean
119
129
  ): string {
120
- return `tenant_id = ${sqlStringLiteral(tenantId)} AND (${buildTagsWhereClause(tags)})`
130
+ const readableWhere =
131
+ principalUrl && principalKind
132
+ ? buildReadableEntitiesWhere({
133
+ tenantId,
134
+ principalUrl,
135
+ principalKind,
136
+ permissionBypass,
137
+ })
138
+ : `tenant_id = ${sqlStringLiteral(tenantId)} AND FALSE`
139
+ return `${readableWhere} AND (${buildTagsWhereClause(tags)})`
121
140
  }
122
141
 
123
142
  function shapeEntityKey(message: ChangeMessage<EntityShapeRow>): string {
124
143
  return message.value.url
125
144
  }
126
145
 
146
+ function principalScopedSourceRef(
147
+ tagSourceRef: string,
148
+ principalUrl: string,
149
+ principalKind: string
150
+ ): string {
151
+ return `${tagSourceRef}-${hashString(
152
+ JSON.stringify({ principalKind, principalUrl })
153
+ )}`
154
+ }
155
+
127
156
  class EntityBridge {
128
157
  readonly sourceRef: string
129
158
  readonly tags: EntityTags
130
159
  readonly streamUrl: string
160
+ private readonly principalUrl?: string
161
+ private readonly principalKind?: string
162
+ private readonly permissionBypass: boolean
131
163
 
132
164
  private currentMembers = new Map<string, EntityMembershipRow>()
133
165
  private producer: IdempotentProducer | null = null
@@ -152,6 +184,9 @@ class EntityBridge {
152
184
  this.sourceRef = row.sourceRef
153
185
  this.tags = normalizeTags(row.tags)
154
186
  this.streamUrl = row.streamUrl
187
+ this.principalUrl = row.principalUrl
188
+ this.principalKind = row.principalKind
189
+ this.permissionBypass = isBuiltInSystemPrincipalUrl(row.principalUrl)
155
190
  this.initialShapeHandle = row.shapeHandle
156
191
  this.initialShapeOffset = row.shapeOffset
157
192
  }
@@ -316,7 +351,13 @@ class EntityBridge {
316
351
  url: electricUrlWithPath(this.electricUrl, `/v1/shape`).toString(),
317
352
  params: {
318
353
  table: `entities`,
319
- where: buildTenantTagsWhereClause(this.tenantId, this.tags),
354
+ where: buildTenantTagsWhereClause(
355
+ this.tenantId,
356
+ this.tags,
357
+ this.principalUrl,
358
+ this.principalKind,
359
+ this.permissionBypass
360
+ ),
320
361
  ...(this.electricSecret ? { secret: this.electricSecret } : {}),
321
362
  columns: [...ENTITY_SHAPE_COLUMNS],
322
363
  replica: `full`,
@@ -564,7 +605,11 @@ export class EntityBridgeManager implements EntityBridgeCoordinator {
564
605
  )
565
606
  }
566
607
 
567
- async register(tagsInput: unknown): Promise<{
608
+ async register(
609
+ tagsInput: unknown,
610
+ principalUrl: string,
611
+ principalKind: string
612
+ ): Promise<{
568
613
  sourceRef: string
569
614
  streamUrl: string
570
615
  }> {
@@ -573,13 +618,19 @@ export class EntityBridgeManager implements EntityBridgeCoordinator {
573
618
  }
574
619
 
575
620
  const tags = normalizeTags(assertTags(tagsInput))
576
- const sourceRef = sourceRefForTags(tags)
621
+ const sourceRef = principalScopedSourceRef(
622
+ sourceRefForTags(tags),
623
+ principalUrl,
624
+ principalKind
625
+ )
577
626
  const streamUrl = getEntitiesStreamPath(sourceRef)
578
627
 
579
628
  const row = await this.registry.upsertEntityBridge({
580
629
  sourceRef,
581
630
  tags,
582
631
  streamUrl,
632
+ principalUrl,
633
+ principalKind,
583
634
  })
584
635
  await this.registry.touchEntityBridge(sourceRef)
585
636
  await this.ensureBridge(row)