@electric-ax/agents-server 0.4.19 → 0.5.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/entrypoint.js +692 -45
- package/dist/index.cjs +678 -41
- package/dist/index.d.cts +2519 -2216
- package/dist/index.d.ts +2518 -2217
- package/dist/index.js +679 -42
- package/drizzle/0015_pg_sync_bridges.sql +14 -0
- package/drizzle/0016_entity_type_externally_writable_collections.sql +1 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +6 -6
- package/src/db/schema.ts +32 -0
- package/src/electric-agents-types.ts +23 -0
- package/src/entity-manager.ts +160 -29
- package/src/entity-registry.ts +158 -3
- package/src/manifest-side-effects.ts +10 -0
- package/src/pg-sync-bridge-manager.ts +552 -0
- package/src/routing/context.ts +2 -0
- package/src/routing/entities-router.ts +89 -18
- package/src/routing/entity-types-router.ts +56 -0
- package/src/routing/global-router.ts +3 -0
- package/src/routing/hooks.ts +7 -0
- package/src/routing/internal-router.ts +2 -0
- package/src/routing/pg-sync-router.ts +113 -0
- package/src/runtime.ts +20 -1
- package/src/scheduler.ts +26 -0
- package/src/server.ts +4 -0
- package/src/standalone-runtime.ts +16 -0
- package/src/utils/server-utils.ts +97 -1
- package/src/wake-registry.ts +27 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE "pg_sync_bridges" (
|
|
2
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
3
|
+
"source_ref" text NOT NULL,
|
|
4
|
+
"options" jsonb NOT NULL,
|
|
5
|
+
"stream_url" text NOT NULL,
|
|
6
|
+
"shape_handle" text,
|
|
7
|
+
"shape_offset" text,
|
|
8
|
+
"initial_snapshot_complete" boolean DEFAULT false NOT NULL,
|
|
9
|
+
"last_touched_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
10
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
11
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
12
|
+
CONSTRAINT "pg_sync_bridges_tenant_id_source_ref_pk" PRIMARY KEY("tenant_id","source_ref"),
|
|
13
|
+
CONSTRAINT "uq_pg_sync_bridges_stream_url" UNIQUE("tenant_id","stream_url")
|
|
14
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "entity_types" ADD COLUMN "externally_writable_collections" jsonb;
|
|
@@ -106,6 +106,20 @@
|
|
|
106
106
|
"when": 1780600000000,
|
|
107
107
|
"tag": "0014_entity_type_slash_commands",
|
|
108
108
|
"breakpoints": true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"idx": 15,
|
|
112
|
+
"version": "7",
|
|
113
|
+
"when": 1779728400000,
|
|
114
|
+
"tag": "0015_pg_sync_bridges",
|
|
115
|
+
"breakpoints": true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"idx": 16,
|
|
119
|
+
"version": "7",
|
|
120
|
+
"when": 1781200000000,
|
|
121
|
+
"tag": "0016_entity_type_externally_writable_collections",
|
|
122
|
+
"breakpoints": true
|
|
109
123
|
}
|
|
110
124
|
]
|
|
111
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Electric Agents entity runtime server",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@durable-streams/client": "^0.2.6",
|
|
40
40
|
"@durable-streams/server": "^0.3.7",
|
|
41
41
|
"@durable-streams/state": "^0.3.1",
|
|
42
|
-
"@electric-sql/client": "^1.5.
|
|
42
|
+
"@electric-sql/client": "^1.5.21",
|
|
43
43
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1",
|
|
45
45
|
"@sinclair/typebox": "^0.34.48",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"pino-pretty": "^13.0.0",
|
|
55
55
|
"postgres": "^3.4.0",
|
|
56
56
|
"undici": "^7.24.7",
|
|
57
|
-
"@electric-ax/agents-runtime": "0.
|
|
57
|
+
"@electric-ax/agents-runtime": "0.4.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^22.19.15",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"tsx": "^4.19.0",
|
|
66
66
|
"typescript": "^5.0.0",
|
|
67
67
|
"vitest": "^4.1.0",
|
|
68
|
-
"@electric-ax/agents": "0.4.
|
|
69
|
-
"@electric-ax/agents-server-conformance-tests": "0.1.
|
|
70
|
-
"@electric-ax/agents-server-ui": "0.
|
|
68
|
+
"@electric-ax/agents": "0.4.18",
|
|
69
|
+
"@electric-ax/agents-server-conformance-tests": "0.1.12",
|
|
70
|
+
"@electric-ax/agents-server-ui": "0.5.0"
|
|
71
71
|
},
|
|
72
72
|
"files": [
|
|
73
73
|
"dist",
|
package/src/db/schema.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
timestamp,
|
|
15
15
|
unique,
|
|
16
16
|
} from 'drizzle-orm/pg-core'
|
|
17
|
+
import type { ExternallyWritableCollectionConfig } from '../electric-agents-types.js'
|
|
17
18
|
|
|
18
19
|
export const entityTypes = pgTable(
|
|
19
20
|
`entity_types`,
|
|
@@ -24,6 +25,9 @@ export const entityTypes = pgTable(
|
|
|
24
25
|
creationSchema: jsonb(`creation_schema`),
|
|
25
26
|
inboxSchemas: jsonb(`inbox_schemas`),
|
|
26
27
|
stateSchemas: jsonb(`state_schemas`),
|
|
28
|
+
externallyWritableCollections: jsonb(
|
|
29
|
+
`externally_writable_collections`
|
|
30
|
+
).$type<Record<string, ExternallyWritableCollectionConfig>>(),
|
|
27
31
|
slashCommands: jsonb(`slash_commands`),
|
|
28
32
|
serveEndpoint: text(`serve_endpoint`),
|
|
29
33
|
defaultDispatchPolicy: jsonb(`default_dispatch_policy`),
|
|
@@ -621,6 +625,34 @@ export const scheduledTasks = pgTable(
|
|
|
621
625
|
]
|
|
622
626
|
)
|
|
623
627
|
|
|
628
|
+
export const pgSyncBridges = pgTable(
|
|
629
|
+
`pg_sync_bridges`,
|
|
630
|
+
{
|
|
631
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
632
|
+
sourceRef: text(`source_ref`).notNull(),
|
|
633
|
+
options: jsonb(`options`).notNull(),
|
|
634
|
+
streamUrl: text(`stream_url`).notNull(),
|
|
635
|
+
shapeHandle: text(`shape_handle`),
|
|
636
|
+
shapeOffset: text(`shape_offset`),
|
|
637
|
+
initialSnapshotComplete: boolean(`initial_snapshot_complete`)
|
|
638
|
+
.notNull()
|
|
639
|
+
.default(false),
|
|
640
|
+
lastTouchedAt: timestamp(`last_touched_at`, { withTimezone: true })
|
|
641
|
+
.notNull()
|
|
642
|
+
.defaultNow(),
|
|
643
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
644
|
+
.notNull()
|
|
645
|
+
.defaultNow(),
|
|
646
|
+
updatedAt: timestamp(`updated_at`, { withTimezone: true })
|
|
647
|
+
.notNull()
|
|
648
|
+
.defaultNow(),
|
|
649
|
+
},
|
|
650
|
+
(table) => [
|
|
651
|
+
primaryKey({ columns: [table.tenantId, table.sourceRef] }),
|
|
652
|
+
unique(`uq_pg_sync_bridges_stream_url`).on(table.tenantId, table.streamUrl),
|
|
653
|
+
]
|
|
654
|
+
)
|
|
655
|
+
|
|
624
656
|
export const entityBridges = pgTable(
|
|
625
657
|
`entity_bridges`,
|
|
626
658
|
{
|
|
@@ -494,12 +494,31 @@ export function toPublicEntity(
|
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
/** Per-collection config making an entity-state collection externally writable via the router. */
|
|
498
|
+
export interface ExternallyWritableCollectionConfig {
|
|
499
|
+
/** Durable-stream event type for this collection, e.g. `state:comments`. */
|
|
500
|
+
type: string
|
|
501
|
+
/** Well-known contract this collection implements, e.g. `comments/v1`. */
|
|
502
|
+
contract?: string
|
|
503
|
+
/**
|
|
504
|
+
* Allowlist of external write operations. When set, the router rejects any
|
|
505
|
+
* operation not listed (403). When unset, only `insert` is permitted — the
|
|
506
|
+
* safe default, since open update/delete lets a client overwrite or remove
|
|
507
|
+
* another principal's rows by key.
|
|
508
|
+
*/
|
|
509
|
+
operations?: Array<`insert` | `update` | `delete`>
|
|
510
|
+
}
|
|
511
|
+
|
|
497
512
|
export interface ElectricAgentsEntityType {
|
|
498
513
|
name: string
|
|
499
514
|
description: string
|
|
500
515
|
creation_schema?: Record<string, unknown>
|
|
501
516
|
inbox_schemas?: Record<string, Record<string, unknown>>
|
|
502
517
|
state_schemas?: Record<string, Record<string, unknown>>
|
|
518
|
+
externally_writable_collections?: Record<
|
|
519
|
+
string,
|
|
520
|
+
ExternallyWritableCollectionConfig
|
|
521
|
+
>
|
|
503
522
|
slash_commands?: Array<SlashCommandDefinition>
|
|
504
523
|
serve_endpoint?: string
|
|
505
524
|
default_dispatch_policy?: DispatchPolicy
|
|
@@ -514,6 +533,10 @@ export interface RegisterEntityTypeRequest {
|
|
|
514
533
|
creation_schema?: Record<string, unknown>
|
|
515
534
|
inbox_schemas?: Record<string, Record<string, unknown>>
|
|
516
535
|
state_schemas?: Record<string, Record<string, unknown>>
|
|
536
|
+
externally_writable_collections?: Record<
|
|
537
|
+
string,
|
|
538
|
+
ExternallyWritableCollectionConfig
|
|
539
|
+
>
|
|
517
540
|
slash_commands?: Array<SlashCommandDefinition>
|
|
518
541
|
serve_endpoint?: string
|
|
519
542
|
default_dispatch_policy?: DispatchPolicy
|
package/src/entity-manager.ts
CHANGED
|
@@ -71,6 +71,7 @@ import type {
|
|
|
71
71
|
SignalRequest,
|
|
72
72
|
SignalResponse,
|
|
73
73
|
TypedSpawnRequest,
|
|
74
|
+
ExternallyWritableCollectionConfig,
|
|
74
75
|
} from './electric-agents-types.js'
|
|
75
76
|
import type { EntityBridgeCoordinator } from './entity-bridge-manager.js'
|
|
76
77
|
import type { Principal } from './principal.js'
|
|
@@ -131,6 +132,23 @@ export interface CreateAttachmentRequest {
|
|
|
131
132
|
meta?: Record<string, unknown>
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
export interface WriteCollectionPrincipal {
|
|
136
|
+
url: string
|
|
137
|
+
kind: string
|
|
138
|
+
id: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface WriteCollectionRequest {
|
|
142
|
+
operation: `insert` | `update` | `delete`
|
|
143
|
+
key?: string
|
|
144
|
+
value?: Record<string, unknown>
|
|
145
|
+
principal: WriteCollectionPrincipal
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface WriteCollectionResult {
|
|
149
|
+
key: string
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
export interface ReadAttachmentResult {
|
|
135
153
|
attachment: ManifestAttachmentEntry
|
|
136
154
|
bytes: Uint8Array
|
|
@@ -336,6 +354,14 @@ function cloneRecord<T extends Record<string, unknown>>(value: T): T {
|
|
|
336
354
|
return JSON.parse(JSON.stringify(value)) as T
|
|
337
355
|
}
|
|
338
356
|
|
|
357
|
+
function withOptionalTxid<T>(
|
|
358
|
+
entity: T,
|
|
359
|
+
txid: number | undefined
|
|
360
|
+
): T & { txid?: number } {
|
|
361
|
+
if (txid === undefined) return entity as T & { txid?: number }
|
|
362
|
+
return { ...entity, txid }
|
|
363
|
+
}
|
|
364
|
+
|
|
339
365
|
/**
|
|
340
366
|
* Orchestrates the Electric Agents entity lifecycle: register types, spawn, send, kill.
|
|
341
367
|
*
|
|
@@ -480,6 +506,7 @@ export class EntityManager {
|
|
|
480
506
|
creation_schema: req.creation_schema,
|
|
481
507
|
inbox_schemas: req.inbox_schemas,
|
|
482
508
|
state_schemas: req.state_schemas,
|
|
509
|
+
externally_writable_collections: req.externally_writable_collections,
|
|
483
510
|
slash_commands: req.slash_commands,
|
|
484
511
|
serve_endpoint: req.serve_endpoint,
|
|
485
512
|
default_dispatch_policy: defaultDispatchPolicy,
|
|
@@ -2336,7 +2363,7 @@ export class EntityManager {
|
|
|
2336
2363
|
entityUrl: string,
|
|
2337
2364
|
req: SendRequest,
|
|
2338
2365
|
opts?: { producerId?: string }
|
|
2339
|
-
): Promise<
|
|
2366
|
+
): Promise<{ txid: string }> {
|
|
2340
2367
|
const entity = await this.validateSendRequest(entityUrl, req)
|
|
2341
2368
|
if (
|
|
2342
2369
|
this.isForkWorkLockedEntity(entityUrl) &&
|
|
@@ -2384,9 +2411,11 @@ export class EntityManager {
|
|
|
2384
2411
|
await this.entityBridgeManager?.onEntityChanged(entityUrl)
|
|
2385
2412
|
}
|
|
2386
2413
|
|
|
2414
|
+
const txid = crypto.randomUUID()
|
|
2387
2415
|
const envelope = entityStateSchema.inbox.insert({
|
|
2388
2416
|
key,
|
|
2389
2417
|
value,
|
|
2418
|
+
headers: { txid },
|
|
2390
2419
|
} as any)
|
|
2391
2420
|
|
|
2392
2421
|
const encoded = this.encodeChangeEvent(envelope as Record<string, unknown>)
|
|
@@ -2395,7 +2424,7 @@ export class EntityManager {
|
|
|
2395
2424
|
await this.streamClient.appendIdempotent(entity.streams.main, encoded, {
|
|
2396
2425
|
producerId: opts.producerId,
|
|
2397
2426
|
})
|
|
2398
|
-
return
|
|
2427
|
+
return { txid }
|
|
2399
2428
|
}
|
|
2400
2429
|
|
|
2401
2430
|
await this.streamClient.append(entity.streams.main, encoded)
|
|
@@ -2407,9 +2436,11 @@ export class EntityManager {
|
|
|
2407
2436
|
type: `identity`,
|
|
2408
2437
|
key: `self`,
|
|
2409
2438
|
value: identity,
|
|
2439
|
+
headers: { txid },
|
|
2410
2440
|
})
|
|
2411
2441
|
)
|
|
2412
2442
|
}
|
|
2443
|
+
return { txid }
|
|
2413
2444
|
} catch (err) {
|
|
2414
2445
|
if (this.isClosedStreamError(err)) {
|
|
2415
2446
|
throw new ElectricAgentsError(
|
|
@@ -2422,6 +2453,106 @@ export class EntityManager {
|
|
|
2422
2453
|
}
|
|
2423
2454
|
}
|
|
2424
2455
|
|
|
2456
|
+
async writeCollection(
|
|
2457
|
+
entityUrl: string,
|
|
2458
|
+
collection: string,
|
|
2459
|
+
req: WriteCollectionRequest
|
|
2460
|
+
): Promise<WriteCollectionResult> {
|
|
2461
|
+
const entity = await this.registry.getEntity(entityUrl)
|
|
2462
|
+
if (!entity) {
|
|
2463
|
+
throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404)
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
const { externallyWritableCollections } =
|
|
2467
|
+
await this.getEffectiveSchemas(entity)
|
|
2468
|
+
const config = externallyWritableCollections?.[collection]
|
|
2469
|
+
if (!config) {
|
|
2470
|
+
throw new ElectricAgentsError(
|
|
2471
|
+
ErrCodeUnauthorized,
|
|
2472
|
+
`Collection "${collection}" is not writable`,
|
|
2473
|
+
403
|
|
2474
|
+
)
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
const allowedOperations = config.operations ?? [`insert`]
|
|
2478
|
+
if (!allowedOperations.includes(req.operation)) {
|
|
2479
|
+
throw new ElectricAgentsError(
|
|
2480
|
+
ErrCodeUnauthorized,
|
|
2481
|
+
`Operation "${req.operation}" is not allowed on collection "${collection}"`,
|
|
2482
|
+
403
|
|
2483
|
+
)
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
if (rejectsNormalWrites(entity.status)) {
|
|
2487
|
+
throw new ElectricAgentsError(
|
|
2488
|
+
ErrCodeNotRunning,
|
|
2489
|
+
`Entity is not accepting writes`,
|
|
2490
|
+
409
|
|
2491
|
+
)
|
|
2492
|
+
}
|
|
2493
|
+
if (this.isForkWorkLockedEntity(entityUrl)) {
|
|
2494
|
+
this.assertEntityNotForkWorkLocked(entityUrl)
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
if (
|
|
2498
|
+
req.operation !== `delete` &&
|
|
2499
|
+
(req.value === undefined || req.value === null)
|
|
2500
|
+
) {
|
|
2501
|
+
throw new ElectricAgentsError(
|
|
2502
|
+
ErrCodeInvalidRequest,
|
|
2503
|
+
`value is required for ${req.operation}`,
|
|
2504
|
+
400
|
|
2505
|
+
)
|
|
2506
|
+
}
|
|
2507
|
+
if (req.operation !== `insert` && !req.key) {
|
|
2508
|
+
throw new ElectricAgentsError(
|
|
2509
|
+
ErrCodeInvalidRequest,
|
|
2510
|
+
`key is required for ${req.operation}`,
|
|
2511
|
+
400
|
|
2512
|
+
)
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
const key = req.key ?? `${collection}-${randomUUID()}`
|
|
2516
|
+
|
|
2517
|
+
const event: Record<string, unknown> = {
|
|
2518
|
+
type: config.type,
|
|
2519
|
+
key,
|
|
2520
|
+
headers: {
|
|
2521
|
+
operation: req.operation,
|
|
2522
|
+
timestamp: new Date().toISOString(),
|
|
2523
|
+
principal: req.principal,
|
|
2524
|
+
},
|
|
2525
|
+
}
|
|
2526
|
+
if (req.operation !== `delete`) {
|
|
2527
|
+
event.value = req.value
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
const validationError = await this.validateWriteEvent(entity, event)
|
|
2531
|
+
if (validationError) {
|
|
2532
|
+
throw new ElectricAgentsError(
|
|
2533
|
+
validationError.code,
|
|
2534
|
+
validationError.message,
|
|
2535
|
+
validationError.status
|
|
2536
|
+
)
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
const encoded = this.encodeChangeEvent(event)
|
|
2540
|
+
try {
|
|
2541
|
+
await this.streamClient.append(entity.streams.main, encoded)
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
if (this.isClosedStreamError(err)) {
|
|
2544
|
+
throw new ElectricAgentsError(
|
|
2545
|
+
ErrCodeNotRunning,
|
|
2546
|
+
`Entity is stopped`,
|
|
2547
|
+
409
|
|
2548
|
+
)
|
|
2549
|
+
}
|
|
2550
|
+
throw err
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
return { key }
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2425
2556
|
async updateInboxMessage(
|
|
2426
2557
|
entityUrl: string,
|
|
2427
2558
|
key: string,
|
|
@@ -2431,7 +2562,7 @@ export class EntityManager {
|
|
|
2431
2562
|
mode?: `immediate` | `queued` | `paused` | `steer`
|
|
2432
2563
|
status?: `pending` | `processed` | `cancelled`
|
|
2433
2564
|
}
|
|
2434
|
-
): Promise<
|
|
2565
|
+
): Promise<{ txid: string }> {
|
|
2435
2566
|
const entity = await this.registry.getEntity(entityUrl)
|
|
2436
2567
|
if (!entity) {
|
|
2437
2568
|
throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404)
|
|
@@ -2463,17 +2594,23 @@ export class EntityManager {
|
|
|
2463
2594
|
)
|
|
2464
2595
|
}
|
|
2465
2596
|
|
|
2597
|
+
const txid = crypto.randomUUID()
|
|
2466
2598
|
const envelope = entityStateSchema.inbox.update({
|
|
2467
2599
|
key,
|
|
2468
2600
|
value,
|
|
2601
|
+
headers: { txid },
|
|
2469
2602
|
} as any)
|
|
2470
2603
|
await this.streamClient.append(
|
|
2471
2604
|
entity.streams.main,
|
|
2472
2605
|
this.encodeChangeEvent(envelope as Record<string, unknown>)
|
|
2473
2606
|
)
|
|
2607
|
+
return { txid }
|
|
2474
2608
|
}
|
|
2475
2609
|
|
|
2476
|
-
async deleteInboxMessage(
|
|
2610
|
+
async deleteInboxMessage(
|
|
2611
|
+
entityUrl: string,
|
|
2612
|
+
key: string
|
|
2613
|
+
): Promise<{ txid: string }> {
|
|
2477
2614
|
const entity = await this.registry.getEntity(entityUrl)
|
|
2478
2615
|
if (!entity) {
|
|
2479
2616
|
throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404)
|
|
@@ -2486,11 +2623,16 @@ export class EntityManager {
|
|
|
2486
2623
|
)
|
|
2487
2624
|
}
|
|
2488
2625
|
|
|
2489
|
-
const
|
|
2626
|
+
const txid = crypto.randomUUID()
|
|
2627
|
+
const envelope = entityStateSchema.inbox.delete({
|
|
2628
|
+
key,
|
|
2629
|
+
headers: { txid },
|
|
2630
|
+
} as any)
|
|
2490
2631
|
await this.streamClient.append(
|
|
2491
2632
|
entity.streams.main,
|
|
2492
2633
|
this.encodeChangeEvent(envelope as Record<string, unknown>)
|
|
2493
2634
|
)
|
|
2635
|
+
return { txid }
|
|
2494
2636
|
}
|
|
2495
2637
|
|
|
2496
2638
|
// ==========================================================================
|
|
@@ -2686,21 +2828,12 @@ export class EntityManager {
|
|
|
2686
2828
|
async setTag(
|
|
2687
2829
|
entityUrl: string,
|
|
2688
2830
|
key: string,
|
|
2689
|
-
req: SetTagRequest
|
|
2690
|
-
|
|
2691
|
-
): Promise<ElectricAgentsEntity> {
|
|
2831
|
+
req: SetTagRequest
|
|
2832
|
+
): Promise<ElectricAgentsEntity & { txid?: number }> {
|
|
2692
2833
|
const entity = await this.registry.getEntity(entityUrl)
|
|
2693
2834
|
if (!entity) {
|
|
2694
2835
|
throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404)
|
|
2695
2836
|
}
|
|
2696
|
-
|
|
2697
|
-
if (!this.isValidWriteToken(entity, token)) {
|
|
2698
|
-
throw new ElectricAgentsError(
|
|
2699
|
-
ErrCodeUnauthorized,
|
|
2700
|
-
`Invalid write token`,
|
|
2701
|
-
401
|
|
2702
|
-
)
|
|
2703
|
-
}
|
|
2704
2837
|
if (rejectsNormalWrites(entity.status)) {
|
|
2705
2838
|
throw new ElectricAgentsError(
|
|
2706
2839
|
ErrCodeNotRunning,
|
|
@@ -2731,26 +2864,17 @@ export class EntityManager {
|
|
|
2731
2864
|
await this.entityBridgeManager.onEntityChanged(entityUrl)
|
|
2732
2865
|
}
|
|
2733
2866
|
|
|
2734
|
-
return updated
|
|
2867
|
+
return withOptionalTxid(updated, result.txid)
|
|
2735
2868
|
}
|
|
2736
2869
|
|
|
2737
2870
|
async deleteTag(
|
|
2738
2871
|
entityUrl: string,
|
|
2739
|
-
key: string
|
|
2740
|
-
|
|
2741
|
-
): Promise<ElectricAgentsEntity> {
|
|
2872
|
+
key: string
|
|
2873
|
+
): Promise<ElectricAgentsEntity & { txid?: number }> {
|
|
2742
2874
|
const entity = await this.registry.getEntity(entityUrl)
|
|
2743
2875
|
if (!entity) {
|
|
2744
2876
|
throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404)
|
|
2745
2877
|
}
|
|
2746
|
-
|
|
2747
|
-
if (!this.isValidWriteToken(entity, token)) {
|
|
2748
|
-
throw new ElectricAgentsError(
|
|
2749
|
-
ErrCodeUnauthorized,
|
|
2750
|
-
`Invalid write token`,
|
|
2751
|
-
401
|
|
2752
|
-
)
|
|
2753
|
-
}
|
|
2754
2878
|
if (rejectsNormalWrites(entity.status)) {
|
|
2755
2879
|
throw new ElectricAgentsError(
|
|
2756
2880
|
ErrCodeNotRunning,
|
|
@@ -2773,7 +2897,7 @@ export class EntityManager {
|
|
|
2773
2897
|
await this.entityBridgeManager.onEntityChanged(entityUrl)
|
|
2774
2898
|
}
|
|
2775
2899
|
|
|
2776
|
-
return updated
|
|
2900
|
+
return withOptionalTxid(updated, result.txid)
|
|
2777
2901
|
}
|
|
2778
2902
|
|
|
2779
2903
|
async ensureEntitiesMembershipStream(
|
|
@@ -3871,11 +3995,16 @@ export class EntityManager {
|
|
|
3871
3995
|
private async getEffectiveSchemas(entity: ElectricAgentsEntity): Promise<{
|
|
3872
3996
|
inboxSchemas?: Record<string, Record<string, unknown>>
|
|
3873
3997
|
stateSchemas?: Record<string, Record<string, unknown>>
|
|
3998
|
+
externallyWritableCollections?: Record<
|
|
3999
|
+
string,
|
|
4000
|
+
ExternallyWritableCollectionConfig
|
|
4001
|
+
>
|
|
3874
4002
|
}> {
|
|
3875
4003
|
if (!entity.type) {
|
|
3876
4004
|
return {
|
|
3877
4005
|
inboxSchemas: entity.inbox_schemas,
|
|
3878
4006
|
stateSchemas: entity.state_schemas,
|
|
4007
|
+
externallyWritableCollections: undefined,
|
|
3879
4008
|
}
|
|
3880
4009
|
}
|
|
3881
4010
|
|
|
@@ -3888,6 +4017,8 @@ export class EntityManager {
|
|
|
3888
4017
|
stateSchemas: latestType?.state_schemas
|
|
3889
4018
|
? { ...(entity.state_schemas ?? {}), ...latestType.state_schemas }
|
|
3890
4019
|
: entity.state_schemas,
|
|
4020
|
+
externallyWritableCollections:
|
|
4021
|
+
latestType?.externally_writable_collections,
|
|
3891
4022
|
}
|
|
3892
4023
|
}
|
|
3893
4024
|
|