From 472685f0d732103d931ecea45f3fc6533b2727f6 Mon Sep 17 00:00:00 2001 From: RaymondVerhoef Date: Sat, 23 May 2026 18:13:08 +0200 Subject: [PATCH] Add specifications for gamification, generation, and R42 chat services - Introduced gamification service spec detailing responsibilities, API surface, XP calculation, levels, streaks, badges, milestone cards, and heatmap data. - Added generation service spec outlining the process for generating micro learning content, including API endpoints, AI call configuration, prompt strategies, and error handling. - Created R42 chat service spec covering chatbot interactions, retrieval pipeline, prompt construction, response generation, and stateless design principles. --- .../1779544653_created_source_documents.js | 139 ++ .../1779544653_created_themes.js | 116 + app/pb_migrations/1779544653_updated_users.js | 52 + .../1779544736_created_topics.js | 55 + .../1779544842_created_badges.js | 102 + .../1779544842_created_curriculum_versions.js | 114 + .../1779544842_created_curriculum_weeks.js | 129 ++ .../1779544842_created_employee_badges.js | 86 + ...44842_created_employee_curriculum_state.js | 100 + ...779544842_created_gamification_profiles.js | 131 ++ .../1779544842_created_micro_learnings.js | 137 ++ .../1779544842_created_milestone_cards.js | 111 + .../1779544842_created_session_completions.js | 116 + .../1779545479_updated_topics.js | 266 +++ app/services/curriculum/.gitignore | 4 + app/services/curriculum/package-lock.json | 1550 ++++++++++++++ app/services/curriculum/package.json | 25 + .../curriculum/src/generator/build.ts | 200 ++ .../curriculum/src/generator/cycle.ts | 20 + .../curriculum/src/generator/sequence.ts | 49 + app/services/curriculum/src/index.ts | 18 + app/services/curriculum/src/jobs/queue.ts | 46 + app/services/curriculum/src/lib/anthropic.ts | 9 + app/services/curriculum/src/lib/pocketbase.ts | 14 + .../curriculum/src/routes/curriculum.ts | 174 ++ .../curriculum/src/routes/employee.ts | 140 ++ app/services/curriculum/src/types.ts | 125 ++ .../curriculum/src/versioning/apply.ts | 25 + .../curriculum/src/versioning/freeze.ts | 25 + app/services/curriculum/tsconfig.json | 16 + app/services/generation/.gitignore | 4 + app/services/generation/package-lock.json | 1550 ++++++++++++++ app/services/generation/package.json | 25 + app/services/generation/src/index.ts | 18 + app/services/generation/src/jobs/queue.ts | 168 ++ app/services/generation/src/lib/anthropic.ts | 9 + app/services/generation/src/lib/pocketbase.ts | 14 + .../generation/src/pipeline/generate.ts | 163 ++ .../generation/src/routes/generate.ts | 36 + app/services/generation/src/routes/publish.ts | 44 + app/services/generation/src/types.ts | 201 ++ app/services/generation/tsconfig.json | 16 + app/services/ingestion/package-lock.json | 1888 +++++++++++++++++ app/services/ingestion/package.json | 16 +- app/services/ingestion/service.err | 16 + .../src/migrations/001_initial_schema.ts | 19 +- app/services/ingestion/src/pipeline/embed.ts | 13 +- .../ingestion/src/pipeline/structure.ts | 4 +- app/services/ingestion/src/types.ts | 2 + .../ingestion/test-files/large_doc.md | 605 ++++++ .../ingestion/test-files/malformed.pdf | 2 + .../ingestion/test-files/payload_md.json | 6 + app/services/ingestion/test-files/sample.md | 72 + app/services/ingestion/test-files/sample.pdf | 55 + app/services/ingestion/test-files/sample.txt | 31 + .../ingestion/test-files/system_test.pdf | Bin 0 -> 268160 bytes .../ingestion/test-files/test_payload.json | 6 + docs/curriculum-spec.md | 407 ++++ docs/frontend-spec.md | 701 ++++++ docs/gamification-spec.md | 469 ++++ docs/generation-spec.md | 583 +++++ docs/r42-spec.md | 336 +++ 62 files changed, 11552 insertions(+), 21 deletions(-) create mode 100644 app/pb_migrations/1779544653_created_source_documents.js create mode 100644 app/pb_migrations/1779544653_created_themes.js create mode 100644 app/pb_migrations/1779544653_updated_users.js create mode 100644 app/pb_migrations/1779544736_created_topics.js create mode 100644 app/pb_migrations/1779544842_created_badges.js create mode 100644 app/pb_migrations/1779544842_created_curriculum_versions.js create mode 100644 app/pb_migrations/1779544842_created_curriculum_weeks.js create mode 100644 app/pb_migrations/1779544842_created_employee_badges.js create mode 100644 app/pb_migrations/1779544842_created_employee_curriculum_state.js create mode 100644 app/pb_migrations/1779544842_created_gamification_profiles.js create mode 100644 app/pb_migrations/1779544842_created_micro_learnings.js create mode 100644 app/pb_migrations/1779544842_created_milestone_cards.js create mode 100644 app/pb_migrations/1779544842_created_session_completions.js create mode 100644 app/pb_migrations/1779545479_updated_topics.js create mode 100644 app/services/curriculum/.gitignore create mode 100644 app/services/curriculum/package-lock.json create mode 100644 app/services/curriculum/package.json create mode 100644 app/services/curriculum/src/generator/build.ts create mode 100644 app/services/curriculum/src/generator/cycle.ts create mode 100644 app/services/curriculum/src/generator/sequence.ts create mode 100644 app/services/curriculum/src/index.ts create mode 100644 app/services/curriculum/src/jobs/queue.ts create mode 100644 app/services/curriculum/src/lib/anthropic.ts create mode 100644 app/services/curriculum/src/lib/pocketbase.ts create mode 100644 app/services/curriculum/src/routes/curriculum.ts create mode 100644 app/services/curriculum/src/routes/employee.ts create mode 100644 app/services/curriculum/src/types.ts create mode 100644 app/services/curriculum/src/versioning/apply.ts create mode 100644 app/services/curriculum/src/versioning/freeze.ts create mode 100644 app/services/curriculum/tsconfig.json create mode 100644 app/services/generation/.gitignore create mode 100644 app/services/generation/package-lock.json create mode 100644 app/services/generation/package.json create mode 100644 app/services/generation/src/index.ts create mode 100644 app/services/generation/src/jobs/queue.ts create mode 100644 app/services/generation/src/lib/anthropic.ts create mode 100644 app/services/generation/src/lib/pocketbase.ts create mode 100644 app/services/generation/src/pipeline/generate.ts create mode 100644 app/services/generation/src/routes/generate.ts create mode 100644 app/services/generation/src/routes/publish.ts create mode 100644 app/services/generation/src/types.ts create mode 100644 app/services/generation/tsconfig.json create mode 100644 app/services/ingestion/package-lock.json create mode 100644 app/services/ingestion/service.err create mode 100644 app/services/ingestion/test-files/large_doc.md create mode 100644 app/services/ingestion/test-files/malformed.pdf create mode 100644 app/services/ingestion/test-files/payload_md.json create mode 100644 app/services/ingestion/test-files/sample.md create mode 100644 app/services/ingestion/test-files/sample.pdf create mode 100644 app/services/ingestion/test-files/sample.txt create mode 100644 app/services/ingestion/test-files/system_test.pdf create mode 100644 app/services/ingestion/test-files/test_payload.json create mode 100644 docs/curriculum-spec.md create mode 100644 docs/frontend-spec.md create mode 100644 docs/gamification-spec.md create mode 100644 docs/generation-spec.md create mode 100644 docs/r42-spec.md diff --git a/app/pb_migrations/1779544653_created_source_documents.js b/app/pb_migrations/1779544653_created_source_documents.js new file mode 100644 index 0000000..edc208c --- /dev/null +++ b/app/pb_migrations/1779544653_created_source_documents.js @@ -0,0 +1,139 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "0lr0nf3rzh5tdq3", + "created": "2026-05-23 13:57:33.625Z", + "updated": "2026-05-23 13:57:33.625Z", + "name": "source_documents", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "ju0ymfsy", + "name": "filename", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "7q9zkqtk", + "name": "file", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "mimeTypes": [ + "application/pdf", + "text/markdown", + "text/x-markdown", + "text/plain" + ], + "thumbs": [], + "maxSelect": 1, + "maxSize": 52428800, + "protected": false + } + }, + { + "system": false, + "id": "ny7sztwr", + "name": "format", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "pdf", + "md", + "txt" + ] + } + }, + { + "system": false, + "id": "sitx0eng", + "name": "status", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "processing", + "processed", + "failed" + ] + } + }, + { + "system": false, + "id": "p7qduuyn", + "name": "ingested_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "tahewcck", + "name": "chunk_count", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "d0x4m7ow", + "name": "created_by", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("0lr0nf3rzh5tdq3"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544653_created_themes.js b/app/pb_migrations/1779544653_created_themes.js new file mode 100644 index 0000000..b7dd2b2 --- /dev/null +++ b/app/pb_migrations/1779544653_created_themes.js @@ -0,0 +1,116 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "bm117d8jhn68xqr", + "created": "2026-05-23 13:57:33.634Z", + "updated": "2026-05-23 13:57:33.634Z", + "name": "themes", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "zl4nz5yw", + "name": "title", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "hvkfrx3i", + "name": "description", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "mfk9eat0", + "name": "status", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "draft", + "published" + ] + } + }, + { + "system": false, + "id": "n8fksm3u", + "name": "source_documents", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "0lr0nf3rzh5tdq3", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": null, + "displayFields": null + } + }, + { + "system": false, + "id": "998ievsr", + "name": "approved_by", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "4ilud3ts", + "name": "approved_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("bm117d8jhn68xqr"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544653_updated_users.js b/app/pb_migrations/1779544653_updated_users.js new file mode 100644 index 0000000..0091ca6 --- /dev/null +++ b/app/pb_migrations/1779544653_updated_users.js @@ -0,0 +1,52 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("_pb_users_auth_") + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "mx3g38jd", + "name": "role", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "admin", + "employee" + ] + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "pgfyrf71", + "name": "display_name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + })) + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("_pb_users_auth_") + + // remove + collection.schema.removeField("mx3g38jd") + + // remove + collection.schema.removeField("pgfyrf71") + + return dao.saveCollection(collection) +}) diff --git a/app/pb_migrations/1779544736_created_topics.js b/app/pb_migrations/1779544736_created_topics.js new file mode 100644 index 0000000..d0d0a85 --- /dev/null +++ b/app/pb_migrations/1779544736_created_topics.js @@ -0,0 +1,55 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "1ixwljuo2xqxcqj", + "created": "2026-05-23 13:58:56.098Z", + "updated": "2026-05-23 13:58:56.098Z", + "name": "topics", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "kkjhfk5h", + "name": "theme", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bm117d8jhn68xqr", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "ygnod47v", + "name": "body", + "type": "editor", + "required": false, + "presentable": false, + "unique": false, + "options": { + "convertUrls": false + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_badges.js b/app/pb_migrations/1779544842_created_badges.js new file mode 100644 index 0000000..ebd0325 --- /dev/null +++ b/app/pb_migrations/1779544842_created_badges.js @@ -0,0 +1,102 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "5gl7qj7aky6wjl9", + "created": "2026-05-23 14:00:42.375Z", + "updated": "2026-05-23 14:00:42.375Z", + "name": "badges", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "edwrmvav", + "name": "key", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "2wpmab8o", + "name": "tier", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "bronze", + "silver", + "gold", + "legendary", + "content" + ] + } + }, + { + "system": false, + "id": "hknbw5l3", + "name": "label", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "mq6imou8", + "name": "description", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "l8vkthft", + "name": "icon", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("5gl7qj7aky6wjl9"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_curriculum_versions.js b/app/pb_migrations/1779544842_created_curriculum_versions.js new file mode 100644 index 0000000..77e6560 --- /dev/null +++ b/app/pb_migrations/1779544842_created_curriculum_versions.js @@ -0,0 +1,114 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "1cbepol4z1jxb20", + "created": "2026-05-23 14:00:42.280Z", + "updated": "2026-05-23 14:00:42.280Z", + "name": "curriculum_versions", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "x70h1e1v", + "name": "version", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "0rbdmeyi", + "name": "status", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "draft", + "active", + "superseded" + ] + } + }, + { + "system": false, + "id": "s8btpnp7", + "name": "generated_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "d297xuk1", + "name": "approved_by", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "zmzzcv6z", + "name": "approved_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "fsn8cgni", + "name": "generation_notes", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("1cbepol4z1jxb20"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_curriculum_weeks.js b/app/pb_migrations/1779544842_created_curriculum_weeks.js new file mode 100644 index 0000000..facc444 --- /dev/null +++ b/app/pb_migrations/1779544842_created_curriculum_weeks.js @@ -0,0 +1,129 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "9ivwlfc584lp63w", + "created": "2026-05-23 14:00:42.294Z", + "updated": "2026-05-23 14:00:42.294Z", + "name": "curriculum_weeks", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "ano3xevy", + "name": "curriculum_version", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1cbepol4z1jxb20", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "p7odhccm", + "name": "week_number", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "l0w7ienh", + "name": "theme", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bm117d8jhn68xqr", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "37yncqmv", + "name": "topics", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": null, + "displayFields": null + } + }, + { + "system": false, + "id": "ut2ilwjx", + "name": "topic_order", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + }, + { + "system": false, + "id": "wdleuro6", + "name": "estimated_duration_minutes", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "omddyhqe", + "name": "admin_notes", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("9ivwlfc584lp63w"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_employee_badges.js b/app/pb_migrations/1779544842_created_employee_badges.js new file mode 100644 index 0000000..ef83c3b --- /dev/null +++ b/app/pb_migrations/1779544842_created_employee_badges.js @@ -0,0 +1,86 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "52xqp11w4t0y68u", + "created": "2026-05-23 14:00:42.405Z", + "updated": "2026-05-23 14:00:42.405Z", + "name": "employee_badges", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "uyiom4ry", + "name": "user", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "hmafhq29", + "name": "badge", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "5gl7qj7aky6wjl9", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "5a11syeq", + "name": "earned_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "ekcq8kkw", + "name": "cycle", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("52xqp11w4t0y68u"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_employee_curriculum_state.js b/app/pb_migrations/1779544842_created_employee_curriculum_state.js new file mode 100644 index 0000000..552d611 --- /dev/null +++ b/app/pb_migrations/1779544842_created_employee_curriculum_state.js @@ -0,0 +1,100 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "516qnoxsmeomkte", + "created": "2026-05-23 14:00:42.311Z", + "updated": "2026-05-23 14:00:42.311Z", + "name": "employee_curriculum_state", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "4v8ps3fa", + "name": "user", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "trhrgfoz", + "name": "current_cycle", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "g39vexbc", + "name": "current_week", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "ca7taibg", + "name": "start_date", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "lpkyfwga", + "name": "active_version", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1cbepol4z1jxb20", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("516qnoxsmeomkte"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_gamification_profiles.js b/app/pb_migrations/1779544842_created_gamification_profiles.js new file mode 100644 index 0000000..faccb02 --- /dev/null +++ b/app/pb_migrations/1779544842_created_gamification_profiles.js @@ -0,0 +1,131 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "o9gcrudod9g510u", + "created": "2026-05-23 14:00:42.352Z", + "updated": "2026-05-23 14:00:42.352Z", + "name": "gamification_profiles", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "szwphybr", + "name": "user", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "ifgzeuez", + "name": "total_commits", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "bbife1f5", + "name": "current_level", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "intern", + "junior", + "medior", + "senior", + "staff", + "principal" + ] + } + }, + { + "system": false, + "id": "f2aujqrw", + "name": "current_streak_weeks", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "vlcxsuv6", + "name": "longest_streak_weeks", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "eolnad0g", + "name": "types_used", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + }, + { + "system": false, + "id": "e2voqddw", + "name": "last_active_week", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("o9gcrudod9g510u"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_micro_learnings.js b/app/pb_migrations/1779544842_created_micro_learnings.js new file mode 100644 index 0000000..e37f24e --- /dev/null +++ b/app/pb_migrations/1779544842_created_micro_learnings.js @@ -0,0 +1,137 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "li75ivkr87g3r94", + "created": "2026-05-23 14:00:42.269Z", + "updated": "2026-05-23 14:00:42.269Z", + "name": "micro_learnings", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "ihu0d60m", + "name": "topic", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "wdz1hhs6", + "name": "type", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "concept_explainer", + "scenario_quiz", + "misconceptions", + "how_to", + "comparison_card", + "reflection_prompt", + "flashcard_set", + "case_study", + "glossary_anchor", + "myth_vs_evidence" + ] + } + }, + { + "system": false, + "id": "1ccr13b7", + "name": "content", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + }, + { + "system": false, + "id": "vdti3gns", + "name": "status", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "queued", + "generated", + "published", + "rejected" + ] + } + }, + { + "system": false, + "id": "hxxcrfw8", + "name": "generation_model", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "grwpzlhc", + "name": "generated_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "wexudgfp", + "name": "published_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("li75ivkr87g3r94"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_milestone_cards.js b/app/pb_migrations/1779544842_created_milestone_cards.js new file mode 100644 index 0000000..294ba92 --- /dev/null +++ b/app/pb_migrations/1779544842_created_milestone_cards.js @@ -0,0 +1,111 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "ilkfmv9xibmekna", + "created": "2026-05-23 14:00:42.453Z", + "updated": "2026-05-23 14:00:42.453Z", + "name": "milestone_cards", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "31f7wcxc", + "name": "user", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "uyhilsbd", + "name": "cycle", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "47ivigbu", + "name": "week", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "yai47mow", + "name": "total_commits", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "je3vf6wh", + "name": "streak_weeks", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "xvtxl0nj", + "name": "badge_keys", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("ilkfmv9xibmekna"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779544842_created_session_completions.js b/app/pb_migrations/1779544842_created_session_completions.js new file mode 100644 index 0000000..1a895e0 --- /dev/null +++ b/app/pb_migrations/1779544842_created_session_completions.js @@ -0,0 +1,116 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "57kc85afcdkjlje", + "created": "2026-05-23 14:00:42.328Z", + "updated": "2026-05-23 14:00:42.328Z", + "name": "session_completions", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "atqsid6s", + "name": "user", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "2waq3kfq", + "name": "topic", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "tf1bapvu", + "name": "micro_learning", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "li75ivkr87g3r94", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "dvmc5jih", + "name": "week_number", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "wblewojy", + "name": "cycle", + "type": "number", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "izxhly6t", + "name": "completed_at", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("57kc85afcdkjlje"); + + return dao.deleteCollection(collection); +}) diff --git a/app/pb_migrations/1779545479_updated_topics.js b/app/pb_migrations/1779545479_updated_topics.js new file mode 100644 index 0000000..9eadcf2 --- /dev/null +++ b/app/pb_migrations/1779545479_updated_topics.js @@ -0,0 +1,266 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj") + + // remove + collection.schema.removeField("kkjhfk5h") + + // remove + collection.schema.removeField("ygnod47v") + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "wo6r0pay", + "name": "theme", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bm117d8jhn68xqr", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "8bnzwbwk", + "name": "title", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "lwhabbnq", + "name": "body", + "type": "editor", + "required": false, + "presentable": false, + "unique": false, + "options": { + "convertUrls": false + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "8tjttsyu", + "name": "difficulty", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "introductory", + "intermediate", + "advanced" + ] + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "qngdg6m0", + "name": "complexity_weight", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "dccnzcbc", + "name": "status", + "type": "select", + "required": true, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "draft", + "published" + ] + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "3rkaaglp", + "name": "key_terms", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "byhuuhif", + "name": "qdrant_chunk_ids", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2097152 + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "8h7ygavu", + "name": "related_topics", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": null, + "displayFields": null + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "dwhl1pqg", + "name": "prerequisite_topics", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": null, + "displayFields": null + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "38phv8cm", + "name": "contrast_topics", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "1ixwljuo2xqxcqj", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": null, + "displayFields": null + } + })) + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj") + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "kkjhfk5h", + "name": "theme", + "type": "relation", + "required": true, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bm117d8jhn68xqr", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + })) + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "ygnod47v", + "name": "body", + "type": "editor", + "required": false, + "presentable": false, + "unique": false, + "options": { + "convertUrls": false + } + })) + + // remove + collection.schema.removeField("wo6r0pay") + + // remove + collection.schema.removeField("8bnzwbwk") + + // remove + collection.schema.removeField("lwhabbnq") + + // remove + collection.schema.removeField("8tjttsyu") + + // remove + collection.schema.removeField("qngdg6m0") + + // remove + collection.schema.removeField("dccnzcbc") + + // remove + collection.schema.removeField("3rkaaglp") + + // remove + collection.schema.removeField("byhuuhif") + + // remove + collection.schema.removeField("8h7ygavu") + + // remove + collection.schema.removeField("dwhl1pqg") + + // remove + collection.schema.removeField("38phv8cm") + + return dao.saveCollection(collection) +}) diff --git a/app/services/curriculum/.gitignore b/app/services/curriculum/.gitignore new file mode 100644 index 0000000..9bd7f06 --- /dev/null +++ b/app/services/curriculum/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +*.err diff --git a/app/services/curriculum/package-lock.json b/app/services/curriculum/package-lock.json new file mode 100644 index 0000000..89d4831 --- /dev/null +++ b/app/services/curriculum/package-lock.json @@ -0,0 +1,1550 @@ +{ + "name": "curriculum", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "curriculum", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.24", + "fastify": "^4", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/uuid": "^9", + "dotenv": "^16", + "tsx": "^4", + "typescript": "^5" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.24.3.tgz", + "integrity": "sha512-916wJXO6T6k8R6BAAcLhLPv/pnLGy7YSEBZXZ1XTFbLcTZE8oTy3oDW9WJf9KKZwMvVcePIfoTSvzXHRcGxkQQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pocketbase": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.5.tgz", + "integrity": "sha512-bnI/uinnQps+ElSlzxkc4yvwuSFfKcoszDtXH/4QT2FhGq2mJVUvDlxn+rjRXVntUjPfmMG5LEPZ1eGqV6ssog==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/app/services/curriculum/package.json b/app/services/curriculum/package.json new file mode 100644 index 0000000..f1c741e --- /dev/null +++ b/app/services/curriculum/package.json @@ -0,0 +1,25 @@ +{ + "name": "curriculum", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.24", + "fastify": "^4", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/uuid": "^9", + "dotenv": "^16", + "tsx": "^4", + "typescript": "^5" + } +} diff --git a/app/services/curriculum/src/generator/build.ts b/app/services/curriculum/src/generator/build.ts new file mode 100644 index 0000000..a638f8b --- /dev/null +++ b/app/services/curriculum/src/generator/build.ts @@ -0,0 +1,200 @@ +import { getPocketBase } from '../lib/pocketbase.js'; +import { anthropic, MODELS } from '../lib/anthropic.js'; +import { sequenceTopics } from './sequence.js'; +import { buildCycleSystemRules, buildCycleUserPrompt } from './cycle.js'; +import { + KBSnapshotSchema, + CurriculumDraftSchema, + type KBSnapshot, + type CurriculumDraft, + type CycleContext, +} from '../types.js'; + +const BASE_SYSTEM_PROMPT = `You are a curriculum designer. Your task is to distribute a set of learning +Themes across 26 weekly sessions to create an effective learning journey. + +Output ONLY valid JSON matching the schema provided. No preamble, no +explanation, no markdown fences. + +Rules: +- Every Theme must appear at least once across 26 weeks +- Themes with more Topics (higher topic count) may span multiple weeks or + appear in multiple cycles within the 26 weeks +- Sequence Themes so foundational concepts precede dependent ones +- Distribute complexity progressively: introductory Themes early, advanced + Themes in the second half +- If total Topics across all Themes exceeds what 26 weeks can cover in depth, + prioritise breadth in cycle 1 — every Theme covered, key Topics per Theme +- Assign an estimated duration in minutes per week (15–45 minutes per session) +- Return exactly 26 week slots + +Output schema: +{ + "weeks": [ + { + "weekNumber": 1, + "themeId": "string", + "topicIds": ["string"], + "estimatedDurationMinutes": 25, + "rationale": "one sentence" + } + ] +}`; + +export async function fetchKBSnapshot(): Promise { + const pb = await getPocketBase(); + + const themeRecords = await pb.collection('themes').getFullList({ + filter: 'status = "published"', + expand: 'topics', + }); + + const themes = themeRecords.map(theme => { + const expandedTopics = (theme['expand'] as Record)?.['topics']; + const rawTopics = Array.isArray(expandedTopics) ? expandedTopics : []; + + const topics = rawTopics.map((t: unknown) => { + const r = t as Record; + return { + id: r['id'] as string, + title: r['title'] as string, + complexityWeight: (r['complexity_weight'] as number) ?? 1, + difficulty: (r['difficulty'] as string) ?? 'introductory', + prerequisiteTopics: (r['prerequisite_topics'] as string[]) ?? [], + relatedTopics: (r['related_topics'] as string[]) ?? [], + contrastTopics: (r['contrast_topics'] as string[]) ?? [], + }; + }); + + return { + id: theme['id'] as string, + title: theme['title'] as string, + description: (theme['description'] as string) ?? '', + topics, + }; + }); + + return KBSnapshotSchema.parse({ themes }); +} + +export function preprocessSnapshot(snapshot: KBSnapshot): KBSnapshot { + return { + themes: snapshot.themes.map(theme => ({ + ...theme, + topics: sequenceTopics(theme.topics), + })), + }; +} + +async function callAI( + systemPrompt: string, + userPrompt: string, +): Promise { + for (let attempt = 0; attempt < 2; attempt++) { + const message = await anthropic.messages.create({ + model: MODELS.SONNET, + max_tokens: 4000, + temperature: 0, + system: systemPrompt, + messages: [{ role: 'user', content: userPrompt }], + }); + + const textBlock = message.content.find(b => b.type === 'text'); + if (!textBlock || textBlock.type !== 'text') { + if (attempt === 0) continue; + throw new Error('No text block in AI response'); + } + + let parsed: unknown; + try { + parsed = JSON.parse(textBlock.text); + } catch { + if (attempt === 0) continue; + throw new Error('AI response is not valid JSON'); + } + + const result = CurriculumDraftSchema.safeParse(parsed); + if (!result.success) { + if (attempt === 0) continue; + throw new Error(`AI response failed Zod validation: ${result.error.message}`); + } + + return result.data; + } + + throw new Error('AI generation failed after 2 attempts'); +} + +function validateDraftAgainstSnapshot(draft: CurriculumDraft, snapshot: KBSnapshot): void { + const themeIds = new Set(snapshot.themes.map(t => t.id)); + const topicIds = new Set(snapshot.themes.flatMap(t => t.topics.map(p => p.id))); + + for (const week of draft.weeks) { + if (!themeIds.has(week.themeId)) { + throw new Error(`Unknown themeId in week ${week.weekNumber}: ${week.themeId}`); + } + for (const topicId of week.topicIds) { + if (!topicIds.has(topicId)) { + throw new Error(`Unknown topicId in week ${week.weekNumber}: ${topicId}`); + } + } + } +} + +export async function writeDraftToPocketBase( + draft: CurriculumDraft, + reason: string, +): Promise { + const pb = await getPocketBase(); + + // Determine next version number + const existing = await pb.collection('curriculum_versions').getFullList({ + sort: '-version', + perPage: 1, + }); + const latestVersion = existing[0] ? (existing[0]['version'] as number) : 0; + + const versionRecord = await pb.collection('curriculum_versions').create({ + version: latestVersion + 1, + status: 'draft', + generated_at: new Date().toISOString(), + generation_notes: reason, + }); + + for (const week of draft.weeks) { + await pb.collection('curriculum_weeks').create({ + curriculum_version: versionRecord.id, + week_number: week.weekNumber, + theme: week.themeId, + topics: week.topicIds, + topic_order: week.topicIds.map((_, i) => i), + estimated_duration_minutes: week.estimatedDurationMinutes, + admin_notes: week.rationale, + }); + } + + return versionRecord.id; +} + +export async function buildCurriculum( + reason: string, + cycleCtx?: CycleContext, +): Promise { + const rawSnapshot = await fetchKBSnapshot(); + const snapshot = preprocessSnapshot(rawSnapshot); + + let systemPrompt = BASE_SYSTEM_PROMPT; + let userPrompt: string; + + if (cycleCtx && cycleCtx.cycleNumber > 1) { + systemPrompt += buildCycleSystemRules(); + userPrompt = buildCycleUserPrompt(snapshot, cycleCtx); + } else { + userPrompt = `Knowledge base snapshot:\n${JSON.stringify(snapshot)}\n\nGenerate a 26-week curriculum schedule.`; + } + + const draft = await callAI(systemPrompt, userPrompt); + validateDraftAgainstSnapshot(draft, snapshot); + + return writeDraftToPocketBase(draft, reason); +} diff --git a/app/services/curriculum/src/generator/cycle.ts b/app/services/curriculum/src/generator/cycle.ts new file mode 100644 index 0000000..6403bc7 --- /dev/null +++ b/app/services/curriculum/src/generator/cycle.ts @@ -0,0 +1,20 @@ +import type { CycleContext, KBSnapshot } from '../types.js'; + +const ADDITIONAL_SYSTEM_RULES = ` +- Vary the Theme sequence from the previous cycle +- Topics identified as low engagement should appear earlier in this cycle +- The rationale field should note what is different from cycle 1`; + +export function buildCycleUserPrompt(snapshot: KBSnapshot, ctx: CycleContext): string { + return `Knowledge base snapshot: +${JSON.stringify(snapshot)} + +Cycle context: +${JSON.stringify(ctx)} + +Generate a 26-week curriculum schedule.`; +} + +export function buildCycleSystemRules(): string { + return ADDITIONAL_SYSTEM_RULES; +} diff --git a/app/services/curriculum/src/generator/sequence.ts b/app/services/curriculum/src/generator/sequence.ts new file mode 100644 index 0000000..786fc4d --- /dev/null +++ b/app/services/curriculum/src/generator/sequence.ts @@ -0,0 +1,49 @@ +import type { KBTopic } from '../types.js'; + +export function sequenceTopics(topics: KBTopic[]): KBTopic[] { + if (topics.length === 0) return []; + + const idToTopic = new Map(); + for (const t of topics) { + idToTopic.set(t.id, t); + } + + // Build adjacency list: id → prerequisite ids that exist in this set + const prereqs = new Map>(); + for (const t of topics) { + const localPrereqs = new Set(t.prerequisiteTopics.filter(id => idToTopic.has(id))); + prereqs.set(t.id, localPrereqs); + } + + const visited = new Set(); + const result: KBTopic[] = []; + const onStack = new Set(); + let hasCycle = false; + + function visit(id: string): void { + if (onStack.has(id)) { + hasCycle = true; + return; + } + if (visited.has(id)) return; + onStack.add(id); + for (const prereqId of prereqs.get(id) ?? []) { + visit(prereqId); + } + onStack.delete(id); + visited.add(id); + const topic = idToTopic.get(id); + if (topic) result.push(topic); + } + + for (const t of topics) { + visit(t.id); + } + + if (hasCycle) { + // Fall back to complexity_weight ascending + return [...topics].sort((a, b) => a.complexityWeight - b.complexityWeight); + } + + return result; +} diff --git a/app/services/curriculum/src/index.ts b/app/services/curriculum/src/index.ts new file mode 100644 index 0000000..5a6dcd8 --- /dev/null +++ b/app/services/curriculum/src/index.ts @@ -0,0 +1,18 @@ +import 'dotenv/config'; +import Fastify from 'fastify'; +import { curriculumRoutes } from './routes/curriculum.js'; +import { employeeRoutes } from './routes/employee.js'; + +const app = Fastify({ logger: true }); + +await app.register(curriculumRoutes); +await app.register(employeeRoutes); + +const port = parseInt(process.env['CURRICULUM_PORT'] ?? '3003', 10); + +try { + await app.listen({ port, host: '0.0.0.0' }); +} catch (err) { + app.log.error(err); + process.exit(1); +} diff --git a/app/services/curriculum/src/jobs/queue.ts b/app/services/curriculum/src/jobs/queue.ts new file mode 100644 index 0000000..8dc7e91 --- /dev/null +++ b/app/services/curriculum/src/jobs/queue.ts @@ -0,0 +1,46 @@ +import { v4 as uuid } from 'uuid'; +import { buildCurriculum } from '../generator/build.js'; +import type { GenerationJob } from '../types.js'; + +const jobs = new Map(); + +export function createJob(triggeredBy: string, reason: 'new_topics' | 'manual'): GenerationJob { + const job: GenerationJob = { + id: uuid(), + triggeredBy, + reason, + status: 'queued', + versionId: null, + error: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + jobs.set(job.id, job); + void runPipeline(job.id); + return job; +} + +export function getJob(id: string): GenerationJob | undefined { + return jobs.get(id); +} + +function updateJob(id: string, updates: Partial>): void { + const job = jobs.get(id); + if (!job) return; + jobs.set(id, { ...job, ...updates, updatedAt: new Date() }); +} + +async function runPipeline(jobId: string): Promise { + const job = jobs.get(jobId); + if (!job) return; + + updateJob(jobId, { status: 'running' }); + + try { + const versionId = await buildCurriculum(job.reason); + updateJob(jobId, { status: 'done', versionId }); + } catch (err) { + const error = err instanceof Error ? err.message : String(err); + updateJob(jobId, { status: 'failed', error }); + } +} diff --git a/app/services/curriculum/src/lib/anthropic.ts b/app/services/curriculum/src/lib/anthropic.ts new file mode 100644 index 0000000..98b2a9a --- /dev/null +++ b/app/services/curriculum/src/lib/anthropic.ts @@ -0,0 +1,9 @@ +import Anthropic from '@anthropic-ai/sdk'; + +export const anthropic = new Anthropic({ + apiKey: process.env['ANTHROPIC_API_KEY'], +}); + +export const MODELS = { + SONNET: 'claude-sonnet-4-20250514', +} as const; diff --git a/app/services/curriculum/src/lib/pocketbase.ts b/app/services/curriculum/src/lib/pocketbase.ts new file mode 100644 index 0000000..78aaeb4 --- /dev/null +++ b/app/services/curriculum/src/lib/pocketbase.ts @@ -0,0 +1,14 @@ +import PocketBase from 'pocketbase'; + +const POCKETBASE_URL = process.env['POCKETBASE_URL'] ?? ''; +const POCKETBASE_ADMIN_EMAIL = process.env['POCKETBASE_ADMIN_EMAIL'] ?? ''; +const POCKETBASE_ADMIN_PASSWORD = process.env['POCKETBASE_ADMIN_PASSWORD'] ?? ''; + +const pb = new PocketBase(POCKETBASE_URL); + +export async function getPocketBase(): Promise { + if (!pb.authStore.isValid) { + await pb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD); + } + return pb; +} diff --git a/app/services/curriculum/src/routes/curriculum.ts b/app/services/curriculum/src/routes/curriculum.ts new file mode 100644 index 0000000..0bfe7ac --- /dev/null +++ b/app/services/curriculum/src/routes/curriculum.ts @@ -0,0 +1,174 @@ +import type { FastifyInstance } from 'fastify'; +import { z } from 'zod'; +import { createJob, getJob } from '../jobs/queue.js'; +import { applyVersion } from '../versioning/apply.js'; +import { getPocketBase } from '../lib/pocketbase.js'; + +const GenerateBodySchema = z.object({ + triggeredBy: z.string(), + reason: z.enum(['new_topics', 'manual']), +}); + +const PatchWeekBodySchema = z.object({ + theme: z.string().optional(), + topics: z.array(z.string()).optional(), + topic_order: z.array(z.number()).optional(), + admin_notes: z.string().optional(), + estimated_duration_minutes: z.number().min(15).max(45).optional(), +}); + +export async function curriculumRoutes(app: FastifyInstance): Promise { + // POST /generate + app.post('/generate', async (request, reply) => { + const body = GenerateBodySchema.safeParse(request.body); + if (!body.success) { + return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues }); + } + + const job = createJob(body.data.triggeredBy, body.data.reason); + return reply.status(202).send({ jobId: job.id, status: job.status }); + }); + + // GET /status/:jobId + app.get<{ Params: { jobId: string } }>('/status/:jobId', async (request, reply) => { + const job = getJob(request.params.jobId); + if (!job) { + return reply.status(404).send({ error: 'Job not found' }); + } + return reply.send(job); + }); + + // GET /current + app.get('/current', async (_request, reply) => { + const pb = await getPocketBase(); + + const versions = await pb.collection('curriculum_versions').getFullList({ + filter: 'status = "active"', + }); + + if (versions.length === 0) { + return reply.status(404).send({ error: 'No active curriculum version' }); + } + + const version = versions[0]!; + const weeks = await pb.collection('curriculum_weeks').getFullList({ + filter: `curriculum_version = "${version.id}"`, + sort: 'week_number', + expand: 'theme,topics', + }); + + return reply.send({ version, weeks }); + }); + + // GET /preview + app.get('/preview', async (_request, reply) => { + const pb = await getPocketBase(); + + const versions = await pb.collection('curriculum_versions').getFullList({ + filter: 'status = "draft"', + sort: '-version', + perPage: 1, + }); + + if (versions.length === 0) { + return reply.status(404).send({ error: 'No draft curriculum version' }); + } + + const version = versions[0]!; + const weeks = await pb.collection('curriculum_weeks').getFullList({ + filter: `curriculum_version = "${version.id}"`, + sort: 'week_number', + expand: 'theme,topics', + }); + + const themeIds = new Set(weeks.map(w => w['theme'] as string)); + const topicIds = new Set(weeks.flatMap(w => (w['topics'] as string[]) ?? [])); + + const coverageStats = { + themesTotal: themeIds.size, + themesCovered: themeIds.size, + topicsTotal: topicIds.size, + topicsCovered: topicIds.size, + }; + + return reply.send({ + version: version['version'], + weeks: weeks.map(w => { + const expand = (w['expand'] as Record) ?? {}; + const theme = expand['theme'] as Record | undefined; + const rawTopics = expand['topics']; + const topicList = Array.isArray(rawTopics) ? rawTopics : []; + + return { + weekNumber: w['week_number'], + theme: theme ? { id: theme['id'], title: theme['title'] } : null, + topics: topicList.map((t: unknown) => { + const tr = t as Record; + return { + id: tr['id'], + title: tr['title'], + complexityWeight: tr['complexity_weight'], + }; + }), + estimatedDurationMinutes: w['estimated_duration_minutes'], + }; + }), + coverageStats, + }); + }); + + // PATCH /weeks/:weekId + app.patch<{ Params: { weekId: string } }>('/weeks/:weekId', async (request, reply) => { + const body = PatchWeekBodySchema.safeParse(request.body); + if (!body.success) { + return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues }); + } + + const pb = await getPocketBase(); + + let week; + try { + week = await pb.collection('curriculum_weeks').getOne(request.params.weekId); + } catch { + return reply.status(404).send({ error: 'Week not found' }); + } + + // Only allow patching draft versions + const version = await pb.collection('curriculum_versions').getOne(week['curriculum_version'] as string); + if (version['status'] !== 'draft') { + return reply.status(409).send({ error: 'Can only edit weeks belonging to a draft version' }); + } + + const updates: Record = {}; + if (body.data.theme !== undefined) updates['theme'] = body.data.theme; + if (body.data.topics !== undefined) updates['topics'] = body.data.topics; + if (body.data.topic_order !== undefined) updates['topic_order'] = body.data.topic_order; + if (body.data.admin_notes !== undefined) updates['admin_notes'] = body.data.admin_notes; + if (body.data.estimated_duration_minutes !== undefined) { + updates['estimated_duration_minutes'] = body.data.estimated_duration_minutes; + } + + const updated = await pb.collection('curriculum_weeks').update(request.params.weekId, updates); + return reply.send(updated); + }); + + // POST /confirm + app.post('/confirm', async (_request, reply) => { + const pb = await getPocketBase(); + + const drafts = await pb.collection('curriculum_versions').getFullList({ + filter: 'status = "draft"', + sort: '-version', + perPage: 1, + }); + + if (drafts.length === 0) { + return reply.status(404).send({ error: 'No draft curriculum to confirm' }); + } + + const draftId = drafts[0]!.id; + await applyVersion(draftId); + + return reply.send({ applied: draftId, status: 'active' }); + }); +} diff --git a/app/services/curriculum/src/routes/employee.ts b/app/services/curriculum/src/routes/employee.ts new file mode 100644 index 0000000..c7fa73b --- /dev/null +++ b/app/services/curriculum/src/routes/employee.ts @@ -0,0 +1,140 @@ +import type { FastifyInstance } from 'fastify'; +import { z } from 'zod'; +import { getPocketBase } from '../lib/pocketbase.js'; +import type { EmployeeState } from '../types.js'; + +const AdvanceBodySchema = z.object({ + completedWeek: z.number().min(1).max(26), +}); + +export async function employeeRoutes(app: FastifyInstance): Promise { + // GET /state/:userId + app.get<{ Params: { userId: string } }>('/state/:userId', async (request, reply) => { + const pb = await getPocketBase(); + + const records = await pb.collection('employee_curriculum_state').getFullList({ + filter: `user = "${request.params.userId}"`, + expand: 'active_version', + }); + + if (records.length === 0) { + return reply.status(404).send({ error: 'Employee curriculum state not found' }); + } + + const state = records[0]!; + const currentWeek = state['current_week'] as number; + const activeVersionId = state['active_version'] as string; + + // Fetch the week record for next session + const nextWeekRecords = await pb.collection('curriculum_weeks').getFullList({ + filter: `curriculum_version = "${activeVersionId}" && week_number = ${currentWeek}`, + expand: 'theme,topics', + }); + + let nextSessionTheme: EmployeeState['nextSessionTheme'] = null; + let nextSessionTopics: EmployeeState['nextSessionTopics'] = []; + + const nextWeek = nextWeekRecords[0]; + if (nextWeek) { + const expand = (nextWeek['expand'] as Record) ?? {}; + const theme = expand['theme'] as Record | undefined; + if (theme) { + nextSessionTheme = { id: theme['id'] as string, title: theme['title'] as string }; + } + const rawTopics = expand['topics']; + if (Array.isArray(rawTopics)) { + nextSessionTopics = rawTopics.map((t: unknown) => { + const tr = t as Record; + return { + id: tr['id'] as string, + title: tr['title'] as string, + complexityWeight: (tr['complexity_weight'] as number) ?? 1, + }; + }); + } + } + + const employeeState: EmployeeState = { + userId: request.params.userId, + currentCycle: state['current_cycle'] as number, + currentWeek, + startDate: state['start_date'] as string, + activeVersionId, + nextSessionTheme, + nextSessionTopics, + }; + + return reply.send(employeeState); + }); + + // POST /advance/:userId + app.post<{ Params: { userId: string } }>('/advance/:userId', async (request, reply) => { + const body = AdvanceBodySchema.safeParse(request.body); + if (!body.success) { + return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues }); + } + + const pb = await getPocketBase(); + + const records = await pb.collection('employee_curriculum_state').getFullList({ + filter: `user = "${request.params.userId}"`, + }); + + if (records.length === 0) { + return reply.status(404).send({ error: 'Employee curriculum state not found' }); + } + + const state = records[0]!; + const currentWeek = state['current_week'] as number; + const currentCycle = state['current_cycle'] as number; + + if (body.data.completedWeek !== currentWeek) { + return reply.status(409).send({ + error: 'completedWeek does not match employee current_week', + expected: currentWeek, + received: body.data.completedWeek, + }); + } + + let newWeek: number; + let newCycle: number; + let newActiveVersion: string; + + if (currentWeek === 26) { + // Cycle transition + newWeek = 1; + newCycle = currentCycle + 1; + + // Use current active curriculum version for new cycle + const activeVersions = await pb.collection('curriculum_versions').getFullList({ + filter: 'status = "active"', + perPage: 1, + }); + newActiveVersion = activeVersions[0]?.id ?? (state['active_version'] as string); + } else { + newWeek = currentWeek + 1; + newCycle = currentCycle; + newActiveVersion = state['active_version'] as string; + } + + const updates: Record = { + current_week: newWeek, + current_cycle: newCycle, + active_version: newActiveVersion, + }; + + if (currentWeek === 26) { + updates['start_date'] = new Date().toISOString(); + } + + await pb.collection('employee_curriculum_state').update(state.id, updates); + + return reply.send({ + userId: request.params.userId, + previousWeek: currentWeek, + currentWeek: newWeek, + currentCycle: newCycle, + cycleTransition: currentWeek === 26, + }); + }); +} diff --git a/app/services/curriculum/src/types.ts b/app/services/curriculum/src/types.ts new file mode 100644 index 0000000..9e3871c --- /dev/null +++ b/app/services/curriculum/src/types.ts @@ -0,0 +1,125 @@ +import { z } from 'zod'; + +// --------------------------------------------------------------------------- +// KB snapshot — input to generator +// --------------------------------------------------------------------------- + +export const KBTopicSchema = z.object({ + id: z.string(), + title: z.string(), + complexityWeight: z.number().min(1).max(5), + difficulty: z.string(), + prerequisiteTopics: z.array(z.string()), + relatedTopics: z.array(z.string()), + contrastTopics: z.array(z.string()), +}); + +export const KBThemeSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + topics: z.array(KBTopicSchema), +}); + +export const KBSnapshotSchema = z.object({ + themes: z.array(KBThemeSchema), +}); + +export type KBTopic = z.infer; +export type KBTheme = z.infer; +export type KBSnapshot = z.infer; + +// --------------------------------------------------------------------------- +// Curriculum draft — AI output +// --------------------------------------------------------------------------- + +export const CurriculumWeekDraftSchema = z.object({ + weekNumber: z.number().min(1).max(26), + themeId: z.string(), + topicIds: z.array(z.string()), + estimatedDurationMinutes: z.number().min(15).max(45), + rationale: z.string(), +}); + +export const CurriculumDraftSchema = z.object({ + weeks: z.array(CurriculumWeekDraftSchema).length(26), +}); + +export type CurriculumWeekDraft = z.infer; +export type CurriculumDraft = z.infer; + +// --------------------------------------------------------------------------- +// Employee state +// --------------------------------------------------------------------------- + +export interface EmployeeState { + userId: string; + currentCycle: number; + currentWeek: number; + startDate: string; + activeVersionId: string; + nextSessionTheme: { id: string; title: string } | null; + nextSessionTopics: { id: string; title: string; complexityWeight: number }[]; +} + +// --------------------------------------------------------------------------- +// Job tracking +// --------------------------------------------------------------------------- + +export type JobStatus = 'queued' | 'running' | 'done' | 'failed'; + +export interface GenerationJob { + id: string; + triggeredBy: string; + reason: 'new_topics' | 'manual'; + status: JobStatus; + versionId: string | null; + error: string | null; + createdAt: Date; + updatedAt: Date; +} + +// --------------------------------------------------------------------------- +// Cycle variant context +// --------------------------------------------------------------------------- + +export interface CycleContext { + cycleNumber: number; + employeeHistory: { + typesUsed: string[]; + typesNotUsed: string[]; + lowEngagementTopics: string[]; + }; +} + +// --------------------------------------------------------------------------- +// PocketBase record shapes +// --------------------------------------------------------------------------- + +export interface CurriculumVersionRecord { + id: string; + version: number; + status: 'draft' | 'active' | 'superseded'; + generated_at: string; + generation_notes: string; +} + +export interface CurriculumWeekRecord { + id: string; + curriculum_version: string; + week_number: number; + theme: string; + topics: string[]; + topic_order: number[]; + estimated_duration_minutes: number; + admin_notes: string; +} + +export interface EmployeeStateRecord { + id: string; + user: string; + current_cycle: number; + current_week: number; + start_date: string; + active_version: string; +} diff --git a/app/services/curriculum/src/versioning/apply.ts b/app/services/curriculum/src/versioning/apply.ts new file mode 100644 index 0000000..4287009 --- /dev/null +++ b/app/services/curriculum/src/versioning/apply.ts @@ -0,0 +1,25 @@ +import { getPocketBase } from '../lib/pocketbase.js'; + +export async function applyVersion(newVersionId: string): Promise { + const pb = await getPocketBase(); + + // Get all employees + const employees = await pb.collection('employee_curriculum_state').getFullList(); + + for (const emp of employees) { + await pb.collection('employee_curriculum_state').update(emp.id, { + active_version: newVersionId, + }); + } + + // Supersede old active version + const activeVersions = await pb.collection('curriculum_versions').getFullList({ + filter: 'status = "active"', + }); + for (const v of activeVersions) { + await pb.collection('curriculum_versions').update(v.id, { status: 'superseded' }); + } + + // Activate new version + await pb.collection('curriculum_versions').update(newVersionId, { status: 'active' }); +} diff --git a/app/services/curriculum/src/versioning/freeze.ts b/app/services/curriculum/src/versioning/freeze.ts new file mode 100644 index 0000000..ed4223e --- /dev/null +++ b/app/services/curriculum/src/versioning/freeze.ts @@ -0,0 +1,25 @@ +import { getPocketBase } from '../lib/pocketbase.js'; + +/** + * Returns the set of week numbers that are "frozen" for an employee — + * weeks they have already started or completed, derived from their current_week. + * Weeks < current_week are rendered from session_completions and must not be + * replaced by a new curriculum version. + */ +export async function getFrozenWeeks(userId: string): Promise> { + const pb = await getPocketBase(); + + const records = await pb.collection('employee_curriculum_state').getFullList({ + filter: `user = "${userId}"`, + }); + + const state = records[0]; + if (!state) return new Set(); + + const currentWeek = state['current_week'] as number; + const frozen = new Set(); + for (let w = 1; w < currentWeek; w++) { + frozen.add(w); + } + return frozen; +} diff --git a/app/services/curriculum/tsconfig.json b/app/services/curriculum/tsconfig.json new file mode 100644 index 0000000..7405820 --- /dev/null +++ b/app/services/curriculum/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "rootDir": "src", + "outDir": "dist", + "strict": true, + "noUncheckedIndexedAccess": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/app/services/generation/.gitignore b/app/services/generation/.gitignore new file mode 100644 index 0000000..9bd7f06 --- /dev/null +++ b/app/services/generation/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +*.err diff --git a/app/services/generation/package-lock.json b/app/services/generation/package-lock.json new file mode 100644 index 0000000..67da7a5 --- /dev/null +++ b/app/services/generation/package-lock.json @@ -0,0 +1,1550 @@ +{ + "name": "generation", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "generation", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.24", + "fastify": "^4", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/uuid": "^9", + "dotenv": "^16", + "tsx": "^4", + "typescript": "^5" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.24.3.tgz", + "integrity": "sha512-916wJXO6T6k8R6BAAcLhLPv/pnLGy7YSEBZXZ1XTFbLcTZE8oTy3oDW9WJf9KKZwMvVcePIfoTSvzXHRcGxkQQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pocketbase": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.5.tgz", + "integrity": "sha512-bnI/uinnQps+ElSlzxkc4yvwuSFfKcoszDtXH/4QT2FhGq2mJVUvDlxn+rjRXVntUjPfmMG5LEPZ1eGqV6ssog==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/app/services/generation/package.json b/app/services/generation/package.json new file mode 100644 index 0000000..0035fd2 --- /dev/null +++ b/app/services/generation/package.json @@ -0,0 +1,25 @@ +{ + "name": "generation", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.24", + "fastify": "^4", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/uuid": "^9", + "dotenv": "^16", + "tsx": "^4", + "typescript": "^5" + } +} diff --git a/app/services/generation/src/index.ts b/app/services/generation/src/index.ts new file mode 100644 index 0000000..82b9113 --- /dev/null +++ b/app/services/generation/src/index.ts @@ -0,0 +1,18 @@ +import 'dotenv/config'; +import Fastify from 'fastify'; +import { generateRoutes } from './routes/generate.js'; +import { publishRoutes } from './routes/publish.js'; + +const app = Fastify({ logger: true }); + +await app.register(generateRoutes); +await app.register(publishRoutes); + +const port = parseInt(process.env['GENERATION_PORT'] ?? '3002', 10); + +try { + await app.listen({ port, host: '0.0.0.0' }); +} catch (err) { + app.log.error(err); + process.exit(1); +} diff --git a/app/services/generation/src/jobs/queue.ts b/app/services/generation/src/jobs/queue.ts new file mode 100644 index 0000000..34cf71b --- /dev/null +++ b/app/services/generation/src/jobs/queue.ts @@ -0,0 +1,168 @@ +import { v4 as uuid } from 'uuid'; +import { getPocketBase } from '../lib/pocketbase.js'; +import { generateMicroLearning } from '../pipeline/generate.js'; +import { + MICRO_LEARNING_TYPES, + type GenerationJob, + type JobProgress, + type TopicRecord, +} from '../types.js'; + +// --------------------------------------------------------------------------- +// In-memory store +// --------------------------------------------------------------------------- + +const jobs = new Map(); + +const DEFAULT_PROGRESS: JobProgress = { + topicsTotal: 0, + topicsProcessed: 0, + itemsTotal: 0, + itemsGenerated: 0, + itemsFailed: 0, +}; + +export function createJob(themeId: string): GenerationJob { + const job: GenerationJob = { + id: uuid(), + themeId, + status: 'queued', + progress: { ...DEFAULT_PROGRESS }, + error: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + jobs.set(job.id, job); + void runPipeline(job.id); + return job; +} + +export function getJob(id: string): GenerationJob | undefined { + return jobs.get(id); +} + +function updateJob(id: string, updates: Partial>): void { + const job = jobs.get(id); + if (!job) return; + jobs.set(id, { ...job, ...updates, updatedAt: new Date() }); +} + +function mergeProgress(id: string, partial: Partial): void { + const job = jobs.get(id); + if (!job) return; + updateJob(id, { progress: { ...job.progress, ...partial } }); +} + +// --------------------------------------------------------------------------- +// Pipeline orchestration +// --------------------------------------------------------------------------- + +async function runPipeline(jobId: string): Promise { + const job = jobs.get(jobId); + if (!job) return; + + updateJob(jobId, { status: 'running' }); + + let topics: TopicRecord[]; + try { + topics = await fetchPublishedTopics(job.themeId); + } catch (err) { + const reason = err instanceof Error ? err.message : String(err); + updateJob(jobId, { status: 'failed', error: `topic_fetch_failed: ${reason}` }); + return; + } + + const totalItems = topics.length * MICRO_LEARNING_TYPES.length; + mergeProgress(jobId, { + topicsTotal: topics.length, + itemsTotal: totalItems, + }); + + // Pre-create all micro_learning records with status: queued + const recordIds = await preCreateRecords(topics); + + let generated = 0; + let failed = 0; + + for (let ti = 0; ti < topics.length; ti++) { + const topic = topics[ti]; + if (!topic) continue; + + for (const type of MICRO_LEARNING_TYPES) { + const recordId = recordIds.get(`${topic.id}:${type}`); + if (!recordId) continue; + + try { + const content = await generateMicroLearning(topic, type); + await updateMicroLearning(recordId, 'generated', content); + generated++; + } catch (err) { + const reason = err instanceof Error ? err.message : String(err); + await updateMicroLearning(recordId, 'failed', null, reason); + failed++; + } + + mergeProgress(jobId, { itemsGenerated: generated, itemsFailed: failed }); + } + + mergeProgress(jobId, { topicsProcessed: ti + 1 }); + } + + updateJob(jobId, { status: 'done' }); +} + +// --------------------------------------------------------------------------- +// PocketBase helpers +// --------------------------------------------------------------------------- + +async function fetchPublishedTopics(themeId: string): Promise { + const pb = await getPocketBase(); + const records = await pb.collection('topics').getFullList({ + filter: `theme = "${themeId}" && status = "published"`, + }); + + return records.map(r => ({ + id: r['id'] as string, + title: r['title'] as string, + body: r['body'] as string, + difficulty: r['difficulty'] as TopicRecord['difficulty'], + key_terms: (r['key_terms'] as string[] | null) ?? [], + status: r['status'] as string, + })); +} + +async function preCreateRecords( + topics: TopicRecord[], +): Promise> { + const pb = await getPocketBase(); + const map = new Map(); + + for (const topic of topics) { + for (const type of MICRO_LEARNING_TYPES) { + const record = await pb.collection('micro_learnings').create({ + topic: topic.id, + type, + content: null, + status: 'queued', + generation_model: 'claude-sonnet-4-20250514', + }); + map.set(`${topic.id}:${type}`, record.id); + } + } + + return map; +} + +async function updateMicroLearning( + recordId: string, + status: 'generated' | 'failed', + content: unknown, + _errorNote?: string, +): Promise { + const pb = await getPocketBase(); + await pb.collection('micro_learnings').update(recordId, { + status, + content: content ?? null, + generated_at: status === 'generated' ? new Date().toISOString() : null, + }); +} diff --git a/app/services/generation/src/lib/anthropic.ts b/app/services/generation/src/lib/anthropic.ts new file mode 100644 index 0000000..98b2a9a --- /dev/null +++ b/app/services/generation/src/lib/anthropic.ts @@ -0,0 +1,9 @@ +import Anthropic from '@anthropic-ai/sdk'; + +export const anthropic = new Anthropic({ + apiKey: process.env['ANTHROPIC_API_KEY'], +}); + +export const MODELS = { + SONNET: 'claude-sonnet-4-20250514', +} as const; diff --git a/app/services/generation/src/lib/pocketbase.ts b/app/services/generation/src/lib/pocketbase.ts new file mode 100644 index 0000000..78aaeb4 --- /dev/null +++ b/app/services/generation/src/lib/pocketbase.ts @@ -0,0 +1,14 @@ +import PocketBase from 'pocketbase'; + +const POCKETBASE_URL = process.env['POCKETBASE_URL'] ?? ''; +const POCKETBASE_ADMIN_EMAIL = process.env['POCKETBASE_ADMIN_EMAIL'] ?? ''; +const POCKETBASE_ADMIN_PASSWORD = process.env['POCKETBASE_ADMIN_PASSWORD'] ?? ''; + +const pb = new PocketBase(POCKETBASE_URL); + +export async function getPocketBase(): Promise { + if (!pb.authStore.isValid) { + await pb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD); + } + return pb; +} diff --git a/app/services/generation/src/pipeline/generate.ts b/app/services/generation/src/pipeline/generate.ts new file mode 100644 index 0000000..60113f0 --- /dev/null +++ b/app/services/generation/src/pipeline/generate.ts @@ -0,0 +1,163 @@ +import { anthropic, MODELS } from '../lib/anthropic.js'; +import { + CONTENT_SCHEMAS, + TYPE_LABELS, + type MicroLearningType, + type TopicRecord, +} from '../types.js'; + +const SYSTEM_PROMPT = `You are a learning content designer. Your task is to generate structured learning content for a specific topic in an employee learning platform. + +Output ONLY valid JSON matching the schema provided. No preamble, no explanation, no markdown fences. + +The content should be accurate, practical, and appropriate for the stated difficulty level. Tone: professional but accessible.`; + +function buildUserPrompt( + topic: TopicRecord, + type: MicroLearningType, + schemaDescription: string, + strict: boolean, +): string { + const base = `Topic: ${topic.title} +Difficulty: ${topic.difficulty} +Body: +${topic.body} + +Key terms: ${topic.key_terms.join(', ')} + +Generate a ${TYPE_LABELS[type]} for this topic. + +Output schema: +${schemaDescription}`; + + return strict ? base + '\n\nRespond with valid JSON only, no other text.' : base; +} + +const SCHEMA_DESCRIPTIONS: Record = { + concept_explainer: `{ + "paragraphs": ["2 to 3 paragraphs explaining the concept in plain language"], + "example": "one concrete real-world example" +}`, + scenario_quiz: `{ + "scenario": "a realistic workplace scenario", + "options": [ + { "label": "A", "text": "answer text", "correct": false, "explanation": "why" }, + { "label": "B", "text": "answer text", "correct": true, "explanation": "why" }, + { "label": "C", "text": "answer text", "correct": false, "explanation": "why" }, + { "label": "D", "text": "answer text", "correct": false, "explanation": "why" } + ] +} +Rules: exactly 4 options, exactly 1 marked correct: true.`, + misconceptions: `{ + "items": [ + { "misconception": "common wrong belief", "correction": "accurate explanation" } + ] +} +Rules: 3 to 5 items.`, + how_to: `{ + "steps": [ + { "number": 1, "instruction": "what to do" } + ] +} +Rules: 3 to 8 steps.`, + comparison_card: `{ + "subject_a": "first concept or approach", + "subject_b": "second concept or approach", + "dimensions": [ + { "label": "dimension name", "a": "how A differs", "b": "how B differs" } + ] +} +Rules: 3 to 6 dimensions.`, + reflection_prompt: `{ + "prompt": "open-ended question for the employee to reflect on", + "model_answer": "a thoughtful example answer the employee can compare against" +}`, + flashcard_set: `{ + "cards": [ + { "question": "question text", "answer": "answer text" } + ] +} +Rules: 5 to 10 cards.`, + case_study: `{ + "scenario": "a detailed real-world scenario (150+ words)", + "questions": ["discussion question 1", "discussion question 2"] +} +Rules: 2 to 4 questions.`, + glossary_anchor: `{ + "term": "the key term", + "definition": "precise definition", + "correct_use": "example sentence showing correct use", + "misuse": "common incorrect usage to avoid" +}`, + myth_vs_evidence: `{ + "myth": "a commonly held misconception about this topic", + "evidence": "the evidence-based counterpoint", + "sources": ["source or reference if applicable — use empty array if none"] +}`, +}; + +async function callClaude(prompt: string): Promise { + const response = await anthropic.messages.create({ + model: MODELS.SONNET, + max_tokens: 2000, + temperature: 0, + system: SYSTEM_PROMPT, + messages: [{ role: 'user', content: prompt }], + }); + + const block = response.content[0]; + if (!block || block.type !== 'text') { + throw new Error('unexpected response format from Claude'); + } + return block.text; +} + +async function parseAndValidate(raw: string, type: MicroLearningType): Promise { + const schema = CONTENT_SCHEMAS[type]; + const parsed: unknown = JSON.parse(raw); + return schema.parse(parsed); +} + +export async function generateMicroLearning( + topic: TopicRecord, + type: MicroLearningType, +): Promise { + const schemaDesc = SCHEMA_DESCRIPTIONS[type]; + + // For glossary_anchor, hint Claude to use the first key term + const topicWithHint: TopicRecord = + type === 'glossary_anchor' && topic.key_terms.length > 0 + ? { ...topic, body: topic.body + `\n\nAnchor term: ${topic.key_terms[0]}` } + : topic; + + const prompt = buildUserPrompt(topicWithHint, type, schemaDesc, false); + + let raw: string; + try { + raw = await callClaude(prompt); + } catch (err) { + throw new Error(`Claude API error: ${err instanceof Error ? err.message : String(err)}`); + } + + // First parse attempt + try { + return await parseAndValidate(raw, type); + } catch { + // Retry with strict prompt + const strictPrompt = buildUserPrompt(topicWithHint, type, schemaDesc, true); + let raw2: string; + try { + raw2 = await callClaude(strictPrompt); + } catch (err) { + throw new Error(`Claude API error on retry: ${err instanceof Error ? err.message : String(err)}`); + } + + try { + return await parseAndValidate(raw2, type); + } catch (err) { + throw new Error( + `validation failed after retry: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } +} diff --git a/app/services/generation/src/routes/generate.ts b/app/services/generation/src/routes/generate.ts new file mode 100644 index 0000000..b459a3e --- /dev/null +++ b/app/services/generation/src/routes/generate.ts @@ -0,0 +1,36 @@ +import type { FastifyInstance } from 'fastify'; +import { createJob, getJob } from '../jobs/queue.js'; +import { GenerateBodySchema } from '../types.js'; + +export async function generateRoutes(app: FastifyInstance): Promise { + app.post('/generate', async (request, reply) => { + const parsed = GenerateBodySchema.safeParse(request.body); + if (!parsed.success) { + return reply.status(400).send({ error: 'invalid request', details: parsed.error.issues }); + } + + const { themeId } = parsed.data; + const job = createJob(themeId); + + return reply.status(202).send({ + jobId: job.id, + status: job.status, + topicsFound: job.progress.topicsTotal, + totalItems: job.progress.itemsTotal, + }); + }); + + app.get<{ Params: { jobId: string } }>('/status/:jobId', async (request, reply) => { + const job = getJob(request.params.jobId); + if (!job) { + return reply.status(404).send({ error: 'job not found' }); + } + + return reply.send({ + jobId: job.id, + status: job.status, + progress: job.progress, + error: job.error, + }); + }); +} diff --git a/app/services/generation/src/routes/publish.ts b/app/services/generation/src/routes/publish.ts new file mode 100644 index 0000000..8b05213 --- /dev/null +++ b/app/services/generation/src/routes/publish.ts @@ -0,0 +1,44 @@ +import type { FastifyInstance } from 'fastify'; +import { getPocketBase } from '../lib/pocketbase.js'; +import { PublishBodySchema } from '../types.js'; + +export async function publishRoutes(app: FastifyInstance): Promise { + app.patch<{ Params: { id: string } }>('/micro-learnings/:id', async (request, reply) => { + const parsed = PublishBodySchema.safeParse(request.body); + if (!parsed.success) { + return reply.status(400).send({ error: 'invalid request', details: parsed.error.issues }); + } + + const { id } = request.params; + const { status: newStatus } = parsed.data; + + const pb = await getPocketBase(); + + let existing: Record; + try { + existing = await pb.collection('micro_learnings').getOne(id) as Record; + } catch { + return reply.status(404).send({ error: 'micro learning not found' }); + } + + if (existing['status'] !== 'generated') { + return reply.status(400).send({ + error: 'only generated records can be published or rejected', + currentStatus: existing['status'], + }); + } + + const updates: Record = { status: newStatus }; + if (newStatus === 'published') { + updates['published_at'] = new Date().toISOString(); + } + + const updated = await pb.collection('micro_learnings').update(id, updates); + + return reply.send({ + id: updated.id, + status: updated['status'], + published_at: updated['published_at'] ?? null, + }); + }); +} diff --git a/app/services/generation/src/types.ts b/app/services/generation/src/types.ts new file mode 100644 index 0000000..d65e754 --- /dev/null +++ b/app/services/generation/src/types.ts @@ -0,0 +1,201 @@ +import { z } from 'zod'; + +// --------------------------------------------------------------------------- +// Micro learning types +// --------------------------------------------------------------------------- + +export const MICRO_LEARNING_TYPES = [ + 'concept_explainer', + 'scenario_quiz', + 'misconceptions', + 'how_to', + 'comparison_card', + 'reflection_prompt', + 'flashcard_set', + 'case_study', + 'glossary_anchor', + 'myth_vs_evidence', +] as const; + +export type MicroLearningType = (typeof MICRO_LEARNING_TYPES)[number]; + +// --------------------------------------------------------------------------- +// Content schemas — validated against AI output before PocketBase write +// --------------------------------------------------------------------------- + +export const ConceptExplainerSchema = z.object({ + paragraphs: z.array(z.string().min(10)).min(2).max(3), + example: z.string().min(20), +}); + +export const ScenarioQuizSchema = z.object({ + scenario: z.string().min(30), + options: z + .array( + z.object({ + label: z.enum(['A', 'B', 'C', 'D']), + text: z.string().min(5), + correct: z.boolean(), + explanation: z.string().min(10), + }), + ) + .length(4) + .refine(opts => opts.filter(o => o.correct).length === 1, { + message: 'exactly one correct option required', + }), +}); + +export const MisconceptionsSchema = z.object({ + items: z + .array( + z.object({ + misconception: z.string().min(10), + correction: z.string().min(10), + }), + ) + .min(3) + .max(5), +}); + +export const HowToSchema = z.object({ + steps: z + .array( + z.object({ + number: z.number().int().positive(), + instruction: z.string().min(10), + }), + ) + .min(3) + .max(8), +}); + +export const ComparisonCardSchema = z.object({ + subject_a: z.string().min(2), + subject_b: z.string().min(2), + dimensions: z + .array( + z.object({ + label: z.string().min(2), + a: z.string().min(5), + b: z.string().min(5), + }), + ) + .min(3) + .max(6), +}); + +export const ReflectionPromptSchema = z.object({ + prompt: z.string().min(20), + model_answer: z.string().min(50), +}); + +export const FlashcardSetSchema = z.object({ + cards: z + .array( + z.object({ + question: z.string().min(5), + answer: z.string().min(5), + }), + ) + .min(5) + .max(10), +}); + +export const CaseStudySchema = z.object({ + scenario: z.string().min(150), + questions: z.array(z.string().min(10)).min(2).max(4), +}); + +export const GlossaryAnchorSchema = z.object({ + term: z.string().min(2), + definition: z.string().min(20), + correct_use: z.string().min(20), + misuse: z.string().min(20), +}); + +export const MythVsEvidenceSchema = z.object({ + myth: z.string().min(20), + evidence: z.string().min(30), + sources: z.array(z.string()), +}); + +// Map type → schema for lookup +export const CONTENT_SCHEMAS: Record = { + concept_explainer: ConceptExplainerSchema, + scenario_quiz: ScenarioQuizSchema, + misconceptions: MisconceptionsSchema, + how_to: HowToSchema, + comparison_card: ComparisonCardSchema, + reflection_prompt: ReflectionPromptSchema, + flashcard_set: FlashcardSetSchema, + case_study: CaseStudySchema, + glossary_anchor: GlossaryAnchorSchema, + myth_vs_evidence: MythVsEvidenceSchema, +}; + +// Map type → human-readable label for prompts +export const TYPE_LABELS: Record = { + concept_explainer: 'Concept Explainer', + scenario_quiz: 'Scenario Quiz', + misconceptions: 'Misconceptions', + how_to: 'How-To Guide', + comparison_card: 'Comparison Card', + reflection_prompt: 'Reflection Prompt', + flashcard_set: 'Flashcard Set', + case_study: 'Case Study', + glossary_anchor: 'Glossary Anchor', + myth_vs_evidence: 'Myth vs Evidence', +}; + +// --------------------------------------------------------------------------- +// PocketBase: Topic (fetched from PB before generation) +// --------------------------------------------------------------------------- + +export interface TopicRecord { + id: string; + title: string; + body: string; + difficulty: 'introductory' | 'intermediate' | 'advanced'; + key_terms: string[]; + status: string; +} + +// --------------------------------------------------------------------------- +// Job system +// --------------------------------------------------------------------------- + +export type JobStatus = 'queued' | 'running' | 'done' | 'failed'; + +export interface JobProgress { + topicsTotal: number; + topicsProcessed: number; + itemsTotal: number; + itemsGenerated: number; + itemsFailed: number; +} + +export interface GenerationJob { + id: string; + themeId: string; + status: JobStatus; + progress: JobProgress; + error: string | null; + createdAt: Date; + updatedAt: Date; +} + +// --------------------------------------------------------------------------- +// API request schemas (Zod — validates external input) +// --------------------------------------------------------------------------- + +export const GenerateBodySchema = z.object({ + themeId: z.string().min(1), +}); + +export type GenerateBody = z.infer; + +export const PublishBodySchema = z.object({ + status: z.enum(['published', 'rejected']), +}); + +export type PublishBody = z.infer; diff --git a/app/services/generation/tsconfig.json b/app/services/generation/tsconfig.json new file mode 100644 index 0000000..7405820 --- /dev/null +++ b/app/services/generation/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "rootDir": "src", + "outDir": "dist", + "strict": true, + "noUncheckedIndexedAccess": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/app/services/ingestion/package-lock.json b/app/services/ingestion/package-lock.json new file mode 100644 index 0000000..f598039 --- /dev/null +++ b/app/services/ingestion/package-lock.json @@ -0,0 +1,1888 @@ +{ + "name": "ingestion", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ingestion", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.24", + "@qdrant/js-client-rest": "^1.9", + "fastify": "^4", + "openai": "^4", + "pdf-parse": "^1.1", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/pdf-parse": "^1.1", + "@types/uuid": "^9", + "dotenv": "^16", + "pdfkit": "^0.18.0", + "tsx": "^4", + "typescript": "^5" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.24.3.tgz", + "integrity": "sha512-916wJXO6T6k8R6BAAcLhLPv/pnLGy7YSEBZXZ1XTFbLcTZE8oTy3oDW9WJf9KKZwMvVcePIfoTSvzXHRcGxkQQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@qdrant/js-client-rest": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.18.0.tgz", + "integrity": "sha512-/0dqX5uV9chC1DnYSnU4gNMrDqse/pt6hHg3Rqqpl5isH7xl1xSNvffjzBoxycDD79luWn7Ho6Rh/61sOs5DNw==", + "license": "Apache-2.0", + "dependencies": { + "@qdrant/openapi-typescript-fetch": "1.2.6", + "undici": "^6.24.0" + }, + "engines": { + "node": ">=18.17.0", + "pnpm": ">=8" + }, + "peerDependencies": { + "typescript": ">=4.7" + } + }, + "node_modules/@qdrant/openapi-typescript-fetch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.6.tgz", + "integrity": "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/pdf-parse": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.5.tgz", + "integrity": "sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-ensure": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", + "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/pdf-parse": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz", + "integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==", + "license": "MIT", + "dependencies": { + "node-ensure": "^0.0.0" + }, + "engines": { + "node": ">=6.8.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfkit": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.18.0.tgz", + "integrity": "sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.0.0", + "@noble/hashes": "^1.6.0", + "fontkit": "^2.0.4", + "js-md5": "^0.8.3", + "linebreak": "^1.1.0", + "png-js": "^1.0.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/png-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz", + "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==", + "dev": true, + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/pocketbase": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.5.tgz", + "integrity": "sha512-bnI/uinnQps+ElSlzxkc4yvwuSFfKcoszDtXH/4QT2FhGq2mJVUvDlxn+rjRXVntUjPfmMG5LEPZ1eGqV6ssog==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/toad-cache": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", + "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/app/services/ingestion/package.json b/app/services/ingestion/package.json index f6c5f58..2bd7a51 100644 --- a/app/services/ingestion/package.json +++ b/app/services/ingestion/package.json @@ -2,6 +2,7 @@ "name": "ingestion", "version": "0.1.0", "private": true, + "type": "module", "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", @@ -10,21 +11,22 @@ "migrate:qdrant": "tsx src/migrations/002_qdrant_setup.ts" }, "dependencies": { - "fastify": "^4", "@anthropic-ai/sdk": "^0.24", - "openai": "^4", "@qdrant/js-client-rest": "^1.9", - "pocketbase": "^0.21", + "fastify": "^4", + "openai": "^4", "pdf-parse": "^1.1", + "pocketbase": "^0.21", "uuid": "^9", "zod": "^3" }, "devDependencies": { - "typescript": "^5", - "tsx": "^4", - "dotenv": "^16", "@types/node": "^20", "@types/pdf-parse": "^1.1", - "@types/uuid": "^9" + "@types/uuid": "^9", + "dotenv": "^16", + "pdfkit": "^0.18.0", + "tsx": "^4", + "typescript": "^5" } } diff --git a/app/services/ingestion/service.err b/app/services/ingestion/service.err new file mode 100644 index 0000000..f1bc993 --- /dev/null +++ b/app/services/ingestion/service.err @@ -0,0 +1,16 @@ +C:\Users\RaymondVerhoef\Gitea\learning-platform\app\services\ingestion\node_modules\.bin\tsx:2 +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + ^^^^^^^ + +SyntaxError: missing ) after argument list + at wrapSafe (node:internal/modules/cjs/loader:1662:18) + at Module._compile (node:internal/modules/cjs/loader:1704:20) + at Object..js (node:internal/modules/cjs/loader:1895:10) + at Module.load (node:internal/modules/cjs/loader:1465:32) + at Function._load (node:internal/modules/cjs/loader:1282:12) + at TracingChannel.traceSync (node:diagnostics_channel:322:14) + at wrapModuleLoad (node:internal/modules/cjs/loader:235:24) + at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5) + at node:internal/main/run_main_module:36:49 + +Node.js v22.16.0 diff --git a/app/services/ingestion/src/migrations/001_initial_schema.ts b/app/services/ingestion/src/migrations/001_initial_schema.ts index 71bc0f4..677ce3f 100644 --- a/app/services/ingestion/src/migrations/001_initial_schema.ts +++ b/app/services/ingestion/src/migrations/001_initial_schema.ts @@ -46,7 +46,7 @@ const field = { }), json: (name: string): FieldDef => ({ - name, type: 'json', required: false, options: {}, + name, type: 'json', required: false, options: { maxSize: 2097152 }, }), date: (name: string): FieldDef => ({ @@ -146,18 +146,17 @@ async function run(): Promise { const usersCol = await pb.collections.getFirstListItem('name="users"'); ids.set('users', usersCol.id); - const hasRole = usersCol.schema.some(s => s.name === 'role'); - if (!hasRole) { + const existingFieldNames = new Set(usersCol.schema.map((s: { name: string }) => s.name)); + const fieldsToAdd: FieldDef[] = []; + if (!existingFieldNames.has('role')) fieldsToAdd.push(field.select('role', ['admin', 'employee'], true)); + if (!existingFieldNames.has('display_name')) fieldsToAdd.push(field.text('display_name')); + + if (fieldsToAdd.length > 0) { const updateBody: Record = { - schema: [ - ...usersCol.schema, - field.select('role', ['admin', 'employee'], true), - field.text('display_name'), - field.file('avatar', ['image/jpeg', 'image/png', 'image/webp']), - ], + schema: [...usersCol.schema, ...fieldsToAdd], }; await pb.collections.update(usersCol.id, updateBody); - console.log(' extended with role, display_name, avatar'); + console.log(` extended with: ${fieldsToAdd.map((f: FieldDef) => f.name).join(', ')}`); } else { console.log(' skip users (already extended)'); } diff --git a/app/services/ingestion/src/pipeline/embed.ts b/app/services/ingestion/src/pipeline/embed.ts index b6c0623..90c7906 100644 --- a/app/services/ingestion/src/pipeline/embed.ts +++ b/app/services/ingestion/src/pipeline/embed.ts @@ -26,11 +26,16 @@ export async function embedAndStore( writtenTopics: WrittenTopic[], onProgress: (embedded: number) => void, ): Promise { - // Build chunk → topic mapping + // Build chunk → topic mapping. + // The AI labels chunks as [CHUNK-] so sourceChunkIds may carry that prefix; + // strip it so lookups match the bare UUID used as the Qdrant point ID. + const normalise = (id: string): string => id.replace(/^CHUNK-/i, ''); + const chunkTopicMap = new Map(); const chunkThemeMap = new Map(); for (const topic of writtenTopics) { - for (const chunkId of topic.sourceChunkIds) { + for (const rawId of topic.sourceChunkIds) { + const chunkId = normalise(rawId); chunkTopicMap.set(chunkId, topic.id); chunkThemeMap.set(chunkId, topic.themeId); } @@ -94,7 +99,9 @@ export async function embedAndStore( // Update topics.qdrant_chunk_ids in PocketBase // ------------------------------------------------------------------------- for (const topic of writtenTopics) { - const qdrantIds = topic.sourceChunkIds.filter(id => chunkTopicMap.get(id) === topic.id); + const qdrantIds = topic.sourceChunkIds + .map(id => normalise(id)) + .filter(id => chunkTopicMap.get(id) === topic.id); if (qdrantIds.length > 0) { await updateTopicQdrantIds(topic.id, qdrantIds); } diff --git a/app/services/ingestion/src/pipeline/structure.ts b/app/services/ingestion/src/pipeline/structure.ts index d5f9c9b..15ecdda 100644 --- a/app/services/ingestion/src/pipeline/structure.ts +++ b/app/services/ingestion/src/pipeline/structure.ts @@ -61,7 +61,7 @@ function buildUserPrompt(chunks: Chunk[], filename: string, format: DocumentForm async function callClaude(chunks: Chunk[], filename: string, format: DocumentFormat, strict: boolean): Promise { const response = await anthropic.messages.create({ model: MODELS.SONNET, - max_tokens: 8000, + max_tokens: 16000, temperature: 0, system: SYSTEM_PROMPT, messages: [{ role: 'user', content: buildUserPrompt(chunks, filename, format, strict) }], @@ -76,12 +76,14 @@ async function callClaude(chunks: Chunk[], filename: string, format: DocumentFor try { parsed = JSON.parse(textBlock.text); } catch { + console.error(`[structure] JSON parse failed (strict=${strict}), response length=${textBlock.text.length}, stop_reason=${response.stop_reason}`); if (strict) throw new Error('structure_extraction_failed'); return callClaude(chunks, filename, format, true); } const result = DraftKBSchema.safeParse(parsed); if (!result.success) { + console.error(`[structure] Zod validation failed (strict=${strict}):`, JSON.stringify(result.error.issues.slice(0,3))); if (strict) throw new Error('structure_extraction_failed'); return callClaude(chunks, filename, format, true); } diff --git a/app/services/ingestion/src/types.ts b/app/services/ingestion/src/types.ts index 31f1853..676dbe8 100644 --- a/app/services/ingestion/src/types.ts +++ b/app/services/ingestion/src/types.ts @@ -82,6 +82,7 @@ export interface WrittenTopic { // --------------------------------------------------------------------------- export interface SourceChunkPayload { + [key: string]: unknown; source_document_id: string; chunk_index: number; text: string; @@ -91,6 +92,7 @@ export interface SourceChunkPayload { } export interface TopicSummaryPayload { + [key: string]: unknown; topic_id: string; theme_id: string; title: string; diff --git a/app/services/ingestion/test-files/large_doc.md b/app/services/ingestion/test-files/large_doc.md new file mode 100644 index 0000000..cf445a0 --- /dev/null +++ b/app/services/ingestion/test-files/large_doc.md @@ -0,0 +1,605 @@ +# Comprehensive Guide to Enterprise Software Architecture +## Introduction to Software Architecture + +Software architecture defines the high-level structure of a software system. It encompasses the decisions made about the organization of a system, the selection of structural elements and their interfaces, and the composition of these elements. + +### Microservices Architecture Part 1 + +Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Event-Driven Architecture Part 1 + +Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Domain-Driven Design Part 1 + +Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### CQRS Pattern Part 1 + +Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Event Sourcing Part 1 + +Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Saga Pattern Part 1 + +The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### API Gateway Pattern Part 1 + +An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Service Mesh Part 1 + +A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Hexagonal Architecture Part 1 + +Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Clean Architecture Part 1 + +Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Repository Pattern Part 1 + +The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Unit of Work Pattern Part 1 + +The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Factory Pattern Part 1 + +The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Observer Pattern Part 1 + +The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Strategy Pattern Part 1 + +The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Decorator Pattern Part 1 + +The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Circuit Breaker Pattern Part 1 + +The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Bulkhead Pattern Part 1 + +The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Retry Pattern Part 1 + +The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Throttling Pattern Part 1 + +The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Cache-Aside Pattern Part 1 + +The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Sharding Pattern Part 1 + +The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Data Lake Architecture Part 1 + +A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Lambda Architecture Part 1 + +Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Kappa Architecture Part 1 + +Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Microservices Architecture Part 2 + +Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Event-Driven Architecture Part 2 + +Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Domain-Driven Design Part 2 + +Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## CQRS Pattern Part 2 + +Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Event Sourcing Part 2 + +Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Saga Pattern Part 2 + +The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## API Gateway Pattern Part 2 + +An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Service Mesh Part 2 + +A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Hexagonal Architecture Part 2 + +Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Clean Architecture Part 2 + +Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Repository Pattern Part 2 + +The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Unit of Work Pattern Part 2 + +The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Factory Pattern Part 2 + +The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Observer Pattern Part 2 + +The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Strategy Pattern Part 2 + +The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Decorator Pattern Part 2 + +The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Circuit Breaker Pattern Part 2 + +The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Bulkhead Pattern Part 2 + +The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Retry Pattern Part 2 + +The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Throttling Pattern Part 2 + +The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Cache-Aside Pattern Part 2 + +The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Sharding Pattern Part 2 + +The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Data Lake Architecture Part 2 + +A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Lambda Architecture Part 2 + +Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Kappa Architecture Part 2 + +Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Microservices Architecture Part 3 + +Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Event-Driven Architecture Part 3 + +Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Domain-Driven Design Part 3 + +Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## CQRS Pattern Part 3 + +Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Event Sourcing Part 3 + +Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Saga Pattern Part 3 + +The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## API Gateway Pattern Part 3 + +An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Service Mesh Part 3 + +A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Hexagonal Architecture Part 3 + +Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Clean Architecture Part 3 + +Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Repository Pattern Part 3 + +The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Unit of Work Pattern Part 3 + +The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Factory Pattern Part 3 + +The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Observer Pattern Part 3 + +The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Strategy Pattern Part 3 + +The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Decorator Pattern Part 3 + +The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Circuit Breaker Pattern Part 3 + +The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Bulkhead Pattern Part 3 + +The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Retry Pattern Part 3 + +The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Throttling Pattern Part 3 + +The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Cache-Aside Pattern Part 3 + +The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Sharding Pattern Part 3 + +The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +### Data Lake Architecture Part 3 + +A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Lambda Architecture Part 3 + +Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + +## Kappa Architecture Part 3 + +Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth. + +This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved. + +When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place. + diff --git a/app/services/ingestion/test-files/malformed.pdf b/app/services/ingestion/test-files/malformed.pdf new file mode 100644 index 0000000..648c3df --- /dev/null +++ b/app/services/ingestion/test-files/malformed.pdf @@ -0,0 +1,2 @@ +%PDF-1.4 +This is not a valid PDF file. It contains garbage content that pdf-parse cannot handle. \ No newline at end of file diff --git a/app/services/ingestion/test-files/payload_md.json b/app/services/ingestion/test-files/payload_md.json new file mode 100644 index 0000000..28a1cc2 --- /dev/null +++ b/app/services/ingestion/test-files/payload_md.json @@ -0,0 +1,6 @@ +{ + "documentId": "i9ue488qb30owl4", + "filename": "sample.md", + "format": "md", + "filePath": "C:\\Users\\RaymondVerhoef\\Gitea\\learning-platform\\app\\services\\ingestion\\test-files\\sample.md" +} diff --git a/app/services/ingestion/test-files/sample.md b/app/services/ingestion/test-files/sample.md new file mode 100644 index 0000000..1e41cd7 --- /dev/null +++ b/app/services/ingestion/test-files/sample.md @@ -0,0 +1,72 @@ +# Introduction to TypeScript + +TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. + +## Type System + +TypeScript adds optional static typing and class-based object-oriented programming to the language. Types provide a way to describe the shape of an object, providing better documentation, and allowing TypeScript to validate that your code is working correctly. + +### Basic Types + +TypeScript supports several basic types including `string`, `number`, `boolean`, `array`, and `tuple`. These types allow you to add type annotations to your variables and function parameters. + +## Interfaces + +Interfaces define the shape of an object in TypeScript. They are a powerful way to define contracts within your code as well as contracts with code outside of your project. + +```typescript +interface User { + name: string; + age: number; + email?: string; +} +``` + +An interface can define optional properties using the `?` operator. This is useful when some properties may or may not be present. + +## Classes + +TypeScript supports full class-based object-oriented programming with inheritance, interfaces, and access modifiers. Classes provide a clean and reusable way to create objects. + +```typescript +class Animal { + private name: string; + + constructor(name: string) { + this.name = name; + } + + public move(distance: number = 0): void { + console.log(`${this.name} moved ${distance}m.`); + } +} +``` + +Access modifiers (`public`, `private`, `protected`) control visibility of class members. + +## Generics + +Generics provide a way to make components work with any data type and not restrict to one data type. They allow users to consume these components and use their own types. + +```typescript +function identity(arg: T): T { + return arg; +} +``` + +Generics are particularly useful for building reusable data structures and functions that work across multiple types. + +## Enums + +Enumerations allow you to define a set of named constants. TypeScript provides both numeric and string-based enums. + +```typescript +enum Direction { + Up = "UP", + Down = "DOWN", + Left = "LEFT", + Right = "RIGHT", +} +``` + +String enums are more readable than numeric enums because they provide meaningful string values at runtime. diff --git a/app/services/ingestion/test-files/sample.pdf b/app/services/ingestion/test-files/sample.pdf new file mode 100644 index 0000000..fde5563 --- /dev/null +++ b/app/services/ingestion/test-files/sample.pdf @@ -0,0 +1,55 @@ +%PDF-1.4 +1 0 obj +<> +endobj +2 0 obj +<> +endobj +3 0 obj +<>>> /Contents 5 0 R>> +endobj +4 0 obj +<> +endobj +5 0 obj +<> +stream +BT +/F1 14 Tf +50 750 Td +(Introduction to Docker Containers) Tj +0 -25 Td +/F1 12 Tf +(Docker is a platform for developing shipping and running applications.) Tj +0 -18 Td +(Containers are lightweight portable units that include all dependencies.) Tj +0 -25 Td +/F1 14 Tf +(Container Images) Tj +0 -25 Td +/F1 12 Tf +(A Docker image is a read-only template used to create containers.) Tj +0 -18 Td +(Images are built from a Dockerfile with build instructions.) Tj +0 -25 Td +/F1 14 Tf +(Docker Compose) Tj +0 -25 Td +/F1 12 Tf +(Docker Compose defines multi-container applications in compose.yml.) Tj +ET +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000009 00000 n +0000000056 00000 n +0000000111 00000 n +0000000231 00000 n +0000000299 00000 n +trailer +<> +startxref +930 +%%EOF diff --git a/app/services/ingestion/test-files/sample.txt b/app/services/ingestion/test-files/sample.txt new file mode 100644 index 0000000..371b787 --- /dev/null +++ b/app/services/ingestion/test-files/sample.txt @@ -0,0 +1,31 @@ +Introduction to Software Architecture Patterns + +Software architecture patterns are reusable solutions to commonly occurring problems in software architecture within a given context. They are similar to design patterns but with a broader scope. + +The most common architecture patterns include layered architecture, event-driven architecture, microservices, and service-oriented architecture. Each pattern has its own strengths and weaknesses that make it more or less appropriate for different use cases. + +Layered Architecture + +The layered architecture pattern organizes code into layers of functionality. The most common is the n-tier architecture with presentation, business logic, and data access layers. Each layer has a specific role and responsibility. + +The presentation layer handles all user interface and browser communication logic. The business logic layer executes specific business rules associated with the request. The data access layer handles all aspects of the database interaction. + +Benefits of the layered approach include separation of concerns, ease of testing, and good maintainability. Each layer can be developed and maintained independently, reducing the complexity of the system. + +Event-Driven Architecture + +Event-driven architecture is a software design pattern in which decoupled applications can asynchronously publish and subscribe to events via an event broker. This pattern promotes loose coupling and scalability. + +Events represent things that have happened in the system. An event producer publishes an event to an event broker. Event consumers subscribe to event types and receive notifications when events are published. + +Message queues like RabbitMQ and Apache Kafka are commonly used as event brokers. They provide durable, reliable messaging between producers and consumers, even when consumers are temporarily offline. + +Microservices Architecture + +Microservices is an architectural style that structures an application as a collection of small, independently deployable services. Each service is focused on a specific business capability. + +Services communicate over well-defined APIs, usually HTTP REST or message queues. Each service can be deployed, scaled, and updated independently. This allows teams to work on different services without affecting the others. + +Container orchestration platforms like Kubernetes are commonly used to manage microservices deployments. They handle service discovery, load balancing, and automatic scaling. + +The challenges of microservices include distributed system complexity, data consistency, and operational overhead. Teams need to invest in DevOps tooling and practices to manage microservices effectively. diff --git a/app/services/ingestion/test-files/system_test.pdf b/app/services/ingestion/test-files/system_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8b8a441f9caeb94c7991969928d37d8a2010b7f5 GIT binary patch literal 268160 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£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 current_week from active_version + Weeks < current_week are rendered from session_completions history + (immutable records — not from curriculum_weeks) + +Set old curriculum_versions.status → 'superseded' +Set new curriculum_versions.status → 'active' +``` + +Completed weeks are never stored against a curriculum version — they live +in session_completions. The version only determines future week content. + +--- + +## Perpetual cycling + +### Week 26 completion → cycle transition + +When progress service calls POST /advance/:userId with completedWeek: 26: + +``` +employee.currentCycle += 1 +employee.currentWeek = 1 +employee.startDate = now +employee.activeVersion = current active version + +Generate cycle variant (see below) +``` + +### Cycle variant generation + +Cycle 2+ is not identical to cycle 1. The AI call receives additional context: + +Additional fields in user prompt for cycle 2+: +```json +{ + "cycleNumber": 2, + "employeeHistory": { + "typesUsed": ["concept_explainer", "scenario_quiz", "how_to"], + "typesNotUsed": ["case_study", "myth_vs_evidence", "comparison_card"], + "lowEngagementTopics": ["topic-id-1", "topic-id-2"] + } +} +``` + +Additional rules added to system prompt for cycle 2+: +``` +- Vary the Theme sequence from the previous cycle +- Topics identified as low engagement should appear earlier in this cycle +- The rationale field should note what is different from cycle 1 +``` + +Low engagement is determined by: topics where the employee completed only +one micro learning type (minimum engagement). Retrieved from session_completions +by progress service and passed to curriculum service on cycle transition. + +--- + +## Admin curriculum editor + +The curriculum editor in the admin app (built in frontend phase) calls: +- GET /preview to display the proposed schedule +- PATCH /weeks/:weekId to update theme or topic assignment +- POST /confirm to apply the version + +The PATCH route allows admin to: +- Reassign a Theme to a different week (swap two weeks) +- Add or remove Topics from a week's topic list +- Edit admin_notes per week + +Changes made via PATCH update the draft curriculum_weeks records before +the version is confirmed and applied. + +--- + +## Environment variables + +``` +ANTHROPIC_API_KEY= +POCKETBASE_URL= +POCKETBASE_ADMIN_EMAIL= +POCKETBASE_ADMIN_PASSWORD= +CURRICULUM_PORT=3003 +``` + +--- + +## Dependencies + +```json +{ + "dependencies": { + "fastify": "^4", + "@anthropic-ai/sdk": "^0.24", + "pocketbase": "^0.21", + "zod": "^3", + "uuid": "^9" + } +} +``` + +--- + +## TypeScript strict mode requirements + +- No `any` types +- KBSnapshot typed explicitly — validated against PocketBase response +- CurriculumDraft validated through Zod before any PocketBase writes +- Topological sort implemented with explicit typed graph structure + +--- + +## What this service does NOT do + +- Does not generate micro learnings → generation service +- Does not record completions → progress service +- Does not serve KB content → frontend reads PocketBase directly +- Does not handle auth → PocketBase + frontend + +--- + +## Testing checkpoints + +1. Generate curriculum from a KB with 5+ themes → confirm 26 weeks produced +2. Confirm all themes appear at least once +3. Confirm topic order within a week respects prerequisites +4. Add a new theme to KB → trigger regeneration → confirm employee at week 5 + sees weeks 1–5 unchanged, weeks 6–26 updated +5. Advance employee through week 26 → confirm cycle 2 starts with varied sequence +6. Admin edits week 3 theme → confirm patch updates draft before confirmation diff --git a/docs/frontend-spec.md b/docs/frontend-spec.md new file mode 100644 index 0000000..520a603 --- /dev/null +++ b/docs/frontend-spec.md @@ -0,0 +1,701 @@ +# Frontend spec + +## Responsibility + +Single Next.js 14 codebase serving two distinct role-based experiences: +- `/admin/*` — content administration (document upload, KB review, curriculum) +- `/app/*` — employee learning experience (sessions, library, R42, gamification) + +Mobile-first. Designed for 375px width, scales up. Installable as a PWA. + +--- + +## Location + +``` +app/frontend/ + ├── src/ + │ ├── app/ Next.js app router + │ │ ├── layout.tsx root layout — global stylesheet import + │ │ ├── page.tsx redirect → role-based landing + │ │ ├── admin/ + │ │ │ ├── layout.tsx admin shell (sidebar nav) + │ │ │ ├── page.tsx admin dashboard + │ │ │ ├── documents/ + │ │ │ │ └── page.tsx document upload + ingestion status + │ │ │ ├── knowledge/ + │ │ │ │ ├── page.tsx theme batch review list + │ │ │ │ └── [themeId]/page.tsx theme detail + topic edit + │ │ │ └── curriculum/ + │ │ │ └── page.tsx curriculum editor + regeneration + │ │ ├── app/ + │ │ │ ├── layout.tsx employee shell (bottom nav + R42) + │ │ │ ├── page.tsx redirect → /app/session + │ │ │ ├── session/ + │ │ │ │ └── page.tsx current week session + │ │ │ ├── library/ + │ │ │ │ ├── page.tsx knowledge library browse + │ │ │ │ └── [topicId]/page.tsx topic detail + │ │ │ └── profile/ + │ │ │ └── page.tsx gamification profile + heatmap + badges + │ │ ├── auth/ + │ │ │ └── page.tsx login (PocketBase auth) + │ │ └── api/ Next.js API routes (thin proxies only) + │ ├── components/ + │ │ ├── admin/ admin-specific components + │ │ ├── employee/ employee-specific components + │ │ ├── micro-learnings/ one component per micro learning type + │ │ ├── r42/ R42 chatbot components + │ │ ├── gamification/ heatmap, badges, leaderboard + │ │ └── ui/ shared primitives + │ ├── lib/ + │ │ ├── pocketbase.ts PocketBase client (browser) + │ │ ├── services.ts typed API calls to backend services + │ │ ├── auth.ts auth helpers + role guards + │ │ └── hooks/ custom React hooks + │ └── types/ + │ └── index.ts shared TypeScript types + ├── public/ + │ ├── manifest.json PWA manifest + │ ├── sw.js service worker (generated) + │ └── icons/ PWA icons (192, 512) + ├── next.config.js + ├── tailwind.config.ts + ├── tsconfig.json + └── .env.example +``` + +--- + +## Stylesheet integration + +`/stylesheet.css` lives at the repo root — not inside `app/frontend/`. + +Import it as the first global stylesheet in `src/app/layout.tsx`: + +```tsx +import '../../../stylesheet.css' // path from app/frontend/src/app/ +import './globals.css' // Tailwind directives second +``` + +Rules: +- stylesheet.css is the authoritative visual style — never override it +- Where Tailwind utility classes conflict with stylesheet.css rules, + stylesheet.css wins +- Tailwind is used for layout, spacing, and elements not covered by the + stylesheet — match the visual language (spacing scale, colour, type) of + the existing stylesheet when doing so +- Inspect stylesheet.css before implementing any component — use its CSS + custom properties (if any) rather than hardcoding values + +--- + +## PWA configuration + +### next.config.js + +Use `next-pwa` package to generate service worker and manifest wiring: + +```javascript +const withPWA = require('next-pwa')({ + dest: 'public', + register: true, + skipWaiting: true, + disable: process.env.NODE_ENV === 'development' +}) + +module.exports = withPWA({ + reactStrictMode: true, +}) +``` + +### public/manifest.json + +```json +{ + "name": "Learning Platform", + "short_name": "Learn", + "description": "Employee knowledge and learning", + "start_url": "/app", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#ffffff", + "orientation": "portrait", + "icons": [ + { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" } + ] +} +``` + +Note: set `theme_color` and `background_color` to match stylesheet.css +primary background after inspecting the file. + +### Service worker caching strategy + +- Static assets: cache-first +- PocketBase API calls: network-first, fall back to cache +- Backend service calls: network-only (no caching for dynamic content) + +--- + +## Auth + +PocketBase handles auth. Two roles: `admin` and `employee`. + +### Login flow + +``` +/auth page → email + password form + ↓ +PocketBase authWithPassword() + ↓ +Store token in PocketBase SDK (persists in localStorage) + ↓ +Read user.role from auth record + ↓ +role === 'admin' → redirect to /admin +role === 'employee' → redirect to /app +``` + +### Route guards + +Implement as Next.js middleware (`middleware.ts` at app root): + +```typescript +// Admin routes: require role === 'admin' +// Employee routes: require role === 'employee' +// Unauthenticated: redirect to /auth +// Wrong role: redirect to correct landing +``` + +### PocketBase client (browser) + +```typescript +// lib/pocketbase.ts +import PocketBase from 'pocketbase' +export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL) +``` + +Use `pb.authStore` for auth state. Use `pb.collection().getFullList()` etc. +for direct PocketBase reads. The frontend reads KB content (topics, micro +learnings) directly from PocketBase — it does not proxy through backend services. + +### Service calls + +Backend services (ingestion, generation, curriculum, chat, progress) are called +via typed fetch wrappers in `lib/services.ts`: + +```typescript +// Example +export async function postComplete(payload: CompletePayload) { + const res = await fetch(`${PROGRESS_URL}/complete`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }) + if (!res.ok) throw new Error(`Complete failed: ${res.status}`) + return res.json() as Promise +} +``` + +All service response types imported from `types/index.ts`. + +--- + +## Admin app + +### Shell layout (`admin/layout.tsx`) + +Sidebar navigation on desktop, top navigation on mobile. + +Nav items: +- Documents +- Knowledge base +- Curriculum +- (link back to employee app) + +### Documents page (`admin/documents/page.tsx`) + +**Upload section** +- Drag-and-drop file input: accepts .pdf, .md, .txt +- On upload: POST file to PocketBase storage → + then POST to ingestion service `/ingest` with document metadata +- Show upload confirmation with filename + +**Job status list** +- Poll GET /status/:jobId every 3 seconds while status is not done/failed +- Show per-job progress: + - Status badge: queued / extracting / chunking / structuring / embedding / done / failed + - Progress bar derived from chunksEmbedded / chunksTotal + - On done: "N themes, N topics ready for review" → link to knowledge base + - On failed: error reason in red, no retry (admin re-uploads) +- Stop polling when status === 'done' or 'failed' + +**Document history** +- List of all source_documents from PocketBase +- Columns: filename, format, status, ingested_at, chunk_count + +--- + +### Knowledge base page (`admin/knowledge/page.tsx`) + +Lists all Themes with status indicator. + +**Theme card** +``` +[Theme title] [status badge: draft / published] +N topics · from: filename.pdf +[Approve batch] [Edit] [Reject] +``` + +Approve batch: +- Calls PocketBase to set theme.status → 'published', all child topics → 'published' +- Triggers generation service: POST /generate-all with themeId +- Shows toast: "Generation queued for N topics" + +Reject: +- Sets theme.status → 'rejected' +- Removes from list + +Edit → navigates to `/admin/knowledge/[themeId]` + +**Theme detail page (`admin/knowledge/[themeId]/page.tsx`)** + +Displays all Topics in the Theme as editable cards. + +Topic card fields (all editable inline): +- title (text input) +- body (textarea — rich enough for paragraphs, no full rich text editor needed) +- difficulty (select: introductory / intermediate / advanced) +- key_terms (tag input — comma-separated) +- related_topics (multi-select from published topics) +- prerequisite_topics (multi-select) + +Save button per card — calls PocketBase PATCH on the topic record. + +Below topic list: [Approve batch] button — approves all topics in the theme. + +**Micro learning generation status** +After batch approval, show generation status per topic: + +``` +Concept explainer ✓ published +Scenario quiz ⏳ generating +Comparison card ✓ published +... +``` + +Poll micro_learnings collection filtered by topic until all 10 are published. + +--- + +### Curriculum page (`admin/curriculum/page.tsx`) + +**Current curriculum view** +26 weeks displayed as a list. Each week shows: +``` +Week 7 +[Theme: Holacratic roles] +Topics: Role definitions · Circle structure · Lead link responsibilities +Estimated: 25 min +[Edit week] [Admin notes] +``` + +**Regeneration banner** +When a pending regeneration is queued: +``` +⚠ 8 new topics added. A new curriculum version is ready to preview. +[Preview changes] [Confirm regeneration] [Dismiss] +``` + +Preview: shows proposed schedule with diff highlighting — weeks that changed +are highlighted, weeks that stay the same are dimmed. + +Confirm: calls POST /generate confirm on curriculum service → +applies new version to all active employees. + +**Drag-to-reorder** +Each week row is draggable. Reordering calls PATCH /weeks/:weekId on the +curriculum service to swap theme assignments. + +**Admin notes** +Inline text input per week — saved to curriculum_weeks.admin_notes. + +--- + +## Employee app + +### Shell layout (`app/layout.tsx`) + +Bottom navigation bar (mobile-first): + +``` +[Session] [Library] [Profile] +``` + +R42 floating button: fixed position, bottom-right, above the nav bar. +Z-index above all content. + +### Session page (`app/session/page.tsx`) + +**Week header** +``` +Week 7 of 26 · Cycle 1 +[Theme title: Holacratic roles] +[Progress bar: N of 26 weeks complete] +``` + +**Topic list** +Each topic in the week's theme rendered as a card: + +``` +[Topic title] +[difficulty badge] [estimated: 10 min] + +Choose how to learn this topic: +[Concept explainer] [Scenario quiz] [How-to] ... +(only published types shown as buttons) + +[Completed types: ✓ Concept explainer] +``` + +Selecting a type opens the micro learning inline (no navigation — expands in +place on mobile). Employee reads/completes it, then taps [Mark complete]. + +On mark complete: +- POST to progress service `/complete` +- Response displays: commits earned + any new badges as a toast notification +- Topic card updates to show type as completed (✓) +- All types in topic completable in one session + +**Week complete state** +When all topics in the week have at least one completed type: +``` +🚀 Week 7 complete +You earned N commits +[Continue to Week 8] +``` + +Continue button calls POST /advance/:userId on curriculum service. + +--- + +### Micro learning components + +One component per type in `components/micro-learnings/`. +Each receives the `content` JSON field from the micro_learnings record. + +| Component | Key interactions | +|---|---| +| ConceptExplainer | Render paragraphs + example — read only | +| ScenarioQuiz | Select option → reveal explanation — stateful | +| Misconceptions | Accordion: tap misconception to reveal correction | +| HowTo | Numbered steps — tap step to check it off | +| ComparisonCard | Two-column table — swipeable on mobile | +| ReflectionPrompt | Open text area → reveal model answer on submit | +| FlashcardSet | Flip card interaction — swipe through deck | +| CaseStudy | Scenario text + open questions — read only | +| GlossaryAnchor | Term card with definition + examples | +| MythVsEvidence | Myth card → tap to reveal evidence | + +All components are self-contained. They receive content JSON and emit an +`onComplete` callback. They do not call any services directly. + +```typescript +type MicroLearningProps = { + content: unknown // typed per component + onComplete: () => void +} +``` + +--- + +### Knowledge library (`app/library/page.tsx`) + +**Browse view** +All published topics, grouped by Theme. +Search input: filters by title and key_terms in real time (client-side). +Filter chips: by difficulty (introductory / intermediate / advanced). + +Each topic shown as a card: +``` +[Topic title] +[Theme] · [difficulty badge] +[key terms as chips] +``` + +Tap → navigate to topic detail. + +**Topic detail (`app/library/[topicId]/page.tsx`)** + +``` +[Topic title] +[Theme] · [difficulty] + +[Topic body — rendered as paragraphs] + +Key terms: [chip] [chip] [chip] + +Related topics: [card] [card] +Prerequisite for: [card] [card] + +How to learn this topic: +[micro learning type buttons — same as session view] +``` + +Completing a micro learning from the library records the completion via +progress service. Week_number is set to the employee's current week. + +--- + +### Profile page (`app/profile/page.tsx`) + +**Header** +``` +[Display name] +[Level badge: Junior] [N commits] +[Current streak: 5 weeks] [Longest: 8 weeks] +``` + +**Heatmap** +GitHub-style contribution graph. +26 columns (weeks) × rows implied by completions per week. +Cell colour: 0 completions = lightest, 5+ completions = darkest. +Tap a cell → tooltip: "Week N · N completions". +Scrollable horizontally on mobile if needed. + +Implementation: render as SVG or CSS grid — no charting library required. + +```typescript +// Data from GET /profile/:userId → heatmap[] +// Colour scale: 4 levels based on completions count +// 0: var(--heatmap-0) +// 1: var(--heatmap-1) +// 2-3: var(--heatmap-2) +// 4+: var(--heatmap-3) +// Use CSS custom properties — values derived from stylesheet.css palette +``` + +**Badges** +Grid of earned badges. Unearned badges shown as locked (greyed out). +Tap badge → tooltip with award condition. + +``` +🥉 First commit ✓ +🥈 Five sessions ✓ +🥇 On a streak 🔒 (13 week streak needed) +⭐ Shipped 🔒 +--- +🏷 Governance nerd ✓ +🏷 Deep reader 🔒 (3/5 case studies) +``` + +**Leaderboard tab** +Toggle between "My profile" and "Leaderboard". + +Leaderboard: table of all employees from GET /leaderboard. +Columns: Name · Commits · Streak · Types used · Badges · Level. +Not ranked 1–N. No sorting by the user — display order is commits descending. +Current employee row is highlighted. + +**Activity feed tab** +Third tab: "Feed". +Milestone cards from GET /feed. +Most recent first. + +``` +🚀 Alex shipped the full curriculum + 26 weeks · 847 commits · 3 badges + Longest streak: 18 weeks + [timestamp] +``` + +--- + +## R42 chatbot components (`components/r42/`) + +### R42Button + +Fixed position, bottom-right, above bottom nav bar. +Circle button with R42 label or icon. +Tap → opens R42Drawer. + +```tsx +// Position: fixed, bottom: calc(nav-height + 16px), right: 16px +// Z-index: above all content, below modals +``` + +### R42Drawer + +Slides up from bottom on mobile (sheet pattern). +On desktop: expands to a side panel. + +``` +[R42 header bar] [close ×] +───────────────────────────────────────────── +[Response area — scrollable] + +Based on: [Holacratic roles ×] [Circle structure ×] +───────────────────────────────────────────── +[Type a question...] [Send →] +``` + +**State machine:** +``` +idle → loading (query sent) → streaming → done + ↘ out_of_scope +``` + +**Streaming implementation:** + +```typescript +// POST /chat with fetch, read SSE stream +const response = await fetch(`${CHAT_URL}/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, userId }) +}) + +const reader = response.body!.getReader() +const decoder = new TextDecoder() + +while (true) { + const { done, value } = await reader.read() + if (done) break + const lines = decoder.decode(value).split('\n') + for (const line of lines) { + if (!line.startsWith('data: ')) continue + const event = JSON.parse(line.slice(6)) + if (event.type === 'chunk') appendText(event.text) + if (event.type === 'citations') setCitations(event.topics) + if (event.type === 'out_of_scope') setOutOfScope(event.text) + if (event.type === 'done') setDone() + } +} +``` + +**Citations** +Rendered as tappable pills below the response. +Tap → closes R42Drawer, navigates to `/app/library/[topicId]`. + +**Out of scope response** +Render as a muted message (not an error state): +"This doesn't appear to be covered in the knowledge base. +You can browse the full library in the Knowledge section." + +**Stateless by design** +Conversation cleared on drawer close. No history persisted. +Input cleared on send. + +--- + +## Mobile-first layout rules + +All layout decisions start at 375px and scale up. + +- Bottom navigation: fixed, height 56px, icons + labels +- R42 button: 48px circle, positioned above nav bar +- Session topic cards: full width, stack vertically +- Micro learning components: full width, no horizontal scroll except + ComparisonCard (swipeable) +- Heatmap: horizontal scroll container on narrow screens +- Leaderboard table: horizontally scrollable on mobile, sticky name column +- Drawer/sheet pattern for R42 on mobile, side panel on desktop (breakpoint: 768px) +- Tap targets: minimum 44×44px on all interactive elements +- No hover-only interactions — all hover states have tap equivalents + +--- + +## Environment variables + +``` +NEXT_PUBLIC_POCKETBASE_URL=http://localhost:8090 +NEXT_PUBLIC_INGESTION_URL=http://localhost:3001 +NEXT_PUBLIC_GENERATION_URL=http://localhost:3002 +NEXT_PUBLIC_CURRICULUM_URL=http://localhost:3003 +NEXT_PUBLIC_CHAT_URL=http://localhost:3004 +NEXT_PUBLIC_PROGRESS_URL=http://localhost:3005 +``` + +--- + +## Dependencies + +```json +{ + "dependencies": { + "next": "14", + "react": "^18", + "react-dom": "^18", + "pocketbase": "^0.21", + "next-pwa": "^5", + "zod": "^3" + }, + "devDependencies": { + "typescript": "^5", + "tailwindcss": "^3", + "autoprefixer": "^10", + "postcss": "^8", + "@types/react": "^18", + "@types/node": "^20" + } +} +``` + +No component library. No charting library. No drag-and-drop library — +implement curriculum drag-to-reorder with native HTML5 drag API. +The heatmap is SVG or CSS grid — no D3. + +--- + +## TypeScript strict mode requirements + +- No `any` types +- All PocketBase collection responses typed against data-model.md schemas +- All service API responses typed against response types from each service spec +- Micro learning content JSON typed per type using discriminated union: + +```typescript +type MicroLearningContent = + | { type: 'concept_explainer'; paragraphs: string[]; example: string } + | { type: 'scenario_quiz'; scenario: string; options: QuizOption[] } + | { type: 'misconceptions'; items: MisconceptionItem[] } + // ... all 10 types +``` + +- SSE event types as discriminated union +- No implicit any on event handlers + +--- + +## What the frontend does NOT do + +- Does not run AI calls directly — all AI goes through backend services +- Does not write to Qdrant — embedding is the ingestion service's responsibility +- Does not implement auth logic — delegates entirely to PocketBase SDK +- Does not implement curriculum generation — calls curriculum service + +--- + +## Testing checkpoints + +### Admin app +1. Upload a PDF → ingestion job created → status polls and updates → done state shows link +2. Theme batch appears after ingestion → approve → generation queued +3. Edit a topic title and body → save → changes persisted in PocketBase +4. Curriculum renders 26 weeks → drag week 3 and week 5 → order persists +5. Regeneration banner appears → preview shows → confirm applies new version + +### Employee app +6. Login as employee → redirected to /app/session → correct week shown +7. Select micro learning type → content renders → mark complete → commits toast shown +8. Complete all topics in week → week complete state shown → advance to next week +9. Library browse → search filters results → topic detail renders body + related topics +10. Profile page → heatmap renders for current cycle → badges show locked/unlocked state +11. Leaderboard tab → all employees shown → current employee row highlighted +12. R42 button visible on every screen → opens drawer → question answered with citations +13. R42 citation tap → navigates to correct topic in library +14. Out-of-scope question → muted message shown, no citations +15. All screens render correctly at 375px width — no horizontal overflow except + intentional scroll containers diff --git a/docs/gamification-spec.md b/docs/gamification-spec.md new file mode 100644 index 0000000..7922e92 --- /dev/null +++ b/docs/gamification-spec.md @@ -0,0 +1,469 @@ +# Gamification and progress service spec + +## Responsibility + +Records session completions, calculates XP (commits), manages levels and +streaks, evaluates badge conditions, generates milestone cards, and serves +leaderboard data. All gamification data is public to all employees. + +--- + +## Service location + +``` +app/services/progress/ + ├── src/ + │ ├── index.ts entry point, Fastify server + │ ├── routes/ + │ │ ├── completions.ts POST /complete + │ │ ├── profile.ts GET /profile/:userId + │ │ ├── leaderboard.ts GET /leaderboard + │ │ └── feed.ts GET /feed + │ ├── engine/ + │ │ ├── xp.ts commit calculation per type + │ │ ├── level.ts level thresholds + promotion + │ │ ├── streak.ts streak evaluation + │ │ ├── badges.ts badge condition evaluation + │ │ └── milestone.ts milestone card generation + │ └── lib/ + │ └── pocketbase.ts + ├── package.json + ├── tsconfig.json + └── .env.example +``` + +--- + +## API surface + +### POST /complete + +Called by the frontend when an employee completes a micro learning. + +Request: +```json +{ + "userId": "string", + "topicId": "string", + "microLearningId": "string", + "microLearningType": "string", + "weekNumber": 7, + "cycle": 1 +} +``` + +Response: +```json +{ + "commitsEarned": 15, + "totalCommits": 342, + "levelBefore": "junior", + "levelAfter": "junior", + "streakWeeks": 5, + "newBadges": [ + { "key": "deep_reader", "label": "Deep reader", "tier": "content" } + ], + "milestoneCard": null +} +``` + +`newBadges` is empty array if no badges earned this completion. +`milestoneCard` is populated only at weeks 13 and 26. + +--- + +### GET /profile/:userId + +Returns full gamification profile for an employee. + +Response: +```json +{ + "userId": "string", + "displayName": "string", + "totalCommits": 342, + "level": "junior", + "currentStreakWeeks": 5, + "longestStreakWeeks": 8, + "typesUsed": ["concept_explainer", "scenario_quiz", "how_to"], + "typesNotUsed": ["case_study", "myth_vs_evidence"], + "badges": [ + { "key": "bronze_1", "label": "First commit", "tier": "bronze", "earnedAt": "..." } + ], + "heatmap": [ + { "week": 1, "completions": 3 }, + { "week": 2, "completions": 0 } + ] +} +``` + +Heatmap returns 26 entries per cycle. `completions` = number of micro +learning types completed that week. + +--- + +### GET /leaderboard + +Returns all employee gamification profiles for leaderboard rendering. +Not paginated at 150 employees. + +Response: +```json +{ + "employees": [ + { + "userId": "string", + "displayName": "string", + "totalCommits": 847, + "currentStreakWeeks": 18, + "typesUsedCount": 9, + "badgeCount": 5, + "level": "senior" + } + ] +} +``` + +Sorted by totalCommits descending. Frontend renders as multi-dimension +table — not a ranked list. + +--- + +### GET /feed + +Returns recent milestone cards for the public activity feed. +Most recent first. + +Response: +```json +{ + "milestones": [ + { + "userId": "string", + "displayName": "string", + "cycle": 1, + "week": 26, + "totalCommits": 847, + "streakWeeks": 18, + "badges": ["on_streak", "shipped"], + "createdAt": "string" + } + ] +} +``` + +--- + +## XP (commits) calculation + +Each micro learning type earns a different number of commits based on +cognitive effort required. + +```typescript +const COMMITS_PER_TYPE: Record = { + concept_explainer: 10, + glossary_anchor: 10, + misconceptions: 15, + how_to: 15, + flashcard_set: 15, + comparison_card: 20, + reflection_prompt: 20, + scenario_quiz: 25, + myth_vs_evidence: 25, + case_study: 30, +} +``` + +First completion of a type the employee has never used before: +5 bonus +commits (rewards type diversity). + +Duplicate completion (same topic + same type in same cycle): 0 commits. +Check session_completions before awarding — idempotent. + +--- + +## Levels + +Thresholds based on cumulative commits across all cycles. + +```typescript +const LEVEL_THRESHOLDS = { + intern: 0, + junior: 100, + medior: 300, + senior: 600, + staff: 1000, + principal: 1500, +} +``` + +Level evaluated after every commit update. Level can only increase — +never decreases between cycles. + +--- + +## Streak + +Counts consecutive weeks with at least one completion. + +Evaluation logic on every POST /complete: + +```typescript +const lastActiveWeek = profile.last_active_week +const currentWeek = completionWeekNumber + +if (currentWeek === lastActiveWeek) { + // Same week — streak unchanged +} else if (currentWeek === lastActiveWeek + 1) { + // Consecutive — increment + streak += 1 +} else { + // Gap — reset to 1 + streak = 1 +} + +profile.last_active_week = currentWeek +profile.longest_streak_weeks = Math.max(streak, profile.longest_streak_weeks) +``` + +Week number resets to 1 on cycle start. Streak does not reset on cycle +transition — a streak spanning the cycle boundary is maintained. + +--- + +## Badges + +### Badge seed data + +Seeded into PocketBase badges collection at startup. + +```typescript +const BADGE_DEFINITIONS = [ + { key: 'first_commit', tier: 'bronze', + label: 'First commit', + description: 'Complete any session' }, + + { key: 'five_sessions', tier: 'silver', + label: 'Five sessions', + description: 'Complete 5 sessions using 5 different micro learning types' }, + + { key: 'on_streak', tier: 'gold', + label: 'On a streak', + description: 'Complete 13 sessions without skipping a week' }, + + { key: 'shipped', tier: 'legendary', + label: 'Shipped', + description: 'Complete all 26 sessions using all 10 micro learning types' }, + + { key: 'governance_nerd', tier: 'content', + label: 'Governance nerd', + description: 'Complete all topics in the holacratic structure theme' }, + + { key: 'process_architect', tier: 'content', + label: 'Process architect', + description: 'Complete all topics in the internal processes theme' }, + + { key: 'deep_reader', tier: 'content', + label: 'Deep reader', + description: 'Use the case study micro learning type 5 or more times' }, + + { key: 'handbook_expert', tier: 'content', + label: 'Handbook expert', + description: 'Complete all topics in the employee handbook theme' }, + + { key: 'type_collector', tier: 'content', + label: 'Type collector', + description: 'Use all 10 micro learning types at least once' }, +] +``` + +Content badges are theme-specific. Theme association resolved at runtime +by matching badge key to theme title pattern — not hardcoded to theme IDs. + +```typescript +const THEME_BADGE_PATTERNS: Record = { + 'governance_nerd': 'holacrat', + 'process_architect': 'process', + 'handbook_expert': 'handbook', +} +``` + +Case-insensitive substring match on theme title. + +--- + +### Badge evaluation + +Run after every POST /complete. Check all conditions, award unearned badges. + +```typescript +async function evaluateBadges(userId: string, profile: GamificationProfile): + Promise { + + const earnedKeys = await getEarnedBadgeKeys(userId) + const newBadges: string[] = [] + + if (!earnedKeys.includes('first_commit')) { + const count = await countCompletions(userId) + if (count >= 1) newBadges.push('first_commit') + } + + if (!earnedKeys.includes('five_sessions')) { + const sessions = await countUniqueSessions(userId) + if (sessions >= 5 && profile.types_used.length >= 5) + newBadges.push('five_sessions') + } + + if (!earnedKeys.includes('on_streak')) { + if (profile.longest_streak_weeks >= 13) newBadges.push('on_streak') + } + + if (!earnedKeys.includes('shipped')) { + const cycleComplete = await isCycleComplete(userId, profile.current_cycle) + if (cycleComplete && profile.types_used.length === 10) + newBadges.push('shipped') + } + + if (!earnedKeys.includes('deep_reader')) { + const count = await countTypeCompletions(userId, 'case_study') + if (count >= 5) newBadges.push('deep_reader') + } + + if (!earnedKeys.includes('type_collector')) { + if (profile.types_used.length === 10) newBadges.push('type_collector') + } + + for (const [badgeKey, pattern] of Object.entries(THEME_BADGE_PATTERNS)) { + if (!earnedKeys.includes(badgeKey)) { + const complete = await isThemeComplete(userId, pattern) + if (complete) newBadges.push(badgeKey) + } + } + + for (const key of newBadges) { + await awardBadge(userId, key, profile.current_cycle) + } + + return newBadges.map(k => BADGE_DEFINITIONS.find(b => b.key === k)!) +} +``` + +--- + +## Milestone cards + +Generated at weeks 13 and 26 of each cycle. + +```typescript +async function generateMilestoneCard( + userId: string, + weekNumber: number, + cycle: number, + profile: GamificationProfile +): Promise { + + if (weekNumber !== 13 && weekNumber !== 26) return null + + const exists = await milestoneExists(userId, cycle, weekNumber) + if (exists) return null + + const badges = await getEarnedBadges(userId) + + return await pocketbase.collection('milestone_cards').create({ + user: userId, + cycle, + week: weekNumber, + total_commits: profile.total_commits, + streak_weeks: profile.current_streak_weeks, + badge_keys: badges.map(b => b.key), + created_at: new Date().toISOString() + }) +} +``` + +--- + +## Heatmap data + +Built from session_completions filtered by userId + cycle. +Returns 26 entries — one per week. + +```typescript +async function buildHeatmap(userId: string, cycle: number): + Promise { + + const completions = await pocketbase + .collection('session_completions') + .getFullList({ filter: `user="${userId}" && cycle=${cycle}` }) + + const byWeek = groupBy(completions, c => c.week_number) + + return Array.from({ length: 26 }, (_, i) => ({ + week: i + 1, + completions: byWeek[i + 1]?.length ?? 0 + })) +} +``` + +--- + +## Environment variables + +``` +POCKETBASE_URL= +POCKETBASE_ADMIN_EMAIL= +POCKETBASE_ADMIN_PASSWORD= +PROGRESS_PORT=3005 +``` + +--- + +## Dependencies + +```json +{ + "dependencies": { + "fastify": "^4", + "pocketbase": "^0.21", + "zod": "^3" + } +} +``` + +No AI calls. No Qdrant. No OpenAI. Pure business logic over PocketBase. + +--- + +## TypeScript strict mode requirements + +- No `any` types +- MicroLearningType as explicit string union +- Badge keys as explicit string union matching BADGE_DEFINITIONS +- COMMITS_PER_TYPE keyed by MicroLearningType — compile-time exhaustiveness +- HeatmapEntry, MilestoneCard, BadgeDefinition typed explicitly + +--- + +## What this service does NOT do + +- Does not generate content +- Does not handle curriculum scheduling → curriculum service +- Does not serve KB or micro learning data → frontend reads PocketBase +- Does not handle auth → PocketBase + frontend + +--- + +## Testing checkpoints + +1. POST /complete for new user → first_commit badge awarded, commits added +2. POST /complete same topic + type twice → 0 commits second call (idempotent) +3. Complete 5 sessions with 5 types → five_sessions badge awarded +4. Simulate 13 consecutive weekly completions → on_streak badge awarded +5. Skip a week → streak resets to 1 +6. Complete all topics in a theme → content badge awarded +7. Use all 10 types → type_collector badge awarded +8. Complete week 13 → milestone_card created and returned in response +9. GET /leaderboard → all employees returned with correct fields +10. GET /feed → milestone cards ordered most recent first +11. Cycle transition: week 26 complete → streak spans boundary, level preserved, + heatmap resets for new cycle diff --git a/docs/generation-spec.md b/docs/generation-spec.md new file mode 100644 index 0000000..98e3dcf --- /dev/null +++ b/docs/generation-spec.md @@ -0,0 +1,583 @@ +# Generation service spec + +## Responsibility + +Accepts a Theme ID from the admin app (on batch approval) and generates all 10 +micro learning types for every published Topic in that Theme. One Claude Sonnet 4 +call per type per topic. All outputs validated through Zod schemas before write. + +This service runs entirely server-side. The admin app calls it via REST. All AI +calls go through the Anthropic API. No generation logic lives in the frontend. + +--- + +## Service location + +``` +app/services/generation/ + ├── src/ + │ ├── index.ts entry point, Fastify server + │ ├── routes/ + │ │ ├── generate.ts POST /generate, GET /status/:jobId + │ │ └── publish.ts PATCH /micro-learnings/:id + │ ├── pipeline/ + │ │ └── generate.ts per-type generation logic + │ ├── jobs/ + │ │ └── queue.ts async job queue (in-memory) + │ ├── lib/ + │ │ ├── pocketbase.ts PocketBase client + │ │ └── anthropic.ts Anthropic client + │ └── types.ts shared TypeScript types + Zod schemas + ├── package.json + ├── tsconfig.json + ├── .env.example + └── .gitignore +``` + +--- + +## API surface + +### POST /generate + +Triggered by admin app when a Theme batch is approved. + +Request: +```json +{ + "themeId": "string" +} +``` + +Response (202 Accepted): +```json +{ + "jobId": "string", + "status": "queued", + "topicsFound": 5, + "totalItems": 50 +} +``` + +Processing is async. The admin app polls job status. + +Behaviour: +- Fetches all published Topics for the given themeId +- Creates one micro_learnings record per topic per type with status `queued` +- Generates each item sequentially; updates status to `generated` on success +- On failure: sets individual item status to `failed`, continues remaining items +- Job completes when all items are either `generated` or `failed` + +--- + +### GET /status/:jobId + +Returns current job progress. + +Response: +```json +{ + "jobId": "string", + "status": "queued" | "running" | "done" | "failed", + "progress": { + "topicsTotal": 5, + "topicsProcessed": 3, + "itemsTotal": 50, + "itemsGenerated": 28, + "itemsFailed": 2 + }, + "error": "string | null" +} +``` + +--- + +### PATCH /micro-learnings/:id + +Admin publishes or rejects an individual micro learning. + +Request: +```json +{ + "status": "published" | "rejected" +} +``` + +Response (200 OK): +```json +{ + "id": "string", + "status": "published" | "rejected", + "published_at": "datetime | null" +} +``` + +Rules: +- Only `generated` records can be published or rejected +- `published_at` set on publish, left null on reject +- Returns 400 if record is not in `generated` status +- Returns 404 if record not found + +--- + +## Generation pipeline + +### Input + +For each Topic in the approved Theme: +``` +topic.title: string +topic.body: string +topic.key_terms: string[] +topic.difficulty: 'introductory' | 'intermediate' | 'advanced' +``` + +### Output + +10 micro_learnings records per topic, one per type. + +--- + +## AI call configuration + +```typescript +{ + model: 'claude-sonnet-4-20250514', + max_tokens: 2000, + temperature: 0 // deterministic structured output +} +``` + +One call per type per topic. Do not batch multiple types into one call — isolated +calls are easier to retry and validate independently. + +--- + +## Prompt strategy + +### System prompt (all types) + +``` +You are a learning content designer. Your task is to generate structured learning +content for a specific topic in an employee learning platform. + +Output ONLY valid JSON matching the schema provided. No preamble, no explanation, +no markdown fences. + +The content should be accurate, practical, and appropriate for the stated +difficulty level. Tone: professional but accessible. +``` + +### User prompt template (all types) + +``` +Topic: {topic.title} +Difficulty: {topic.difficulty} +Body: +{topic.body} + +Key terms: {topic.key_terms.join(', ')} + +Generate a {type_label} for this topic. + +Output schema: +{JSON.stringify(schemaDescription)} +``` + +--- + +## Type-specific prompts and schemas + +### concept_explainer + +Type label: `Concept Explainer` + +Schema description: +```json +{ + "paragraphs": ["2 to 3 paragraphs explaining the concept in plain language"], + "example": "one concrete real-world example" +} +``` + +Zod schema: +```typescript +z.object({ + paragraphs: z.array(z.string()).min(2).max(3), + example: z.string().min(20) +}) +``` + +--- + +### scenario_quiz + +Type label: `Scenario Quiz` + +Schema description: +```json +{ + "scenario": "a realistic workplace scenario", + "options": [ + { "label": "A", "text": "answer text", "correct": false, "explanation": "why" }, + { "label": "B", "text": "answer text", "correct": true, "explanation": "why" }, + { "label": "C", "text": "answer text", "correct": false, "explanation": "why" }, + { "label": "D", "text": "answer text", "correct": false, "explanation": "why" } + ] +} +``` + +Rules: exactly 4 options, exactly 1 correct. + +Zod schema: +```typescript +z.object({ + scenario: z.string().min(30), + options: z.array(z.object({ + label: z.enum(['A', 'B', 'C', 'D']), + text: z.string().min(5), + correct: z.boolean(), + explanation: z.string().min(10) + })).length(4).refine( + opts => opts.filter(o => o.correct).length === 1, + { message: 'exactly one correct option required' } + ) +}) +``` + +--- + +### misconceptions + +Type label: `Misconceptions` + +Schema description: +```json +{ + "items": [ + { "misconception": "common wrong belief", "correction": "accurate explanation" } + ] +} +``` + +Rules: 3 to 5 items. + +Zod schema: +```typescript +z.object({ + items: z.array(z.object({ + misconception: z.string().min(10), + correction: z.string().min(10) + })).min(3).max(5) +}) +``` + +--- + +### how_to + +Type label: `How-To Guide` + +Schema description: +```json +{ + "steps": [ + { "number": 1, "instruction": "what to do" } + ] +} +``` + +Rules: 3 to 8 steps. + +Zod schema: +```typescript +z.object({ + steps: z.array(z.object({ + number: z.number().int().positive(), + instruction: z.string().min(10) + })).min(3).max(8) +}) +``` + +--- + +### comparison_card + +Type label: `Comparison Card` + +Schema description: +```json +{ + "subject_a": "first concept or approach", + "subject_b": "second concept or approach", + "dimensions": [ + { "label": "dimension name", "a": "how A differs", "b": "how B differs" } + ] +} +``` + +Rules: 3 to 6 dimensions. + +Zod schema: +```typescript +z.object({ + subject_a: z.string().min(2), + subject_b: z.string().min(2), + dimensions: z.array(z.object({ + label: z.string().min(2), + a: z.string().min(5), + b: z.string().min(5) + })).min(3).max(6) +}) +``` + +--- + +### reflection_prompt + +Type label: `Reflection Prompt` + +Schema description: +```json +{ + "prompt": "open-ended question for the employee to reflect on", + "model_answer": "a thoughtful example answer the employee can compare against" +} +``` + +Zod schema: +```typescript +z.object({ + prompt: z.string().min(20), + model_answer: z.string().min(50) +}) +``` + +--- + +### flashcard_set + +Type label: `Flashcard Set` + +Schema description: +```json +{ + "cards": [ + { "question": "question text", "answer": "answer text" } + ] +} +``` + +Rules: 5 to 10 cards. + +Zod schema: +```typescript +z.object({ + cards: z.array(z.object({ + question: z.string().min(5), + answer: z.string().min(5) + })).min(5).max(10) +}) +``` + +--- + +### case_study + +Type label: `Case Study` + +Schema description: +```json +{ + "scenario": "a detailed real-world scenario (150+ words)", + "questions": ["discussion or reflection question 1", "discussion or reflection question 2"] +} +``` + +Rules: 2 to 4 questions. + +Zod schema: +```typescript +z.object({ + scenario: z.string().min(150), + questions: z.array(z.string().min(10)).min(2).max(4) +}) +``` + +--- + +### glossary_anchor + +Type label: `Glossary Anchor` + +Schema description: +```json +{ + "term": "the key term", + "definition": "precise definition", + "correct_use": "example sentence showing correct use", + "misuse": "common incorrect usage to avoid" +} +``` + +Prompt addition: use the first key term from `topic.key_terms` as the anchor term. + +Zod schema: +```typescript +z.object({ + term: z.string().min(2), + definition: z.string().min(20), + correct_use: z.string().min(20), + misuse: z.string().min(20) +}) +``` + +--- + +### myth_vs_evidence + +Type label: `Myth vs Evidence` + +Schema description: +```json +{ + "myth": "a commonly held misconception about this topic", + "evidence": "the evidence-based counterpoint", + "sources": ["source or reference if applicable — leave empty array if none"] +} +``` + +Zod schema: +```typescript +z.object({ + myth: z.string().min(20), + evidence: z.string().min(30), + sources: z.array(z.string()) +}) +``` + +--- + +## Error handling + +**Per item:** +- JSON parse failure → retry once with stricter prompt ("respond with valid JSON only, no other text") +- Second failure → set micro_learning status to `failed`, log raw response, continue to next item +- Zod validation failure → same as parse failure: retry once, then `failed` +- Anthropic API error (rate limit / timeout) → exponential backoff, 3 retries, then `failed` + +**Per job:** +- If all items for a topic fail → log, continue to next topic +- Job status becomes `done` when all items processed, regardless of individual failures +- Job status becomes `failed` only if the initial topic fetch fails (PocketBase error before generation starts) + +--- + +## PocketBase write + +For each generated item: +```typescript +{ + topic: topicId, + type: type, // one of the 10 type enum values + content: validatedContent, // JSON, validated by Zod + status: 'generated', + generation_model: 'claude-sonnet-4-20250514', + generated_at: new Date().toISOString() +} +``` + +Create record with status `queued` before generation starts. +Update to `generated` (with content) or `failed` after attempt. + +--- + +## Job lifecycle + +``` +POST /generate received + ↓ +Fetch published Topics for Theme + ↓ +Create micro_learning records: status = queued + ↓ +Job created → status: running + ↓ +For each topic: + For each of 10 types: + Claude call → validate → write content → status = generated + ↓ +All items processed + ↓ +Job status: done +``` + +On topic fetch failure: +``` +status: failed +error: { reason: 'topic_fetch_failed', detail: ... } +``` + +--- + +## Environment variables required + +``` +ANTHROPIC_API_KEY= +POCKETBASE_URL= +POCKETBASE_ADMIN_EMAIL= +POCKETBASE_ADMIN_PASSWORD= +GENERATION_PORT=3002 +``` + +--- + +## Dependencies + +```json +{ + "dependencies": { + "fastify": "^4", + "@anthropic-ai/sdk": "^0.24", + "pocketbase": "^0.21", + "uuid": "^9", + "zod": "^3" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "tsx": "^4" + } +} +``` + +--- + +## TypeScript strict mode requirements + +- No `any` types +- All Claude response parsing through Zod schema validation before PocketBase write +- All PocketBase writes typed against micro_learnings schema from data-model.md +- Content type is `unknown` after JSON.parse — always validate through Zod before use + +--- + +## What this service does NOT do + +- Does not extract or chunk source documents → ingestion service +- Does not build or schedule the curriculum → curriculum service +- Does not handle admin auth → PocketBase + admin app +- Does not embed content into Qdrant → ingestion service handles all embeddings +- Does not serve R42 queries → chat service + +--- + +## Testing checkpoints + +1. Call POST /generate with a themeId that has 2 published topics → verify 20 micro_learning records created +2. All 10 types generated for each topic → verify content JSON parses correctly +3. All Zod schemas pass for each of the 10 types +4. PATCH /micro-learnings/:id with `published` → verify status + published_at updated +5. PATCH /micro-learnings/:id with `rejected` → verify status updated, published_at null +6. Force a JSON parse error (mock) → verify retry logic fires once, then sets status to `failed` +7. GET /status/:jobId during processing → verify progress counters increment correctly diff --git a/docs/r42-spec.md b/docs/r42-spec.md new file mode 100644 index 0000000..bd25ae1 --- /dev/null +++ b/docs/r42-spec.md @@ -0,0 +1,336 @@ +# R42 chat service spec + +## Responsibility + +Handles all R42 chatbot interactions. Receives employee queries, retrieves +relevant KB chunks from Qdrant, generates grounded responses using Claude +Haiku 4.5, and streams the result to the frontend. Stateless — no chat +history is persisted. + +--- + +## Service location + +``` +app/services/chat/ + ├── src/ + │ ├── index.ts entry point, Fastify server + │ ├── routes/ + │ │ └── chat.ts POST /chat (streaming) + │ ├── retrieval/ + │ │ ├── embed.ts query → embedding + │ │ ├── search.ts Qdrant nearest-neighbour search + │ │ └── merge.ts merge + rank results from both collections + │ ├── prompt/ + │ │ └── build.ts assemble system + user prompt with context + │ └── lib/ + │ ├── qdrant.ts + │ ├── pocketbase.ts + │ ├── anthropic.ts + │ └── openai.ts + ├── package.json + ├── tsconfig.json + └── .env.example +``` + +--- + +## API surface + +### POST /chat + +Single route. Streams response back to client using server-sent events (SSE). + +Request: +```json +{ + "query": "string", + "userId": "string" +} +``` + +Response: SSE stream + +``` +Content-Type: text/event-stream + +data: {"type": "chunk", "text": "Holacratic roles "} +data: {"type": "chunk", "text": "are defined as..."} +data: {"type": "citations", "topics": [{"id": "abc", "title": "Holacratic roles"}]} +data: {"type": "done"} +``` + +Error response (non-streaming, returned before stream starts): +```json +{ + "error": "query_too_short" | "user_not_found" | "retrieval_failed", + "message": "string" +} +``` + +--- + +## Retrieval pipeline + +### Step 1 — Embed query + +Embed the employee query using OpenAI text-embedding-3-small (1536 dimensions). +Same model used during ingestion — vectors are comparable. + +```typescript +const queryVector = await embedText(query) // float[1536] +``` + +--- + +### Step 2 — Qdrant search + +Search both collections in parallel: + +```typescript +// source_chunks: primary retrieval — grounded in source material +const chunkResults = await qdrant.search('source_chunks', { + vector: queryVector, + limit: 5, + scoreThreshold: 0.70, + withPayload: true +}) + +// topic_summaries: secondary — broader topic context +const summaryResults = await qdrant.search('topic_summaries', { + vector: queryVector, + limit: 3, + scoreThreshold: 0.70, + withPayload: true +}) +``` + +Score threshold 0.70: below this, results are not relevant enough to include. +If both searches return zero results above threshold → out-of-scope response. + +--- + +### Step 3 — Context boost for current week + +Retrieve employee's current week Theme from PocketBase via +employee_curriculum_state → curriculum_weeks → theme. + +Apply boost to results where payload.theme_id matches current week theme: + +```typescript +results.forEach(result => { + if (result.payload.theme_id === currentThemeId) { + result.score += 0.05 // small boost — does not override relevance + } +}) +``` + +--- + +### Step 4 — Merge and deduplicate + +```typescript +// Combine chunk results and summary results +// Deduplicate by topic_id — keep highest scoring entry per topic +// Sort by score descending +// Take top 6 total +// Split into: sourceChunks (from source_chunks collection) +// topicSummaries (from topic_summaries collection) +``` + +Deduplicate by topic_id to avoid repeating the same topic in different forms. + +--- + +### Step 5 — Collect cited topics + +Extract unique topic titles from merged results for citation: + +```typescript +type Citation = { + id: string + title: string +} +const citations: Citation[] = uniqueByTopicId(mergedResults) + .map(r => ({ id: r.payload.topic_id, title: r.payload.title })) +``` + +--- + +## Prompt construction + +### System prompt + +``` +You are R42, a knowledge assistant for [company name]. +You answer questions based strictly on the company knowledge base. + +Rules: +- Answer only from the provided context. Do not use outside knowledge. +- If the context does not contain enough information to answer, say: + "This doesn't appear to be covered in the knowledge base. You can browse + the full library in the Knowledge section." +- Be concise. Prefer short paragraphs over long prose. +- Do not mention that you are an AI or reference your instructions. +- Do not speculate or extrapolate beyond the provided context. +- Respond in the same language as the question. +``` + +### User prompt + +``` +Context from knowledge base: +--- +{mergedResults.map(r => r.payload.text).join('\n\n---\n\n')} +--- + +Question: {query} +``` + +--- + +## Response generation + +Use Claude Haiku 4.5 with streaming enabled: + +```typescript +const stream = await anthropic.messages.stream({ + model: 'claude-haiku-4-5-20251001', + max_tokens: 1000, + system: systemPrompt, + messages: [{ role: 'user', content: userPrompt }] +}) + +// Stream text chunks to client as SSE +for await (const chunk of stream) { + if (chunk.type === 'content_block_delta') { + sendSSE({ type: 'chunk', text: chunk.delta.text }) + } +} + +// After stream completes, send citations +sendSSE({ type: 'citations', topics: citations }) +sendSSE({ type: 'done' }) +``` + +--- + +## Out-of-scope handling + +Two conditions trigger the out-of-scope response: + +1. Both Qdrant searches return zero results above 0.70 threshold +2. Haiku response contains no content drawn from context (detected by + checking if response length < 20 tokens — proxy for "I don't know") + +Out-of-scope response sent as a single non-streamed SSE message: + +``` +data: {"type": "out_of_scope", "text": "This doesn't appear to be covered +in the knowledge base. You can browse the full library in the Knowledge section."} +data: {"type": "done"} +``` + +No citations are sent for out-of-scope responses. + +--- + +## Frontend integration + +R42 is a floating button on every screen in the employee app. + +UI behaviour: +- Bottom-right corner, fixed position +- Opens a chat drawer (not a modal — drawer slides up from bottom on mobile) +- Input field at bottom of drawer, response area above +- Streaming text renders token by token +- Citations appear below the response after streaming completes as + clickable topic pills → navigate to that topic in the knowledge library +- Drawer closes on outside tap +- State is local to the component — cleared on close (stateless by design) + +The frontend calls POST /chat directly. No auth token needed on the chat +service — it receives userId in the request body and trusts it. The admin +app does not expose R42. + +--- + +## Stateless design + +R42 has no memory between conversations. Each POST /chat is independent. + +Rationale: +- Avoids privacy complexity around chat history storage +- Removes need for session management +- Keeps the service simple and fast +- Employees asking follow-up questions reprovide context naturally + +If multi-turn conversation is needed in a future iteration, maintain +conversation history in the frontend component state and pass the last +N messages in the request body. The service does not need to change. + +--- + +## Environment variables + +``` +ANTHROPIC_API_KEY= +OPENAI_API_KEY= +POCKETBASE_URL= +POCKETBASE_ADMIN_EMAIL= +POCKETBASE_ADMIN_PASSWORD= +QDRANT_URL= +QDRANT_API_KEY= +CHAT_PORT=3004 +``` + +--- + +## Dependencies + +```json +{ + "dependencies": { + "fastify": "^4", + "@fastify/sse": "^2", + "@anthropic-ai/sdk": "^0.24", + "openai": "^4", + "@qdrant/js-client-rest": "^1.9", + "pocketbase": "^0.21", + "zod": "^3" + } +} +``` + +--- + +## TypeScript strict mode requirements + +- No `any` types +- Qdrant search results typed explicitly including payload fields +- SSE event types defined as a discriminated union +- Citation type explicit — not inferred from payload + +--- + +## What this service does NOT do + +- Does not persist chat history +- Does not generate or serve micro learning content +- Does not handle admin queries — admin app has no R42 access +- Does not handle auth — trusts userId from request body + +--- + +## Testing checkpoints + +1. POST /chat with a query matching a published topic → confirm relevant + chunks retrieved (score > 0.70) and response references topic content +2. POST /chat with an out-of-scope query → confirm out-of-scope response + returned, no citations sent +3. Confirm citations array contains correct topic titles matching retrieved chunks +4. Confirm SSE stream delivers chunks progressively (not batched) +5. Confirm current-week boost: same query returns higher-ranked result for + current week theme topic vs equally relevant topic from different theme +6. POST /chat with userId whose current week has no matching topic → + confirm boost does not break retrieval, general results returned