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 +20 -0
- package/README.md +37 -0
- package/dist/cms/client.d.ts +22 -4
- package/dist/cms/client.d.ts.map +1 -1
- package/dist/cms/formula.d.ts +10 -0
- package/dist/cms/formula.d.ts.map +1 -0
- package/dist/cms/index.d.ts +3 -2
- package/dist/cms/index.d.ts.map +1 -1
- package/dist/cms/index.js +284 -57
- package/dist/cms/internal.d.ts +7 -1
- package/dist/cms/internal.d.ts.map +1 -1
- package/dist/cms/scaffold.d.ts +5 -0
- package/dist/cms/scaffold.d.ts.map +1 -1
- package/dist/cms/types.d.ts +23 -0
- package/dist/cms/types.d.ts.map +1 -1
- package/package.json +3 -2
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:
|
package/dist/cms/client.d.ts
CHANGED
|
@@ -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
|
-
|
|
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?:
|
|
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?:
|
|
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
|
package/dist/cms/client.d.ts.map
CHANGED
|
@@ -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;
|
|
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,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAQL,KAAK,SAAS,EACd,KAAK,OAAO,EAEb,MAAM,eAAe,CAAC;AA4BvB,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;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG1B;gBAEQ,IAAI,EAAE,gBAAgB;IAYlC,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,EAC/D,EAAE,EAAE,MAAM,GACT,QAAQ,CAAC,CAAC,CAAC;IAId,sDAAsD;IAChD,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAiD1C,KAAK,IAAI,IAAI;IAQb;;;;;;OAMG;IACH,MAAM,CAAC,CAAC,EACN,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC5B,MAAM,GAAE;QACN,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QACnD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;KAC7B,GACL,WAAW;IAmDd,gBAAgB;IACV,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAgBnD,gBAAgB;IACV,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAcvC,gBAAgB;IACV,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;CAkB7E;AAED,qBAAa,aAAa,CACxB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAGzD,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;IAoDjD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAYtE;;;;;;OAMG;IACH,SAAS,CACP,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAC3B,IAAI,GAAE,oBAAyB,GAC9B,WAAW;CAUf;AAED,qBAAa,QAAQ,CACnB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAGzD,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,CACP,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,EAC5B,IAAI,GAAE,qBAA0B,GAC/B,WAAW;CAUf;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAEjE"}
|
|
@@ -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"}
|
package/dist/cms/index.d.ts
CHANGED
|
@@ -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
|
package/dist/cms/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cms/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
this.client.
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
package/dist/cms/internal.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cms/scaffold.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cms/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/cms/types.d.ts.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cms/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trellis",
|
|
3
|
-
"version": "3.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
|
},
|