@nospt/plugin-dev-ai-hub-backend 0.3.0 → 0.3.1
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.
|
@@ -30,8 +30,10 @@ class TelemetryStore {
|
|
|
30
30
|
* The salt used to hash actor identity (ADR-0007). A configured value wins
|
|
31
31
|
* and is never persisted, so a deployment can keep the salt outside the
|
|
32
32
|
* database it protects. Otherwise the salt is generated once and stored,
|
|
33
|
-
* which is what makes it survive restarts
|
|
34
|
-
*
|
|
33
|
+
* which is what makes it survive restarts. No current query groups by
|
|
34
|
+
* `actor_hash` — counts are raw — but stability is what keeps hashes
|
|
35
|
+
* comparable across restarts, and so keeps distinct-viewer counting possible
|
|
36
|
+
* later without a backfill. It is deliberately not required from config: a
|
|
35
37
|
* missing value used to fail the plugin's init and take the whole backend
|
|
36
38
|
* down with it.
|
|
37
39
|
*
|
|
@@ -68,6 +70,14 @@ class TelemetryStore {
|
|
|
68
70
|
occurred_at: now.toISOString()
|
|
69
71
|
});
|
|
70
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Popularity for one resource (ADR-0007): every action counted raw, as a
|
|
75
|
+
* lifetime total. `view` included — it is recorded only when a user opens a
|
|
76
|
+
* resource, so each row is already one deliberate look and there is nothing
|
|
77
|
+
* to dedup away. The read-time per-(hash, day) dedup the ADR originally
|
|
78
|
+
* called for was dropped once the render-time trigger it existed to cancel
|
|
79
|
+
* was removed; see the 2026-08-03 amendment.
|
|
80
|
+
*/
|
|
71
81
|
async getCounts(entityRef) {
|
|
72
82
|
const rows = await this.db("telemetry_events").where("entity_ref", entityRef).groupBy("action").select("action").count(
|
|
73
83
|
"id as count"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelemetryStore.cjs.js","sources":["../../src/database/TelemetryStore.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { Knex } from 'knex';\nimport {\n resolvePackagePath,\n type DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport type {\n TelemetryAction,\n TelemetryCounts,\n} from '@nospt/plugin-dev-ai-hub-common';\n\n/** Settings key holding the salt used to hash telemetry actor identity. */\nconst SALT_KEY = 'actor_salt';\n\nconst ZERO_COUNTS: TelemetryCounts = {\n install: 0,\n copy: 0,\n download: 0,\n view: 0,\n};\n\nexport interface TelemetryEventRecord {\n entityRef: string;\n action: TelemetryAction;\n actorHash: string | null;\n tool?: string;\n}\n\n/**\n * Telemetry event log (ADR-0007): store-all writes, raw
|
|
1
|
+
{"version":3,"file":"TelemetryStore.cjs.js","sources":["../../src/database/TelemetryStore.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { Knex } from 'knex';\nimport {\n resolvePackagePath,\n type DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport type {\n TelemetryAction,\n TelemetryCounts,\n} from '@nospt/plugin-dev-ai-hub-common';\n\n/** Settings key holding the salt used to hash telemetry actor identity. */\nconst SALT_KEY = 'actor_salt';\n\nconst ZERO_COUNTS: TelemetryCounts = {\n install: 0,\n copy: 0,\n download: 0,\n view: 0,\n};\n\nexport interface TelemetryEventRecord {\n entityRef: string;\n action: TelemetryAction;\n actorHash: string | null;\n tool?: string;\n}\n\n/**\n * Telemetry event log (ADR-0007): store-all writes, raw lifetime totals on\n * read. Every action counts each occurrence, `view` included.\n */\nexport class TelemetryStore {\n private constructor(private readonly db: Knex) {}\n\n static async create(options: {\n database: DatabaseService;\n }): Promise<TelemetryStore> {\n const db = await options.database.getClient();\n await db.migrate.latest({\n directory: resolvePackagePath(\n '@nospt/plugin-dev-ai-hub-backend',\n 'migrations',\n ),\n loadExtensions: ['.js'],\n });\n return new TelemetryStore(db);\n }\n\n /**\n * The salt used to hash actor identity (ADR-0007). A configured value wins\n * and is never persisted, so a deployment can keep the salt outside the\n * database it protects. Otherwise the salt is generated once and stored,\n * which is what makes it survive restarts. No current query groups by\n * `actor_hash` — counts are raw — but stability is what keeps hashes\n * comparable across restarts, and so keeps distinct-viewer counting possible\n * later without a backfill. It is deliberately not required from config: a\n * missing value used to fail the plugin's init and take the whole backend\n * down with it.\n *\n * Concurrent replicas starting together race on the insert; the loser reads\n * back the winner's value, so every replica ends up on the same salt.\n */\n async resolveSalt(configured?: string): Promise<string> {\n if (configured) {\n return configured;\n }\n const existing = await this.readSalt();\n if (existing) {\n return existing;\n }\n await this.db('telemetry_settings')\n .insert({ key: SALT_KEY, value: randomBytes(32).toString('hex') })\n .onConflict('key')\n .ignore();\n const stored = await this.readSalt();\n if (!stored) {\n throw new Error('Failed to persist the telemetry actor salt');\n }\n return stored;\n }\n\n private async readSalt(): Promise<string | undefined> {\n const row = await this.db('telemetry_settings')\n .where('key', SALT_KEY)\n .first<{ value: string } | undefined>('value');\n return row?.value;\n }\n\n async record(event: TelemetryEventRecord): Promise<void> {\n const now = new Date();\n await this.db('telemetry_events').insert({\n entity_ref: event.entityRef,\n action: event.action,\n actor_hash: event.actorHash,\n tool: event.tool ?? null,\n day: now.toISOString().slice(0, 10),\n occurred_at: now.toISOString(),\n });\n }\n\n /**\n * Popularity for one resource (ADR-0007): every action counted raw, as a\n * lifetime total. `view` included — it is recorded only when a user opens a\n * resource, so each row is already one deliberate look and there is nothing\n * to dedup away. The read-time per-(hash, day) dedup the ADR originally\n * called for was dropped once the render-time trigger it existed to cancel\n * was removed; see the 2026-08-03 amendment.\n */\n async getCounts(entityRef: string): Promise<TelemetryCounts> {\n const rows = await this.db('telemetry_events')\n .where('entity_ref', entityRef)\n .groupBy('action')\n .select('action')\n .count<Array<{ action: TelemetryAction; count: string | number }>>(\n 'id as count',\n );\n\n const counts = { ...ZERO_COUNTS };\n for (const row of rows) {\n counts[row.action] = Number(row.count);\n }\n return counts;\n }\n}\n"],"names":["resolvePackagePath","randomBytes"],"mappings":";;;;;AAYA,MAAM,QAAA,GAAW,YAAA;AAEjB,MAAM,WAAA,GAA+B;AAAA,EACnC,OAAA,EAAS,CAAA;AAAA,EACT,IAAA,EAAM,CAAA;AAAA,EACN,QAAA,EAAU,CAAA;AAAA,EACV,IAAA,EAAM;AACR,CAAA;AAaO,MAAM,cAAA,CAAe;AAAA,EAClB,YAA6B,EAAA,EAAU;AAAV,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AAAA,EAAW;AAAA,EAAX,EAAA;AAAA,EAErC,aAAa,OAAO,OAAA,EAEQ;AAC1B,IAAA,MAAM,EAAA,GAAK,MAAM,OAAA,CAAQ,QAAA,CAAS,SAAA,EAAU;AAC5C,IAAA,MAAM,EAAA,CAAG,QAAQ,MAAA,CAAO;AAAA,MACtB,SAAA,EAAWA,mCAAA;AAAA,QACT,kCAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,cAAA,EAAgB,CAAC,KAAK;AAAA,KACvB,CAAA;AACD,IAAA,OAAO,IAAI,eAAe,EAAE,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,YAAY,UAAA,EAAsC;AACtD,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,OAAO,UAAA;AAAA,IACT;AACA,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,EAAS;AACrC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAO,QAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,CAAK,GAAG,oBAAoB,CAAA,CAC/B,OAAO,EAAE,GAAA,EAAK,UAAU,KAAA,EAAOC,uBAAA,CAAY,EAAE,CAAA,CAAE,QAAA,CAAS,KAAK,CAAA,EAAG,EAChE,UAAA,CAAW,KAAK,EAChB,MAAA,EAAO;AACV,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,QAAA,EAAS;AACnC,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,IAC9D;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEA,MAAc,QAAA,GAAwC;AACpD,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,EAAA,CAAG,oBAAoB,CAAA,CAC3C,KAAA,CAAM,KAAA,EAAO,QAAQ,CAAA,CACrB,KAAA,CAAqC,OAAO,CAAA;AAC/C,IAAA,OAAO,GAAA,EAAK,KAAA;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,KAAA,EAA4C;AACvD,IAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,IAAA,MAAM,IAAA,CAAK,EAAA,CAAG,kBAAkB,CAAA,CAAE,MAAA,CAAO;AAAA,MACvC,YAAY,KAAA,CAAM,SAAA;AAAA,MAClB,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,YAAY,KAAA,CAAM,SAAA;AAAA,MAClB,IAAA,EAAM,MAAM,IAAA,IAAQ,IAAA;AAAA,MACpB,KAAK,GAAA,CAAI,WAAA,EAAY,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AAAA,MAClC,WAAA,EAAa,IAAI,WAAA;AAAY,KAC9B,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,UAAU,SAAA,EAA6C;AAC3D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,EAAA,CAAG,kBAAkB,CAAA,CAC1C,KAAA,CAAM,YAAA,EAAc,SAAS,EAC7B,OAAA,CAAQ,QAAQ,CAAA,CAChB,MAAA,CAAO,QAAQ,CAAA,CACf,KAAA;AAAA,MACC;AAAA,KACF;AAEF,IAAA,MAAM,MAAA,GAAS,EAAE,GAAG,WAAA,EAAY;AAChC,IAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,MAAA,MAAA,CAAO,GAAA,CAAI,MAAM,CAAA,GAAI,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,IACvC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nospt/plugin-dev-ai-hub-backend",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Backend plugin for Dev AI Hub",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.cjs.js",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@backstage/backend-plugin-api": "^1.9.1",
|
|
62
62
|
"@backstage/catalog-model": "^1.9.0",
|
|
63
63
|
"@backstage/plugin-catalog-node": "^2.2.2",
|
|
64
|
-
"@nospt/plugin-dev-ai-hub-common": "^0.3.
|
|
64
|
+
"@nospt/plugin-dev-ai-hub-common": "^0.3.1",
|
|
65
65
|
"archiver": "^6.0.0",
|
|
66
66
|
"express": "^5.2.1",
|
|
67
67
|
"js-yaml": "^4.1.0",
|