@electric-ax/agents-server 0.4.15 → 0.4.17

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.
@@ -96,6 +96,8 @@ export function buildElectricProxyTarget(options: {
96
96
  electricSecret?: string
97
97
  tenantId: string
98
98
  principalUrl?: string
99
+ principalKind?: string
100
+ permissionBypass?: boolean
99
101
  }): URL {
100
102
  const targetPath = options.incomingUrl.pathname.replace(
101
103
  `/_electric/electric`,
@@ -119,15 +121,31 @@ export function buildElectricProxyTarget(options: {
119
121
  if (table === `entities`) {
120
122
  target.searchParams.set(
121
123
  `columns`,
122
- `"tenant_id","url","type","status","dispatch_policy","tags","spawn_args","sandbox","parent","type_revision","inbox_schemas","state_schemas","created_at","updated_at"`
124
+ `"tenant_id","url","type","status","dispatch_policy","tags","spawn_args","sandbox","parent","created_by","type_revision","inbox_schemas","state_schemas","created_at","updated_at"`
125
+ )
126
+ applyShapeWhere(
127
+ target,
128
+ buildReadableEntitiesWhere({
129
+ tenantId: options.tenantId,
130
+ principalUrl: options.principalUrl ?? ``,
131
+ principalKind: options.principalKind ?? ``,
132
+ permissionBypass: options.permissionBypass,
133
+ })
123
134
  )
124
- applyTenantShapeWhere(target, options.tenantId)
125
135
  } else if (table === `entity_types`) {
126
136
  target.searchParams.set(
127
137
  `columns`,
128
- `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`
138
+ `"tenant_id","name","description","creation_schema","inbox_schemas","state_schemas","slash_commands","serve_endpoint","default_dispatch_policy","revision","created_at","updated_at"`
139
+ )
140
+ applyShapeWhere(
141
+ target,
142
+ buildSpawnableEntityTypesWhere({
143
+ tenantId: options.tenantId,
144
+ principalUrl: options.principalUrl ?? ``,
145
+ principalKind: options.principalKind ?? ``,
146
+ permissionBypass: options.permissionBypass,
147
+ })
129
148
  )
130
- applyTenantShapeWhere(target, options.tenantId)
131
149
  } else if (table === `runners`) {
132
150
  target.searchParams.set(
133
151
  `columns`,
@@ -136,6 +154,26 @@ export function buildElectricProxyTarget(options: {
136
154
  applyTenantShapeWhere(target, options.tenantId, [
137
155
  `owner_principal = ${sqlStringLiteral(options.principalUrl ?? ``)}`,
138
156
  ])
157
+ } else if (table === `users`) {
158
+ target.searchParams.set(
159
+ `columns`,
160
+ `"tenant_id","id","display_name","email","avatar_url","created_at","updated_at"`
161
+ )
162
+ applyTenantShapeWhere(target, options.tenantId)
163
+ } else if (table === `entity_effective_permissions`) {
164
+ target.searchParams.set(
165
+ `columns`,
166
+ `"tenant_id","id","entity_url","source_entity_url","source_grant_id","permission","subject_kind","subject_value","expires_at","created_at"`
167
+ )
168
+ applyShapeWhere(
169
+ target,
170
+ buildCurrentPrincipalEntityEffectivePermissionsWhere({
171
+ tenantId: options.tenantId,
172
+ principalUrl: options.principalUrl ?? ``,
173
+ principalKind: options.principalKind ?? ``,
174
+ permissionBypass: options.permissionBypass,
175
+ })
176
+ )
139
177
  } else if (table === `runner_runtime_diagnostics`) {
140
178
  target.searchParams.set(
141
179
  `columns`,
@@ -149,24 +187,154 @@ export function buildElectricProxyTarget(options: {
149
187
  `columns`,
150
188
  `"tenant_id","entity_url","pending_source_streams","pending_reason","pending_since","outstanding_wake_id","outstanding_wake_target","outstanding_wake_created_at","active_consumer_id","active_runner_id","active_epoch","active_claimed_at","active_lease_expires_at","last_wake_id","last_claimed_at","last_released_at","last_completed_at","last_error","updated_at"`
151
189
  )
152
- applyTenantShapeWhere(target, options.tenantId)
190
+ applyShapeWhere(
191
+ target,
192
+ buildReadableEntityUrlWhere({
193
+ tenantId: options.tenantId,
194
+ principalUrl: options.principalUrl ?? ``,
195
+ principalKind: options.principalKind ?? ``,
196
+ permissionBypass: options.permissionBypass,
197
+ })
198
+ )
153
199
  } else if (table === `wake_notifications`) {
154
200
  target.searchParams.set(
155
201
  `columns`,
156
202
  `"tenant_id","wake_id","entity_url","target_type","target_runner_id","target_webhook_url","target_worker_pool_id","runner_wake_stream","runner_wake_stream_offset","notification_public","delivery_status","claim_status","created_at","delivered_at","claimed_at","resolved_at"`
157
203
  )
158
- applyTenantShapeWhere(target, options.tenantId)
204
+ applyShapeWhere(
205
+ target,
206
+ buildReadableEntityUrlWhere({
207
+ tenantId: options.tenantId,
208
+ principalUrl: options.principalUrl ?? ``,
209
+ principalKind: options.principalKind ?? ``,
210
+ permissionBypass: options.permissionBypass,
211
+ })
212
+ )
159
213
  } else if (table === `consumer_claims`) {
160
214
  target.searchParams.set(
161
215
  `columns`,
162
216
  `"tenant_id","consumer_id","epoch","wake_id","entity_url","stream_path","runner_id","status","claimed_at","last_heartbeat_at","lease_expires_at","released_at","acked_streams","updated_at"`
163
217
  )
164
- applyTenantShapeWhere(target, options.tenantId)
218
+ applyShapeWhere(
219
+ target,
220
+ buildReadableEntityUrlWhere({
221
+ tenantId: options.tenantId,
222
+ principalUrl: options.principalUrl ?? ``,
223
+ principalKind: options.principalKind ?? ``,
224
+ permissionBypass: options.permissionBypass,
225
+ })
226
+ )
165
227
  }
166
228
 
167
229
  return target
168
230
  }
169
231
 
232
+ export function buildReadableEntitiesWhere(options: {
233
+ tenantId: string
234
+ principalUrl: string
235
+ principalKind: string
236
+ permissionBypass?: boolean
237
+ }): string {
238
+ const tenant = sqlStringLiteral(options.tenantId)
239
+ if (options.permissionBypass) {
240
+ return `tenant_id = ${tenant}`
241
+ }
242
+ const principalUrl = sqlStringLiteral(options.principalUrl)
243
+ const principalKind = sqlStringLiteral(options.principalKind)
244
+ return [
245
+ `tenant_id = ${tenant}`,
246
+ `AND (`,
247
+ ` created_by = ${principalUrl}`,
248
+ ` OR url IN (`,
249
+ ` SELECT entity_url`,
250
+ ` FROM entity_effective_permissions`,
251
+ ` WHERE tenant_id = ${tenant}`,
252
+ ` AND permission IN ('read', 'manage')`,
253
+ ` AND (`,
254
+ ` (subject_kind = 'principal' AND subject_value = ${principalUrl})`,
255
+ ` OR (subject_kind = 'principal_kind' AND subject_value = ${principalKind})`,
256
+ ` )`,
257
+ ` )`,
258
+ `)`,
259
+ ].join(`\n`)
260
+ }
261
+
262
+ export function buildReadableEntityUrlWhere(options: {
263
+ tenantId: string
264
+ principalUrl: string
265
+ principalKind: string
266
+ permissionBypass?: boolean
267
+ }): string {
268
+ const tenant = sqlStringLiteral(options.tenantId)
269
+ if (options.permissionBypass) {
270
+ return `tenant_id = ${tenant}`
271
+ }
272
+ return [
273
+ `tenant_id = ${tenant}`,
274
+ `AND entity_url IN (`,
275
+ ` SELECT url`,
276
+ ` FROM entities`,
277
+ ` WHERE ${indentWhere(
278
+ buildReadableEntitiesWhere(options),
279
+ ` `
280
+ ).trimStart()}`,
281
+ `)`,
282
+ ].join(`\n`)
283
+ }
284
+
285
+ export function buildCurrentPrincipalEntityEffectivePermissionsWhere(options: {
286
+ tenantId: string
287
+ principalUrl: string
288
+ principalKind: string
289
+ permissionBypass?: boolean
290
+ }): string {
291
+ const tenant = sqlStringLiteral(options.tenantId)
292
+ if (options.permissionBypass) {
293
+ return `tenant_id = ${tenant}`
294
+ }
295
+ const principalUrl = sqlStringLiteral(options.principalUrl)
296
+ const principalKind = sqlStringLiteral(options.principalKind)
297
+ return [
298
+ `tenant_id = ${tenant}`,
299
+ `AND (`,
300
+ ` (subject_kind = 'principal' AND subject_value = ${principalUrl})`,
301
+ ` OR (subject_kind = 'principal_kind' AND subject_value = ${principalKind})`,
302
+ `)`,
303
+ `AND entity_url IN (`,
304
+ ` SELECT url`,
305
+ ` FROM entities`,
306
+ ` WHERE ${buildReadableEntitiesWhere(options)}`,
307
+ `)`,
308
+ ].join(`\n`)
309
+ }
310
+
311
+ export function buildSpawnableEntityTypesWhere(options: {
312
+ tenantId: string
313
+ principalUrl: string
314
+ principalKind: string
315
+ permissionBypass?: boolean
316
+ }): string {
317
+ const tenant = sqlStringLiteral(options.tenantId)
318
+ if (options.permissionBypass) {
319
+ return `tenant_id = ${tenant}`
320
+ }
321
+ const principalUrl = sqlStringLiteral(options.principalUrl)
322
+ const principalKind = sqlStringLiteral(options.principalKind)
323
+ return [
324
+ `tenant_id = ${tenant}`,
325
+ `AND name IN (`,
326
+ ` SELECT entity_type`,
327
+ ` FROM entity_type_permission_grants`,
328
+ ` WHERE tenant_id = ${tenant}`,
329
+ ` AND permission IN ('spawn', 'manage')`,
330
+ ` AND (`,
331
+ ` (subject_kind = 'principal' AND subject_value = ${principalUrl})`,
332
+ ` OR (subject_kind = 'principal_kind' AND subject_value = ${principalKind})`,
333
+ ` )`,
334
+ `)`,
335
+ ].join(`\n`)
336
+ }
337
+
170
338
  export async function forwardFetchRequest(options: {
171
339
  request: {
172
340
  method: string
@@ -248,17 +416,29 @@ function applyTenantShapeWhere(
248
416
  tenantId: string,
249
417
  extraConditions: Array<string> = []
250
418
  ): void {
251
- const tenantWhere = [
252
- `tenant_id = ${sqlStringLiteral(tenantId)}`,
253
- ...extraConditions,
254
- ].join(` AND `)
419
+ applyShapeWhere(
420
+ target,
421
+ [`tenant_id = ${sqlStringLiteral(tenantId)}`, ...extraConditions].join(
422
+ ` AND `
423
+ )
424
+ )
425
+ }
426
+
427
+ function applyShapeWhere(target: URL, enforcedWhere: string): void {
255
428
  const existingWhere = target.searchParams.get(`where`)
256
429
  target.searchParams.set(
257
430
  `where`,
258
- existingWhere ? `${tenantWhere} AND (${existingWhere})` : tenantWhere
431
+ existingWhere ? `${enforcedWhere} AND (${existingWhere})` : enforcedWhere
259
432
  )
260
433
  }
261
434
 
262
435
  function sqlStringLiteral(value: string): string {
263
436
  return `'${value.replace(/'/g, `''`)}'`
264
437
  }
438
+
439
+ function indentWhere(where: string, prefix: string): string {
440
+ return where
441
+ .split(`\n`)
442
+ .map((line) => `${prefix}${line}`)
443
+ .join(`\n`)
444
+ }
@@ -42,6 +42,8 @@ export interface WakeEvalResult {
42
42
  kind: `insert` | `update` | `delete`
43
43
  key: string
44
44
  from?: string
45
+ from_principal?: string
46
+ from_agent?: string
45
47
  payload?: unknown
46
48
  timestamp?: string
47
49
  message_type?: string
@@ -944,6 +946,12 @@ export class WakeRegistry {
944
946
  if (eventType === `inbox`) {
945
947
  const value = event.value as Record<string, unknown> | undefined
946
948
  if (typeof value?.from === `string`) change.from = value.from
949
+ if (typeof value?.from_principal === `string`) {
950
+ change.from_principal = value.from_principal
951
+ }
952
+ if (typeof value?.from_agent === `string`) {
953
+ change.from_agent = value.from_agent
954
+ }
947
955
  if (`payload` in (value ?? {})) change.payload = value?.payload
948
956
  if (typeof value?.timestamp === `string`)
949
957
  change.timestamp = value.timestamp