zam-core 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -806,6 +806,8 @@ interface Token {
806
806
  created_at: string;
807
807
  updated_at: string;
808
808
  deprecated_at: string | null;
809
+ provider: string | null;
810
+ topic_id: string | null;
809
811
  }
810
812
  interface CreateTokenInput {
811
813
  slug: string;
@@ -816,6 +818,8 @@ interface CreateTokenInput {
816
818
  symbiosis_mode?: SymbiosisMode | null;
817
819
  source_link?: string | null;
818
820
  question?: string | null;
821
+ provider?: string | null;
822
+ topic_id?: string | null;
819
823
  }
820
824
  interface UpdateTokenInput {
821
825
  concept?: string;
@@ -825,6 +829,8 @@ interface UpdateTokenInput {
825
829
  symbiosis_mode?: SymbiosisMode | null;
826
830
  source_link?: string | null;
827
831
  question?: string | null;
832
+ provider?: string | null;
833
+ topic_id?: string | null;
828
834
  }
829
835
  interface ListTokensOptions {
830
836
  domain?: string;
@@ -922,6 +928,8 @@ interface PersonalCard {
922
928
  elapsedDays: number | null;
923
929
  scheduledDays: number | null;
924
930
  blocked: number | null;
931
+ provider: string | null;
932
+ topicId: string | null;
925
933
  }
926
934
  declare function slugify(text: string): string;
927
935
  declare function generateTokenSlug(db: Database, domain: string, concept: string, question?: string | null): Promise<string>;
@@ -937,6 +945,8 @@ interface CurriculumCardInput {
937
945
  context?: string;
938
946
  bloom_level?: number;
939
947
  symbiosis_mode?: string | null;
948
+ provider?: string | null;
949
+ topic_id?: string | null;
940
950
  }
941
951
  interface ImportCurriculumResult {
942
952
  createdCount: number;
@@ -990,6 +1000,9 @@ interface SourceProposalInput {
990
1000
  symbiosis_mode: string;
991
1001
  excerpt: string;
992
1002
  page_number?: string | null;
1003
+ provider?: string | null;
1004
+ topic_id?: string | null;
1005
+ source_id?: string | null;
993
1006
  }
994
1007
  /**
995
1008
  * Confirm source import transaction.
package/dist/index.js CHANGED
@@ -539,7 +539,9 @@ CREATE TABLE IF NOT EXISTS tokens (
539
539
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
540
540
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
541
541
  deprecated_at TEXT,
542
- question TEXT
542
+ question TEXT,
543
+ provider TEXT,
544
+ topic_id TEXT
543
545
  );
544
546
 
545
547
  -- Prerequisite dependency graph: "to learn A, first know B"
@@ -979,6 +981,14 @@ async function runMigrations(db) {
979
981
  PRIMARY KEY (token_id, source_id)
980
982
  )
981
983
  `);
984
+ if (tokenCols.length > 0) {
985
+ if (!tokenCols.some((c) => c.name === "provider")) {
986
+ await db.exec(`ALTER TABLE tokens ADD COLUMN provider TEXT`);
987
+ }
988
+ if (!tokenCols.some((c) => c.name === "topic_id")) {
989
+ await db.exec(`ALTER TABLE tokens ADD COLUMN topic_id TEXT`);
990
+ }
991
+ }
982
992
  }
983
993
 
984
994
  // src/kernel/db/snapshot.ts
@@ -1481,8 +1491,8 @@ async function createToken(db, input) {
1481
1491
  throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1482
1492
  }
1483
1493
  await db.prepare(`
1484
- INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, created_at, updated_at)
1485
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1494
+ INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
1495
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1486
1496
  `).run(
1487
1497
  id,
1488
1498
  input.slug,
@@ -1493,16 +1503,33 @@ async function createToken(db, input) {
1493
1503
  input.symbiosis_mode ?? null,
1494
1504
  input.source_link ?? null,
1495
1505
  input.question ?? null,
1506
+ input.provider ?? null,
1507
+ input.topic_id ?? null,
1496
1508
  now,
1497
1509
  now
1498
1510
  );
1499
1511
  return await getTokenById(db, id);
1500
1512
  }
1513
+ function parseTokenFallback(token) {
1514
+ if (token && !token.provider && token.source_link) {
1515
+ if (token.source_link.includes("lehrplanplus.bayern.de")) {
1516
+ token.provider = "lehrplanplus-bayern";
1517
+ const match = token.source_link.match(/#(.*)$/);
1518
+ if (match) {
1519
+ token.topic_id = match[1];
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1501
1524
  async function getTokenBySlug(db, slug) {
1502
- return await db.prepare("SELECT * FROM tokens WHERE slug = ?").get(slug);
1525
+ const token = await db.prepare("SELECT * FROM tokens WHERE slug = ?").get(slug);
1526
+ parseTokenFallback(token);
1527
+ return token;
1503
1528
  }
1504
1529
  async function getTokenById(db, id) {
1505
- return await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1530
+ const token = await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1531
+ parseTokenFallback(token);
1532
+ return token;
1506
1533
  }
1507
1534
  async function updateToken(db, slug, updates) {
1508
1535
  const token = await getTokenBySlug(db, slug);
@@ -1548,6 +1575,14 @@ async function updateToken(db, slug, updates) {
1548
1575
  fields.push("question = ?");
1549
1576
  values.push(updates.question);
1550
1577
  }
1578
+ if (updates.provider !== void 0) {
1579
+ fields.push("provider = ?");
1580
+ values.push(updates.provider);
1581
+ }
1582
+ if (updates.topic_id !== void 0) {
1583
+ fields.push("topic_id = ?");
1584
+ values.push(updates.topic_id);
1585
+ }
1551
1586
  if (fields.length === 0) {
1552
1587
  throw new Error("updateToken called with no fields to update");
1553
1588
  }
@@ -1673,14 +1708,20 @@ async function findTokens(db, query) {
1673
1708
  return scored;
1674
1709
  }
1675
1710
  async function listTokens(db, options) {
1711
+ let tokens;
1676
1712
  if (options?.domain) {
1677
- return await db.prepare(
1713
+ tokens = await db.prepare(
1678
1714
  "SELECT * FROM tokens WHERE domain = ? AND deprecated_at IS NULL ORDER BY bloom_level, slug"
1679
1715
  ).all(options.domain);
1716
+ } else {
1717
+ tokens = await db.prepare(
1718
+ "SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
1719
+ ).all();
1680
1720
  }
1681
- return await db.prepare(
1682
- "SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
1683
- ).all();
1721
+ for (const token of tokens) {
1722
+ parseTokenFallback(token);
1723
+ }
1724
+ return tokens;
1684
1725
  }
1685
1726
  function slugify(text) {
1686
1727
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
@@ -1732,6 +1773,8 @@ async function listPersonalCards(db, userId, options) {
1732
1773
  t.question,
1733
1774
  t.created_at AS createdAt,
1734
1775
  t.updated_at AS updatedAt,
1776
+ t.provider,
1777
+ t.topic_id AS topicId,
1735
1778
  c.id AS cardId,
1736
1779
  c.state,
1737
1780
  c.due_at AS dueAt,
@@ -1761,6 +1804,17 @@ async function listPersonalCards(db, userId, options) {
1761
1804
  }
1762
1805
  sql += " ORDER BY t.created_at DESC";
1763
1806
  const rows = await db.prepare(sql).all(...values);
1807
+ for (const row of rows) {
1808
+ if (!row.provider && row.sourceLink) {
1809
+ if (row.sourceLink.includes("lehrplanplus.bayern.de")) {
1810
+ row.provider = "lehrplanplus-bayern";
1811
+ const match = row.sourceLink.match(/#(.*)$/);
1812
+ if (match) {
1813
+ row.topicId = match[1];
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1764
1818
  return rows;
1765
1819
  }
1766
1820
  async function importCurriculumCards(db, userId, cards) {
@@ -1807,9 +1861,18 @@ async function importCurriculumCards(db, userId, cards) {
1807
1861
  context: card.context || "",
1808
1862
  symbiosis_mode: symbiosisMode,
1809
1863
  source_link: card.source_link || null,
1810
- question: card.question || null
1864
+ question: card.question || null,
1865
+ provider: card.provider || null,
1866
+ topic_id: card.topic_id || null
1811
1867
  });
1812
1868
  createdCount++;
1869
+ } else {
1870
+ if (!token.provider && card.provider) {
1871
+ await updateToken(tx, token.slug, {
1872
+ provider: card.provider,
1873
+ topic_id: card.topic_id || null
1874
+ });
1875
+ }
1813
1876
  }
1814
1877
  const existingCard = await getCard(tx, token.id, userId);
1815
1878
  if (!existingCard) {
@@ -2031,11 +2094,12 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2031
2094
  let createdCount = 0;
2032
2095
  let linkedCount = 0;
2033
2096
  await db.transaction(async (tx) => {
2034
- const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(sourceId);
2035
- if (!source) {
2036
- throw new Error(`Source not found: ${sourceId}`);
2037
- }
2038
2097
  for (const card of proposals) {
2098
+ const cardSourceId = card.source_id || sourceId;
2099
+ const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(cardSourceId);
2100
+ if (!source) {
2101
+ throw new Error(`Source not found: ${cardSourceId}`);
2102
+ }
2039
2103
  const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
2040
2104
  const cleanDomain = slugify(card.domain || "");
2041
2105
  const cleanBase = slugify(baseText);
@@ -2066,11 +2130,19 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2066
2130
  bloom_level: bloom,
2067
2131
  context: card.excerpt || "",
2068
2132
  symbiosis_mode: symbiosisMode,
2069
- question: card.question || null
2133
+ question: card.question || null,
2134
+ provider: card.provider || null,
2135
+ topic_id: card.topic_id || null
2070
2136
  });
2071
2137
  createdCount++;
2072
2138
  } else {
2073
2139
  linkedCount++;
2140
+ if (!token.provider && card.provider) {
2141
+ await updateToken(tx, token.slug, {
2142
+ provider: card.provider,
2143
+ topic_id: card.topic_id || null
2144
+ });
2145
+ }
2074
2146
  }
2075
2147
  const existingCard = await getCard(tx, token.id, userId);
2076
2148
  if (!existingCard) {
@@ -2082,7 +2154,12 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2082
2154
  ON CONFLICT(token_id, source_id) DO UPDATE SET
2083
2155
  excerpt = excluded.excerpt,
2084
2156
  page_number = excluded.page_number`
2085
- ).run(token.id, sourceId, card.excerpt || "", card.page_number || null);
2157
+ ).run(
2158
+ token.id,
2159
+ cardSourceId,
2160
+ card.excerpt || "",
2161
+ card.page_number || null
2162
+ );
2086
2163
  }
2087
2164
  });
2088
2165
  return { createdCount, linkedCount };