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