@elizaos/plugin-sql 1.6.4-alpha.8 → 1.6.4-beta.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.
@@ -7937,7 +7937,7 @@ var init_migration_service = __esm(() => {
7937
7937
  });
7938
7938
 
7939
7939
  // src/index.node.ts
7940
- import { logger as logger12 } from "@elizaos/core";
7940
+ import { logger as logger13, stringToUuid } from "@elizaos/core";
7941
7941
 
7942
7942
  // src/pglite/adapter.ts
7943
7943
  import { logger as logger9 } from "@elizaos/core";
@@ -8173,6 +8173,7 @@ init_pg_core();
8173
8173
  var agentTable = pgTable("agents", {
8174
8174
  id: uuid("id").primaryKey().defaultRandom(),
8175
8175
  enabled: boolean("enabled").default(true).notNull(),
8176
+ owner_id: uuid("owner_id"),
8176
8177
  createdAt: timestamp("created_at", { withTimezone: true }).default(sql`now()`).notNull(),
8177
8178
  updatedAt: timestamp("updated_at", { withTimezone: true }).default(sql`now()`).notNull(),
8178
8179
  name: text("name").notNull(),
@@ -8321,6 +8322,7 @@ __export(exports_schema, {
8321
8322
  roomTable: () => roomTable,
8322
8323
  relationshipTable: () => relationshipTable,
8323
8324
  participantTable: () => participantTable,
8325
+ ownersTable: () => ownersTable,
8324
8326
  messageTable: () => messageTable,
8325
8327
  messageServerTable: () => messageServerTable,
8326
8328
  memoryTable: () => memoryTable,
@@ -8396,6 +8398,14 @@ var logTable = pgTable("logs", {
8396
8398
  foreignColumns: [entityTable.id]
8397
8399
  }).onDelete("cascade")
8398
8400
  ]);
8401
+ // src/schema/owners.ts
8402
+ init_drizzle_orm();
8403
+ init_pg_core();
8404
+ var ownersTable = pgTable("owners", {
8405
+ id: uuid("id").primaryKey(),
8406
+ createdAt: timestamp("created_at", { withTimezone: true }).default(sql`now()`).notNull(),
8407
+ updatedAt: timestamp("updated_at", { withTimezone: true }).default(sql`now()`).notNull()
8408
+ });
8399
8409
  // src/schema/participant.ts
8400
8410
  init_drizzle_orm();
8401
8411
  init_pg_core();
@@ -8560,6 +8570,24 @@ class BaseDrizzleAdapter extends DatabaseAdapter {
8560
8570
  super();
8561
8571
  this.agentId = agentId;
8562
8572
  }
8573
+ normalizeEntityNames(names) {
8574
+ if (names == null) {
8575
+ return [];
8576
+ }
8577
+ if (typeof names === "string") {
8578
+ return [names];
8579
+ }
8580
+ if (Array.isArray(names)) {
8581
+ return names.map(String);
8582
+ }
8583
+ if (names instanceof Set) {
8584
+ return Array.from(names).map(String);
8585
+ }
8586
+ if (typeof names === "object" && typeof names[Symbol.iterator] === "function") {
8587
+ return Array.from(names).map(String);
8588
+ }
8589
+ return [String(names)];
8590
+ }
8563
8591
  async withRetry(operation) {
8564
8592
  let lastError = new Error("Unknown error");
8565
8593
  for (let attempt = 1;attempt <= this.maxRetries; attempt++) {
@@ -8831,7 +8859,12 @@ class BaseDrizzleAdapter extends DatabaseAdapter {
8831
8859
  return this.withDatabase(async () => {
8832
8860
  try {
8833
8861
  return await this.db.transaction(async (tx) => {
8834
- await tx.insert(entityTable).values(entities);
8862
+ const normalizedEntities = entities.map((entity2) => ({
8863
+ ...entity2,
8864
+ names: this.normalizeEntityNames(entity2.names),
8865
+ metadata: entity2.metadata || {}
8866
+ }));
8867
+ await tx.insert(entityTable).values(normalizedEntities);
8835
8868
  logger8.debug(`${entities.length} Entities created successfully`);
8836
8869
  return true;
8837
8870
  });
@@ -8865,7 +8898,12 @@ class BaseDrizzleAdapter extends DatabaseAdapter {
8865
8898
  throw new Error("Entity ID is required for update");
8866
8899
  }
8867
8900
  return this.withDatabase(async () => {
8868
- await this.db.update(entityTable).set(entity2).where(eq(entityTable.id, entity2.id));
8901
+ const normalizedEntity = {
8902
+ ...entity2,
8903
+ names: this.normalizeEntityNames(entity2.names),
8904
+ metadata: entity2.metadata || {}
8905
+ };
8906
+ await this.db.update(entityTable).set(normalizedEntity).where(eq(entityTable.id, entity2.id));
8869
8907
  });
8870
8908
  }
8871
8909
  async deleteEntity(entityId) {
@@ -10943,8 +10981,13 @@ import { logger as logger11 } from "@elizaos/core";
10943
10981
  class PostgresConnectionManager {
10944
10982
  pool;
10945
10983
  db;
10946
- constructor(connectionString) {
10947
- this.pool = new Pool2({ connectionString });
10984
+ constructor(connectionString, rlsOwnerId) {
10985
+ const poolConfig = { connectionString };
10986
+ if (rlsOwnerId) {
10987
+ poolConfig.application_name = rlsOwnerId;
10988
+ logger11.debug(`[RLS] Pool configured with application_name: ${rlsOwnerId}`);
10989
+ }
10990
+ this.pool = new Pool2(poolConfig);
10948
10991
  this.db = drizzle2(this.pool);
10949
10992
  }
10950
10993
  getDatabase() {
@@ -11028,6 +11071,246 @@ function resolvePgliteDir(dir, fallbackDir) {
11028
11071
 
11029
11072
  // src/index.node.ts
11030
11073
  init_migration_service();
11074
+
11075
+ // src/rls.ts
11076
+ init_drizzle_orm();
11077
+ import { logger as logger12, validateUuid } from "@elizaos/core";
11078
+ async function installRLSFunctions(adapter) {
11079
+ const db2 = adapter.db;
11080
+ await db2.execute(sql`
11081
+ CREATE TABLE IF NOT EXISTS owners (
11082
+ id UUID PRIMARY KEY,
11083
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
11084
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
11085
+ )
11086
+ `);
11087
+ await db2.execute(sql`
11088
+ CREATE OR REPLACE FUNCTION current_owner_id() RETURNS UUID AS $$
11089
+ DECLARE
11090
+ app_name TEXT;
11091
+ BEGIN
11092
+ app_name := NULLIF(current_setting('application_name', TRUE), '');
11093
+
11094
+ -- Return NULL if application_name is not set or not a valid UUID
11095
+ -- This allows admin queries to work without RLS restrictions
11096
+ BEGIN
11097
+ RETURN app_name::UUID;
11098
+ EXCEPTION WHEN OTHERS THEN
11099
+ RETURN NULL;
11100
+ END;
11101
+ END;
11102
+ $$ LANGUAGE plpgsql STABLE;
11103
+ `);
11104
+ await db2.execute(sql`
11105
+ CREATE OR REPLACE FUNCTION add_owner_isolation(
11106
+ schema_name text,
11107
+ table_name text
11108
+ ) RETURNS void AS $$
11109
+ DECLARE
11110
+ full_table_name text;
11111
+ column_exists boolean;
11112
+ orphaned_count bigint;
11113
+ BEGIN
11114
+ full_table_name := schema_name || '.' || table_name;
11115
+
11116
+ -- Check if owner_id column already exists
11117
+ SELECT EXISTS (
11118
+ SELECT 1 FROM information_schema.columns
11119
+ WHERE information_schema.columns.table_schema = schema_name
11120
+ AND information_schema.columns.table_name = add_owner_isolation.table_name
11121
+ AND information_schema.columns.column_name = 'owner_id'
11122
+ ) INTO column_exists;
11123
+
11124
+ -- Add owner_id column if missing (DEFAULT populates it automatically for new rows)
11125
+ IF NOT column_exists THEN
11126
+ EXECUTE format('ALTER TABLE %I.%I ADD COLUMN owner_id UUID DEFAULT current_owner_id()', schema_name, table_name);
11127
+
11128
+ -- Backfill existing rows with current owner_id
11129
+ -- This ensures all existing data belongs to the tenant that is enabling RLS
11130
+ EXECUTE format('UPDATE %I.%I SET owner_id = current_owner_id() WHERE owner_id IS NULL', schema_name, table_name);
11131
+ ELSE
11132
+ -- Column already exists (RLS was previously enabled then disabled)
11133
+ -- Restore the DEFAULT clause (may have been removed during uninstallRLS)
11134
+ EXECUTE format('ALTER TABLE %I.%I ALTER COLUMN owner_id SET DEFAULT current_owner_id()', schema_name, table_name);
11135
+
11136
+ -- Only backfill NULL owner_id rows, do NOT steal data from other owners
11137
+ EXECUTE format('SELECT COUNT(*) FROM %I.%I WHERE owner_id IS NULL', schema_name, table_name) INTO orphaned_count;
11138
+
11139
+ IF orphaned_count > 0 THEN
11140
+ RAISE NOTICE 'Backfilling % rows with NULL owner_id in %.%', orphaned_count, schema_name, table_name;
11141
+ EXECUTE format('UPDATE %I.%I SET owner_id = current_owner_id() WHERE owner_id IS NULL', schema_name, table_name);
11142
+ END IF;
11143
+ END IF;
11144
+
11145
+ -- Create index for efficient owner_id filtering
11146
+ EXECUTE format('CREATE INDEX IF NOT EXISTS idx_%I_owner_id ON %I.%I(owner_id)', table_name, schema_name, table_name);
11147
+
11148
+ -- Enable RLS on the table
11149
+ EXECUTE format('ALTER TABLE %I.%I ENABLE ROW LEVEL SECURITY', schema_name, table_name);
11150
+
11151
+ -- FORCE RLS even for table owners (critical for security)
11152
+ EXECUTE format('ALTER TABLE %I.%I FORCE ROW LEVEL SECURITY', schema_name, table_name);
11153
+
11154
+ -- Drop existing policy if present
11155
+ EXECUTE format('DROP POLICY IF EXISTS owner_isolation_policy ON %I.%I', schema_name, table_name);
11156
+
11157
+ -- Create isolation policy: users can only see/modify rows where owner_id matches current tenant
11158
+ -- No NULL clause - all rows must have a valid owner_id (backfilled during column addition)
11159
+ EXECUTE format('
11160
+ CREATE POLICY owner_isolation_policy ON %I.%I
11161
+ USING (owner_id = current_owner_id())
11162
+ WITH CHECK (owner_id = current_owner_id())
11163
+ ', schema_name, table_name);
11164
+ END;
11165
+ $$ LANGUAGE plpgsql;
11166
+ `);
11167
+ await db2.execute(sql`
11168
+ CREATE OR REPLACE FUNCTION apply_rls_to_all_tables() RETURNS void AS $$
11169
+ DECLARE
11170
+ tbl record;
11171
+ BEGIN
11172
+ FOR tbl IN
11173
+ SELECT schemaname, tablename
11174
+ FROM pg_tables
11175
+ WHERE schemaname = 'public'
11176
+ AND tablename NOT IN (
11177
+ 'owners',
11178
+ 'drizzle_migrations',
11179
+ '__drizzle_migrations',
11180
+ 'server_agents'
11181
+ )
11182
+ LOOP
11183
+ BEGIN
11184
+ PERFORM add_owner_isolation(tbl.schemaname, tbl.tablename);
11185
+ EXCEPTION WHEN OTHERS THEN
11186
+ RAISE WARNING 'Failed to apply RLS to %.%: %', tbl.schemaname, tbl.tablename, SQLERRM;
11187
+ END;
11188
+ END LOOP;
11189
+ END;
11190
+ $$ LANGUAGE plpgsql;
11191
+ `);
11192
+ logger12.info("[RLS] PostgreSQL functions installed");
11193
+ }
11194
+ async function getOrCreateRlsOwner(adapter, ownerId) {
11195
+ const db2 = adapter.db;
11196
+ await db2.insert(ownersTable).values({
11197
+ id: ownerId
11198
+ }).onConflictDoNothing();
11199
+ logger12.info(`[RLS] Owner: ${ownerId.slice(0, 8)}…`);
11200
+ return ownerId;
11201
+ }
11202
+ async function setOwnerContext(adapter, ownerId) {
11203
+ if (!validateUuid(ownerId)) {
11204
+ throw new Error(`Invalid owner ID format: ${ownerId}. Must be a valid UUID.`);
11205
+ }
11206
+ const db2 = adapter.db;
11207
+ const owners = await db2.select().from(ownersTable).where(eq(ownersTable.id, ownerId));
11208
+ if (owners.length === 0) {
11209
+ throw new Error(`Owner ${ownerId} does not exist`);
11210
+ }
11211
+ logger12.info(`[RLS] Owner: ${ownerId.slice(0, 8)}…`);
11212
+ logger12.info("[RLS] Context configured successfully (using application_name)");
11213
+ }
11214
+ async function assignAgentToOwner(adapter, agentId, ownerId) {
11215
+ const db2 = adapter.db;
11216
+ const agents = await db2.select().from(agentTable).where(eq(agentTable.id, agentId));
11217
+ if (agents.length > 0) {
11218
+ const agent = agents[0];
11219
+ const currentOwnerId = agent.owner_id;
11220
+ if (currentOwnerId === ownerId) {
11221
+ logger12.debug(`[RLS] Agent ${agent.name} already owned by correct owner`);
11222
+ } else {
11223
+ await db2.update(agentTable).set({ owner_id: ownerId }).where(eq(agentTable.id, agentId));
11224
+ if (currentOwnerId === null) {
11225
+ logger12.info(`[RLS] Agent ${agent.name} assigned to owner`);
11226
+ } else {
11227
+ logger12.warn(`[RLS] Agent ${agent.name} owner changed`);
11228
+ }
11229
+ }
11230
+ } else {
11231
+ logger12.debug(`[RLS] Agent ${agentId} doesn't exist yet`);
11232
+ }
11233
+ }
11234
+ async function applyRLSToNewTables(adapter) {
11235
+ const db2 = adapter.db;
11236
+ try {
11237
+ await db2.execute(sql`SELECT apply_rls_to_all_tables()`);
11238
+ logger12.info("[RLS] Applied to all tables");
11239
+ } catch (error) {
11240
+ logger12.warn("[RLS] Failed to apply to some tables:", String(error));
11241
+ }
11242
+ }
11243
+ async function uninstallRLS(adapter) {
11244
+ const db2 = adapter.db;
11245
+ try {
11246
+ const checkResult = await db2.execute(sql`
11247
+ SELECT EXISTS (
11248
+ SELECT FROM pg_tables
11249
+ WHERE schemaname = 'public' AND tablename = 'owners'
11250
+ ) as rls_enabled
11251
+ `);
11252
+ const rlsEnabled = checkResult.rows?.[0]?.rls_enabled;
11253
+ if (!rlsEnabled) {
11254
+ logger12.debug("[RLS] RLS not installed, skipping cleanup");
11255
+ return;
11256
+ }
11257
+ logger12.info("[RLS] Disabling RLS globally (keeping owner_id columns for schema compatibility)...");
11258
+ await db2.execute(sql`
11259
+ CREATE OR REPLACE FUNCTION _temp_disable_rls_on_table(
11260
+ p_schema_name text,
11261
+ p_table_name text
11262
+ ) RETURNS void AS $$
11263
+ DECLARE
11264
+ policy_rec record;
11265
+ BEGIN
11266
+ -- Drop all policies on this table
11267
+ FOR policy_rec IN
11268
+ SELECT policyname
11269
+ FROM pg_policies
11270
+ WHERE schemaname = p_schema_name AND tablename = p_table_name
11271
+ LOOP
11272
+ EXECUTE format('DROP POLICY IF EXISTS %I ON %I.%I',
11273
+ policy_rec.policyname, p_schema_name, p_table_name);
11274
+ END LOOP;
11275
+
11276
+ -- Disable RLS
11277
+ EXECUTE format('ALTER TABLE %I.%I NO FORCE ROW LEVEL SECURITY', p_schema_name, p_table_name);
11278
+ EXECUTE format('ALTER TABLE %I.%I DISABLE ROW LEVEL SECURITY', p_schema_name, p_table_name);
11279
+ END;
11280
+ $$ LANGUAGE plpgsql;
11281
+ `);
11282
+ const tablesResult = await db2.execute(sql`
11283
+ SELECT schemaname, tablename
11284
+ FROM pg_tables
11285
+ WHERE schemaname = 'public'
11286
+ AND tablename NOT IN ('drizzle_migrations', '__drizzle_migrations')
11287
+ `);
11288
+ for (const row of tablesResult.rows || []) {
11289
+ const schemaName = row.schemaname;
11290
+ const tableName = row.tablename;
11291
+ try {
11292
+ await db2.execute(sql`SELECT _temp_disable_rls_on_table(${schemaName}, ${tableName})`);
11293
+ logger12.debug(`[RLS] Disabled RLS on table: ${schemaName}.${tableName}`);
11294
+ } catch (error) {
11295
+ logger12.warn(`[RLS] Failed to disable RLS on table ${schemaName}.${tableName}:`, String(error));
11296
+ }
11297
+ }
11298
+ await db2.execute(sql`DROP FUNCTION IF EXISTS _temp_disable_rls_on_table(text, text)`);
11299
+ logger12.info("[RLS] Keeping owner_id values intact (prevents data theft on re-enable)");
11300
+ logger12.info("[RLS] Clearing owners table...");
11301
+ await db2.execute(sql`TRUNCATE TABLE owners`);
11302
+ await db2.execute(sql`DROP FUNCTION IF EXISTS apply_rls_to_all_tables() CASCADE`);
11303
+ await db2.execute(sql`DROP FUNCTION IF EXISTS add_owner_isolation(text, text) CASCADE`);
11304
+ await db2.execute(sql`DROP FUNCTION IF EXISTS current_owner_id() CASCADE`);
11305
+ logger12.info("[RLS] Dropped all RLS functions");
11306
+ logger12.success("[RLS] RLS disabled successfully (owner_id columns preserved)");
11307
+ } catch (error) {
11308
+ logger12.error("[RLS] Failed to disable RLS:", String(error));
11309
+ throw error;
11310
+ }
11311
+ }
11312
+
11313
+ // src/index.node.ts
11031
11314
  var GLOBAL_SINGLETONS = Symbol.for("@elizaos/plugin-sql/global-singletons");
11032
11315
  var globalSymbols = globalThis;
11033
11316
  if (!globalSymbols[GLOBAL_SINGLETONS]) {
@@ -11036,10 +11319,28 @@ if (!globalSymbols[GLOBAL_SINGLETONS]) {
11036
11319
  var globalSingletons = globalSymbols[GLOBAL_SINGLETONS];
11037
11320
  function createDatabaseAdapter(config, agentId) {
11038
11321
  if (config.postgresUrl) {
11039
- if (!globalSingletons.postgresConnectionManager) {
11040
- globalSingletons.postgresConnectionManager = new PostgresConnectionManager(config.postgresUrl);
11322
+ const rlsEnabled = process.env.ENABLE_RLS_ISOLATION === "true";
11323
+ let rlsOwnerId;
11324
+ let managerKey = "default";
11325
+ if (rlsEnabled) {
11326
+ const rlsOwnerIdString = process.env.RLS_OWNER_ID;
11327
+ if (!rlsOwnerIdString) {
11328
+ throw new Error("[RLS] ENABLE_RLS_ISOLATION=true requires RLS_OWNER_ID environment variable");
11329
+ }
11330
+ rlsOwnerId = stringToUuid(rlsOwnerIdString);
11331
+ managerKey = rlsOwnerId;
11332
+ logger13.debug(`[RLS] Using connection pool for owner_id: ${rlsOwnerId.slice(0, 8)}… (from RLS_OWNER_ID="${rlsOwnerIdString}")`);
11333
+ }
11334
+ if (!globalSingletons.postgresConnectionManagers) {
11335
+ globalSingletons.postgresConnectionManagers = new Map;
11336
+ }
11337
+ let manager = globalSingletons.postgresConnectionManagers.get(managerKey);
11338
+ if (!manager) {
11339
+ logger13.debug(`[RLS] Creating new connection pool for key: ${managerKey.slice(0, 8)}…`);
11340
+ manager = new PostgresConnectionManager(config.postgresUrl, rlsOwnerId);
11341
+ globalSingletons.postgresConnectionManagers.set(managerKey, manager);
11041
11342
  }
11042
- return new PgDatabaseAdapter(agentId, globalSingletons.postgresConnectionManager);
11343
+ return new PgDatabaseAdapter(agentId, manager);
11043
11344
  }
11044
11345
  const dataDir = resolvePgliteDir(config.dataDir);
11045
11346
  if (!globalSingletons.pgLiteClientManager) {
@@ -11053,18 +11354,18 @@ var plugin = {
11053
11354
  priority: 0,
11054
11355
  schema: exports_schema,
11055
11356
  init: async (_config, runtime) => {
11056
- logger12.info("plugin-sql (node) init starting...");
11357
+ logger13.info("plugin-sql (node) init starting...");
11057
11358
  const adapterRegistered = await runtime.isReady().then(() => true).catch((error) => {
11058
11359
  const message = error instanceof Error ? error.message : String(error);
11059
11360
  if (message.includes("Database adapter not registered")) {
11060
- logger12.info("No pre-registered database adapter detected; registering adapter");
11361
+ logger13.info("No pre-registered database adapter detected; registering adapter");
11061
11362
  } else {
11062
- logger12.warn({ error }, "Database adapter readiness check error; proceeding to register adapter");
11363
+ logger13.warn({ error }, "Database adapter readiness check error; proceeding to register adapter");
11063
11364
  }
11064
11365
  return false;
11065
11366
  });
11066
11367
  if (adapterRegistered) {
11067
- logger12.info("Database adapter already registered, skipping creation");
11368
+ logger13.info("Database adapter already registered, skipping creation");
11068
11369
  return;
11069
11370
  }
11070
11371
  const postgresUrl = runtime.getSetting("POSTGRES_URL");
@@ -11074,16 +11375,22 @@ var plugin = {
11074
11375
  postgresUrl
11075
11376
  }, runtime.agentId);
11076
11377
  runtime.registerDatabaseAdapter(dbAdapter);
11077
- logger12.info("Database adapter created and registered");
11378
+ logger13.info("Database adapter created and registered");
11078
11379
  }
11079
11380
  };
11080
11381
  var index_node_default = plugin;
11081
11382
  export {
11383
+ uninstallRLS,
11384
+ setOwnerContext,
11082
11385
  plugin,
11386
+ installRLSFunctions,
11387
+ getOrCreateRlsOwner,
11083
11388
  index_node_default as default,
11084
11389
  createDatabaseAdapter,
11390
+ assignAgentToOwner,
11391
+ applyRLSToNewTables,
11085
11392
  DatabaseMigrationService
11086
11393
  };
11087
11394
 
11088
- //# debugId=AE1FE5F77043850164756E2164756E21
11395
+ //# debugId=A92CE19BE9F182BF64756E2164756E21
11089
11396
  //# sourceMappingURL=index.node.js.map