sizmo 0.4.1 → 0.5.0

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.
@@ -0,0 +1,137 @@
1
+ // lib/resolver.mjs — id→name maps from the CRM model. Never fabricates a name.
2
+ // makeResolver(model, {now}) → resolver with .resolve(kind, id) and .label(kind, id).
3
+ //
4
+ // resolve(kind, id) → { name: string|null, status: 'hit'|'miss'|'stale', pipelineId?, pipelineName? }
5
+ // hit — found in model, entity is fresh
6
+ // stale — found in model, but entity is past its TTL
7
+ // miss — not found (never fabricated; name=null)
8
+ //
9
+ // label(kind, id) → string suitable for human display:
10
+ // hit/stale → name (stale callers should add age note separately)
11
+ // miss → '<unknown:<id> — run sizmo sync>'
12
+ //
13
+ // Kind mapping:
14
+ // 'pipeline' → pipelines entity (id→name)
15
+ // 'stage' → stages nested in pipelines (id→{name,pipelineId,pipelineName,position})
16
+ // 'calendar' → calendars entity
17
+ // 'tag' → tags entity
18
+ // 'customField' → customFields entity
19
+ // 'user' → users entity
20
+ import { isStale, ENTITY_SPECS } from './model.mjs';
21
+
22
+ const TTL_MAP = Object.fromEntries(ENTITY_SPECS.map(s => [s.name, s.ttlMs]));
23
+
24
+ /**
25
+ * makeResolver — build maps from a model blob; return resolver.
26
+ * @param {object|null} model the loaded model blob (or null if missing)
27
+ * @param {object} opts
28
+ * @param {Function} opts.now injectable clock () => ms
29
+ */
30
+ export function makeResolver(model, { now = Date.now } = {}) {
31
+ // Materialized maps — built once on construction
32
+ const maps = buildMaps(model);
33
+
34
+ function resolveKind(kind, id) {
35
+ const { entityName, map } = kindMeta(kind, maps);
36
+ if (!map) return { name: null, status: 'miss' };
37
+
38
+ const entry = map.get(id);
39
+ if (!entry) return { name: null, status: 'miss' };
40
+
41
+ // Check staleness of the source entity
42
+ const entity = model?.entities?.[entityName];
43
+ const ttl = TTL_MAP[entityName];
44
+ const stale = entity && ttl ? isStale(entity, now(), ttl) : false;
45
+
46
+ const result = { name: entry.name, status: stale ? 'stale' : 'hit' };
47
+ if (entry.pipelineId !== undefined) result.pipelineId = entry.pipelineId;
48
+ if (entry.pipelineName !== undefined) result.pipelineName = entry.pipelineName;
49
+ if (entry.position !== undefined) result.position = entry.position;
50
+ return result;
51
+ }
52
+
53
+ return {
54
+ /**
55
+ * resolve(kind, id) → { name, status, ...extras }
56
+ * Never throws; miss returns { name: null, status: 'miss' }.
57
+ */
58
+ resolve(kind, id) {
59
+ try { return resolveKind(kind, id); }
60
+ catch { return { name: null, status: 'miss' }; }
61
+ },
62
+
63
+ /**
64
+ * label(kind, id) → human string.
65
+ * hit/stale → name; miss → '<unknown:<id> — run sizmo sync>'
66
+ */
67
+ label(kind, id) {
68
+ const r = resolveKind(kind, id);
69
+ if (r.name !== null) return r.name;
70
+ return `<unknown:${id} — run sizmo sync>`;
71
+ },
72
+ };
73
+ }
74
+
75
+ // ── internal ──────────────────────────────────────────────────────────────────
76
+
77
+ function buildMaps(model) {
78
+ const empty = () => new Map();
79
+ if (!model || !model.entities) {
80
+ return { pipeline: empty(), stage: empty(), calendar: empty(), tag: empty(), customField: empty(), user: empty() };
81
+ }
82
+ const e = model.entities;
83
+
84
+ // pipelines + stages
85
+ const pipelineMap = new Map();
86
+ const stageMap = new Map();
87
+ if (e.pipelines && !e.pipelines.blocked && Array.isArray(e.pipelines.items)) {
88
+ for (const pl of e.pipelines.items) {
89
+ pipelineMap.set(pl.id, { name: pl.name });
90
+ for (const s of (pl.stages || [])) {
91
+ stageMap.set(s.id, { name: s.name, pipelineId: pl.id, pipelineName: pl.name, position: s.position ?? 0 });
92
+ }
93
+ }
94
+ }
95
+
96
+ // calendars
97
+ const calMap = new Map();
98
+ if (e.calendars && !e.calendars.blocked && Array.isArray(e.calendars.items)) {
99
+ for (const c of e.calendars.items) calMap.set(c.id, { name: c.name });
100
+ }
101
+
102
+ // tags
103
+ const tagMap = new Map();
104
+ if (e.tags && !e.tags.blocked && Array.isArray(e.tags.items)) {
105
+ for (const t of e.tags.items) tagMap.set(t.id, { name: t.name });
106
+ }
107
+
108
+ // customFields
109
+ const fieldMap = new Map();
110
+ if (e.customFields && !e.customFields.blocked && Array.isArray(e.customFields.items)) {
111
+ for (const f of e.customFields.items) fieldMap.set(f.id, { name: f.name, key: f.fieldKey });
112
+ }
113
+
114
+ // users
115
+ const userMap = new Map();
116
+ if (e.users && !e.users.blocked && Array.isArray(e.users.items)) {
117
+ for (const u of e.users.items) {
118
+ const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || u.name || u.email || u.id;
119
+ userMap.set(u.id, { name });
120
+ }
121
+ }
122
+
123
+ return { pipeline: pipelineMap, stage: stageMap, calendar: calMap, tag: tagMap, customField: fieldMap, user: userMap };
124
+ }
125
+
126
+ // Which entity-name and map corresponds to each public kind string
127
+ function kindMeta(kind, maps) {
128
+ switch (kind) {
129
+ case 'pipeline': return { entityName: 'pipelines', map: maps.pipeline };
130
+ case 'stage': return { entityName: 'pipelines', map: maps.stage };
131
+ case 'calendar': return { entityName: 'calendars', map: maps.calendar };
132
+ case 'tag': return { entityName: 'tags', map: maps.tag };
133
+ case 'customField': return { entityName: 'customFields', map: maps.customField };
134
+ case 'user': return { entityName: 'users', map: maps.user };
135
+ default: return { entityName: null, map: null };
136
+ }
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sizmo",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Unofficial read-only GoHighLevel CLI — read your CRM (leads, bookings, pipeline, A/R, payments) from the terminal. Not affiliated with HighLevel.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,8 +10,8 @@
10
10
  "node": ">=20"
11
11
  },
12
12
  "scripts": {
13
- "test": "node --test",
14
- "prepublishOnly": "node --test"
13
+ "test": "node --test --test-concurrency=1",
14
+ "prepublishOnly": "node --test --test-concurrency=1"
15
15
  },
16
16
  "author": "Sizmo / CJ Salamida",
17
17
  "license": "MIT",