@undefineds.co/xpod 0.2.5 → 0.2.7

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.
Files changed (67) hide show
  1. package/config/cloud.json +1 -1
  2. package/dist/api/chatkit/ai-provider.js +4 -3
  3. package/dist/api/chatkit/ai-provider.js.map +1 -1
  4. package/dist/api/chatkit/default-agent.js +8 -7
  5. package/dist/api/chatkit/default-agent.js.map +1 -1
  6. package/dist/api/chatkit/runtime/PtyThreadRuntime.js +2 -1
  7. package/dist/api/chatkit/runtime/PtyThreadRuntime.js.map +1 -1
  8. package/dist/api/handlers/AdminHandler.js +1 -1
  9. package/dist/api/handlers/AdminHandler.js.map +1 -1
  10. package/dist/api/handlers/EdgeNodeSignalHandler.js +2 -27
  11. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
  12. package/dist/api/handlers/NodeHandler.js +17 -158
  13. package/dist/api/handlers/NodeHandler.js.map +1 -1
  14. package/dist/api/service/VercelChatService.js +23 -23
  15. package/dist/api/service/VercelChatService.js.map +1 -1
  16. package/dist/api/service/platform-ai-config.d.ts +8 -0
  17. package/dist/api/service/platform-ai-config.js +64 -0
  18. package/dist/api/service/platform-ai-config.js.map +1 -0
  19. package/dist/cli/commands/start.js +1 -2
  20. package/dist/cli/commands/start.js.map +1 -1
  21. package/dist/components/context.jsonld +3 -0
  22. package/dist/http/TracingHandler.js +2 -0
  23. package/dist/http/TracingHandler.js.map +1 -1
  24. package/dist/identity/drizzle/DrizzleIndexedStorage.d.ts +10 -1
  25. package/dist/identity/drizzle/DrizzleIndexedStorage.js +99 -6
  26. package/dist/identity/drizzle/DrizzleIndexedStorage.js.map +1 -1
  27. package/dist/identity/drizzle/DrizzleIndexedStorage.jsonld +36 -0
  28. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +3 -4
  29. package/dist/identity/drizzle/EdgeNodeRepository.js +10 -37
  30. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  31. package/dist/identity/drizzle/db.js +13 -2
  32. package/dist/identity/drizzle/db.js.map +1 -1
  33. package/dist/identity/drizzle/schema.pg.js +0 -1
  34. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  35. package/dist/identity/drizzle/schema.sqlite.d.ts +0 -19
  36. package/dist/identity/drizzle/schema.sqlite.js +0 -1
  37. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  38. package/dist/main.js +1 -2
  39. package/dist/main.js.map +1 -1
  40. package/dist/provision/ProvisionPodCreator.d.ts +1 -0
  41. package/dist/provision/ProvisionPodCreator.js +25 -2
  42. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  43. package/dist/provision/ProvisionPodCreator.jsonld +4 -0
  44. package/dist/runtime/bootstrap.js +4 -4
  45. package/dist/runtime/bootstrap.js.map +1 -1
  46. package/dist/storage/MetadataRequestContext.d.ts +13 -0
  47. package/dist/storage/MetadataRequestContext.js +6 -0
  48. package/dist/storage/MetadataRequestContext.js.map +1 -0
  49. package/dist/storage/NodeSqliteDrizzle.js +19 -5
  50. package/dist/storage/NodeSqliteDrizzle.js.map +1 -1
  51. package/dist/storage/accessors/MinioDataAccessor.d.ts +1 -0
  52. package/dist/storage/accessors/MinioDataAccessor.js +25 -2
  53. package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
  54. package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
  55. package/dist/storage/accessors/MixDataAccessor.d.ts +3 -1
  56. package/dist/storage/accessors/MixDataAccessor.js +61 -23
  57. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  58. package/dist/storage/accessors/MixDataAccessor.jsonld +23 -0
  59. package/dist/supervisor/Supervisor.js +1 -1
  60. package/dist/supervisor/Supervisor.js.map +1 -1
  61. package/package.json +1 -1
  62. package/static/app/assets/index.css +1 -1
  63. package/static/app/assets/main.js +50 -10
  64. package/static/dashboard/assets/{dashboard-BfmT5Vc8.js → dashboard-CTk9cn1-.js} +8 -8
  65. package/static/dashboard/assets/dashboard-VQRsvXx9.css +1 -0
  66. package/static/dashboard/dashboard.html +2 -2
  67. package/static/dashboard/assets/dashboard-DkZOr7bV.css +0 -1
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DrizzleIndexedStorage = void 0;
7
+ const global_logger_factory_1 = require("global-logger-factory");
7
8
  const drizzle_orm_1 = require("drizzle-orm");
8
9
  const node_crypto_1 = __importDefault(require("node:crypto"));
9
10
  const db_1 = require("./db");
@@ -30,7 +31,9 @@ function parsePayload(value) {
30
31
  */
31
32
  class DrizzleIndexedStorage {
32
33
  constructor(connectionString, tablePrefix = 'identity_') {
34
+ this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
33
35
  this.definitions = new Map();
36
+ this.createdIndexes = new Set();
34
37
  this.ready = false;
35
38
  this.db = (0, db_1.getIdentityDatabase)(connectionString);
36
39
  this.tableName = `${tablePrefix}store`;
@@ -57,10 +60,29 @@ class DrizzleIndexedStorage {
57
60
  this.definitions.set(type, description);
58
61
  await this.ensureTable();
59
62
  }
60
- async createIndex() {
61
- // Indexes are not required for the current usage.
63
+ async createIndex(type, key) {
64
+ const started = Date.now();
65
+ await this.ensureTable();
66
+ if (key === 'id') {
67
+ return;
68
+ }
69
+ const cacheKey = `${type}:${key}`;
70
+ if (this.createdIndexes.has(cacheKey)) {
71
+ return;
72
+ }
73
+ if ((0, db_1.isDatabaseSqlite)(this.db)) {
74
+ const jsonPath = this.escapeSqlLiteral(this.toSqliteJsonPath(key));
75
+ await (0, db_1.executeStatement)(this.db, drizzle_orm_1.sql.raw(`CREATE INDEX IF NOT EXISTS "${this.buildIndexName(type, key)}" ON "${this.tableName}" (container, json_extract(payload, '${jsonPath}'))`));
76
+ }
77
+ else {
78
+ const escapedKey = this.escapeSqlLiteral(key);
79
+ await (0, db_1.executeStatement)(this.db, drizzle_orm_1.sql.raw(`CREATE INDEX IF NOT EXISTS "${this.buildIndexName(type, key)}" ON "${this.tableName}" (container, (jsonb_extract_path_text(payload, '${escapedKey}')))`));
80
+ }
81
+ this.createdIndexes.add(cacheKey);
82
+ this.logDuration('createIndex', started, { type, key }, 50, 500);
62
83
  }
63
84
  async create(type, value) {
85
+ const started = Date.now();
64
86
  await this.ensureTable();
65
87
  const id = node_crypto_1.default.randomUUID();
66
88
  const payload = serializePayload(value);
@@ -69,18 +91,23 @@ class DrizzleIndexedStorage {
69
91
  INSERT INTO ${tableNameId} (container, id, payload)
70
92
  VALUES (${type}, ${id}, ${this.toJsonSql(payload)})
71
93
  `);
94
+ this.logDuration('create', started, { type, fields: Object.keys(value ?? {}) }, 50, 500);
72
95
  return { id, ...value };
73
96
  }
74
97
  async has(type, id) {
98
+ const started = Date.now();
75
99
  await this.ensureTable();
76
100
  const tableNameId = drizzle_orm_1.sql.identifier([this.tableName]);
77
101
  const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `SELECT 1 FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);
102
+ this.logDuration('has', started, { type, id }, 50, 500);
78
103
  return result.rows.length > 0;
79
104
  }
80
105
  async get(type, id) {
106
+ const started = Date.now();
81
107
  await this.ensureTable();
82
108
  const tableNameId = drizzle_orm_1.sql.identifier([this.tableName]);
83
109
  const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `SELECT payload FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);
110
+ this.logDuration('get', started, { type, id }, 50, 500);
84
111
  if (result.rows.length === 0) {
85
112
  return undefined;
86
113
  }
@@ -88,21 +115,30 @@ class DrizzleIndexedStorage {
88
115
  return { id, ...payload };
89
116
  }
90
117
  async find(type, query) {
118
+ const started = Date.now();
91
119
  await this.ensureTable();
92
120
  const tableNameId = drizzle_orm_1.sql.identifier([this.tableName]);
93
- // Note: This fetches all rows for the container and filters in memory.
94
- // For high volume types, this should be optimized with JSON path queries if possible.
95
- const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);
121
+ const normalizedQuery = (query ?? {});
122
+ const pushdown = this.canPushDownQuery(normalizedQuery);
123
+ const result = pushdown
124
+ ? await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `SELECT id, payload FROM ${tableNameId} WHERE ${this.buildWhereClause(type, normalizedQuery)}`)
125
+ : await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);
96
126
  const matches = [];
97
127
  for (const row of result.rows) {
98
128
  const payload = {
99
129
  id: row.id,
100
130
  ...parsePayload(row.payload),
101
131
  };
102
- if (this.matchesQuery(type, payload, query)) {
132
+ if (this.matchesQuery(type, payload, normalizedQuery)) {
103
133
  matches.push(payload);
104
134
  }
105
135
  }
136
+ this.logDuration('find', started, {
137
+ type,
138
+ keys: Object.keys(normalizedQuery),
139
+ pushdown,
140
+ matched: matches.length,
141
+ }, 50, 500);
106
142
  return matches;
107
143
  }
108
144
  async findIds(type, query) {
@@ -110,6 +146,7 @@ class DrizzleIndexedStorage {
110
146
  return rows.map((row) => row.id);
111
147
  }
112
148
  async set(type, value) {
149
+ const started = Date.now();
113
150
  await this.ensureTable();
114
151
  const tableNameId = drizzle_orm_1.sql.identifier([this.tableName]);
115
152
  const { id, ...rest } = value;
@@ -119,6 +156,7 @@ class DrizzleIndexedStorage {
119
156
  SET payload = ${this.toJsonSql(payload)}
120
157
  WHERE container = ${type} AND id = ${id}
121
158
  `);
159
+ this.logDuration('set', started, { type, id, fields: Object.keys(rest) }, 50, 500);
122
160
  }
123
161
  async setField(type, id, key, value) {
124
162
  const current = await this.get(type, id);
@@ -129,9 +167,11 @@ class DrizzleIndexedStorage {
129
167
  await this.set(type, updated);
130
168
  }
131
169
  async delete(type, id) {
170
+ const started = Date.now();
132
171
  await this.ensureTable();
133
172
  const tableNameId = drizzle_orm_1.sql.identifier([this.tableName]);
134
173
  await (0, db_1.executeStatement)(this.db, (0, drizzle_orm_1.sql) `DELETE FROM ${tableNameId} WHERE container = ${type} AND id = ${id}`);
174
+ this.logDuration('delete', started, { type, id }, 50, 500);
135
175
  }
136
176
  async *entries(type) {
137
177
  await this.ensureTable();
@@ -154,6 +194,59 @@ class DrizzleIndexedStorage {
154
194
  }
155
195
  return true;
156
196
  }
197
+ canPushDownQuery(query) {
198
+ return Object.values(query).every((value) => this.isSupportedQueryValue(value));
199
+ }
200
+ isSupportedQueryValue(value) {
201
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
202
+ }
203
+ buildWhereClause(type, query) {
204
+ const clauses = [(0, drizzle_orm_1.sql) `container = ${type}`];
205
+ for (const [key, value] of Object.entries(query)) {
206
+ if (key === 'id') {
207
+ clauses.push((0, drizzle_orm_1.sql) `id = ${String(value)}`);
208
+ continue;
209
+ }
210
+ if ((0, db_1.isDatabaseSqlite)(this.db)) {
211
+ const jsonPath = this.toSqliteJsonPath(key);
212
+ const wrappedValue = JSON.stringify({ value });
213
+ clauses.push((0, drizzle_orm_1.sql) `json_extract(payload, ${jsonPath}) = json_extract(${wrappedValue}, '$.value')`);
214
+ continue;
215
+ }
216
+ clauses.push((0, drizzle_orm_1.sql) `jsonb_extract_path_text(payload, ${key}) = ${String(value)}`);
217
+ }
218
+ return drizzle_orm_1.sql.join(clauses, (0, drizzle_orm_1.sql) ` AND `);
219
+ }
220
+ buildIndexName(type, key) {
221
+ const rawName = `${this.tableName}_${type}_${key}_idx`;
222
+ const normalized = rawName.replace(/[^a-zA-Z0-9_]/g, '_');
223
+ if (normalized.length <= 63) {
224
+ return normalized;
225
+ }
226
+ return `${normalized.slice(0, 54)}_${node_crypto_1.default.createHash('sha1').update(rawName).digest('hex').slice(0, 8)}`;
227
+ }
228
+ toSqliteJsonPath(key) {
229
+ const escaped = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
230
+ return `$."${escaped}"`;
231
+ }
232
+ escapeSqlLiteral(value) {
233
+ return value.replace(/'/g, "''");
234
+ }
235
+ logDuration(operation, started, details, slowThresholdMs = 100, warnThresholdMs = 1000) {
236
+ const elapsedMs = Date.now() - started;
237
+ if (elapsedMs < slowThresholdMs) {
238
+ return;
239
+ }
240
+ const detailText = Object.entries(details)
241
+ .map(([key, value]) => `${key}=${Array.isArray(value) ? value.join(',') : String(value)}`)
242
+ .join(' ');
243
+ const message = `[timing] DrizzleIndexedStorage.${operation} ${detailText} took=${elapsedMs}ms`;
244
+ if (elapsedMs >= warnThresholdMs) {
245
+ this.logger.warn(message);
246
+ return;
247
+ }
248
+ this.logger.info(message);
249
+ }
157
250
  }
158
251
  exports.DrizzleIndexedStorage = DrizzleIndexedStorage;
159
252
  //# sourceMappingURL=DrizzleIndexedStorage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrizzleIndexedStorage.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DrizzleIndexedStorage.ts"],"names":[],"mappings":"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","sourcesContent":["import { sql } from 'drizzle-orm';\nimport crypto from 'node:crypto';\nimport type {\n CreateTypeObject,\n IndexedQuery,\n IndexedStorage,\n IndexTypeCollection,\n StringKey,\n TypeObject,\n ValueTypeDescription,\n} from '@solid/community-server';\nimport { \n getIdentityDatabase, \n IdentityDatabase, \n executeQuery, \n executeStatement,\n isDatabaseSqlite \n} from './db';\n\nfunction serializePayload(value: Record<string, unknown>): string {\n return JSON.stringify(value ?? {});\n}\n\nfunction parsePayload<T>(value: unknown): T {\n if (value == null) {\n return {} as T;\n }\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return {} as T;\n }\n }\n return value as T;\n}\n\n/**\n * A generic Key-Value storage backed by a single SQL table.\n * Maps `type` (container) and `id` (key) to rows in the table.\n */\nexport class DrizzleIndexedStorage implements IndexedStorage<any> {\n private readonly db: IdentityDatabase;\n private readonly tableName: string;\n private readonly definitions: Map<string, Record<string, ValueTypeDescription<string>>> = new Map();\n private ready: boolean = false;\n\n public constructor(connectionString: string, tablePrefix = 'identity_') {\n this.db = getIdentityDatabase(connectionString);\n this.tableName = `${tablePrefix}store`;\n }\n\n private toJsonSql(payload: string): any {\n return isDatabaseSqlite(this.db) ? sql`${payload}` : sql`${payload}::jsonb`;\n }\n\n private async ensureTable(): Promise<void> {\n if (this.ready) return;\n \n const jsonType = isDatabaseSqlite(this.db) ? sql.raw('TEXT') : sql.raw('JSONB');\n const tableNameId = sql.identifier([this.tableName]);\n \n await executeStatement(this.db, sql`\n CREATE TABLE IF NOT EXISTS ${tableNameId} (\n container TEXT NOT NULL,\n id TEXT NOT NULL,\n payload ${jsonType} NOT NULL,\n PRIMARY KEY (container, id)\n )\n `);\n this.ready = true;\n }\n\n public async defineType(type: string, description: any): Promise<void> {\n this.definitions.set(type, description as Record<string, ValueTypeDescription<string>>);\n await this.ensureTable();\n }\n\n public async createIndex(): Promise<void> {\n // Indexes are not required for the current usage.\n }\n\n public async create(type: string, value: any): Promise<any> {\n await this.ensureTable();\n const id = crypto.randomUUID();\n const payload = serializePayload(value as Record<string, unknown>);\n const tableNameId = sql.identifier([this.tableName]);\n\n await executeStatement(this.db, sql`\n INSERT INTO ${tableNameId} (container, id, payload)\n VALUES (${type}, ${id}, ${this.toJsonSql(payload)})\n `);\n return { id, ...(value as Record<string, unknown>) };\n }\n\n public async has(type: string, id: string): Promise<boolean> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT 1 FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);\n return result.rows.length > 0;\n }\n\n public async get(type: string, id: string): Promise<any | undefined> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT payload FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);\n if (result.rows.length === 0) {\n return undefined;\n }\n const payload = parsePayload<Record<string, unknown>>(result.rows[0].payload);\n return { id, ...payload };\n }\n\n public async find(type: string, query: any): Promise<any[]> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n // Note: This fetches all rows for the container and filters in memory. \n // For high volume types, this should be optimized with JSON path queries if possible.\n const result = await executeQuery(this.db, sql`SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);\n const matches: any[] = [];\n for (const row of result.rows) {\n const payload = {\n id: row.id as string,\n ...parsePayload<Record<string, unknown>>(row.payload),\n };\n if (this.matchesQuery(type, payload, query as Record<string, unknown>)) {\n matches.push(payload);\n }\n }\n return matches;\n }\n\n public async findIds(type: string, query: any): Promise<string[]> {\n const rows = await this.find(type, query);\n return rows.map((row) => row.id as string);\n }\n\n public async set(type: string, value: any): Promise<void> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const { id, ...rest } = value as Record<string, unknown>;\n const payload = serializePayload(rest);\n \n await executeStatement(this.db, sql`\n UPDATE ${tableNameId}\n SET payload = ${this.toJsonSql(payload)}\n WHERE container = ${type} AND id = ${id}\n `);\n }\n\n public async setField(type: string, id: string, key: string, value: any): Promise<void> {\n const current = await this.get(type, id);\n if (!current) {\n return;\n }\n const updated = { ...current, [key]: value };\n await this.set(type, updated);\n }\n\n public async delete(type: string, id: string): Promise<void> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n await executeStatement(this.db, sql`DELETE FROM ${tableNameId} WHERE container = ${type} AND id = ${id}`);\n }\n\n public async *entries(type: string): AsyncIterableIterator<any> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);\n for (const row of result.rows) {\n yield {\n id: row.id as string,\n ...parsePayload<Record<string, unknown>>(row.payload),\n };\n }\n }\n\n private matchesQuery(type: string, payload: any, query: any): boolean {\n // Basic in-memory implementation of query matching\n // This mirrors MemoryIndexedStorage behavior for compatibility\n for (const key of Object.keys(query)) {\n if (payload[key] !== query[key]) {\n return false;\n }\n }\n return true;\n }\n}\n"]}
1
+ {"version":3,"file":"DrizzleIndexedStorage.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DrizzleIndexedStorage.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport { sql } from 'drizzle-orm';\nimport crypto from 'node:crypto';\nimport type {\n CreateTypeObject,\n IndexedQuery,\n IndexedStorage,\n IndexTypeCollection,\n StringKey,\n TypeObject,\n ValueTypeDescription,\n} from '@solid/community-server';\nimport { \n getIdentityDatabase, \n IdentityDatabase, \n executeQuery, \n executeStatement,\n isDatabaseSqlite \n} from './db';\n\nfunction serializePayload(value: Record<string, unknown>): string {\n return JSON.stringify(value ?? {});\n}\n\nfunction parsePayload<T>(value: unknown): T {\n if (value == null) {\n return {} as T;\n }\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return {} as T;\n }\n }\n return value as T;\n}\n\n/**\n * A generic Key-Value storage backed by a single SQL table.\n * Maps `type` (container) and `id` (key) to rows in the table.\n */\nexport class DrizzleIndexedStorage implements IndexedStorage<any> {\n private readonly logger = getLoggerFor(this);\n private readonly db: IdentityDatabase;\n private readonly tableName: string;\n private readonly definitions: Map<string, Record<string, ValueTypeDescription<string>>> = new Map();\n private readonly createdIndexes: Set<string> = new Set();\n private ready: boolean = false;\n\n public constructor(connectionString: string, tablePrefix = 'identity_') {\n this.db = getIdentityDatabase(connectionString);\n this.tableName = `${tablePrefix}store`;\n }\n\n private toJsonSql(payload: string): any {\n return isDatabaseSqlite(this.db) ? sql`${payload}` : sql`${payload}::jsonb`;\n }\n\n private async ensureTable(): Promise<void> {\n if (this.ready) return;\n \n const jsonType = isDatabaseSqlite(this.db) ? sql.raw('TEXT') : sql.raw('JSONB');\n const tableNameId = sql.identifier([this.tableName]);\n \n await executeStatement(this.db, sql`\n CREATE TABLE IF NOT EXISTS ${tableNameId} (\n container TEXT NOT NULL,\n id TEXT NOT NULL,\n payload ${jsonType} NOT NULL,\n PRIMARY KEY (container, id)\n )\n `);\n this.ready = true;\n }\n\n public async defineType(type: string, description: any): Promise<void> {\n this.definitions.set(type, description as Record<string, ValueTypeDescription<string>>);\n await this.ensureTable();\n }\n\n public async createIndex(type: string, key: string): Promise<void> {\n const started = Date.now();\n await this.ensureTable();\n if (key === 'id') {\n return;\n }\n\n const cacheKey = `${type}:${key}`;\n if (this.createdIndexes.has(cacheKey)) {\n return;\n }\n\n if (isDatabaseSqlite(this.db)) {\n const jsonPath = this.escapeSqlLiteral(this.toSqliteJsonPath(key));\n await executeStatement(this.db, sql.raw(\n `CREATE INDEX IF NOT EXISTS \"${this.buildIndexName(type, key)}\" ON \"${this.tableName}\" (container, json_extract(payload, '${jsonPath}'))`,\n ));\n } else {\n const escapedKey = this.escapeSqlLiteral(key);\n await executeStatement(this.db, sql.raw(\n `CREATE INDEX IF NOT EXISTS \"${this.buildIndexName(type, key)}\" ON \"${this.tableName}\" (container, (jsonb_extract_path_text(payload, '${escapedKey}')))` ,\n ));\n }\n\n this.createdIndexes.add(cacheKey);\n this.logDuration('createIndex', started, { type, key }, 50, 500);\n }\n\n public async create(type: string, value: any): Promise<any> {\n const started = Date.now();\n await this.ensureTable();\n const id = crypto.randomUUID();\n const payload = serializePayload(value as Record<string, unknown>);\n const tableNameId = sql.identifier([this.tableName]);\n\n await executeStatement(this.db, sql`\n INSERT INTO ${tableNameId} (container, id, payload)\n VALUES (${type}, ${id}, ${this.toJsonSql(payload)})\n `);\n this.logDuration('create', started, { type, fields: Object.keys(value ?? {}) }, 50, 500);\n return { id, ...(value as Record<string, unknown>) };\n }\n\n public async has(type: string, id: string): Promise<boolean> {\n const started = Date.now();\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT 1 FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);\n this.logDuration('has', started, { type, id }, 50, 500);\n return result.rows.length > 0;\n }\n\n public async get(type: string, id: string): Promise<any | undefined> {\n const started = Date.now();\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT payload FROM ${tableNameId} WHERE container = ${type} AND id = ${id} LIMIT 1`);\n this.logDuration('get', started, { type, id }, 50, 500);\n if (result.rows.length === 0) {\n return undefined;\n }\n const payload = parsePayload<Record<string, unknown>>(result.rows[0].payload);\n return { id, ...payload };\n }\n\n public async find(type: string, query: any): Promise<any[]> {\n const started = Date.now();\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const normalizedQuery = (query ?? {}) as Record<string, unknown>;\n const pushdown = this.canPushDownQuery(normalizedQuery);\n const result = pushdown\n ? await executeQuery(this.db, sql`SELECT id, payload FROM ${tableNameId} WHERE ${this.buildWhereClause(type, normalizedQuery)}`)\n : await executeQuery(this.db, sql`SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);\n const matches: any[] = [];\n for (const row of result.rows) {\n const payload = {\n id: row.id as string,\n ...parsePayload<Record<string, unknown>>(row.payload),\n };\n if (this.matchesQuery(type, payload, normalizedQuery)) {\n matches.push(payload);\n }\n }\n this.logDuration('find', started, {\n type,\n keys: Object.keys(normalizedQuery),\n pushdown,\n matched: matches.length,\n }, 50, 500);\n return matches;\n }\n\n public async findIds(type: string, query: any): Promise<string[]> {\n const rows = await this.find(type, query);\n return rows.map((row) => row.id as string);\n }\n\n public async set(type: string, value: any): Promise<void> {\n const started = Date.now();\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const { id, ...rest } = value as Record<string, unknown>;\n const payload = serializePayload(rest);\n \n await executeStatement(this.db, sql`\n UPDATE ${tableNameId}\n SET payload = ${this.toJsonSql(payload)}\n WHERE container = ${type} AND id = ${id}\n `);\n this.logDuration('set', started, { type, id, fields: Object.keys(rest) }, 50, 500);\n }\n\n public async setField(type: string, id: string, key: string, value: any): Promise<void> {\n const current = await this.get(type, id);\n if (!current) {\n return;\n }\n const updated = { ...current, [key]: value };\n await this.set(type, updated);\n }\n\n public async delete(type: string, id: string): Promise<void> {\n const started = Date.now();\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n await executeStatement(this.db, sql`DELETE FROM ${tableNameId} WHERE container = ${type} AND id = ${id}`);\n this.logDuration('delete', started, { type, id }, 50, 500);\n }\n\n public async *entries(type: string): AsyncIterableIterator<any> {\n await this.ensureTable();\n const tableNameId = sql.identifier([this.tableName]);\n const result = await executeQuery(this.db, sql`SELECT id, payload FROM ${tableNameId} WHERE container = ${type}`);\n for (const row of result.rows) {\n yield {\n id: row.id as string,\n ...parsePayload<Record<string, unknown>>(row.payload),\n };\n }\n }\n\n private matchesQuery(type: string, payload: any, query: any): boolean {\n // Basic in-memory implementation of query matching\n // This mirrors MemoryIndexedStorage behavior for compatibility\n for (const key of Object.keys(query)) {\n if (payload[key] !== query[key]) {\n return false;\n }\n }\n return true;\n }\n\n private canPushDownQuery(query: Record<string, unknown>): boolean {\n return Object.values(query).every((value) => this.isSupportedQueryValue(value));\n }\n\n private isSupportedQueryValue(value: unknown): value is string | number | boolean {\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';\n }\n\n private buildWhereClause(type: string, query: Record<string, unknown>): any {\n const clauses = [sql`container = ${type}`];\n\n for (const [key, value] of Object.entries(query)) {\n if (key === 'id') {\n clauses.push(sql`id = ${String(value)}`);\n continue;\n }\n\n if (isDatabaseSqlite(this.db)) {\n const jsonPath = this.toSqliteJsonPath(key);\n const wrappedValue = JSON.stringify({ value });\n clauses.push(sql`json_extract(payload, ${jsonPath}) = json_extract(${wrappedValue}, '$.value')`);\n continue;\n }\n\n clauses.push(sql`jsonb_extract_path_text(payload, ${key}) = ${String(value)}`);\n }\n\n return sql.join(clauses, sql` AND `);\n }\n\n private buildIndexName(type: string, key: string): string {\n const rawName = `${this.tableName}_${type}_${key}_idx`;\n const normalized = rawName.replace(/[^a-zA-Z0-9_]/g, '_');\n if (normalized.length <= 63) {\n return normalized;\n }\n\n return `${normalized.slice(0, 54)}_${crypto.createHash('sha1').update(rawName).digest('hex').slice(0, 8)}`;\n }\n\n private toSqliteJsonPath(key: string): string {\n const escaped = key.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `$.\"${escaped}\"`;\n }\n\n private escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n }\n\n private logDuration(\n operation: string,\n started: number,\n details: Record<string, unknown>,\n slowThresholdMs = 100,\n warnThresholdMs = 1000,\n ): void {\n const elapsedMs = Date.now() - started;\n if (elapsedMs < slowThresholdMs) {\n return;\n }\n\n const detailText = Object.entries(details)\n .map(([key, value]) => `${key}=${Array.isArray(value) ? value.join(',') : String(value)}`)\n .join(' ');\n const message = `[timing] DrizzleIndexedStorage.${operation} ${detailText} took=${elapsedMs}ms`;\n\n if (elapsedMs >= warnThresholdMs) {\n this.logger.warn(message);\n return;\n }\n\n this.logger.info(message);\n }\n}\n"]}
@@ -40,6 +40,10 @@
40
40
  }
41
41
  ],
42
42
  "memberFields": [
43
+ {
44
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_logger",
45
+ "memberFieldName": "logger"
46
+ },
43
47
  {
44
48
  "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_db",
45
49
  "memberFieldName": "db"
@@ -52,6 +56,10 @@
52
56
  "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_definitions",
53
57
  "memberFieldName": "definitions"
54
58
  },
59
+ {
60
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_createdIndexes",
61
+ "memberFieldName": "createdIndexes"
62
+ },
55
63
  {
56
64
  "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_ready",
57
65
  "memberFieldName": "ready"
@@ -115,6 +123,34 @@
115
123
  {
116
124
  "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_matchesQuery",
117
125
  "memberFieldName": "matchesQuery"
126
+ },
127
+ {
128
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_canPushDownQuery",
129
+ "memberFieldName": "canPushDownQuery"
130
+ },
131
+ {
132
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_isSupportedQueryValue",
133
+ "memberFieldName": "isSupportedQueryValue"
134
+ },
135
+ {
136
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_buildWhereClause",
137
+ "memberFieldName": "buildWhereClause"
138
+ },
139
+ {
140
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_buildIndexName",
141
+ "memberFieldName": "buildIndexName"
142
+ },
143
+ {
144
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_toSqliteJsonPath",
145
+ "memberFieldName": "toSqliteJsonPath"
146
+ },
147
+ {
148
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_escapeSqlLiteral",
149
+ "memberFieldName": "escapeSqlLiteral"
150
+ },
151
+ {
152
+ "@id": "undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld#DrizzleIndexedStorage__member_logDuration",
153
+ "memberFieldName": "logDuration"
118
154
  }
119
155
  ],
120
156
  "constructorArguments": [
@@ -46,11 +46,11 @@ export declare class EdgeNodeRepository {
46
46
  private readonly db;
47
47
  constructor(db: IdentityDatabase);
48
48
  listNodes(): Promise<EdgeNodeSummary[]>;
49
- createNode(displayName?: string, accountId?: string): Promise<CreateEdgeNodeResult>;
49
+ createNode(displayName?: string, _accountId?: string): Promise<CreateEdgeNodeResult>;
50
50
  /**
51
- * Get the owner account ID of a node.
51
+ * Node/account 关系待产品化后单独建模;当前阶段不再在节点表上持久化账号归属。
52
52
  */
53
- getNodeOwner(nodeId: string): Promise<string | undefined>;
53
+ getNodeOwner(_nodeId: string): Promise<string | undefined>;
54
54
  getNodeSecret(nodeId: string): Promise<EdgeNodeSecret | undefined>;
55
55
  updateNodeHeartbeat(nodeId: string, metadata: Record<string, unknown> | null, timestamp: Date): Promise<void>;
56
56
  updateNodeMode(nodeId: string, options: {
@@ -175,7 +175,6 @@ export declare class EdgeNodeRepository {
175
175
  registerSpNode(options: {
176
176
  publicUrl: string;
177
177
  displayName?: string;
178
- ownerAccountId?: string;
179
178
  /** SP 提供的设备 ID,作为 nodeId(不传则随机生成) */
180
179
  nodeId?: string;
181
180
  /** SP 提供的 serviceToken,不传则随机生成 */
@@ -43,15 +43,15 @@ class EdgeNodeRepository {
43
43
  };
44
44
  });
45
45
  }
46
- async createNode(displayName, accountId) {
46
+ async createNode(displayName, _accountId) {
47
47
  const nodeId = (0, node_crypto_1.randomUUID)();
48
48
  const token = (0, node_crypto_1.randomBytes)(32).toString('base64url');
49
49
  const tokenHash = (0, node_crypto_1.createHash)('sha256').update(token).digest('hex');
50
50
  const now = new Date();
51
51
  const ts = (0, db_1.toDbTimestamp)(this.db, now);
52
52
  await (0, db_1.executeStatement)(this.db, (0, drizzle_orm_1.sql) `
53
- INSERT INTO identity_edge_node (id, display_name, owner_account_id, token_hash, created_at, updated_at)
54
- VALUES (${nodeId}, ${displayName ?? null}, ${accountId ?? null}, ${tokenHash}, ${ts}, ${ts})
53
+ INSERT INTO identity_edge_node (id, display_name, token_hash, created_at, updated_at)
54
+ VALUES (${nodeId}, ${displayName ?? null}, ${tokenHash}, ${ts}, ${ts})
55
55
  `);
56
56
  return {
57
57
  nodeId,
@@ -60,19 +60,10 @@ class EdgeNodeRepository {
60
60
  };
61
61
  }
62
62
  /**
63
- * Get the owner account ID of a node.
63
+ * Node/account 关系待产品化后单独建模;当前阶段不再在节点表上持久化账号归属。
64
64
  */
65
- async getNodeOwner(nodeId) {
66
- const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
67
- SELECT owner_account_id
68
- FROM identity_edge_node
69
- WHERE id = ${nodeId}
70
- LIMIT 1
71
- `);
72
- if (result.rows.length === 0) {
73
- return undefined;
74
- }
75
- return result.rows[0].owner_account_id ? String(result.rows[0].owner_account_id) : undefined;
65
+ async getNodeOwner(_nodeId) {
66
+ return undefined;
76
67
  }
77
68
  async getNodeSecret(nodeId) {
78
69
  const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
@@ -444,25 +435,8 @@ class EdgeNodeRepository {
444
435
  * List nodes owned by a specific account
445
436
  */
446
437
  async listNodesByAccount(accountId) {
447
- const result = await (0, db_1.executeQuery)(this.db, (0, drizzle_orm_1.sql) `
448
- SELECT id, display_name, capabilities, metadata, access_mode, last_seen, connectivity_status
449
- FROM identity_edge_node
450
- WHERE owner_account_id = ${accountId} AND node_type = 'edge'
451
- ORDER BY created_at DESC
452
- `);
453
- return result.rows.map((row) => {
454
- const metadata = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata;
455
- const capabilities = typeof row.capabilities === 'string' ? JSON.parse(row.capabilities) : row.capabilities;
456
- return {
457
- nodeId: String(row.id),
458
- displayName: row.display_name ?? undefined,
459
- capabilities: capabilities ?? null,
460
- stringCapabilities: metadata?.capabilities ?? null,
461
- accessMode: row.access_mode ?? null,
462
- lastSeen: (0, db_1.fromDbTimestamp)(row.last_seen) ?? null,
463
- connectivityStatus: row.connectivity_status ?? null,
464
- };
465
- });
438
+ void accountId;
439
+ return [];
466
440
  }
467
441
  /**
468
442
  * Delete a node
@@ -497,13 +471,12 @@ class EdgeNodeRepository {
497
471
  const ts = (0, db_1.toDbTimestamp)(this.db, now);
498
472
  await (0, db_1.executeStatement)(this.db, (0, drizzle_orm_1.sql) `
499
473
  INSERT INTO identity_edge_node (
500
- id, display_name, owner_account_id, token_hash, service_token_hash,
474
+ id, display_name, token_hash, service_token_hash,
501
475
  node_type, public_url,
502
476
  connectivity_status, created_at, updated_at
503
477
  )
504
478
  VALUES (
505
- ${nodeId}, ${options.displayName ?? null}, ${options.ownerAccountId ?? null},
506
- ${nodeTokenHash}, ${serviceToken},
479
+ ${nodeId}, ${options.displayName ?? null}, ${nodeTokenHash}, ${serviceToken},
507
480
  'sp', ${options.publicUrl}, 'unknown', ${ts}, ${ts}
508
481
  )
509
482
  ON CONFLICT (id) DO UPDATE SET
@@ -1 +1 @@
1
- {"version":3,"file":"EdgeNodeRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/EdgeNodeRepository.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes, randomUUID, createHash, timingSafeEqual } from 'node:crypto';\nimport { sql, eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { executeStatement, executeQuery, toDbTimestamp, fromDbTimestamp } from './db';\nimport { edgeNodes } from './schema';\n\nexport interface EdgeNodeSummary {\n nodeId: string;\n displayName?: string;\n nodeType: 'center' | 'edge' | 'sp';\n podCount: number;\n createdAt?: string;\n updatedAt?: string;\n lastSeen?: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface CreateEdgeNodeResult {\n nodeId: string;\n token: string;\n createdAt: string;\n}\n\nexport interface EdgeNodeSecret {\n nodeId: string;\n displayName?: string;\n tokenHash: string;\n nodeType: 'center' | 'edge' | 'sp';\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface CenterNodeInfo {\n nodeId: string;\n displayName?: string;\n internalIp: string;\n internalPort: number;\n connectivityStatus: 'unknown' | 'reachable' | 'unreachable';\n lastSeen?: Date;\n}\n\nexport interface CreateSpNodeResult {\n nodeId: string;\n nodeToken: string;\n serviceToken: string;\n createdAt: string;\n}\n\nexport interface SpNodeInfo {\n nodeId: string;\n displayName?: string;\n publicUrl: string;\n serviceTokenHash: string;\n lastSeen?: Date;\n}\n\nexport class EdgeNodeRepository {\n public constructor(private readonly db: IdentityDatabase) {}\n\n public async listNodes(): Promise<EdgeNodeSummary[]> {\n const result = await executeQuery(this.db, sql`\n SELECT en.id,\n en.display_name,\n en.node_type,\n en.created_at,\n en.updated_at,\n en.last_seen,\n en.metadata,\n COALESCE(pods.count, 0) AS pod_count\n FROM identity_edge_node en\n LEFT JOIN (\n SELECT node_id, COUNT(*) AS count\n FROM identity_edge_node_pod\n GROUP BY node_id\n ) pods ON pods.node_id = en.id\n ORDER BY en.created_at ASC\n `);\n\n return result.rows.map((row: any): EdgeNodeSummary => {\n const createdAt = fromDbTimestamp(row.created_at);\n const updatedAt = fromDbTimestamp(row.updated_at);\n const lastSeen = fromDbTimestamp(row.last_seen);\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n nodeType: (['center', 'edge', 'sp'].includes(row.node_type) ? row.node_type : 'edge') as 'center' | 'edge' | 'sp',\n podCount: Number(row.pod_count ?? 0),\n createdAt: createdAt?.toISOString(),\n updatedAt: updatedAt?.toISOString(),\n lastSeen: lastSeen?.toISOString(),\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n };\n });\n }\n\n public async createNode(displayName?: string, accountId?: string): Promise<CreateEdgeNodeResult> {\n const nodeId = randomUUID();\n const token = randomBytes(32).toString('base64url');\n const tokenHash = createHash('sha256').update(token).digest('hex');\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (id, display_name, owner_account_id, token_hash, created_at, updated_at)\n VALUES (${nodeId}, ${displayName ?? null}, ${accountId ?? null}, ${tokenHash}, ${ts}, ${ts})\n `);\n\n return {\n nodeId,\n token,\n createdAt: now.toISOString(),\n };\n }\n\n /**\n * Get the owner account ID of a node.\n */\n public async getNodeOwner(nodeId: string): Promise<string | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT owner_account_id\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n \n if (result.rows.length === 0) {\n return undefined;\n }\n \n return result.rows[0].owner_account_id ? String(result.rows[0].owner_account_id) : undefined;\n }\n\n public async getNodeSecret(nodeId: string): Promise<EdgeNodeSecret | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, token_hash, node_type, metadata\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n tokenHash: String(row.token_hash ?? ''),\n nodeType: (['center', 'edge', 'sp'].includes(row.node_type) ? row.node_type : 'edge') as 'center' | 'edge' | 'sp',\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n };\n }\n\n public async updateNodeHeartbeat(nodeId: string, metadata: Record<string, unknown> | null, timestamp: Date): Promise<void> {\n const payload = metadata == null ? null : JSON.stringify(metadata);\n const ts = toDbTimestamp(this.db, timestamp);\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET metadata = ${payload},\n last_seen = ${ts},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async updateNodeMode(nodeId: string, options: {\n accessMode: 'direct' | 'proxy';\n ipv4?: string;\n publicPort?: number;\n subdomain?: string;\n connectivityStatus?: 'unknown' | 'reachable' | 'unreachable';\n capabilities?: Record<string, unknown>;\n }): Promise<void> {\n const capabilitiesPayload = options.capabilities ? JSON.stringify(options.capabilities) : null;\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET access_mode = ${options.accessMode},\n ipv4 = ${options.ipv4 ?? null},\n public_port = ${options.publicPort ?? null},\n subdomain = ${options.subdomain ?? null},\n connectivity_status = ${options.connectivityStatus ?? 'unknown'},\n capabilities = ${capabilitiesPayload},\n last_connectivity_check = ${ts},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async getNodeConnectivityInfo(nodeId: string): Promise<{\n nodeId: string;\n accessMode?: string;\n ipv4?: string;\n publicPort?: number;\n subdomain?: string;\n connectivityStatus?: string;\n lastConnectivityCheck?: Date;\n } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, access_mode, ipv4, public_port, subdomain,\n connectivity_status, last_connectivity_check\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n ipv4: row.ipv4 ? String(row.ipv4) : undefined,\n publicPort: row.public_port ? Number(row.public_port) : undefined,\n subdomain: row.subdomain ? String(row.subdomain) : undefined,\n connectivityStatus: row.connectivity_status ? String(row.connectivity_status) : undefined,\n lastConnectivityCheck: fromDbTimestamp(row.last_connectivity_check),\n };\n }\n\n public async mergeNodeMetadata(nodeId: string, patch: Record<string, unknown>): Promise<void> {\n // Read current metadata\n const current = await this.getNodeMetadata(nodeId);\n if (!current) {\n throw new Error(`Node ${nodeId} not found`);\n }\n\n // Merge in application layer\n const merged = { ...(current.metadata ?? {}), ...patch };\n const payload = JSON.stringify(merged);\n const ts = toDbTimestamp(this.db, new Date());\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET metadata = ${payload},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async getNodeMetadata(nodeId: string): Promise<{ nodeId: string; metadata: Record<string, unknown> | null; lastSeen?: Date } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, metadata, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n public async replaceNodePods(nodeId: string, pods: string[]): Promise<void> {\n await this.db.transaction(async (tx: IdentityDatabase) => {\n await tx.execute(sql`DELETE FROM identity_edge_node_pod WHERE node_id = ${nodeId}`);\n if (pods.length > 0) {\n const values = pods.map((baseUrl) => sql`(${nodeId}, ${baseUrl})`);\n await tx.execute(sql`\n INSERT INTO identity_edge_node_pod (node_id, base_url)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT DO NOTHING\n `);\n }\n });\n }\n\n public async findNodeByResourcePath(path: string): Promise<{ nodeId: string; baseUrl: string; accessMode?: string; metadata?: Record<string, unknown> | null } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT en.id,\n en.access_mode,\n en.metadata,\n pods.base_url\n FROM identity_edge_node_pod pods\n JOIN identity_edge_node en ON en.id = pods.node_id\n WHERE ${path} LIKE pods.base_url || '%'\n ORDER BY length(pods.base_url) DESC\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n baseUrl: String(row.base_url),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n metadata: row.metadata ?? null,\n };\n }\n\n public async findNodeBySubdomain(hostname: string): Promise<{ nodeId: string; accessMode?: string; metadata?: Record<string, unknown> | null; subdomain?: string } | undefined> {\n const normalized = hostname.trim().toLowerCase();\n if (normalized.length === 0) {\n return undefined;\n }\n const result = await executeQuery(this.db, sql`\n SELECT id, access_mode, metadata, subdomain\n FROM identity_edge_node\n WHERE subdomain = ${normalized}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n metadata: row.metadata ?? null,\n subdomain: row.subdomain ? String(row.subdomain) : undefined,\n };\n }\n\n public matchesToken(tokenHash: string, token: string): boolean {\n if (!tokenHash || typeof tokenHash !== 'string') {\n return false;\n }\n try {\n const expected = Buffer.from(tokenHash, 'hex');\n const actual = createHash('sha256').update(token).digest();\n if (expected.length !== actual.length) {\n return false;\n }\n return timingSafeEqual(expected, actual);\n } catch {\n return false;\n }\n }\n\n /**\n * Get node capabilities and related information for admin queries\n */\n public async getNodeCapabilities(nodeId: string): Promise<{\n nodeId: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n } | undefined> {\n const row = await this.db\n .select({\n id: edgeNodes.id,\n capabilities: edgeNodes.capabilities,\n metadata: edgeNodes.metadata,\n accessMode: edgeNodes.accessMode,\n lastSeen: edgeNodes.lastSeen,\n connectivityStatus: edgeNodes.connectivityStatus,\n })\n .from(edgeNodes)\n .where(eq(edgeNodes.id, nodeId))\n .limit(1);\n\n if (row.length === 0) {\n return undefined;\n }\n\n const node = row[0];\n const metadata = node.metadata as Record<string, unknown> | null;\n \n return {\n nodeId: node.id,\n capabilities: node.capabilities as Record<string, unknown> | null,\n stringCapabilities: metadata?.capabilities as string[] ?? null,\n accessMode: node.accessMode,\n lastSeen: node.lastSeen,\n connectivityStatus: node.connectivityStatus,\n };\n }\n\n /**\n * List all nodes with their capability information\n */\n public async listNodeCapabilities(): Promise<Array<{\n nodeId: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n }>> {\n const rows = await this.db\n .select({\n id: edgeNodes.id,\n capabilities: edgeNodes.capabilities,\n metadata: edgeNodes.metadata,\n accessMode: edgeNodes.accessMode,\n lastSeen: edgeNodes.lastSeen,\n connectivityStatus: edgeNodes.connectivityStatus,\n })\n .from(edgeNodes)\n .orderBy(edgeNodes.lastSeen);\n\n return rows.map((row: typeof rows[0]) => {\n const metadata = row.metadata as Record<string, unknown> | null;\n \n return {\n nodeId: row.id,\n capabilities: row.capabilities as Record<string, unknown> | null,\n stringCapabilities: metadata?.capabilities as string[] ?? null,\n accessMode: row.accessMode,\n lastSeen: row.lastSeen,\n connectivityStatus: row.connectivityStatus,\n };\n });\n }\n\n // ============ Center Node Methods ============\n\n /**\n * Register or update a center node in the cluster.\n * Center nodes use the same table as edge nodes but with nodeType='center'.\n */\n public async registerCenterNode(options: {\n nodeId: string;\n displayName?: string;\n internalIp: string;\n internalPort: number;\n }): Promise<{ nodeId: string; token: string }> {\n const token = randomBytes(32).toString('base64url');\n const tokenHash = createHash('sha256').update(token).digest('hex');\n const now = Math.floor(Date.now() / 1000); // Unix timestamp for SQLite compatibility\n\n // Use upsert pattern: INSERT ... ON CONFLICT UPDATE\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (\n id, display_name, token_hash, node_type, internal_ip, internal_port,\n connectivity_status, created_at, updated_at, last_seen\n )\n VALUES (\n ${options.nodeId}, ${options.displayName ?? null}, ${tokenHash}, 'center',\n ${options.internalIp}, ${options.internalPort}, 'unknown', ${now}, ${now}, ${now}\n )\n ON CONFLICT (id) DO UPDATE SET\n display_name = EXCLUDED.display_name,\n internal_ip = EXCLUDED.internal_ip,\n internal_port = EXCLUDED.internal_port,\n updated_at = EXCLUDED.updated_at,\n last_seen = EXCLUDED.last_seen\n `);\n\n return { nodeId: options.nodeId, token };\n }\n\n /**\n * Update center node heartbeat with internal endpoint info.\n */\n public async updateCenterNodeHeartbeat(\n nodeId: string,\n internalIp: string,\n internalPort: number,\n timestamp: Date,\n ): Promise<void> {\n const ts = Math.floor(timestamp.getTime() / 1000); // Unix timestamp for SQLite compatibility\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET internal_ip = ${internalIp},\n internal_port = ${internalPort},\n last_seen = ${ts},\n updated_at = ${ts},\n connectivity_status = 'reachable'\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n }\n\n /**\n * List all center nodes in the cluster.\n */\n public async listCenterNodes(): Promise<CenterNodeInfo[]> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE node_type = 'center'\n ORDER BY created_at ASC\n `);\n\n return result.rows.map((row: any): CenterNodeInfo => ({\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n }));\n }\n\n /**\n * Get a specific center node by ID.\n */\n public async getCenterNode(nodeId: string): Promise<CenterNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'center'\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n /**\n * Find a center node by its internal endpoint (for routing).\n */\n public async findCenterNodeByEndpoint(internalIp: string, internalPort: number): Promise<CenterNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE node_type = 'center' AND internal_ip = ${internalIp} AND internal_port = ${internalPort}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n /**\n * Mark a center node as unreachable (for health checks).\n */\n public async markCenterNodeUnreachable(nodeId: string): Promise<void> {\n const ts = toDbTimestamp(this.db, new Date());\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET connectivity_status = 'unreachable',\n updated_at = ${ts}\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n }\n\n /**\n * Remove a center node from the cluster.\n */\n public async removeCenterNode(nodeId: string): Promise<boolean> {\n // Note: For SQLite, we can't easily get affected row count, so just execute and return true\n await executeStatement(this.db, sql`\n DELETE FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n return true;\n }\n\n // ============ Account-based Node Methods ============\n\n /**\n * List nodes owned by a specific account\n */\n public async listNodesByAccount(accountId: string): Promise<Array<{\n nodeId: string;\n displayName?: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n }>> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, capabilities, metadata, access_mode, last_seen, connectivity_status\n FROM identity_edge_node\n WHERE owner_account_id = ${accountId} AND node_type = 'edge'\n ORDER BY created_at DESC\n `);\n\n return result.rows.map((row: any) => {\n const metadata = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata as Record<string, unknown> | null);\n const capabilities = typeof row.capabilities === 'string' ? JSON.parse(row.capabilities) : (row.capabilities as Record<string, unknown> | null);\n return {\n nodeId: String(row.id),\n displayName: row.display_name ?? undefined,\n capabilities: capabilities ?? null,\n stringCapabilities: (metadata?.capabilities as string[] | undefined) ?? null,\n accessMode: row.access_mode ?? null,\n lastSeen: fromDbTimestamp(row.last_seen) ?? null,\n connectivityStatus: row.connectivity_status ?? null,\n };\n });\n }\n\n /**\n * Delete a node\n */\n public async deleteNode(nodeId: string): Promise<boolean> {\n // First delete associated pods\n await executeStatement(this.db, sql`\n DELETE FROM identity_edge_node_pod WHERE node_id = ${nodeId}\n `);\n\n // Then delete the node\n const result = await executeQuery(this.db, sql`\n DELETE FROM identity_edge_node\n WHERE id = ${nodeId}\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n // ============ SP (Storage Provider) Node Methods ============\n\n /**\n * Register or update an SP node (UPSERT by nodeId).\n *\n * SP 本地生成 deviceId 作为 nodeId,注册时带上来。\n * 同一 nodeId 重复注册时更新 publicUrl、token 等,保留原记录。\n * 不传 nodeId 则 Cloud 随机分配。\n */\n public async registerSpNode(options: {\n publicUrl: string;\n displayName?: string;\n ownerAccountId?: string;\n /** SP 提供的设备 ID,作为 nodeId(不传则随机生成) */\n nodeId?: string;\n /** SP 提供的 serviceToken,不传则随机生成 */\n serviceToken?: string;\n }): Promise<CreateSpNodeResult> {\n const nodeId = options.nodeId || randomUUID();\n const nodeToken = randomBytes(32).toString('base64url');\n const nodeTokenHash = createHash('sha256').update(nodeToken).digest('hex');\n const serviceToken = options.serviceToken || randomBytes(32).toString('base64url');\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (\n id, display_name, owner_account_id, token_hash, service_token_hash,\n node_type, public_url,\n connectivity_status, created_at, updated_at\n )\n VALUES (\n ${nodeId}, ${options.displayName ?? null}, ${options.ownerAccountId ?? null},\n ${nodeTokenHash}, ${serviceToken},\n 'sp', ${options.publicUrl}, 'unknown', ${ts}, ${ts}\n )\n ON CONFLICT (id) DO UPDATE SET\n display_name = EXCLUDED.display_name,\n token_hash = EXCLUDED.token_hash,\n service_token_hash = EXCLUDED.service_token_hash,\n public_url = EXCLUDED.public_url,\n updated_at = EXCLUDED.updated_at\n `);\n\n return {\n nodeId,\n nodeToken,\n serviceToken,\n createdAt: now.toISOString(),\n };\n }\n\n /**\n * Get SP node info by nodeId.\n */\n public async getSpNode(nodeId: string): Promise<SpNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, public_url, service_token_hash, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'sp'\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n publicUrl: String(row.public_url ?? ''),\n serviceTokenHash: String(row.service_token_hash ?? ''),\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n}\n"]}
1
+ {"version":3,"file":"EdgeNodeRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/EdgeNodeRepository.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes, randomUUID, createHash, timingSafeEqual } from 'node:crypto';\nimport { sql, eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { executeStatement, executeQuery, toDbTimestamp, fromDbTimestamp } from './db';\nimport { edgeNodes } from './schema';\n\nexport interface EdgeNodeSummary {\n nodeId: string;\n displayName?: string;\n nodeType: 'center' | 'edge' | 'sp';\n podCount: number;\n createdAt?: string;\n updatedAt?: string;\n lastSeen?: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface CreateEdgeNodeResult {\n nodeId: string;\n token: string;\n createdAt: string;\n}\n\nexport interface EdgeNodeSecret {\n nodeId: string;\n displayName?: string;\n tokenHash: string;\n nodeType: 'center' | 'edge' | 'sp';\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface CenterNodeInfo {\n nodeId: string;\n displayName?: string;\n internalIp: string;\n internalPort: number;\n connectivityStatus: 'unknown' | 'reachable' | 'unreachable';\n lastSeen?: Date;\n}\n\nexport interface CreateSpNodeResult {\n nodeId: string;\n nodeToken: string;\n serviceToken: string;\n createdAt: string;\n}\n\nexport interface SpNodeInfo {\n nodeId: string;\n displayName?: string;\n publicUrl: string;\n serviceTokenHash: string;\n lastSeen?: Date;\n}\n\nexport class EdgeNodeRepository {\n public constructor(private readonly db: IdentityDatabase) {}\n\n public async listNodes(): Promise<EdgeNodeSummary[]> {\n const result = await executeQuery(this.db, sql`\n SELECT en.id,\n en.display_name,\n en.node_type,\n en.created_at,\n en.updated_at,\n en.last_seen,\n en.metadata,\n COALESCE(pods.count, 0) AS pod_count\n FROM identity_edge_node en\n LEFT JOIN (\n SELECT node_id, COUNT(*) AS count\n FROM identity_edge_node_pod\n GROUP BY node_id\n ) pods ON pods.node_id = en.id\n ORDER BY en.created_at ASC\n `);\n\n return result.rows.map((row: any): EdgeNodeSummary => {\n const createdAt = fromDbTimestamp(row.created_at);\n const updatedAt = fromDbTimestamp(row.updated_at);\n const lastSeen = fromDbTimestamp(row.last_seen);\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n nodeType: (['center', 'edge', 'sp'].includes(row.node_type) ? row.node_type : 'edge') as 'center' | 'edge' | 'sp',\n podCount: Number(row.pod_count ?? 0),\n createdAt: createdAt?.toISOString(),\n updatedAt: updatedAt?.toISOString(),\n lastSeen: lastSeen?.toISOString(),\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n };\n });\n }\n\n public async createNode(displayName?: string, _accountId?: string): Promise<CreateEdgeNodeResult> {\n const nodeId = randomUUID();\n const token = randomBytes(32).toString('base64url');\n const tokenHash = createHash('sha256').update(token).digest('hex');\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (id, display_name, token_hash, created_at, updated_at)\n VALUES (${nodeId}, ${displayName ?? null}, ${tokenHash}, ${ts}, ${ts})\n `);\n\n return {\n nodeId,\n token,\n createdAt: now.toISOString(),\n };\n }\n\n /**\n * Node/account 关系待产品化后单独建模;当前阶段不再在节点表上持久化账号归属。\n */\n public async getNodeOwner(_nodeId: string): Promise<string | undefined> {\n return undefined;\n }\n\n public async getNodeSecret(nodeId: string): Promise<EdgeNodeSecret | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, token_hash, node_type, metadata\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n tokenHash: String(row.token_hash ?? ''),\n nodeType: (['center', 'edge', 'sp'].includes(row.node_type) ? row.node_type : 'edge') as 'center' | 'edge' | 'sp',\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n };\n }\n\n public async updateNodeHeartbeat(nodeId: string, metadata: Record<string, unknown> | null, timestamp: Date): Promise<void> {\n const payload = metadata == null ? null : JSON.stringify(metadata);\n const ts = toDbTimestamp(this.db, timestamp);\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET metadata = ${payload},\n last_seen = ${ts},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async updateNodeMode(nodeId: string, options: {\n accessMode: 'direct' | 'proxy';\n ipv4?: string;\n publicPort?: number;\n subdomain?: string;\n connectivityStatus?: 'unknown' | 'reachable' | 'unreachable';\n capabilities?: Record<string, unknown>;\n }): Promise<void> {\n const capabilitiesPayload = options.capabilities ? JSON.stringify(options.capabilities) : null;\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET access_mode = ${options.accessMode},\n ipv4 = ${options.ipv4 ?? null},\n public_port = ${options.publicPort ?? null},\n subdomain = ${options.subdomain ?? null},\n connectivity_status = ${options.connectivityStatus ?? 'unknown'},\n capabilities = ${capabilitiesPayload},\n last_connectivity_check = ${ts},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async getNodeConnectivityInfo(nodeId: string): Promise<{\n nodeId: string;\n accessMode?: string;\n ipv4?: string;\n publicPort?: number;\n subdomain?: string;\n connectivityStatus?: string;\n lastConnectivityCheck?: Date;\n } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, access_mode, ipv4, public_port, subdomain,\n connectivity_status, last_connectivity_check\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n ipv4: row.ipv4 ? String(row.ipv4) : undefined,\n publicPort: row.public_port ? Number(row.public_port) : undefined,\n subdomain: row.subdomain ? String(row.subdomain) : undefined,\n connectivityStatus: row.connectivity_status ? String(row.connectivity_status) : undefined,\n lastConnectivityCheck: fromDbTimestamp(row.last_connectivity_check),\n };\n }\n\n public async mergeNodeMetadata(nodeId: string, patch: Record<string, unknown>): Promise<void> {\n // Read current metadata\n const current = await this.getNodeMetadata(nodeId);\n if (!current) {\n throw new Error(`Node ${nodeId} not found`);\n }\n\n // Merge in application layer\n const merged = { ...(current.metadata ?? {}), ...patch };\n const payload = JSON.stringify(merged);\n const ts = toDbTimestamp(this.db, new Date());\n\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET metadata = ${payload},\n updated_at = ${ts}\n WHERE id = ${nodeId}\n `);\n }\n\n public async getNodeMetadata(nodeId: string): Promise<{ nodeId: string; metadata: Record<string, unknown> | null; lastSeen?: Date } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, metadata, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? null),\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n public async replaceNodePods(nodeId: string, pods: string[]): Promise<void> {\n await this.db.transaction(async (tx: IdentityDatabase) => {\n await tx.execute(sql`DELETE FROM identity_edge_node_pod WHERE node_id = ${nodeId}`);\n if (pods.length > 0) {\n const values = pods.map((baseUrl) => sql`(${nodeId}, ${baseUrl})`);\n await tx.execute(sql`\n INSERT INTO identity_edge_node_pod (node_id, base_url)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT DO NOTHING\n `);\n }\n });\n }\n\n public async findNodeByResourcePath(path: string): Promise<{ nodeId: string; baseUrl: string; accessMode?: string; metadata?: Record<string, unknown> | null } | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT en.id,\n en.access_mode,\n en.metadata,\n pods.base_url\n FROM identity_edge_node_pod pods\n JOIN identity_edge_node en ON en.id = pods.node_id\n WHERE ${path} LIKE pods.base_url || '%'\n ORDER BY length(pods.base_url) DESC\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n baseUrl: String(row.base_url),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n metadata: row.metadata ?? null,\n };\n }\n\n public async findNodeBySubdomain(hostname: string): Promise<{ nodeId: string; accessMode?: string; metadata?: Record<string, unknown> | null; subdomain?: string } | undefined> {\n const normalized = hostname.trim().toLowerCase();\n if (normalized.length === 0) {\n return undefined;\n }\n const result = await executeQuery(this.db, sql`\n SELECT id, access_mode, metadata, subdomain\n FROM identity_edge_node\n WHERE subdomain = ${normalized}\n LIMIT 1\n `);\n if (result.rows.length === 0) {\n return undefined;\n }\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n accessMode: row.access_mode ? String(row.access_mode) : undefined,\n metadata: row.metadata ?? null,\n subdomain: row.subdomain ? String(row.subdomain) : undefined,\n };\n }\n\n public matchesToken(tokenHash: string, token: string): boolean {\n if (!tokenHash || typeof tokenHash !== 'string') {\n return false;\n }\n try {\n const expected = Buffer.from(tokenHash, 'hex');\n const actual = createHash('sha256').update(token).digest();\n if (expected.length !== actual.length) {\n return false;\n }\n return timingSafeEqual(expected, actual);\n } catch {\n return false;\n }\n }\n\n /**\n * Get node capabilities and related information for admin queries\n */\n public async getNodeCapabilities(nodeId: string): Promise<{\n nodeId: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n } | undefined> {\n const row = await this.db\n .select({\n id: edgeNodes.id,\n capabilities: edgeNodes.capabilities,\n metadata: edgeNodes.metadata,\n accessMode: edgeNodes.accessMode,\n lastSeen: edgeNodes.lastSeen,\n connectivityStatus: edgeNodes.connectivityStatus,\n })\n .from(edgeNodes)\n .where(eq(edgeNodes.id, nodeId))\n .limit(1);\n\n if (row.length === 0) {\n return undefined;\n }\n\n const node = row[0];\n const metadata = node.metadata as Record<string, unknown> | null;\n \n return {\n nodeId: node.id,\n capabilities: node.capabilities as Record<string, unknown> | null,\n stringCapabilities: metadata?.capabilities as string[] ?? null,\n accessMode: node.accessMode,\n lastSeen: node.lastSeen,\n connectivityStatus: node.connectivityStatus,\n };\n }\n\n /**\n * List all nodes with their capability information\n */\n public async listNodeCapabilities(): Promise<Array<{\n nodeId: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n }>> {\n const rows = await this.db\n .select({\n id: edgeNodes.id,\n capabilities: edgeNodes.capabilities,\n metadata: edgeNodes.metadata,\n accessMode: edgeNodes.accessMode,\n lastSeen: edgeNodes.lastSeen,\n connectivityStatus: edgeNodes.connectivityStatus,\n })\n .from(edgeNodes)\n .orderBy(edgeNodes.lastSeen);\n\n return rows.map((row: typeof rows[0]) => {\n const metadata = row.metadata as Record<string, unknown> | null;\n \n return {\n nodeId: row.id,\n capabilities: row.capabilities as Record<string, unknown> | null,\n stringCapabilities: metadata?.capabilities as string[] ?? null,\n accessMode: row.accessMode,\n lastSeen: row.lastSeen,\n connectivityStatus: row.connectivityStatus,\n };\n });\n }\n\n // ============ Center Node Methods ============\n\n /**\n * Register or update a center node in the cluster.\n * Center nodes use the same table as edge nodes but with nodeType='center'.\n */\n public async registerCenterNode(options: {\n nodeId: string;\n displayName?: string;\n internalIp: string;\n internalPort: number;\n }): Promise<{ nodeId: string; token: string }> {\n const token = randomBytes(32).toString('base64url');\n const tokenHash = createHash('sha256').update(token).digest('hex');\n const now = Math.floor(Date.now() / 1000); // Unix timestamp for SQLite compatibility\n\n // Use upsert pattern: INSERT ... ON CONFLICT UPDATE\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (\n id, display_name, token_hash, node_type, internal_ip, internal_port,\n connectivity_status, created_at, updated_at, last_seen\n )\n VALUES (\n ${options.nodeId}, ${options.displayName ?? null}, ${tokenHash}, 'center',\n ${options.internalIp}, ${options.internalPort}, 'unknown', ${now}, ${now}, ${now}\n )\n ON CONFLICT (id) DO UPDATE SET\n display_name = EXCLUDED.display_name,\n internal_ip = EXCLUDED.internal_ip,\n internal_port = EXCLUDED.internal_port,\n updated_at = EXCLUDED.updated_at,\n last_seen = EXCLUDED.last_seen\n `);\n\n return { nodeId: options.nodeId, token };\n }\n\n /**\n * Update center node heartbeat with internal endpoint info.\n */\n public async updateCenterNodeHeartbeat(\n nodeId: string,\n internalIp: string,\n internalPort: number,\n timestamp: Date,\n ): Promise<void> {\n const ts = Math.floor(timestamp.getTime() / 1000); // Unix timestamp for SQLite compatibility\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET internal_ip = ${internalIp},\n internal_port = ${internalPort},\n last_seen = ${ts},\n updated_at = ${ts},\n connectivity_status = 'reachable'\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n }\n\n /**\n * List all center nodes in the cluster.\n */\n public async listCenterNodes(): Promise<CenterNodeInfo[]> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE node_type = 'center'\n ORDER BY created_at ASC\n `);\n\n return result.rows.map((row: any): CenterNodeInfo => ({\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n }));\n }\n\n /**\n * Get a specific center node by ID.\n */\n public async getCenterNode(nodeId: string): Promise<CenterNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'center'\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n /**\n * Find a center node by its internal endpoint (for routing).\n */\n public async findCenterNodeByEndpoint(internalIp: string, internalPort: number): Promise<CenterNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, internal_ip, internal_port, connectivity_status, last_seen\n FROM identity_edge_node\n WHERE node_type = 'center' AND internal_ip = ${internalIp} AND internal_port = ${internalPort}\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n internalIp: String(row.internal_ip ?? ''),\n internalPort: Number(row.internal_port ?? 0),\n connectivityStatus: (row.connectivity_status ?? 'unknown') as 'unknown' | 'reachable' | 'unreachable',\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n\n /**\n * Mark a center node as unreachable (for health checks).\n */\n public async markCenterNodeUnreachable(nodeId: string): Promise<void> {\n const ts = toDbTimestamp(this.db, new Date());\n await executeStatement(this.db, sql`\n UPDATE identity_edge_node\n SET connectivity_status = 'unreachable',\n updated_at = ${ts}\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n }\n\n /**\n * Remove a center node from the cluster.\n */\n public async removeCenterNode(nodeId: string): Promise<boolean> {\n // Note: For SQLite, we can't easily get affected row count, so just execute and return true\n await executeStatement(this.db, sql`\n DELETE FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'center'\n `);\n return true;\n }\n\n // ============ Account-based Node Methods ============\n\n /**\n * List nodes owned by a specific account\n */\n public async listNodesByAccount(accountId: string): Promise<Array<{\n nodeId: string;\n displayName?: string;\n capabilities: Record<string, unknown> | null;\n stringCapabilities: string[] | null;\n accessMode: string | null;\n lastSeen: Date | null;\n connectivityStatus: string | null;\n }>> {\n void accountId;\n return [];\n }\n\n /**\n * Delete a node\n */\n public async deleteNode(nodeId: string): Promise<boolean> {\n // First delete associated pods\n await executeStatement(this.db, sql`\n DELETE FROM identity_edge_node_pod WHERE node_id = ${nodeId}\n `);\n\n // Then delete the node\n const result = await executeQuery(this.db, sql`\n DELETE FROM identity_edge_node\n WHERE id = ${nodeId}\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n // ============ SP (Storage Provider) Node Methods ============\n\n /**\n * Register or update an SP node (UPSERT by nodeId).\n *\n * SP 本地生成 deviceId 作为 nodeId,注册时带上来。\n * 同一 nodeId 重复注册时更新 publicUrl、token 等,保留原记录。\n * 不传 nodeId 则 Cloud 随机分配。\n */\n public async registerSpNode(options: {\n publicUrl: string;\n displayName?: string;\n /** SP 提供的设备 ID,作为 nodeId(不传则随机生成) */\n nodeId?: string;\n /** SP 提供的 serviceToken,不传则随机生成 */\n serviceToken?: string;\n }): Promise<CreateSpNodeResult> {\n const nodeId = options.nodeId || randomUUID();\n const nodeToken = randomBytes(32).toString('base64url');\n const nodeTokenHash = createHash('sha256').update(nodeToken).digest('hex');\n const serviceToken = options.serviceToken || randomBytes(32).toString('base64url');\n const now = new Date();\n const ts = toDbTimestamp(this.db, now);\n\n await executeStatement(this.db, sql`\n INSERT INTO identity_edge_node (\n id, display_name, token_hash, service_token_hash,\n node_type, public_url,\n connectivity_status, created_at, updated_at\n )\n VALUES (\n ${nodeId}, ${options.displayName ?? null}, ${nodeTokenHash}, ${serviceToken},\n 'sp', ${options.publicUrl}, 'unknown', ${ts}, ${ts}\n )\n ON CONFLICT (id) DO UPDATE SET\n display_name = EXCLUDED.display_name,\n token_hash = EXCLUDED.token_hash,\n service_token_hash = EXCLUDED.service_token_hash,\n public_url = EXCLUDED.public_url,\n updated_at = EXCLUDED.updated_at\n `);\n\n return {\n nodeId,\n nodeToken,\n serviceToken,\n createdAt: now.toISOString(),\n };\n }\n\n /**\n * Get SP node info by nodeId.\n */\n public async getSpNode(nodeId: string): Promise<SpNodeInfo | undefined> {\n const result = await executeQuery(this.db, sql`\n SELECT id, display_name, public_url, service_token_hash, last_seen\n FROM identity_edge_node\n WHERE id = ${nodeId} AND node_type = 'sp'\n LIMIT 1\n `);\n\n if (result.rows.length === 0) {\n return undefined;\n }\n\n const row = result.rows[0] as any;\n return {\n nodeId: String(row.id),\n displayName: row.display_name == null ? undefined : String(row.display_name),\n publicUrl: String(row.public_url ?? ''),\n serviceTokenHash: String(row.service_token_hash ?? ''),\n lastSeen: fromDbTimestamp(row.last_seen),\n };\n }\n}\n"]}