@treeseed/sdk 0.1.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +97 -506
  2. package/dist/{src/cli-tools.d.ts → cli-tools.d.ts} +1 -1
  3. package/dist/cli-tools.js +5 -3
  4. package/dist/{src/content-store.d.ts → content-store.d.ts} +3 -2
  5. package/dist/content-store.js +52 -20
  6. package/dist/{src/d1-store.d.ts → d1-store.d.ts} +62 -1
  7. package/dist/d1-store.js +625 -65
  8. package/dist/field-aliases.d.ts +11 -0
  9. package/dist/field-aliases.js +41 -0
  10. package/dist/graph/build.d.ts +19 -0
  11. package/dist/graph/build.js +949 -0
  12. package/dist/graph/dsl.d.ts +2 -0
  13. package/dist/graph/dsl.js +243 -0
  14. package/dist/graph/query.d.ts +47 -0
  15. package/dist/graph/query.js +447 -0
  16. package/dist/graph/ranking.d.ts +3 -0
  17. package/dist/graph/ranking.js +483 -0
  18. package/dist/graph/schema.d.ts +142 -0
  19. package/dist/graph/schema.js +133 -0
  20. package/dist/graph.d.ts +52 -0
  21. package/dist/graph.js +133 -0
  22. package/dist/index.d.ts +28 -0
  23. package/dist/index.js +91 -2
  24. package/dist/model-registry.d.ts +8 -0
  25. package/dist/model-registry.js +351 -25
  26. package/dist/operations/providers/default.d.ts +10 -0
  27. package/dist/operations/providers/default.js +514 -0
  28. package/dist/operations/runtime.d.ts +7 -0
  29. package/dist/operations/runtime.js +60 -0
  30. package/dist/operations/services/config-runtime.d.ts +269 -0
  31. package/dist/operations/services/config-runtime.js +1397 -0
  32. package/dist/operations/services/d1-migration.d.ts +6 -0
  33. package/dist/operations/services/d1-migration.js +89 -0
  34. package/dist/operations/services/deploy.d.ts +371 -0
  35. package/dist/operations/services/deploy.js +981 -0
  36. package/dist/operations/services/git-workflow.d.ts +49 -0
  37. package/dist/operations/services/git-workflow.js +218 -0
  38. package/dist/operations/services/github-automation.d.ts +156 -0
  39. package/dist/operations/services/github-automation.js +256 -0
  40. package/dist/operations/services/local-dev.d.ts +9 -0
  41. package/dist/operations/services/local-dev.js +106 -0
  42. package/dist/operations/services/mailpit-runtime.d.ts +4 -0
  43. package/dist/operations/services/mailpit-runtime.js +59 -0
  44. package/dist/operations/services/railway-deploy.d.ts +53 -0
  45. package/dist/operations/services/railway-deploy.js +123 -0
  46. package/dist/operations/services/runtime-paths.d.ts +19 -0
  47. package/dist/operations/services/runtime-paths.js +54 -0
  48. package/dist/operations/services/runtime-tools.d.ts +117 -0
  49. package/dist/operations/services/runtime-tools.js +358 -0
  50. package/dist/operations/services/save-deploy-preflight.d.ts +34 -0
  51. package/dist/operations/services/save-deploy-preflight.js +76 -0
  52. package/dist/operations/services/template-registry.d.ts +88 -0
  53. package/dist/operations/services/template-registry.js +407 -0
  54. package/dist/operations/services/watch-dev.d.ts +21 -0
  55. package/dist/operations/services/watch-dev.js +284 -0
  56. package/dist/operations/services/workspace-preflight.d.ts +40 -0
  57. package/dist/operations/services/workspace-preflight.js +165 -0
  58. package/dist/operations/services/workspace-save.d.ts +42 -0
  59. package/dist/operations/services/workspace-save.js +235 -0
  60. package/dist/operations/services/workspace-tools.d.ts +16 -0
  61. package/dist/operations/services/workspace-tools.js +270 -0
  62. package/dist/operations-registry.d.ts +5 -0
  63. package/dist/operations-registry.js +68 -0
  64. package/dist/operations-types.d.ts +71 -0
  65. package/dist/operations-types.js +17 -0
  66. package/dist/operations.d.ts +6 -0
  67. package/dist/operations.js +16 -0
  68. package/dist/platform/books-data.d.ts +1 -0
  69. package/dist/platform/books-data.js +1 -0
  70. package/dist/platform/contracts.d.ts +158 -0
  71. package/dist/platform/contracts.js +0 -0
  72. package/dist/platform/deploy/config.d.ts +4 -0
  73. package/dist/platform/deploy/config.js +222 -0
  74. package/dist/platform/deploy-config.d.ts +1 -0
  75. package/dist/platform/deploy-config.js +1 -0
  76. package/dist/platform/deploy-runtime.d.ts +18 -0
  77. package/dist/platform/deploy-runtime.js +78 -0
  78. package/dist/platform/env.yaml +394 -0
  79. package/dist/platform/environment.d.ts +130 -0
  80. package/dist/platform/environment.js +331 -0
  81. package/dist/platform/plugin.d.ts +2 -0
  82. package/dist/platform/plugin.js +4 -0
  83. package/dist/platform/plugins/constants.d.ts +22 -0
  84. package/dist/platform/plugins/constants.js +29 -0
  85. package/dist/platform/plugins/plugin.d.ts +51 -0
  86. package/dist/platform/plugins/plugin.js +6 -0
  87. package/dist/platform/plugins/runtime.d.ts +35 -0
  88. package/dist/platform/plugins/runtime.js +161 -0
  89. package/dist/platform/plugins.d.ts +6 -0
  90. package/dist/platform/plugins.js +38 -0
  91. package/dist/platform/site-config-schema.js +1 -0
  92. package/dist/platform/tenant/config.d.ts +9 -0
  93. package/dist/platform/tenant/config.js +154 -0
  94. package/dist/platform/tenant/runtime-config.d.ts +4 -0
  95. package/dist/platform/tenant/runtime-config.js +20 -0
  96. package/dist/platform/tenant-config.d.ts +1 -0
  97. package/dist/platform/tenant-config.js +1 -0
  98. package/dist/platform/utils/books-data.d.ts +29 -0
  99. package/dist/platform/utils/books-data.js +82 -0
  100. package/dist/platform/utils/site-config-schema.js +321 -0
  101. package/dist/remote.d.ts +175 -0
  102. package/dist/remote.js +202 -0
  103. package/dist/runtime.js +35 -22
  104. package/dist/scripts/aggregate-book.js +121 -0
  105. package/dist/scripts/build-dist.js +54 -13
  106. package/dist/scripts/build-tenant-worker.js +36 -0
  107. package/dist/scripts/cleanup-markdown.js +373 -0
  108. package/dist/scripts/cli-test-fixtures.js +48 -0
  109. package/dist/scripts/config-treeseed.js +95 -0
  110. package/dist/scripts/ensure-mailpit.js +29 -0
  111. package/dist/scripts/local-dev.js +129 -0
  112. package/dist/scripts/logs-mailpit.js +2 -0
  113. package/dist/scripts/patch-starlight-content-path.js +172 -0
  114. package/dist/scripts/release-verify.js +34 -6
  115. package/dist/scripts/run-fixture-astro-command.js +18 -0
  116. package/dist/scripts/scaffold-site.js +65 -0
  117. package/dist/scripts/stop-mailpit.js +5 -0
  118. package/dist/scripts/sync-dev-vars.js +6 -0
  119. package/dist/scripts/sync-template.js +20 -0
  120. package/dist/scripts/template-catalog.test.js +100 -0
  121. package/dist/scripts/template-command.js +31 -0
  122. package/dist/scripts/tenant-astro-command.js +3 -0
  123. package/dist/scripts/tenant-build.js +16 -0
  124. package/dist/scripts/tenant-check.js +7 -0
  125. package/dist/scripts/tenant-d1-migrate-local.js +11 -0
  126. package/dist/scripts/tenant-deploy.js +180 -0
  127. package/dist/scripts/tenant-destroy.js +104 -0
  128. package/dist/scripts/tenant-dev.js +171 -0
  129. package/dist/scripts/tenant-lint.js +4 -0
  130. package/dist/scripts/tenant-test.js +4 -0
  131. package/dist/scripts/test-cloudflare-local.js +212 -0
  132. package/dist/scripts/test-scaffold.js +314 -0
  133. package/dist/scripts/test-smoke.js +71 -13
  134. package/dist/scripts/treeseed-assert-release-tag-version.js +21 -0
  135. package/dist/scripts/treeseed-build-dist.js +134 -0
  136. package/dist/scripts/treeseed-publish-package.js +19 -0
  137. package/dist/scripts/treeseed-release-verify.js +131 -0
  138. package/dist/scripts/treeseed-run-ts.js +45 -0
  139. package/dist/scripts/validate-templates.js +6 -0
  140. package/dist/scripts/verify-driver.js +29 -0
  141. package/dist/scripts/workflow-commands.test.js +39 -0
  142. package/dist/scripts/workspace-close.js +24 -0
  143. package/dist/scripts/workspace-command-e2e.js +718 -0
  144. package/dist/scripts/workspace-lint.js +9 -0
  145. package/dist/scripts/workspace-preflight.js +22 -0
  146. package/dist/scripts/workspace-publish-changed-packages.js +16 -0
  147. package/dist/scripts/workspace-release-verify.js +81 -0
  148. package/dist/scripts/workspace-release.js +42 -0
  149. package/dist/scripts/workspace-save.js +124 -0
  150. package/dist/scripts/workspace-start-warning.js +3 -0
  151. package/dist/scripts/workspace-start.js +71 -0
  152. package/dist/scripts/workspace-test-unit.js +4 -0
  153. package/dist/scripts/workspace-test.js +11 -0
  154. package/dist/sdk-fields.d.ts +11 -0
  155. package/dist/sdk-fields.js +169 -0
  156. package/dist/sdk-filters.d.ts +4 -0
  157. package/dist/sdk-filters.js +12 -15
  158. package/dist/sdk-types.d.ts +796 -0
  159. package/dist/sdk-types.js +7 -1
  160. package/dist/sdk-version.d.ts +2 -0
  161. package/dist/sdk-version.js +42 -0
  162. package/dist/sdk.d.ts +215 -0
  163. package/dist/sdk.js +235 -11
  164. package/dist/stores/cursor-store.js +9 -3
  165. package/dist/stores/lease-store.js +8 -2
  166. package/dist/{src/stores → stores}/message-store.d.ts +1 -1
  167. package/dist/stores/message-store.js +27 -3
  168. package/dist/stores/operational-store.d.ts +24 -0
  169. package/dist/stores/operational-store.js +279 -0
  170. package/dist/stores/run-store.js +8 -1
  171. package/dist/stores/subscription-store.js +7 -5
  172. package/dist/template-catalog.d.ts +13 -0
  173. package/dist/template-catalog.js +141 -0
  174. package/dist/treeseed/services/compose.yml +7 -0
  175. package/dist/treeseed/template-catalog/catalog.fixture.json +55 -0
  176. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +2 -0
  177. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +3 -0
  178. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +32 -0
  179. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +40 -0
  180. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +1 -0
  181. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +11 -0
  182. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +11 -0
  183. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +1 -0
  184. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +3 -0
  185. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +1 -0
  186. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +19 -0
  187. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +26 -0
  188. package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +9 -0
  189. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +90 -0
  190. package/dist/utils/agents/contracts/messages.d.ts +88 -0
  191. package/dist/utils/agents/contracts/messages.js +138 -0
  192. package/dist/utils/agents/contracts/run.d.ts +20 -0
  193. package/dist/utils/agents/contracts/run.js +0 -0
  194. package/dist/utils/agents/runtime-types.d.ts +117 -0
  195. package/dist/utils/agents/runtime-types.js +4 -0
  196. package/dist/verification.d.ts +20 -0
  197. package/dist/verification.js +98 -0
  198. package/dist/workflow/operations.d.ts +396 -0
  199. package/dist/workflow/operations.js +841 -0
  200. package/dist/workflow-state.d.ts +56 -0
  201. package/dist/workflow-state.js +195 -0
  202. package/dist/workflow-support.d.ts +9 -0
  203. package/dist/workflow-support.js +176 -0
  204. package/dist/workflow.d.ts +111 -0
  205. package/dist/workflow.js +97 -0
  206. package/package.json +111 -5
  207. package/scripts/verify-driver.mjs +29 -0
  208. package/dist/scripts/.ts-run-1775630384291-crtqr3izsa.js +0 -22
  209. package/dist/scripts/.ts-run-1775630388025-vnjle0z75a.js +0 -129
  210. package/dist/scripts/assert-release-tag-version.d.ts +0 -1
  211. package/dist/scripts/build-dist.d.ts +0 -1
  212. package/dist/scripts/fixture-tools.d.ts +0 -5
  213. package/dist/scripts/package-tools.d.ts +0 -15
  214. package/dist/scripts/publish-package.d.ts +0 -1
  215. package/dist/scripts/release-verify.d.ts +0 -1
  216. package/dist/scripts/test-smoke.d.ts +0 -1
  217. package/dist/src/index.d.ts +0 -6
  218. package/dist/src/model-registry.d.ts +0 -4
  219. package/dist/src/sdk-filters.d.ts +0 -4
  220. package/dist/src/sdk-types.d.ts +0 -285
  221. package/dist/src/sdk.d.ts +0 -109
  222. package/dist/test/test-fixture.d.ts +0 -1
  223. package/dist/test/utils/envelopes.test.d.ts +0 -1
  224. package/dist/test/utils/sdk.test.d.ts +0 -1
  225. package/dist/vitest.config.d.ts +0 -2
  226. /package/dist/{src/frontmatter.d.ts → frontmatter.d.ts} +0 -0
  227. /package/dist/{src/git-runtime.d.ts → git-runtime.d.ts} +0 -0
  228. /package/dist/{src/runtime.d.ts → runtime.d.ts} +0 -0
  229. /package/dist/{src/stores → stores}/cursor-store.d.ts +0 -0
  230. /package/dist/{src/stores → stores}/envelopes.d.ts +0 -0
  231. /package/dist/{src/stores → stores}/helpers.d.ts +0 -0
  232. /package/dist/{src/stores → stores}/lease-store.d.ts +0 -0
  233. /package/dist/{src/stores → stores}/run-store.d.ts +0 -0
  234. /package/dist/{src/stores → stores}/subscription-store.d.ts +0 -0
  235. /package/dist/{src/types → types}/agents.d.ts +0 -0
  236. /package/dist/{src/types → types}/cloudflare.d.ts +0 -0
  237. /package/dist/{src/wrangler-d1.d.ts → wrangler-d1.d.ts} +0 -0
@@ -5,7 +5,7 @@ export declare class MessageStore extends SqliteStoreBase {
5
5
  private usesEnvelopeTable;
6
6
  getById(id: number): Promise<SdkMessageEntity | null>;
7
7
  search(request: SdkSearchRequest): Promise<SdkMessageEntity[]>;
8
- claim(request: SdkClaimMessageRequest): Promise<SdkMessageEntity | null>;
8
+ claim(request: SdkClaimMessageRequest, strategy?: 'latest' | 'highest_priority' | 'oldest'): Promise<SdkMessageEntity | null>;
9
9
  ack(request: SdkAckMessageRequest): Promise<void>;
10
10
  create(request: SdkCreateMessageRequest): Promise<SdkMessageEntity>;
11
11
  update(request: SdkUpdateRequest): Promise<SdkMessageEntity | null>;
@@ -1,3 +1,4 @@
1
+ import { assertExpectedVersion } from "../sdk-version.js";
1
2
  import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
2
3
  import { createMessageEnvelope, messageEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
3
4
  function messageFromRow(row) {
@@ -36,6 +37,17 @@ function buildFilterSql(filters = []) {
36
37
  function buildOrderSql(sort = []) {
37
38
  return sort?.length ? `ORDER BY ${sort.map((entry) => `${entry.field} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "";
38
39
  }
40
+ function claimOrderSql(strategy) {
41
+ switch (strategy) {
42
+ case "oldest":
43
+ return "ORDER BY available_at ASC, priority DESC";
44
+ case "latest":
45
+ return "ORDER BY available_at DESC, priority DESC";
46
+ case "highest_priority":
47
+ default:
48
+ return "ORDER BY priority DESC, available_at ASC";
49
+ }
50
+ }
39
51
  class MessageStore extends SqliteStoreBase {
40
52
  async usesEnvelopeTable() {
41
53
  return this.tableExists("message_queue");
@@ -68,11 +80,11 @@ class MessageStore extends SqliteStoreBase {
68
80
  const rows = await this.selectAll(sql);
69
81
  return rows.map(messageFromRow);
70
82
  }
71
- async claim(request) {
83
+ async claim(request, strategy = "highest_priority") {
72
84
  if (await this.usesEnvelopeTable()) {
73
85
  const typeClause2 = request.messageTypes?.length ? ` AND message_type IN (${request.messageTypes.map(toSqlValue).join(", ")})` : "";
74
86
  const row2 = await this.selectFirst(
75
- `SELECT * FROM message_queue WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause2} ORDER BY priority DESC, available_at ASC LIMIT 1`
87
+ `SELECT * FROM message_queue WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause2} ${claimOrderSql(strategy)} LIMIT 1`
76
88
  );
77
89
  if (!row2) {
78
90
  return null;
@@ -86,7 +98,7 @@ class MessageStore extends SqliteStoreBase {
86
98
  }
87
99
  const typeClause = request.messageTypes?.length ? ` AND type IN (${request.messageTypes.map(toSqlValue).join(", ")})` : "";
88
100
  const row = await this.selectFirst(
89
- `SELECT * FROM messages WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause} ORDER BY priority DESC, available_at ASC LIMIT 1`
101
+ `SELECT * FROM messages WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause} ${claimOrderSql(strategy)} LIMIT 1`
90
102
  );
91
103
  if (!row) {
92
104
  return null;
@@ -136,6 +148,11 @@ class MessageStore extends SqliteStoreBase {
136
148
  if (!id) {
137
149
  throw new Error("Message update requires an id.");
138
150
  }
151
+ const existing = await this.getById(id);
152
+ if (!existing) {
153
+ return null;
154
+ }
155
+ assertExpectedVersion(request.expectedVersion, existing, `message ${id}`);
139
156
  if (await this.usesEnvelopeTable()) {
140
157
  const fields2 = [];
141
158
  for (const [key, value] of Object.entries(request.data)) {
@@ -226,18 +243,25 @@ function messageEnvelopeColumn(field) {
226
243
  case "type":
227
244
  return "message_type";
228
245
  case "relatedModel":
246
+ case "related_model":
229
247
  return "related_model";
230
248
  case "relatedId":
249
+ case "related_id":
231
250
  return "related_id";
232
251
  case "maxAttempts":
252
+ case "max_attempts":
233
253
  return "max_attempts";
234
254
  case "availableAt":
255
+ case "available_at":
235
256
  return "available_at";
236
257
  case "claimedBy":
258
+ case "claimed_by":
237
259
  return "claimed_by";
238
260
  case "claimedAt":
261
+ case "claimed_at":
239
262
  return "claimed_at";
240
263
  case "leaseExpiresAt":
264
+ case "lease_expires_at":
241
265
  return "lease_expires_at";
242
266
  default:
243
267
  return field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
@@ -0,0 +1,24 @@
1
+ import type { SdkAppendTaskEventRequest, SdkClaimTaskRequest, SdkCloseWorkDayRequest, SdkCompleteTaskRequest, SdkCreateReportRequest, SdkCreateTaskRequest, SdkFailTaskRequest, SdkGraphRunEntity, SdkReportEntity, SdkStartWorkDayRequest, SdkTaskEntity, SdkTaskEventEntity, SdkTaskOutputEntity, SdkTaskProgressRequest, SdkTaskSearchRequest, SdkWorkDayEntity } from '../sdk-types.ts';
2
+ import { SqliteStoreBase } from './helpers.ts';
3
+ export declare class OperationalStore extends SqliteStoreBase {
4
+ getWorkDay(id: string): Promise<SdkWorkDayEntity | null>;
5
+ searchWorkDays(limit?: number): Promise<SdkWorkDayEntity[]>;
6
+ startWorkDay(request: SdkStartWorkDayRequest): Promise<SdkWorkDayEntity | null>;
7
+ closeWorkDay(request: SdkCloseWorkDayRequest): Promise<SdkWorkDayEntity | null>;
8
+ getTask(id: string): Promise<SdkTaskEntity | null>;
9
+ searchTasks(request?: SdkTaskSearchRequest): Promise<SdkTaskEntity[]>;
10
+ createTask(request: SdkCreateTaskRequest): Promise<SdkTaskEntity | null>;
11
+ claimTask(request: SdkClaimTaskRequest): Promise<SdkTaskEntity | null>;
12
+ recordTaskProgress(request: SdkTaskProgressRequest): Promise<SdkTaskEntity | null>;
13
+ completeTask(request: SdkCompleteTaskRequest): Promise<SdkTaskEntity | null>;
14
+ failTask(request: SdkFailTaskRequest): Promise<SdkTaskEntity | null>;
15
+ appendTaskEvent(request: SdkAppendTaskEventRequest): Promise<SdkTaskEventEntity | null>;
16
+ listTaskEvents(taskId: string): Promise<SdkTaskEventEntity[]>;
17
+ listTaskOutputs(taskId: string): Promise<SdkTaskOutputEntity[]>;
18
+ createGraphRun(input: Omit<SdkGraphRunEntity, 'createdAt'> & {
19
+ createdAt?: string;
20
+ }): Promise<SdkGraphRunEntity | null>;
21
+ getLatestGraphRun(workDayId: string): Promise<SdkGraphRunEntity | null>;
22
+ createReport(request: SdkCreateReportRequest): Promise<SdkReportEntity | null>;
23
+ getReport(id: string): Promise<SdkReportEntity | null>;
24
+ }
@@ -0,0 +1,279 @@
1
+ import crypto from "node:crypto";
2
+ import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
3
+ function json(value) {
4
+ return JSON.stringify(value ?? {});
5
+ }
6
+ function workDayFromRow(row) {
7
+ return {
8
+ id: String(row.id ?? ""),
9
+ projectId: String(row.project_id ?? row.projectId ?? ""),
10
+ state: String(row.state ?? "active"),
11
+ capacityBudget: Number(row.capacity_budget ?? row.capacityBudget ?? 0),
12
+ capacityUsed: Number(row.capacity_used ?? row.capacityUsed ?? 0),
13
+ graphVersion: row.graph_version !== void 0 && row.graph_version !== null ? String(row.graph_version) : row.graphVersion !== void 0 && row.graphVersion !== null ? String(row.graphVersion) : null,
14
+ summaryJson: row.summary_json !== void 0 && row.summary_json !== null ? String(row.summary_json) : row.summaryJson !== void 0 && row.summaryJson !== null ? String(row.summaryJson) : null,
15
+ startedAt: String(row.started_at ?? row.startedAt ?? nowIso()),
16
+ endedAt: row.ended_at !== void 0 && row.ended_at !== null ? String(row.ended_at) : row.endedAt !== void 0 && row.endedAt !== null ? String(row.endedAt) : null,
17
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso()),
18
+ updatedAt: String(row.updated_at ?? row.updatedAt ?? nowIso())
19
+ };
20
+ }
21
+ function taskFromRow(row) {
22
+ return {
23
+ id: String(row.id ?? ""),
24
+ workDayId: String(row.work_day_id ?? row.workDayId ?? ""),
25
+ agentId: String(row.agent_id ?? row.agentId ?? ""),
26
+ type: String(row.type ?? ""),
27
+ state: String(row.state ?? "pending"),
28
+ priority: Number(row.priority ?? 0),
29
+ idempotencyKey: String(row.idempotency_key ?? row.idempotencyKey ?? ""),
30
+ payloadJson: String(row.payload_json ?? row.payloadJson ?? "{}"),
31
+ payloadHash: row.payload_hash !== void 0 && row.payload_hash !== null ? String(row.payload_hash) : row.payloadHash !== void 0 && row.payloadHash !== null ? String(row.payloadHash) : null,
32
+ attemptCount: Number(row.attempt_count ?? row.attemptCount ?? 0),
33
+ maxAttempts: Number(row.max_attempts ?? row.maxAttempts ?? 3),
34
+ claimedBy: row.claimed_by !== void 0 && row.claimed_by !== null ? String(row.claimed_by) : row.claimedBy !== void 0 && row.claimedBy !== null ? String(row.claimedBy) : null,
35
+ leaseExpiresAt: row.lease_expires_at !== void 0 && row.lease_expires_at !== null ? String(row.lease_expires_at) : row.leaseExpiresAt !== void 0 && row.leaseExpiresAt !== null ? String(row.leaseExpiresAt) : null,
36
+ availableAt: String(row.available_at ?? row.availableAt ?? nowIso()),
37
+ lastErrorCode: row.last_error_code !== void 0 && row.last_error_code !== null ? String(row.last_error_code) : row.lastErrorCode !== void 0 && row.lastErrorCode !== null ? String(row.lastErrorCode) : null,
38
+ lastErrorMessage: row.last_error_message !== void 0 && row.last_error_message !== null ? String(row.last_error_message) : row.lastErrorMessage !== void 0 && row.lastErrorMessage !== null ? String(row.lastErrorMessage) : null,
39
+ graphVersion: row.graph_version !== void 0 && row.graph_version !== null ? String(row.graph_version) : row.graphVersion !== void 0 && row.graphVersion !== null ? String(row.graphVersion) : null,
40
+ parentTaskId: row.parent_task_id !== void 0 && row.parent_task_id !== null ? String(row.parent_task_id) : row.parentTaskId !== void 0 && row.parentTaskId !== null ? String(row.parentTaskId) : null,
41
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso()),
42
+ startedAt: row.started_at !== void 0 && row.started_at !== null ? String(row.started_at) : row.startedAt !== void 0 && row.startedAt !== null ? String(row.startedAt) : null,
43
+ completedAt: row.completed_at !== void 0 && row.completed_at !== null ? String(row.completed_at) : row.completedAt !== void 0 && row.completedAt !== null ? String(row.completedAt) : null,
44
+ updatedAt: String(row.updated_at ?? row.updatedAt ?? nowIso())
45
+ };
46
+ }
47
+ function taskEventFromRow(row) {
48
+ return {
49
+ id: String(row.id ?? ""),
50
+ taskId: String(row.task_id ?? row.taskId ?? ""),
51
+ seq: Number(row.seq ?? 0),
52
+ kind: String(row.kind ?? ""),
53
+ dataJson: String(row.data_json ?? row.dataJson ?? "{}"),
54
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso())
55
+ };
56
+ }
57
+ function taskOutputFromRow(row) {
58
+ return {
59
+ id: String(row.id ?? ""),
60
+ taskId: String(row.task_id ?? row.taskId ?? ""),
61
+ outputJson: String(row.output_json ?? row.outputJson ?? "{}"),
62
+ outputRef: row.output_ref !== void 0 && row.output_ref !== null ? String(row.output_ref) : row.outputRef !== void 0 && row.outputRef !== null ? String(row.outputRef) : null,
63
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso())
64
+ };
65
+ }
66
+ function graphRunFromRow(row) {
67
+ return {
68
+ id: String(row.id ?? ""),
69
+ workDayId: String(row.work_day_id ?? row.workDayId ?? ""),
70
+ corpusHash: String(row.corpus_hash ?? row.corpusHash ?? ""),
71
+ graphVersion: String(row.graph_version ?? row.graphVersion ?? ""),
72
+ queryJson: row.query_json !== void 0 && row.query_json !== null ? String(row.query_json) : row.queryJson !== void 0 && row.queryJson !== null ? String(row.queryJson) : null,
73
+ seedIdsJson: row.seed_ids_json !== void 0 && row.seed_ids_json !== null ? String(row.seed_ids_json) : row.seedIdsJson !== void 0 && row.seedIdsJson !== null ? String(row.seedIdsJson) : null,
74
+ selectedNodeIdsJson: row.selected_node_ids_json !== void 0 && row.selected_node_ids_json !== null ? String(row.selected_node_ids_json) : row.selectedNodeIdsJson !== void 0 && row.selectedNodeIdsJson !== null ? String(row.selectedNodeIdsJson) : null,
75
+ statsJson: row.stats_json !== void 0 && row.stats_json !== null ? String(row.stats_json) : row.statsJson !== void 0 && row.statsJson !== null ? String(row.statsJson) : null,
76
+ snapshotRef: row.snapshot_ref !== void 0 && row.snapshot_ref !== null ? String(row.snapshot_ref) : row.snapshotRef !== void 0 && row.snapshotRef !== null ? String(row.snapshotRef) : null,
77
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso())
78
+ };
79
+ }
80
+ function reportFromRow(row) {
81
+ return {
82
+ id: String(row.id ?? ""),
83
+ workDayId: String(row.work_day_id ?? row.workDayId ?? ""),
84
+ kind: String(row.kind ?? ""),
85
+ bodyJson: String(row.body_json ?? row.bodyJson ?? "{}"),
86
+ renderedRef: row.rendered_ref !== void 0 && row.rendered_ref !== null ? String(row.rendered_ref) : row.renderedRef !== void 0 && row.renderedRef !== null ? String(row.renderedRef) : null,
87
+ sentAt: row.sent_at !== void 0 && row.sent_at !== null ? String(row.sent_at) : row.sentAt !== void 0 && row.sentAt !== null ? String(row.sentAt) : null,
88
+ createdAt: String(row.created_at ?? row.createdAt ?? nowIso())
89
+ };
90
+ }
91
+ class OperationalStore extends SqliteStoreBase {
92
+ async getWorkDay(id) {
93
+ const row = await this.selectFirst(`SELECT * FROM work_days WHERE id = ${toSqlValue(id)} LIMIT 1`);
94
+ return row ? workDayFromRow(row) : null;
95
+ }
96
+ async searchWorkDays(limit = 20) {
97
+ const rows = await this.selectAll(`SELECT * FROM work_days ORDER BY updated_at DESC LIMIT ${limit}`);
98
+ return rows.map(workDayFromRow);
99
+ }
100
+ async startWorkDay(request) {
101
+ const id = request.id ?? crypto.randomUUID();
102
+ const timestamp = nowIso();
103
+ await this.execute(
104
+ `INSERT OR REPLACE INTO work_days (id, project_id, state, capacity_budget, capacity_used, graph_version, summary_json, started_at, ended_at, created_at, updated_at) VALUES (${toSqlValue(id)}, ${toSqlValue(request.projectId)}, 'active', ${Number(request.capacityBudget ?? 0)}, 0, ${toSqlValue(request.graphVersion ?? null)}, ${toSqlValue(json(request.summary ?? null))}, ${toSqlValue(timestamp)}, NULL, COALESCE((SELECT created_at FROM work_days WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}), ${toSqlValue(timestamp)})`
105
+ );
106
+ return this.getWorkDay(id);
107
+ }
108
+ async closeWorkDay(request) {
109
+ const existing = await this.getWorkDay(request.id);
110
+ if (!existing) {
111
+ return null;
112
+ }
113
+ const timestamp = nowIso();
114
+ await this.execute(
115
+ `UPDATE work_days SET state = ${toSqlValue(request.state ?? "completed")}, summary_json = ${toSqlValue(json(request.summary ?? null))}, ended_at = ${toSqlValue(timestamp)}, updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
116
+ );
117
+ return this.getWorkDay(request.id);
118
+ }
119
+ async getTask(id) {
120
+ const row = await this.selectFirst(`SELECT * FROM tasks WHERE id = ${toSqlValue(id)} LIMIT 1`);
121
+ return row ? taskFromRow(row) : null;
122
+ }
123
+ async searchTasks(request = {}) {
124
+ const clauses = [];
125
+ if (request.workDayId) clauses.push(`work_day_id = ${toSqlValue(request.workDayId)}`);
126
+ if (request.agentId) clauses.push(`agent_id = ${toSqlValue(request.agentId)}`);
127
+ if (request.state) {
128
+ const states = Array.isArray(request.state) ? request.state : [request.state];
129
+ clauses.push(`state IN (${states.map((entry) => toSqlValue(entry)).join(", ")})`);
130
+ }
131
+ const sql = [
132
+ "SELECT * FROM tasks",
133
+ clauses.length ? `WHERE ${clauses.join(" AND ")}` : "",
134
+ "ORDER BY priority DESC, available_at ASC, created_at ASC",
135
+ `LIMIT ${request.limit ?? 50}`
136
+ ].filter(Boolean).join(" ");
137
+ const rows = await this.selectAll(sql);
138
+ return rows.map(taskFromRow);
139
+ }
140
+ async createTask(request) {
141
+ const id = request.id ?? crypto.randomUUID();
142
+ const timestamp = nowIso();
143
+ await this.execute(
144
+ `INSERT OR REPLACE INTO tasks (id, work_day_id, agent_id, type, state, priority, idempotency_key, payload_json, payload_hash, attempt_count, max_attempts, claimed_by, lease_expires_at, available_at, last_error_code, last_error_message, graph_version, parent_task_id, created_at, started_at, completed_at, updated_at) VALUES (${toSqlValue(id)}, ${toSqlValue(request.workDayId)}, ${toSqlValue(request.agentId)}, ${toSqlValue(request.type)}, ${toSqlValue(request.state ?? "pending")}, ${Number(request.priority ?? 0)}, ${toSqlValue(request.idempotencyKey)}, ${toSqlValue(json(request.payload))}, ${toSqlValue(request.payloadHash ?? null)}, 0, ${Number(request.maxAttempts ?? 3)}, NULL, NULL, ${toSqlValue(request.availableAt ?? timestamp)}, NULL, NULL, ${toSqlValue(request.graphVersion ?? null)}, ${toSqlValue(request.parentTaskId ?? null)}, COALESCE((SELECT created_at FROM tasks WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}), NULL, NULL, ${toSqlValue(timestamp)})`
145
+ );
146
+ return this.getTask(id);
147
+ }
148
+ async claimTask(request) {
149
+ const existing = await this.getTask(request.id);
150
+ if (!existing) {
151
+ return null;
152
+ }
153
+ const timestamp = nowIso();
154
+ const leaseExpiresAt = new Date(Date.now() + request.leaseSeconds * 1e3).toISOString();
155
+ await this.execute(
156
+ `UPDATE tasks SET state = 'claimed', claimed_by = ${toSqlValue(request.workerId)}, lease_expires_at = ${toSqlValue(leaseExpiresAt)}, attempt_count = attempt_count + 1, started_at = COALESCE(started_at, ${toSqlValue(timestamp)}), updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
157
+ );
158
+ return this.getTask(request.id);
159
+ }
160
+ async recordTaskProgress(request) {
161
+ const existing = await this.getTask(request.id);
162
+ if (!existing) {
163
+ return null;
164
+ }
165
+ const patch = request.patch ?? {};
166
+ const currentPayload = JSON.parse(existing.payloadJson);
167
+ const nextPayload = { ...currentPayload, ...patch };
168
+ const timestamp = nowIso();
169
+ await this.execute(
170
+ `UPDATE tasks SET state = ${toSqlValue(request.state ?? existing.state)}, payload_json = ${toSqlValue(json(nextPayload))}, claimed_by = ${toSqlValue(request.workerId ?? existing.claimedBy)}, updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
171
+ );
172
+ if (request.appendEvent?.kind) {
173
+ await this.appendTaskEvent({
174
+ taskId: request.id,
175
+ kind: request.appendEvent.kind,
176
+ data: request.appendEvent.data,
177
+ actor: request.actor
178
+ });
179
+ }
180
+ return this.getTask(request.id);
181
+ }
182
+ async completeTask(request) {
183
+ const existing = await this.getTask(request.id);
184
+ if (!existing) {
185
+ return null;
186
+ }
187
+ const timestamp = nowIso();
188
+ await this.execute(
189
+ `UPDATE tasks SET state = 'completed', completed_at = ${toSqlValue(timestamp)}, lease_expires_at = NULL, updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
190
+ );
191
+ if (request.output) {
192
+ await this.execute(
193
+ `INSERT INTO task_outputs (id, task_id, output_json, output_ref, created_at) VALUES (${toSqlValue(crypto.randomUUID())}, ${toSqlValue(request.id)}, ${toSqlValue(json(request.output))}, ${toSqlValue(request.outputRef ?? null)}, ${toSqlValue(timestamp)})`
194
+ );
195
+ }
196
+ if (request.summary) {
197
+ await this.appendTaskEvent({
198
+ taskId: request.id,
199
+ kind: "completed",
200
+ data: request.summary,
201
+ actor: request.actor
202
+ });
203
+ }
204
+ return this.getTask(request.id);
205
+ }
206
+ async failTask(request) {
207
+ const existing = await this.getTask(request.id);
208
+ if (!existing) {
209
+ return null;
210
+ }
211
+ const timestamp = nowIso();
212
+ const nextState = request.retryable ? "pending" : "failed";
213
+ await this.execute(
214
+ `UPDATE tasks SET state = ${toSqlValue(nextState)}, available_at = ${toSqlValue(request.nextVisibleAt ?? existing.availableAt)}, last_error_code = ${toSqlValue(request.errorCode ?? null)}, last_error_message = ${toSqlValue(request.errorMessage)}, lease_expires_at = NULL, updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
215
+ );
216
+ await this.appendTaskEvent({
217
+ taskId: request.id,
218
+ kind: nextState === "pending" ? "retry_scheduled" : "failed",
219
+ data: { errorCode: request.errorCode ?? null, errorMessage: request.errorMessage },
220
+ actor: request.actor
221
+ });
222
+ return this.getTask(request.id);
223
+ }
224
+ async appendTaskEvent(request) {
225
+ const seqRow = await this.selectFirst(
226
+ `SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq FROM task_events WHERE task_id = ${toSqlValue(request.taskId)}`
227
+ );
228
+ const seq = Number(seqRow?.next_seq ?? 1);
229
+ const id = crypto.randomUUID();
230
+ const timestamp = nowIso();
231
+ await this.execute(
232
+ `INSERT INTO task_events (id, task_id, seq, kind, data_json, created_at) VALUES (${toSqlValue(id)}, ${toSqlValue(request.taskId)}, ${seq}, ${toSqlValue(request.kind)}, ${toSqlValue(json({ ...request.data ?? {}, actor: request.actor }))}, ${toSqlValue(timestamp)})`
233
+ );
234
+ const row = await this.selectFirst(`SELECT * FROM task_events WHERE id = ${toSqlValue(id)} LIMIT 1`);
235
+ return row ? taskEventFromRow(row) : null;
236
+ }
237
+ async listTaskEvents(taskId) {
238
+ const rows = await this.selectAll(
239
+ `SELECT * FROM task_events WHERE task_id = ${toSqlValue(taskId)} ORDER BY seq ASC`
240
+ );
241
+ return rows.map(taskEventFromRow);
242
+ }
243
+ async listTaskOutputs(taskId) {
244
+ const rows = await this.selectAll(
245
+ `SELECT * FROM task_outputs WHERE task_id = ${toSqlValue(taskId)} ORDER BY created_at ASC`
246
+ );
247
+ return rows.map(taskOutputFromRow);
248
+ }
249
+ async createGraphRun(input) {
250
+ const timestamp = input.createdAt ?? nowIso();
251
+ await this.execute(
252
+ `INSERT OR REPLACE INTO graph_runs (id, work_day_id, corpus_hash, graph_version, query_json, seed_ids_json, selected_node_ids_json, stats_json, snapshot_ref, created_at) VALUES (${toSqlValue(input.id)}, ${toSqlValue(input.workDayId)}, ${toSqlValue(input.corpusHash)}, ${toSqlValue(input.graphVersion)}, ${toSqlValue(input.queryJson ?? null)}, ${toSqlValue(input.seedIdsJson ?? null)}, ${toSqlValue(input.selectedNodeIdsJson ?? null)}, ${toSqlValue(input.statsJson ?? null)}, ${toSqlValue(input.snapshotRef ?? null)}, ${toSqlValue(timestamp)})`
253
+ );
254
+ const row = await this.selectFirst(`SELECT * FROM graph_runs WHERE id = ${toSqlValue(input.id)} LIMIT 1`);
255
+ return row ? graphRunFromRow(row) : null;
256
+ }
257
+ async getLatestGraphRun(workDayId) {
258
+ const row = await this.selectFirst(
259
+ `SELECT * FROM graph_runs WHERE work_day_id = ${toSqlValue(workDayId)} ORDER BY created_at DESC LIMIT 1`
260
+ );
261
+ return row ? graphRunFromRow(row) : null;
262
+ }
263
+ async createReport(request) {
264
+ const id = request.id ?? crypto.randomUUID();
265
+ const timestamp = nowIso();
266
+ await this.execute(
267
+ `INSERT OR REPLACE INTO reports (id, work_day_id, kind, body_json, rendered_ref, sent_at, created_at) VALUES (${toSqlValue(id)}, ${toSqlValue(request.workDayId)}, ${toSqlValue(request.kind)}, ${toSqlValue(json(request.body))}, ${toSqlValue(request.renderedRef ?? null)}, ${toSqlValue(request.sentAt ?? null)}, ${toSqlValue(timestamp)})`
268
+ );
269
+ const row = await this.selectFirst(`SELECT * FROM reports WHERE id = ${toSqlValue(id)} LIMIT 1`);
270
+ return row ? reportFromRow(row) : null;
271
+ }
272
+ async getReport(id) {
273
+ const row = await this.selectFirst(`SELECT * FROM reports WHERE id = ${toSqlValue(id)} LIMIT 1`);
274
+ return row ? reportFromRow(row) : null;
275
+ }
276
+ }
277
+ export {
278
+ OperationalStore
279
+ };
@@ -1,3 +1,4 @@
1
+ import { assertExpectedVersion } from "../sdk-version.js";
1
2
  import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
2
3
  import { createRunEnvelope, runEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
3
4
  function runFromRecord(row) {
@@ -109,10 +110,16 @@ class RunStore extends SqliteStoreBase {
109
110
  return run;
110
111
  }
111
112
  async update(request) {
113
+ const runId = String(request.data.run_id ?? request.data.runId ?? request.id ?? request.key ?? "");
114
+ assertExpectedVersion(
115
+ request.expectedVersion,
116
+ runId ? await this.getByKey(runId) : null,
117
+ `agent_run "${runId}"`
118
+ );
112
119
  return this.record({
113
120
  run: {
114
121
  ...request.data,
115
- runId: request.data.runId ?? request.id ?? request.key
122
+ runId
116
123
  }
117
124
  });
118
125
  }
@@ -1,3 +1,4 @@
1
+ import { assertExpectedVersion } from "../sdk-version.js";
1
2
  import { SqliteStoreBase, toSqlValue } from "./helpers.js";
2
3
  import { createSubscriptionEnvelope, subscriptionEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
3
4
  function subscriptionFromRow(row) {
@@ -72,8 +73,8 @@ class SubscriptionStore extends SqliteStoreBase {
72
73
  name: data.name !== void 0 && data.name !== null ? String(data.name) : null,
73
74
  status: typeof data.status === "string" ? data.status : "active",
74
75
  source: typeof data.source === "string" ? data.source : "sdk",
75
- consentAt: typeof data.consent_at === "string" ? data.consent_at : (/* @__PURE__ */ new Date()).toISOString(),
76
- ipHash: typeof data.ip_hash === "string" ? data.ip_hash : ""
76
+ consentAt: typeof (data.consent_at ?? data.consentAt) === "string" ? String(data.consent_at ?? data.consentAt) : (/* @__PURE__ */ new Date()).toISOString(),
77
+ ipHash: typeof (data.ip_hash ?? data.ipHash) === "string" ? String(data.ip_hash ?? data.ipHash) : ""
77
78
  });
78
79
  const now = (/* @__PURE__ */ new Date()).toISOString();
79
80
  await this.execute(
@@ -82,7 +83,7 @@ class SubscriptionStore extends SqliteStoreBase {
82
83
  return this.getByKey(envelope.payload.email);
83
84
  }
84
85
  await this.execute(
85
- `INSERT INTO subscriptions (email, name, status, source, consent_at, created_at, updated_at, ip_hash) VALUES (${toSqlValue(data.email)}, ${toSqlValue(data.name ?? null)}, ${toSqlValue(data.status ?? "active")}, ${toSqlValue(data.source ?? "sdk")}, ${toSqlValue(data.consent_at ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.created_at ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.updated_at ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.ip_hash ?? "")})`
86
+ `INSERT INTO subscriptions (email, name, status, source, consent_at, created_at, updated_at, ip_hash) VALUES (${toSqlValue(data.email)}, ${toSqlValue(data.name ?? null)}, ${toSqlValue(data.status ?? "active")}, ${toSqlValue(data.source ?? "sdk")}, ${toSqlValue(data.consent_at ?? data.consentAt ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.created_at ?? data.createdAt ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.updated_at ?? data.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString())}, ${toSqlValue(data.ip_hash ?? data.ipHash ?? "")})`
86
87
  );
87
88
  return this.getByKey(String(data.email));
88
89
  }
@@ -92,6 +93,7 @@ class SubscriptionStore extends SqliteStoreBase {
92
93
  if (!existing) {
93
94
  throw new Error(`No subscription found for "${key}".`);
94
95
  }
96
+ assertExpectedVersion(request.expectedVersion, existing, `subscription "${existing.email}"`);
95
97
  const next = {
96
98
  ...existing,
97
99
  ...request.data,
@@ -103,8 +105,8 @@ class SubscriptionStore extends SqliteStoreBase {
103
105
  name: next.name ?? null,
104
106
  status: String(next.status ?? "active"),
105
107
  source: typeof next.source === "string" ? next.source : "sdk",
106
- consentAt: typeof next.consent_at === "string" ? next.consent_at : null,
107
- ipHash: typeof next.ip_hash === "string" ? next.ip_hash : "",
108
+ consentAt: typeof (next.consent_at ?? next.consentAt) === "string" ? String(next.consent_at ?? next.consentAt) : null,
109
+ ipHash: typeof (next.ip_hash ?? next.ipHash) === "string" ? String(next.ip_hash ?? next.ipHash) : "",
108
110
  meta: { legacyId: existing.id }
109
111
  });
110
112
  await this.execute(
@@ -0,0 +1,13 @@
1
+ import type { SdkTemplateCatalogEntry, SdkTemplateCatalogResponse } from './sdk-types.ts';
2
+ export interface RemoteTemplateCatalogClientOptions {
3
+ endpoint: string;
4
+ fetchImpl?: typeof fetch;
5
+ }
6
+ export declare function parseTemplateCatalogResponse(payload: unknown): SdkTemplateCatalogResponse;
7
+ export declare class RemoteTemplateCatalogClient {
8
+ private readonly endpoint;
9
+ private readonly fetchImpl;
10
+ constructor(options: RemoteTemplateCatalogClientOptions);
11
+ listTemplates(): Promise<SdkTemplateCatalogResponse>;
12
+ getTemplate(id: string): Promise<SdkTemplateCatalogEntry | null>;
13
+ }
@@ -0,0 +1,141 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ function expectRecord(value, label) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5
+ throw new Error(`Invalid template catalog response: expected ${label} to be an object.`);
6
+ }
7
+ return value;
8
+ }
9
+ function expectString(value, label) {
10
+ if (typeof value !== "string" || value.trim().length === 0) {
11
+ throw new Error(`Invalid template catalog response: expected ${label} to be a non-empty string.`);
12
+ }
13
+ return value.trim();
14
+ }
15
+ function optionalString(value) {
16
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
17
+ }
18
+ function optionalStringArray(value, label) {
19
+ if (value === void 0) {
20
+ return void 0;
21
+ }
22
+ if (!Array.isArray(value)) {
23
+ throw new Error(`Invalid template catalog response: expected ${label} to be an array.`);
24
+ }
25
+ return value.map((entry, index) => expectString(entry, `${label}[${index}]`));
26
+ }
27
+ function optionalBoolean(value, label) {
28
+ if (value === void 0) {
29
+ return void 0;
30
+ }
31
+ if (typeof value !== "boolean") {
32
+ throw new Error(`Invalid template catalog response: expected ${label} to be a boolean.`);
33
+ }
34
+ return value;
35
+ }
36
+ function expectNumber(value, label) {
37
+ if (typeof value !== "number" || Number.isNaN(value)) {
38
+ throw new Error(`Invalid template catalog response: expected ${label} to be a number.`);
39
+ }
40
+ return value;
41
+ }
42
+ function normalizeTemplateCatalogEntry(value) {
43
+ const record = expectRecord(value, "template entry");
44
+ const publisher = expectRecord(record.publisher, "publisher");
45
+ const fulfillment = expectRecord(record.fulfillment, "fulfillment");
46
+ const source = expectRecord(fulfillment.source, "fulfillment.source");
47
+ const offer = record.offer === void 0 ? void 0 : expectRecord(record.offer, "offer");
48
+ return {
49
+ id: expectString(record.id ?? record.slug, "id"),
50
+ displayName: expectString(record.displayName ?? record.title, "displayName"),
51
+ description: expectString(record.description, "description"),
52
+ summary: expectString(record.summary, "summary"),
53
+ status: expectString(record.status ?? "draft", "status"),
54
+ featured: optionalBoolean(record.featured, "featured"),
55
+ category: expectString(record.category, "category"),
56
+ audience: optionalStringArray(record.audience, "audience") ?? [],
57
+ tags: optionalStringArray(record.tags, "tags") ?? [],
58
+ publisher: {
59
+ id: expectString(publisher.id, "publisher.id"),
60
+ name: expectString(publisher.name, "publisher.name"),
61
+ url: optionalString(publisher.url)
62
+ },
63
+ publisherVerified: optionalBoolean(record.publisherVerified, "publisherVerified"),
64
+ templateVersion: expectString(record.templateVersion, "templateVersion"),
65
+ templateApiVersion: expectNumber(record.templateApiVersion, "templateApiVersion"),
66
+ minCliVersion: expectString(record.minCliVersion, "minCliVersion"),
67
+ minCoreVersion: optionalString(record.minCoreVersion),
68
+ fulfillment: {
69
+ source: {
70
+ kind: "git",
71
+ repoUrl: expectString(source.repoUrl, "fulfillment.source.repoUrl"),
72
+ directory: expectString(source.directory, "fulfillment.source.directory"),
73
+ ref: expectString(source.ref, "fulfillment.source.ref"),
74
+ integrity: optionalString(source.integrity)
75
+ },
76
+ hooksPolicy: expectString(fulfillment.hooksPolicy ?? "builtin_only", "fulfillment.hooksPolicy"),
77
+ supportsReconcile: typeof fulfillment.supportsReconcile === "boolean" ? fulfillment.supportsReconcile : true
78
+ },
79
+ offer: offer ? {
80
+ priceModel: optionalString(offer.priceModel),
81
+ license: optionalString(offer.license),
82
+ support: optionalString(offer.support)
83
+ } : void 0,
84
+ relatedBooks: optionalStringArray(record.relatedBooks, "relatedBooks") ?? [],
85
+ relatedKnowledge: optionalStringArray(record.relatedKnowledge, "relatedKnowledge") ?? [],
86
+ relatedObjectives: optionalStringArray(record.relatedObjectives, "relatedObjectives") ?? []
87
+ };
88
+ }
89
+ function parseTemplateCatalogResponse(payload) {
90
+ if (Array.isArray(payload)) {
91
+ return {
92
+ items: payload.map((entry) => normalizeTemplateCatalogEntry(entry)),
93
+ meta: {}
94
+ };
95
+ }
96
+ const record = expectRecord(payload, "root");
97
+ const envelopePayload = record.payload;
98
+ const items = Array.isArray(record.items) ? record.items : Array.isArray(envelopePayload) ? envelopePayload : Array.isArray(expectRecord(envelopePayload ?? {}, "payload").items) ? expectRecord(envelopePayload ?? {}, "payload").items : null;
99
+ if (!items) {
100
+ throw new Error("Invalid template catalog response: expected an item array.");
101
+ }
102
+ return {
103
+ items: items.map((entry) => normalizeTemplateCatalogEntry(entry)),
104
+ meta: typeof record.meta === "object" && record.meta !== null ? record.meta : {}
105
+ };
106
+ }
107
+ async function loadTemplateCatalogPayload(endpoint, fetchImpl) {
108
+ if (endpoint.startsWith("file:")) {
109
+ const filePath = endpoint.startsWith("file://") ? new URL(endpoint) : resolve(process.cwd(), endpoint.slice("file:".length));
110
+ const raw = readFileSync(filePath, "utf8");
111
+ return JSON.parse(raw);
112
+ }
113
+ const response = await fetchImpl(endpoint, {
114
+ headers: {
115
+ accept: "application/json"
116
+ }
117
+ });
118
+ if (!response.ok) {
119
+ throw new Error(`Template catalog request failed with ${response.status} ${response.statusText}.`);
120
+ }
121
+ return response.json();
122
+ }
123
+ class RemoteTemplateCatalogClient {
124
+ endpoint;
125
+ fetchImpl;
126
+ constructor(options) {
127
+ this.endpoint = options.endpoint;
128
+ this.fetchImpl = options.fetchImpl ?? fetch;
129
+ }
130
+ async listTemplates() {
131
+ return parseTemplateCatalogResponse(await loadTemplateCatalogPayload(this.endpoint, this.fetchImpl));
132
+ }
133
+ async getTemplate(id) {
134
+ const catalog = await this.listTemplates();
135
+ return catalog.items.find((entry) => entry.id === id) ?? null;
136
+ }
137
+ }
138
+ export {
139
+ RemoteTemplateCatalogClient,
140
+ parseTemplateCatalogResponse
141
+ };
@@ -0,0 +1,7 @@
1
+ services:
2
+ mailpit:
3
+ image: axllent/mailpit:latest
4
+ container_name: treeseed_mailpit
5
+ ports:
6
+ - "1025:1025"
7
+ - "8025:8025"