trellis 3.1.1 → 3.1.6

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 3.1.2
4
+
5
+ - Fixed `trellis/cms` collection reads for inferred collections whose normalized key differs from the stored entity type casing, such as `blogpost` reading `BlogPost` entities.
6
+ - Added graph-link awareness to CMS entries so reference links such as `post --author--> author` appear in `fields` and can be expanded.
7
+ - Added `status` fact fallback when `cms_status` is absent, preserving content created by agents or lower-level store tools.
8
+ - Added shared polling for CMS subscriptions so duplicate subscribers to the same collection or entry reuse one poll stream.
9
+ - Added `onError` and custom `equals` subscription options.
10
+ - Fixed CMS entity pagination to respect the opencode store route's 1000-entity page limit.
11
+ - Added CMS client/scaffold tests and included them in the default test script.
12
+ - Added `directory` support to CMS consumer scaffolds for multi-instance opencode routing.
13
+
14
+ ## 3.1.1
15
+
16
+ - Published the first `trellis/cms` SDK package update after adding scaffold helpers.
17
+
18
+ ## 3.1.0
19
+
20
+ - Added the `trellis/cms` subpath with `createCmsClient`, collection reads, entry reads, polling subscriptions, reference expansion, collection discovery, and consumer scaffold helpers for vanilla, React, Solid, and Vue.
package/README.md CHANGED
@@ -50,6 +50,43 @@ See the [CLI guide](https://trellis.computer/docs/cli) for complete documentatio
50
50
 
51
51
  ---
52
52
 
53
+ ## CMS Client SDK
54
+
55
+ Use `trellis/cms` from browser apps to read content from a Trellis-compatible HTTP server such as opencode at `http://localhost:4096`.
56
+
57
+ ```typescript
58
+ import { createCmsClient } from 'trellis/cms';
59
+
60
+ const cms = createCmsClient({
61
+ url: 'http://localhost:4096',
62
+ directory: '/path/to/project',
63
+ });
64
+
65
+ const posts = await cms.collection('blogpost').list({
66
+ status: 'all',
67
+ expand: ['author'],
68
+ });
69
+
70
+ const off = cms.collection('blogpost').subscribe(
71
+ (entries) => {
72
+ console.log(entries);
73
+ },
74
+ {
75
+ status: 'published',
76
+ onError: console.error,
77
+ },
78
+ );
79
+
80
+ off();
81
+ ```
82
+
83
+ - **Collections**: `cms.collections()` returns explicit `TypeSchema` collections and inferred collections from existing entity types.
84
+ - **Normalized keys**: `cms.collection('blogpost')` matches stored entity types like `BlogPost`.
85
+ - **References**: `expand` resolves reference ids from facts and graph links into nested entries.
86
+ - **Scaffolds**: `scaffoldConsumer({ collection, framework, directory })` generates starter consumer code for vanilla, React, Solid, or Vue.
87
+
88
+ ---
89
+
53
90
  ## What is Trellis?
54
91
 
55
92
  Trellis is an **event-sourced causal graph engine** that unifies version control, knowledge management, and semantic analysis. Every action is an immutable operation in a causal stream:
@@ -23,7 +23,8 @@
23
23
  *
24
24
  * @module trellis/cms
25
25
  */
26
- import type { CmsClientOptions, Collection, Entry, EntrySubscriber, GetOptions, ListOptions, ListSubscriber, Unsubscribe } from './types.js';
26
+ import type { CmsClientOptions, Collection, Entry, EntrySubscribeOptions, EntrySubscriber, GetOptions, ListOptions, ListSubscribeOptions, ListSubscriber, Unsubscribe } from './types.js';
27
+ import { type RawEntity, type RawFact } from './internal.js';
27
28
  export declare class CmsClient {
28
29
  private readonly url;
29
30
  private readonly basePath;
@@ -31,16 +32,30 @@ export declare class CmsClient {
31
32
  readonly pollIntervalMs: number;
32
33
  private readonly fetchFn;
33
34
  private readonly apiKey?;
35
+ private readonly subscriptions;
34
36
  constructor(opts: CmsClientOptions);
35
37
  collection<T extends Record<string, unknown> = Record<string, unknown>>(key: string): CollectionRef<T>;
36
38
  entry<T extends Record<string, unknown> = Record<string, unknown>>(id: string): EntryRef<T>;
37
39
  /** List all CMS collections (explicit + inferred). */
38
40
  collections(): Promise<Collection[]>;
39
41
  close(): void;
42
+ /**
43
+ * Shared polling subscription. Multiple subscribers to the same key share a
44
+ * single timer and one HTTP request per poll cycle. New subscribers receive
45
+ * the most recently fetched value immediately if one is cached.
46
+ *
47
+ * @internal
48
+ */
49
+ _share<T>(key: string, fetcher: () => Promise<T>, callback: (value: T) => void, extras?: {
50
+ equals?: (prev: unknown, next: unknown) => boolean;
51
+ onError?: (err: unknown) => void;
52
+ }): Unsubscribe;
40
53
  /** @internal */
41
54
  _get<T>(path: string): Promise<T | undefined>;
42
55
  /** @internal */
43
- _entryById(id: string): Promise<Entry | null>;
56
+ _entities(): Promise<RawEntity[]>;
57
+ /** @internal */
58
+ _entryById(id: string, schemaFacts?: RawFact[]): Promise<Entry | null>;
44
59
  }
45
60
  export declare class CollectionRef<T extends Record<string, unknown> = Record<string, unknown>> {
46
61
  private readonly client;
@@ -51,15 +66,18 @@ export declare class CollectionRef<T extends Record<string, unknown> = Record<st
51
66
  /**
52
67
  * Subscribe to changes. Currently implemented as polling; a future SSE-backed
53
68
  * upgrade will replace the transport without changing this API.
69
+ *
70
+ * Multiple subscribers to the same collection + opts share one polling timer
71
+ * and one HTTP request per cycle.
54
72
  */
55
- subscribe(callback: ListSubscriber<T>, opts?: ListOptions): Unsubscribe;
73
+ subscribe(callback: ListSubscriber<T>, opts?: ListSubscribeOptions): Unsubscribe;
56
74
  }
57
75
  export declare class EntryRef<T extends Record<string, unknown> = Record<string, unknown>> {
58
76
  private readonly client;
59
77
  readonly id: string;
60
78
  constructor(client: CmsClient, id: string);
61
79
  get(opts?: GetOptions): Promise<Entry<T> | null>;
62
- subscribe(callback: EntrySubscriber<T>, opts?: GetOptions): Unsubscribe;
80
+ subscribe(callback: EntrySubscriber<T>, opts?: EntrySubscribeOptions): Unsubscribe;
63
81
  }
64
82
  export declare function createCmsClient(opts: CmsClientOptions): CmsClient;
65
83
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cms/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,eAAe,EACf,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAiBpB,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;gBAErB,IAAI,EAAE,gBAAgB;IASlC,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,GAAG,EAAE,MAAM,GACV,aAAa,CAAC,CAAC,CAAC;IAInB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAI3F,sDAAsD;IAChD,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAiD1C,KAAK,IAAI,IAAI;IAIb,gBAAgB;IACV,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAYnD,gBAAgB;IACV,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;CASpD;AAED,qBAAa,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM;gBADH,MAAM,EAAE,SAAS,EACzB,GAAG,EAAE,MAAM;IAGhB,IAAI,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IA+BjD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAYtE;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,WAAgB,GAAG,WAAW;CAyB5E;AAED,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAE7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM;gBADF,MAAM,EAAE,SAAS,EACzB,EAAE,EAAE,MAAM;IAGf,GAAG,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAY1D,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,UAAe,GAAG,WAAW;CAyB5E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAEjE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cms/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Entry, FieldDefinition } from './types.js';
2
+ export declare function evaluateFormula(expr: string, fields: Record<string, unknown>): number | undefined;
3
+ export declare function parseFields(raw: unknown): FieldDefinition[];
4
+ export declare function schemaFields(facts: {
5
+ e: string;
6
+ a: string;
7
+ v: unknown;
8
+ }[], names: string[]): FieldDefinition[];
9
+ export declare function applyFormulas<T extends Record<string, unknown>>(entry: Entry<T>, defs: FieldDefinition[]): Entry<T>;
10
+ //# sourceMappingURL=formula.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formula.d.ts","sourceRoot":"","sources":["../../src/cms/formula.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA2FzD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,GAAG,SAAS,CASpB;AAcD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,CAa3D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,EAC7C,KAAK,EAAE,MAAM,EAAE,GACd,eAAe,EAAE,CASnB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,EAAE,eAAe,EAAE,GACtB,KAAK,CAAC,CAAC,CAAC,CAcV"}
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * @module trellis/cms
5
5
  */
6
- export { CmsClient, CollectionRef, EntryRef, createCmsClient } from './client.js';
7
- export type { CmsClientOptions, Collection, Entry, EntryStatus, EntrySubscriber, Framework, GetOptions, ListOptions, ListSubscriber, Unsubscribe, } from './types.js';
6
+ export { CmsClient, CollectionRef, EntryRef, createCmsClient, } from './client.js';
7
+ export type { CmsClientOptions, Collection, Entry, EntryStatus, EntrySubscribeOptions, EntrySubscriber, FieldDefinition, FieldKind, Framework, GetOptions, ListOptions, ListSubscribeOptions, ListSubscriber, SubscribeExtras, Unsubscribe, } from './types.js';
8
+ export { applyFormulas, evaluateFormula, parseFields } from './formula.js';
8
9
  export { scaffoldConsumer, scaffoldFilename } from './scaffold.js';
9
10
  export type { ScaffoldOptions } from './scaffold.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cms/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElF,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,WAAW,EACX,eAAe,EACf,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cms/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,SAAS,EACT,aAAa,EACb,QAAQ,EACR,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,SAAS,EACT,SAAS,EACT,UAAU,EACV,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
package/dist/cms/index.js CHANGED
@@ -29,18 +29,34 @@ function isSystemType(type) {
29
29
  function typeKey(type) {
30
30
  return type.trim().toLowerCase();
31
31
  }
32
- function entryFromFacts(entity, facts) {
32
+ function entryFromFacts(entity, facts, links) {
33
33
  const fields = {};
34
34
  let status = "draft";
35
+ let cmsStatusSeen = false;
35
36
  for (const f of facts) {
36
37
  if (f.a === "type")
37
38
  continue;
38
39
  if (f.a === "cms_status") {
39
40
  status = f.v === "published" ? "published" : "draft";
41
+ cmsStatusSeen = true;
40
42
  continue;
41
43
  }
42
44
  fields[f.a] = f.v;
43
45
  }
46
+ if (!cmsStatusSeen) {
47
+ if (fields.status === "published")
48
+ status = "published";
49
+ else if (fields.status === "draft")
50
+ status = "draft";
51
+ }
52
+ if (links) {
53
+ for (const link of links) {
54
+ if (link.e1 !== entity.id)
55
+ continue;
56
+ if (!(link.a in fields))
57
+ fields[link.a] = link.e2;
58
+ }
59
+ }
44
60
  return { id: entity.id, type: entity.type, status, fields };
45
61
  }
46
62
  function groupFactsByEntity(facts) {
@@ -54,6 +70,17 @@ function groupFactsByEntity(facts) {
54
70
  }
55
71
  return map;
56
72
  }
73
+ function groupLinksBySource(links) {
74
+ const map = new Map;
75
+ for (const l of links) {
76
+ const list = map.get(l.e1);
77
+ if (list)
78
+ list.push(l);
79
+ else
80
+ map.set(l.e1, [l]);
81
+ }
82
+ return map;
83
+ }
57
84
  async function expandReferences(entries, expandKeys, fetchEntity) {
58
85
  const ids = new Set;
59
86
  for (const entry of entries) {
@@ -88,11 +115,153 @@ function fingerprint(value) {
88
115
  return JSON.stringify(value);
89
116
  }
90
117
 
118
+ // src/cms/formula.ts
119
+ var OPS = new Set(["+", "-", "*", "/", "(", ")"]);
120
+ function num(value) {
121
+ if (typeof value === "number")
122
+ return Number.isFinite(value) ? value : undefined;
123
+ if (typeof value !== "string" || value.trim() === "")
124
+ return;
125
+ const parsed = Number(value);
126
+ return Number.isFinite(parsed) ? parsed : undefined;
127
+ }
128
+ function tokenize(expr) {
129
+ const tokens = [];
130
+ let i = 0;
131
+ while (i < expr.length) {
132
+ const ch = expr[i];
133
+ if (/\s/.test(ch)) {
134
+ i++;
135
+ continue;
136
+ }
137
+ if (OPS.has(ch)) {
138
+ tokens.push(ch);
139
+ i++;
140
+ continue;
141
+ }
142
+ if (/\d|\./.test(ch)) {
143
+ let end = i + 1;
144
+ while (end < expr.length && /\d|\./.test(expr[end]))
145
+ end++;
146
+ const value = Number(expr.slice(i, end));
147
+ if (!Number.isFinite(value))
148
+ return;
149
+ tokens.push(value);
150
+ i = end;
151
+ continue;
152
+ }
153
+ return;
154
+ }
155
+ return tokens;
156
+ }
157
+ function parse(tokens) {
158
+ let i = 0;
159
+ const peek = () => tokens[i];
160
+ const take = () => tokens[i++];
161
+ const primary = () => {
162
+ const token = take();
163
+ if (typeof token === "number")
164
+ return token;
165
+ if (token === "+")
166
+ return primary();
167
+ if (token === "-") {
168
+ const value2 = primary();
169
+ return value2 === undefined ? undefined : -value2;
170
+ }
171
+ if (token === "(") {
172
+ const value2 = add();
173
+ if (take() !== ")")
174
+ return;
175
+ return value2;
176
+ }
177
+ return;
178
+ };
179
+ const mul = () => {
180
+ let left = primary();
181
+ while (peek() === "*" || peek() === "/") {
182
+ const op = take();
183
+ const right = primary();
184
+ if (left === undefined || right === undefined)
185
+ return;
186
+ left = op === "*" ? left * right : left / right;
187
+ }
188
+ return left;
189
+ };
190
+ const add = () => {
191
+ let left = mul();
192
+ while (peek() === "+" || peek() === "-") {
193
+ const op = take();
194
+ const right = mul();
195
+ if (left === undefined || right === undefined)
196
+ return;
197
+ left = op === "+" ? left + right : left - right;
198
+ }
199
+ return left;
200
+ };
201
+ const value = add();
202
+ if (i !== tokens.length || value === undefined || !Number.isFinite(value))
203
+ return;
204
+ return value;
205
+ }
206
+ function evaluateFormula(expr, fields) {
207
+ const interpolated = expr.replace(/\{([^{}]+)\}/g, (match, key) => {
208
+ const value = num(fields[key.trim()]);
209
+ return value === undefined ? match : String(value);
210
+ });
211
+ if (interpolated.includes("{") || interpolated.includes("}"))
212
+ return;
213
+ const tokens = tokenize(interpolated);
214
+ return tokens ? parse(tokens) : undefined;
215
+ }
216
+ function keys(value) {
217
+ const raw = value.replace(/^schema:/, "").trim();
218
+ const camel = raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
219
+ const lower = raw.toLowerCase().replace(/\s+/g, "_");
220
+ const snake = camel.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
221
+ const compact = lower.replace(/[^a-z0-9]+/g, "");
222
+ return [...new Set([lower, snake, compact].filter(Boolean))];
223
+ }
224
+ function parseFields(raw) {
225
+ if (typeof raw !== "string")
226
+ return [];
227
+ try {
228
+ const parsed = JSON.parse(raw);
229
+ if (!Array.isArray(parsed))
230
+ return [];
231
+ return parsed.filter((item) => {
232
+ if (!item || typeof item !== "object")
233
+ return false;
234
+ const def = item;
235
+ return typeof def.key === "string" && typeof def.type === "string";
236
+ });
237
+ } catch {
238
+ return [];
239
+ }
240
+ }
241
+ function schemaFields(facts, names) {
242
+ const wanted = new Set(names.flatMap(keys));
243
+ const fact = facts.find((item) => item.e.startsWith("schema:") && item.a === "props" && keys(item.e).some((key) => wanted.has(key)));
244
+ return parseFields(fact?.v);
245
+ }
246
+ function applyFormulas(entry, defs) {
247
+ const formulas = defs.filter((def) => def.type === "formula" && typeof def.formula === "string" && def.formula.trim());
248
+ if (formulas.length === 0)
249
+ return entry;
250
+ const fields = { ...entry.fields };
251
+ for (let pass = 0;pass < formulas.length; pass++) {
252
+ for (const def of formulas)
253
+ fields[def.key] = evaluateFormula(def.formula, fields);
254
+ }
255
+ return { ...entry, fields };
256
+ }
257
+
91
258
  // src/cms/client.ts
92
259
  var DEFAULT_BASE_PATH = "/trellis/store";
93
260
  var DEFAULT_POLL_MS = 2000;
94
261
  var MIN_POLL_MS = 500;
95
262
  var MAX_FACTS_PER_FETCH = 5000;
263
+ var MAX_ENTITIES_PER_FETCH = 1000;
264
+ var defaultEquals = (prev, next) => fingerprint(prev) === fingerprint(next);
96
265
 
97
266
  class CmsClient {
98
267
  url;
@@ -101,6 +270,7 @@ class CmsClient {
101
270
  pollIntervalMs;
102
271
  fetchFn;
103
272
  apiKey;
273
+ subscriptions = new Map;
104
274
  constructor(opts) {
105
275
  this.url = opts.url.replace(/\/+$/, "");
106
276
  this.basePath = (opts.basePath ?? DEFAULT_BASE_PATH).replace(/\/+$/, "");
@@ -117,7 +287,7 @@ class CmsClient {
117
287
  }
118
288
  async collections() {
119
289
  const [entities, facts] = await Promise.all([
120
- this._get("/entities?limit=10000"),
290
+ this._entities(),
121
291
  this._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`)
122
292
  ]);
123
293
  const factsByEntity = groupFactsByEntity(facts ?? []);
@@ -160,7 +330,64 @@ class CmsClient {
160
330
  }
161
331
  return [...out.values()].sort((a, b) => a.label.localeCompare(b.label));
162
332
  }
163
- close() {}
333
+ close() {
334
+ for (const sub of this.subscriptions.values()) {
335
+ clearInterval(sub.interval);
336
+ sub.subscribers.clear();
337
+ }
338
+ this.subscriptions.clear();
339
+ }
340
+ _share(key, fetcher, callback, extras = {}) {
341
+ let sub = this.subscriptions.get(key);
342
+ if (!sub) {
343
+ const fresh = {
344
+ subscribers: new Set,
345
+ interval: undefined,
346
+ hasLast: false,
347
+ fetcher
348
+ };
349
+ const tick = async () => {
350
+ try {
351
+ const next = await fresh.fetcher();
352
+ fresh.last = next;
353
+ fresh.hasLast = true;
354
+ for (const item2 of fresh.subscribers) {
355
+ if (!item2.hasLast || !item2.equals(item2.last, next)) {
356
+ item2.last = next;
357
+ item2.hasLast = true;
358
+ item2.callback(next);
359
+ }
360
+ }
361
+ } catch (err) {
362
+ for (const item2 of fresh.subscribers)
363
+ item2.onError?.(err);
364
+ }
365
+ };
366
+ fresh.interval = setInterval(tick, this.pollIntervalMs);
367
+ this.subscriptions.set(key, fresh);
368
+ sub = fresh;
369
+ tick();
370
+ }
371
+ const item = {
372
+ callback,
373
+ equals: extras.equals ?? defaultEquals,
374
+ onError: extras.onError,
375
+ hasLast: false
376
+ };
377
+ sub.subscribers.add(item);
378
+ if (sub.hasLast) {
379
+ item.last = sub.last;
380
+ item.hasLast = true;
381
+ callback(sub.last);
382
+ }
383
+ return () => {
384
+ sub.subscribers.delete(item);
385
+ if (sub.subscribers.size === 0) {
386
+ clearInterval(sub.interval);
387
+ this.subscriptions.delete(key);
388
+ }
389
+ };
390
+ }
164
391
  async _get(path) {
165
392
  const u = new URL(`${this.basePath}${path}`, this.url);
166
393
  if (this.directory && !u.searchParams.has("directory")) {
@@ -169,17 +396,31 @@ class CmsClient {
169
396
  const res = await this.fetchFn(u.toString(), {
170
397
  headers: this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
171
398
  });
172
- if (!res.ok)
399
+ if (res.status === 404)
173
400
  return;
401
+ if (!res.ok)
402
+ throw new Error(`Trellis CMS request failed (${res.status}) ${u.pathname}`);
174
403
  return await res.json();
175
404
  }
176
- async _entryById(id) {
405
+ async _entities() {
406
+ const out = [];
407
+ let offset = 0;
408
+ while (true) {
409
+ const page = await this._get(`/entities?limit=${MAX_ENTITIES_PER_FETCH}&offset=${offset}`) ?? [];
410
+ out.push(...page);
411
+ if (page.length < MAX_ENTITIES_PER_FETCH)
412
+ return out;
413
+ offset += MAX_ENTITIES_PER_FETCH;
414
+ }
415
+ }
416
+ async _entryById(id, schemaFacts) {
177
417
  const detail = await this._get(`/entity/${encodeURIComponent(id)}`);
178
418
  if (!detail)
179
419
  return null;
180
420
  const typeFact = detail.facts.find((f) => f.a === "type");
181
421
  const type = typeof typeFact?.v === "string" ? typeFact.v : "unknown";
182
- return entryFromFacts({ id: detail.id, type }, detail.facts);
422
+ const facts = schemaFacts ?? await this._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`) ?? [];
423
+ return applyFormulas(entryFromFacts({ id: detail.id, type }, detail.facts, detail.links), schemaFields(facts, [type]));
183
424
  }
184
425
  }
185
426
 
@@ -193,19 +434,31 @@ class CollectionRef {
193
434
  async list(opts = {}) {
194
435
  const status = opts.status ?? "published";
195
436
  const limit = opts.limit ?? 100;
196
- const [entities, facts] = await Promise.all([
197
- this.client._get(`/entities?type=${encodeURIComponent(this.key)}&limit=${limit}`),
198
- this.client._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`)
437
+ const wantsExpand = opts.expand && opts.expand.length > 0;
438
+ const [allEntities, facts, links] = await Promise.all([
439
+ this.client._entities(),
440
+ this.client._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`),
441
+ this.client._get(`/links`)
199
442
  ]);
200
- if (!entities || entities.length === 0)
443
+ if (!allEntities)
444
+ return [];
445
+ const wantedKey = typeKey(this.key);
446
+ const matching = allEntities.filter((e) => typeKey(e.type) === wantedKey);
447
+ if (matching.length === 0)
201
448
  return [];
202
449
  const factsByEntity = groupFactsByEntity(facts ?? []);
203
- let entries = entities.map((e) => entryFromFacts(e, factsByEntity.get(e.id) ?? []));
450
+ const linksBySource = groupLinksBySource(links ?? []);
451
+ const defs = schemaFields(facts ?? [], [
452
+ this.key,
453
+ ...matching.map((e) => e.type)
454
+ ]);
455
+ let entries = matching.map((e) => applyFormulas(entryFromFacts(e, factsByEntity.get(e.id) ?? [], linksBySource.get(e.id) ?? []), defs));
204
456
  if (status !== "all") {
205
457
  entries = entries.filter((e) => e.status === status);
206
458
  }
207
- if (opts.expand && opts.expand.length > 0) {
208
- entries = await expandReferences(entries, opts.expand, (id) => this.client._entryById(id));
459
+ entries = entries.slice(0, limit);
460
+ if (wantsExpand) {
461
+ entries = await expandReferences(entries, opts.expand, (id) => this.client._entryById(id, facts ?? []));
209
462
  }
210
463
  return entries;
211
464
  }
@@ -220,26 +473,9 @@ class CollectionRef {
220
473
  return entry;
221
474
  }
222
475
  subscribe(callback, opts = {}) {
223
- let stopped = false;
224
- let last = "";
225
- const tick = async () => {
226
- if (stopped)
227
- return;
228
- try {
229
- const entries = await this.list(opts);
230
- const sig = fingerprint(entries);
231
- if (sig !== last) {
232
- last = sig;
233
- callback(entries);
234
- }
235
- } catch {}
236
- };
237
- tick();
238
- const interval = setInterval(tick, this.client.pollIntervalMs);
239
- return () => {
240
- stopped = true;
241
- clearInterval(interval);
242
- };
476
+ const { onError, equals, ...listOpts } = opts;
477
+ const key = `coll:${typeKey(this.key)}:${JSON.stringify(listOpts)}`;
478
+ return this.client._share(key, () => this.list(listOpts), callback, { onError, equals });
243
479
  }
244
480
  }
245
481
 
@@ -261,26 +497,9 @@ class EntryRef {
261
497
  return entry;
262
498
  }
263
499
  subscribe(callback, opts = {}) {
264
- let stopped = false;
265
- let last = "";
266
- const tick = async () => {
267
- if (stopped)
268
- return;
269
- try {
270
- const entry = await this.get(opts);
271
- const sig = fingerprint(entry);
272
- if (sig !== last) {
273
- last = sig;
274
- callback(entry);
275
- }
276
- } catch {}
277
- };
278
- tick();
279
- const interval = setInterval(tick, this.client.pollIntervalMs);
280
- return () => {
281
- stopped = true;
282
- clearInterval(interval);
283
- };
500
+ const { onError, equals, ...getOpts } = opts;
501
+ const key = `entry:${this.id}:${JSON.stringify(getOpts)}`;
502
+ return this.client._share(key, () => this.get(getOpts), callback, { onError, equals });
284
503
  }
285
504
  }
286
505
  function createCmsClient(opts) {
@@ -293,12 +512,17 @@ function expandLiteral(expand) {
293
512
  return "";
294
513
  return ` expand: ${JSON.stringify(expand)},`;
295
514
  }
515
+ function clientLiteral(url, directory) {
516
+ if (!directory)
517
+ return `createCmsClient({ url: "${url}" })`;
518
+ return `createCmsClient({ url: "${url}", directory: ${JSON.stringify(directory)} })`;
519
+ }
296
520
  function vanilla(opts) {
297
521
  const url = opts.url ?? DEFAULT_URL;
298
522
  const exp = expandLiteral(opts.expand);
299
523
  return `import { createCmsClient } from "trellis/cms";
300
524
 
301
- const cms = createCmsClient({ url: "${url}" });
525
+ const cms = ${clientLiteral(url, opts.directory)};
302
526
 
303
527
  const collection = cms.collection("${opts.collection}");
304
528
 
@@ -322,7 +546,7 @@ function react(opts) {
322
546
  return `import { useEffect, useState } from "react";
323
547
  import { createCmsClient, type Entry } from "trellis/cms";
324
548
 
325
- const cms = createCmsClient({ url: "${url}" });
549
+ const cms = ${clientLiteral(url, opts.directory)};
326
550
 
327
551
  export function use${pascal(opts.collection)}() {
328
552
  const [entries, setEntries] = useState<Entry[]>([]);
@@ -344,7 +568,7 @@ function solid(opts) {
344
568
  return `import { createSignal, onCleanup } from "solid-js";
345
569
  import { createCmsClient, type Entry } from "trellis/cms";
346
570
 
347
- const cms = createCmsClient({ url: "${url}" });
571
+ const cms = ${clientLiteral(url, opts.directory)};
348
572
 
349
573
  export function create${pascal(opts.collection)}() {
350
574
  const [entries, setEntries] = createSignal<Entry[]>([]);
@@ -364,7 +588,7 @@ function vue(opts) {
364
588
  return `import { ref, onUnmounted } from "vue";
365
589
  import { createCmsClient, type Entry } from "trellis/cms";
366
590
 
367
- const cms = createCmsClient({ url: "${url}" });
591
+ const cms = ${clientLiteral(url, opts.directory)};
368
592
 
369
593
  export function use${pascal(opts.collection)}() {
370
594
  const entries = ref<Entry[]>([]);
@@ -406,7 +630,10 @@ function scaffoldFilename(opts) {
406
630
  export {
407
631
  scaffoldFilename,
408
632
  scaffoldConsumer,
633
+ parseFields,
634
+ evaluateFormula,
409
635
  createCmsClient,
636
+ applyFormulas,
410
637
  EntryRef,
411
638
  CollectionRef,
412
639
  CmsClient
@@ -11,10 +11,16 @@ export type RawEntity = {
11
11
  id: string;
12
12
  type: string;
13
13
  };
14
+ export type RawLink = {
15
+ e1: string;
16
+ a: string;
17
+ e2: string;
18
+ };
14
19
  export declare function isSystemType(type: string): boolean;
15
20
  export declare function typeKey(type: string): string;
16
- export declare function entryFromFacts(entity: RawEntity, facts: RawFact[]): Entry;
21
+ export declare function entryFromFacts(entity: RawEntity, facts: RawFact[], links?: RawLink[]): Entry;
17
22
  export declare function groupFactsByEntity(facts: RawFact[]): Map<string, RawFact[]>;
23
+ export declare function groupLinksBySource(links: RawLink[]): Map<string, RawLink[]>;
18
24
  /**
19
25
  * For each entry, replace string ids in `expand` field keys with the resolved Entry.
20
26
  * Lookups are batched in parallel.
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/cms/internal.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBrD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAYzE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAQ3E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,KAAK,EAAE,EAChB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GACjD,OAAO,CAAC,KAAK,EAAE,CAAC,CA+BlB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/cms/internal.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,OAAO,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAwB5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,CA+B5F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAQ3E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAQ3E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,KAAK,EAAE,EAChB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GACjD,OAAO,CAAC,KAAK,EAAE,CAAC,CA+BlB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD"}
@@ -12,6 +12,11 @@ export type ScaffoldOptions = {
12
12
  expand?: string[];
13
13
  /** Override the CMS server URL. Default: http://localhost:4096 */
14
14
  url?: string;
15
+ /**
16
+ * Project directory for multi-instance backends (e.g. opencode requires this).
17
+ * Baked into the generated client literal so requests route to the correct project.
18
+ */
19
+ directory?: string;
15
20
  };
16
21
  export declare function scaffoldConsumer(opts: ScaffoldOptions): string;
17
22
  export declare function scaffoldFilename(opts: ScaffoldOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cms/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAwGF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cms/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA6GF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D"}
@@ -4,6 +4,17 @@
4
4
  * @module trellis/cms
5
5
  */
6
6
  export type EntryStatus = 'draft' | 'published';
7
+ export type FieldKind = 'text' | 'rich_text' | 'number' | 'boolean' | 'date' | 'email' | 'url' | 'color' | 'select' | 'multiselect' | 'file' | 'formula' | 'reference' | 'image';
8
+ export type FieldDefinition = {
9
+ key: string;
10
+ label?: string;
11
+ type: FieldKind;
12
+ required?: boolean;
13
+ default?: string;
14
+ options?: string[];
15
+ formula?: string;
16
+ target?: string;
17
+ };
7
18
  export type Entry<T extends Record<string, unknown> = Record<string, unknown>> = {
8
19
  /** Stable entity id (e.g. "blog_post:abc123" or "BlogPost:abc"). */
9
20
  id: string;
@@ -25,6 +36,18 @@ export type ListOptions = {
25
36
  export type GetOptions = {
26
37
  expand?: string[];
27
38
  };
39
+ export type SubscribeExtras = {
40
+ /** Called when a poll fails. If absent, errors are silently swallowed. */
41
+ onError?: (err: unknown) => void;
42
+ /**
43
+ * Custom equality check between consecutive results. Default: deep JSON
44
+ * comparison. Override with a faster check (e.g. id/version-based) for
45
+ * very large payloads.
46
+ */
47
+ equals?: (prev: unknown, next: unknown) => boolean;
48
+ };
49
+ export type ListSubscribeOptions = ListOptions & SubscribeExtras;
50
+ export type EntrySubscribeOptions = GetOptions & SubscribeExtras;
28
51
  export type Unsubscribe = () => void;
29
52
  export type ListSubscriber<T extends Record<string, unknown> = Record<string, unknown>> = (entries: Entry<T>[]) => void;
30
53
  export type EntrySubscriber<T extends Record<string, unknown> = Record<string, unknown>> = (entry: Entry<T> | null) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cms/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhD,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAC/E,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,MAAM,EAAE,WAAW,CAAC;IACpB,4FAA4F;IAC5F,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC7B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACxF,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAChB,IAAI,CAAC;AACV,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACzF,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,KACnB,IAAI,CAAC;AAEV,MAAM,MAAM,UAAU,GAAG;IACvB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,QAAQ,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iGAAiG;IACjG,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cms/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhD,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,WAAW,GACX,QAAQ,GACR,SAAS,GACT,MAAM,GACN,OAAO,GACP,KAAK,GACL,OAAO,GACP,QAAQ,GACR,aAAa,GACb,MAAM,GACN,SAAS,GACT,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC3E;IACE,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,MAAM,EAAE,WAAW,CAAC;IACpB,4FAA4F;IAC5F,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAEJ,MAAM,MAAM,WAAW,GAAG;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC7B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,eAAe,CAAC;AACjE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,eAAe,CAAC;AAEjE,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,cAAc,CACxB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzD,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAClC,MAAM,MAAM,eAAe,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzD,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;AAErC,MAAM,MAAM,UAAU,GAAG;IACvB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,QAAQ,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iGAAiG;IACjG,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trellis",
3
- "version": "3.1.1",
3
+ "version": "3.1.6",
4
4
  "description": "Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -99,6 +99,7 @@
99
99
  "logo.png",
100
100
  "logo.svg",
101
101
  "LICENSE",
102
+ "CHANGELOG.md",
102
103
  "README.md"
103
104
  ],
104
105
  "scripts": {
@@ -108,7 +109,7 @@
108
109
  "mcp:docs": "bun run src/mcp/docs.ts",
109
110
  "build": "bun build src/index.ts src/core/index.ts src/vcs/index.ts src/embeddings/index.ts src/links/index.ts src/decisions/index.ts src/server/index.ts src/client/index.ts src/react/index.ts src/db/index.ts src/cli/index.ts src/cms/index.ts --outdir dist --target bun --splitting --format esm --root src --external @xenova/transformers --external @huggingface/transformers --external react && mkdir -p dist/ui && cp src/ui/client.html dist/ui/client.html && tsc -p tsconfig.build.json --emitDeclarationOnly --noEmit false --noEmitOnError false && bun run build:inspector",
110
111
  "build:inspector": "vite build --config vite.inspector.config.ts",
111
- "test": "bun test test/core test/vcs test/git test/p2 test/p3 test/p4 test/p5 test/p6 test/p7 test/engine.test.ts",
112
+ "test": "bun test test/core test/cms test/vcs test/git test/p2 test/p3 test/p4 test/p5 test/p6 test/p7 test/engine.test.ts",
112
113
  "test:all": "bun test",
113
114
  "prepublishOnly": "npm run test && npm run build"
114
115
  },