catence 0.2.0-beta.14 → 0.2.0-beta.16

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.
@@ -3,7 +3,7 @@
3
3
  * Keep these values synchronized with release/manifest.json; release checks
4
4
  * deliberately reject drift before a package can be published.
5
5
  */
6
- export declare const CATENCE_RUNTIME_VERSION = "0.2.0-beta.14";
6
+ export declare const CATENCE_RUNTIME_VERSION = "0.2.0-beta.16";
7
7
  export declare const CATENCE_PROTOCOL_VERSION = 1;
8
8
  export type CatenceRuntimeCapabilities = {
9
9
  mcp: true;
@@ -3,7 +3,7 @@
3
3
  * Keep these values synchronized with release/manifest.json; release checks
4
4
  * deliberately reject drift before a package can be published.
5
5
  */
6
- export const CATENCE_RUNTIME_VERSION = '0.2.0-beta.14';
6
+ export const CATENCE_RUNTIME_VERSION = '0.2.0-beta.16';
7
7
  export const CATENCE_PROTOCOL_VERSION = 1;
8
8
  export function catenceRuntimeHealth() {
9
9
  return {
@@ -528,7 +528,7 @@ export async function reimportNutrition(paths) {
528
528
  }
529
529
  const days = (await database.rows('SELECT count(*) AS count FROM nutrition_days'))[0]?.count ?? 0;
530
530
  const items = (await database.rows('SELECT count(*) AS count FROM nutrition_items'))[0]?.count ?? 0;
531
- return { reimportedEntities: rows.length, nutritionDays: days, nutritionItems: items };
531
+ return { reimportedEntities: rows.length, nutritionDays: Number(days), nutritionItems: Number(items) };
532
532
  }
533
533
  finally {
534
534
  await database.close();
@@ -63,7 +63,8 @@ function swimSourceLabel(payload) {
63
63
  function datePart(value) {
64
64
  if (!value)
65
65
  return null;
66
- const match = value.match(/^\d{4}-\d{2}-\d{2}/);
66
+ const s = typeof value === 'string' ? value : value.toISOString();
67
+ const match = s.match(/^\d{4}-\d{2}-\d{2}/);
67
68
  return match?.[0] ?? null;
68
69
  }
69
70
  function providerActivityId(provider, remoteId) {
@@ -143,6 +144,8 @@ function snake(value) {
143
144
  return value.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '').toLowerCase();
144
145
  }
145
146
  function timestamp(value) {
147
+ if (value instanceof Date)
148
+ return value.toISOString();
146
149
  if (typeof value === 'string' && value.length >= 10)
147
150
  return value;
148
151
  if (typeof value !== 'number' || !Number.isFinite(value))
@@ -414,7 +417,7 @@ function nested(payload, key) {
414
417
  return object(payload[key]);
415
418
  }
416
419
  function metricObservedAt(occurredOn, payload) {
417
- return firstTimestamp(payload, ['timestamp', 'timestampLocal', 'calendarDate', 'date']) ?? (occurredOn ? `${occurredOn}T00:00:00Z` : null);
420
+ return firstTimestamp(payload, ['timestamp', 'timestampLocal', 'calendarDate', 'date']) ?? (occurredOn ? `${datePart(occurredOn)}T00:00:00Z` : null);
418
421
  }
419
422
  async function importGarminActivityPerformance(database, remoteId, payload, rawHash) {
420
423
  const fields = activityFields('garmin', payload);
@@ -5,6 +5,28 @@ import { ReadOnlyRepository } from '../../core/query/repository.js';
5
5
  import { SYNC_STAGES, STALE_RUN_TIMEOUT_MS } from '../../contracts/progress.js';
6
6
  import { migrations } from './migrations.js';
7
7
  import { json } from './sql.js';
8
+ /**
9
+ * DuckDB's node API throws "Cannot create values of type ANY" when a bound
10
+ * parameter resolves to an untyped value. JS `Date` objects surface as `ANY`
11
+ * (and are not auto-coerced the way strings are), so any date/timestamp
12
+ * parameter read back from the database and re-bound crashes the statement.
13
+ * Serializing dates to ISO strings keeps every parameter concretely typed.
14
+ */
15
+ function serializeValue(value) {
16
+ if (value instanceof Date)
17
+ return value.toISOString();
18
+ return value;
19
+ }
20
+ function serializeBindings(bindings) {
21
+ if (!bindings)
22
+ return bindings;
23
+ if (Array.isArray(bindings))
24
+ return bindings.map(serializeValue);
25
+ const out = {};
26
+ for (const [key, value] of Object.entries(bindings))
27
+ out[key] = serializeValue(value);
28
+ return out;
29
+ }
8
30
  export class CatenceDatabase {
9
31
  instance;
10
32
  connection;
@@ -62,10 +84,10 @@ export class CatenceDatabase {
62
84
  }
63
85
  }
64
86
  async run(sql, values) {
65
- await this.connection.run(sql, values);
87
+ await this.connection.run(sql, serializeBindings(values));
66
88
  }
67
89
  async rows(sql, values) {
68
- const result = await this.connection.runAndReadAll(sql, values);
90
+ const result = await this.connection.runAndReadAll(sql, serializeBindings(values));
69
91
  return result.getRowObjectsJS();
70
92
  }
71
93
  async tableNames(query) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catence",
3
- "version": "0.2.0-beta.14",
3
+ "version": "0.2.0-beta.16",
4
4
  "description": "Local MCP access to normalized Garmin, Intervals.icu, and targeted Strava fitness data.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "repository": {