@aphexcms/cms-core 0.1.12 → 0.1.14
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/LICENSE +21 -0
- package/dist/api/documents.d.ts +1 -0
- package/dist/api/documents.d.ts.map +1 -1
- package/dist/api/documents.js +9 -1
- package/dist/api/types.d.ts +24 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/auth-hooks.d.ts.map +1 -1
- package/dist/auth/auth-hooks.js +18 -4
- package/dist/cli/generate-types.js +218 -0
- package/dist/cli/index.js +86 -0
- package/dist/components/AdminApp.svelte +15 -12
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentEditor.svelte +60 -14
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ImageField.svelte +22 -13
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +2 -3
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/asset.d.ts +22 -0
- package/dist/db/interfaces/asset.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +25 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/field-validation/utils.d.ts +19 -1
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +33 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +80 -12
- package/dist/lib/auth/provider.js +1 -0
- package/dist/lib/db/index.js +4 -0
- package/dist/lib/db/interfaces/asset.js +1 -0
- package/dist/lib/db/interfaces/document.js +1 -0
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/organization.js +1 -0
- package/dist/lib/db/interfaces/schema.js +1 -0
- package/dist/lib/db/interfaces/user.js +1 -0
- package/dist/lib/email/index.js +4 -0
- package/dist/lib/email/interfaces/email.js +1 -0
- package/dist/lib/field-validation/rule.js +221 -0
- package/dist/lib/field-validation/utils.js +99 -0
- package/dist/lib/storage/interfaces/index.js +2 -0
- package/dist/lib/storage/interfaces/storage.js +1 -0
- package/dist/lib/types/asset.js +2 -0
- package/dist/lib/types/auth.js +41 -0
- package/dist/lib/types/config.js +1 -0
- package/dist/lib/types/document.js +1 -0
- package/dist/lib/types/filters.js +5 -0
- package/dist/lib/types/index.js +9 -0
- package/dist/lib/types/organization.js +3 -0
- package/dist/lib/types/schemas.js +1 -0
- package/dist/lib/types/sidebar.js +1 -0
- package/dist/lib/types/user.js +1 -0
- package/dist/local-api/auth-helpers.d.ts +65 -0
- package/dist/local-api/auth-helpers.d.ts.map +1 -0
- package/dist/local-api/auth-helpers.js +102 -0
- package/dist/local-api/collection-api.d.ts +138 -0
- package/dist/local-api/collection-api.d.ts.map +1 -0
- package/dist/local-api/collection-api.js +276 -0
- package/dist/local-api/index.d.ts +108 -0
- package/dist/local-api/index.d.ts.map +1 -0
- package/dist/local-api/index.js +157 -0
- package/dist/local-api/permissions.d.ts +45 -0
- package/dist/local-api/permissions.d.ts.map +1 -0
- package/dist/local-api/permissions.js +117 -0
- package/dist/local-api/types.d.ts +65 -0
- package/dist/local-api/types.d.ts.map +1 -0
- package/dist/local-api/types.js +4 -0
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +84 -63
- package/dist/routes/documents-publish.d.ts.map +1 -1
- package/dist/routes/documents-publish.js +57 -72
- package/dist/routes/documents-query.d.ts +24 -0
- package/dist/routes/documents-query.d.ts.map +1 -0
- package/dist/routes/documents-query.js +95 -0
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +80 -75
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/types/config.d.ts +18 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/document.d.ts +1 -0
- package/dist/types/document.d.ts.map +1 -1
- package/dist/types/filters.d.ts +173 -0
- package/dist/types/filters.d.ts.map +1 -0
- package/dist/types/filters.js +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/package.json +101 -95
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db/index.js';
|
|
2
|
+
import type { Where, WhereTyped, FindOptions, FindResult } from '../types/filters.js';
|
|
3
|
+
import type { Document } from '../types/document.js';
|
|
4
|
+
import type { LocalAPIContext } from './types.js';
|
|
5
|
+
import type { SchemaType } from '../types/schemas.js';
|
|
6
|
+
import { PermissionChecker } from './permissions.js';
|
|
7
|
+
/**
|
|
8
|
+
* Collection API - provides type-safe operations for a single collection
|
|
9
|
+
* Generic type T represents the document type for this collection
|
|
10
|
+
*/
|
|
11
|
+
export declare class CollectionAPI<T = Document> {
|
|
12
|
+
private collectionName;
|
|
13
|
+
private databaseAdapter;
|
|
14
|
+
private _schema;
|
|
15
|
+
private permissions;
|
|
16
|
+
constructor(collectionName: string, databaseAdapter: DatabaseAdapter, _schema: SchemaType, permissions: PermissionChecker);
|
|
17
|
+
/**
|
|
18
|
+
* Get the schema for this collection
|
|
19
|
+
*/
|
|
20
|
+
get schema(): SchemaType;
|
|
21
|
+
/**
|
|
22
|
+
* Find multiple documents with advanced filtering and pagination
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const result = await api.collections.pages.find(
|
|
27
|
+
* { organizationId: 'org_123', user },
|
|
28
|
+
* {
|
|
29
|
+
* where: {
|
|
30
|
+
* status: { equals: 'published' },
|
|
31
|
+
* 'author.name': { contains: 'John' }
|
|
32
|
+
* },
|
|
33
|
+
* limit: 20,
|
|
34
|
+
* sort: '-publishedAt'
|
|
35
|
+
* }
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
find(context: LocalAPIContext, options?: FindOptions<T>): Promise<FindResult<T>>;
|
|
40
|
+
/**
|
|
41
|
+
* Find a single document by ID
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const page = await api.collections.pages.findByID(
|
|
46
|
+
* { organizationId: 'org_123', user },
|
|
47
|
+
* 'doc_123',
|
|
48
|
+
* { depth: 1, perspective: 'published' }
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
findByID(context: LocalAPIContext, id: string, options?: Partial<FindOptions<T>>): Promise<T | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Count documents matching a where clause
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const count = await api.collections.pages.count(
|
|
59
|
+
* { organizationId: 'org_123', user },
|
|
60
|
+
* { where: { status: { equals: 'published' } } }
|
|
61
|
+
* );
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
count(context: LocalAPIContext, options?: {
|
|
65
|
+
where?: WhereTyped<T> | Where;
|
|
66
|
+
}): Promise<number>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a new document
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const page = await api.collections.pages.create(
|
|
73
|
+
* { organizationId: 'org_123', user },
|
|
74
|
+
* {
|
|
75
|
+
* title: 'New Page',
|
|
76
|
+
* slug: 'new-page',
|
|
77
|
+
* content: []
|
|
78
|
+
* }
|
|
79
|
+
* );
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
create(context: LocalAPIContext, data: Record<string, unknown>, options?: {
|
|
83
|
+
publish?: boolean;
|
|
84
|
+
}): Promise<T>;
|
|
85
|
+
/**
|
|
86
|
+
* Update an existing document
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const updated = await api.collections.pages.update(
|
|
91
|
+
* { organizationId: 'org_123', user },
|
|
92
|
+
* 'doc_123',
|
|
93
|
+
* { title: 'Updated Title' },
|
|
94
|
+
* { publish: true }
|
|
95
|
+
* );
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
update(context: LocalAPIContext, id: string, data: Record<string, unknown>, options?: {
|
|
99
|
+
publish?: boolean;
|
|
100
|
+
}): Promise<T | null>;
|
|
101
|
+
/**
|
|
102
|
+
* Delete a document
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const deleted = await api.collections.pages.delete(
|
|
107
|
+
* { organizationId: 'org_123', user },
|
|
108
|
+
* 'doc_123'
|
|
109
|
+
* );
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
delete(context: LocalAPIContext, id: string): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Publish a document
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const published = await api.collections.pages.publish(
|
|
119
|
+
* { organizationId: 'org_123', user },
|
|
120
|
+
* 'doc_123'
|
|
121
|
+
* );
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
publish(context: LocalAPIContext, id: string): Promise<T | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Unpublish a document
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const unpublished = await api.collections.pages.unpublish(
|
|
131
|
+
* { organizationId: 'org_123', user },
|
|
132
|
+
* 'doc_123'
|
|
133
|
+
* );
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
unpublish(context: LocalAPIContext, id: string): Promise<T | null>;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=collection-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-api.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/collection-api.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AA6BlD;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC,GAAG,QAAQ;IAErC,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;gBAHX,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,iBAAiB;IAMvC;;OAEG;IACH,IAAI,MAAM,IAAI,UAAU,CAEvB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACG,IAAI,CACT,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,WAAW,CAAC,CAAC,CAAM,GAC1B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAqBzB;;;;;;;;;;;OAWG;IACG,QAAQ,CACb,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAC/B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAmBpB;;;;;;;;;;OAUG;IACG,KAAK,CACV,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;KAAE,GACzC,OAAO,CAAC,MAAM,CAAC;IAWlB;;;;;;;;;;;;;;OAcG;IACG,MAAM,CACX,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7B,OAAO,CAAC,CAAC,CAAC;IAuCb;;;;;;;;;;;;OAYG;IACG,MAAM,CACX,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IA6CpB;;;;;;;;;;OAUG;IACG,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOpE;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAgCtE;;;;;;;;;;OAUG;IACG,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CAUxE"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// local-api/collection-api.ts
|
|
2
|
+
//
|
|
3
|
+
// Collection API - provides type-safe CRUD operations for a single collection
|
|
4
|
+
import { validateDocumentData } from '../field-validation/utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Transform a raw database document into a typed document with data extracted
|
|
7
|
+
* based on perspective (draft or published)
|
|
8
|
+
*/
|
|
9
|
+
function transformDocument(doc, perspective = 'draft') {
|
|
10
|
+
const data = perspective === 'draft' ? doc.draftData : doc.publishedData;
|
|
11
|
+
// Merge document metadata with the content data
|
|
12
|
+
return {
|
|
13
|
+
id: doc.id,
|
|
14
|
+
...data,
|
|
15
|
+
// Include metadata fields
|
|
16
|
+
_meta: {
|
|
17
|
+
type: doc.type,
|
|
18
|
+
status: doc.status,
|
|
19
|
+
organizationId: doc.organizationId,
|
|
20
|
+
createdAt: doc.createdAt,
|
|
21
|
+
updatedAt: doc.updatedAt,
|
|
22
|
+
createdBy: doc.createdBy,
|
|
23
|
+
updatedBy: doc.updatedBy,
|
|
24
|
+
publishedAt: doc.publishedAt,
|
|
25
|
+
publishedHash: doc.publishedHash
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Collection API - provides type-safe operations for a single collection
|
|
31
|
+
* Generic type T represents the document type for this collection
|
|
32
|
+
*/
|
|
33
|
+
export class CollectionAPI {
|
|
34
|
+
collectionName;
|
|
35
|
+
databaseAdapter;
|
|
36
|
+
_schema;
|
|
37
|
+
permissions;
|
|
38
|
+
constructor(collectionName, databaseAdapter, _schema, permissions) {
|
|
39
|
+
this.collectionName = collectionName;
|
|
40
|
+
this.databaseAdapter = databaseAdapter;
|
|
41
|
+
this._schema = _schema;
|
|
42
|
+
this.permissions = permissions;
|
|
43
|
+
// Validate collection exists
|
|
44
|
+
this.permissions.validateCollection(collectionName);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the schema for this collection
|
|
48
|
+
*/
|
|
49
|
+
get schema() {
|
|
50
|
+
return this._schema;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Find multiple documents with advanced filtering and pagination
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const result = await api.collections.pages.find(
|
|
58
|
+
* { organizationId: 'org_123', user },
|
|
59
|
+
* {
|
|
60
|
+
* where: {
|
|
61
|
+
* status: { equals: 'published' },
|
|
62
|
+
* 'author.name': { contains: 'John' }
|
|
63
|
+
* },
|
|
64
|
+
* limit: 20,
|
|
65
|
+
* sort: '-publishedAt'
|
|
66
|
+
* }
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
async find(context, options = {}) {
|
|
71
|
+
// Permission check (unless overrideAccess)
|
|
72
|
+
await this.permissions.canRead(context, this.collectionName);
|
|
73
|
+
// Call adapter's advanced find method
|
|
74
|
+
const result = await this.databaseAdapter.findManyDocAdvanced(context.organizationId, this.collectionName, options);
|
|
75
|
+
// Transform documents to extract data based on perspective
|
|
76
|
+
const perspective = options.perspective || 'draft';
|
|
77
|
+
const transformedDocs = result.docs.map((doc) => transformDocument(doc, perspective));
|
|
78
|
+
return {
|
|
79
|
+
...result,
|
|
80
|
+
docs: transformedDocs
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Find a single document by ID
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const page = await api.collections.pages.findByID(
|
|
89
|
+
* { organizationId: 'org_123', user },
|
|
90
|
+
* 'doc_123',
|
|
91
|
+
* { depth: 1, perspective: 'published' }
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
async findByID(context, id, options) {
|
|
96
|
+
// Permission check (unless overrideAccess)
|
|
97
|
+
await this.permissions.canRead(context, this.collectionName);
|
|
98
|
+
const result = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id, options);
|
|
99
|
+
if (!result) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Transform document to extract data based on perspective
|
|
103
|
+
const perspective = options?.perspective || 'draft';
|
|
104
|
+
return transformDocument(result, perspective);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Count documents matching a where clause
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const count = await api.collections.pages.count(
|
|
112
|
+
* { organizationId: 'org_123', user },
|
|
113
|
+
* { where: { status: { equals: 'published' } } }
|
|
114
|
+
* );
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
async count(context, options) {
|
|
118
|
+
// Permission check (unless overrideAccess)
|
|
119
|
+
await this.permissions.canRead(context, this.collectionName);
|
|
120
|
+
return this.databaseAdapter.countDocuments(context.organizationId, this.collectionName, options?.where);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a new document
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const page = await api.collections.pages.create(
|
|
128
|
+
* { organizationId: 'org_123', user },
|
|
129
|
+
* {
|
|
130
|
+
* title: 'New Page',
|
|
131
|
+
* slug: 'new-page',
|
|
132
|
+
* content: []
|
|
133
|
+
* }
|
|
134
|
+
* );
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
async create(context, data, options) {
|
|
138
|
+
// Permission check (unless overrideAccess)
|
|
139
|
+
await this.permissions.canWrite(context, this.collectionName);
|
|
140
|
+
// Validate data if publishing immediately
|
|
141
|
+
if (options?.publish) {
|
|
142
|
+
await this.permissions.canPublish(context, this.collectionName);
|
|
143
|
+
const validationResult = await validateDocumentData(this._schema, data, data);
|
|
144
|
+
if (!validationResult.isValid) {
|
|
145
|
+
const errorMessage = validationResult.errors
|
|
146
|
+
.map((e) => `${e.field}: ${e.errors.join(', ')}`)
|
|
147
|
+
.join('; ');
|
|
148
|
+
throw new Error(`Cannot publish: validation errors - ${errorMessage}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Create document with draft data
|
|
152
|
+
const document = await this.databaseAdapter.createDocument({
|
|
153
|
+
organizationId: context.organizationId,
|
|
154
|
+
type: this.collectionName,
|
|
155
|
+
draftData: data,
|
|
156
|
+
createdBy: context.user?.id
|
|
157
|
+
});
|
|
158
|
+
// Publish immediately if requested (validation already done above)
|
|
159
|
+
if (options?.publish) {
|
|
160
|
+
const published = await this.databaseAdapter.publishDoc(context.organizationId, document.id);
|
|
161
|
+
if (published) {
|
|
162
|
+
return transformDocument(published, 'published');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return transformDocument(document, 'draft');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update an existing document
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const updated = await api.collections.pages.update(
|
|
173
|
+
* { organizationId: 'org_123', user },
|
|
174
|
+
* 'doc_123',
|
|
175
|
+
* { title: 'Updated Title' },
|
|
176
|
+
* { publish: true }
|
|
177
|
+
* );
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
async update(context, id, data, options) {
|
|
181
|
+
// Permission check (unless overrideAccess)
|
|
182
|
+
await this.permissions.canWrite(context, this.collectionName);
|
|
183
|
+
// Update draft data
|
|
184
|
+
const document = await this.databaseAdapter.updateDocDraft(context.organizationId, id, data, context.user?.id);
|
|
185
|
+
if (!document) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Validate and publish immediately if requested
|
|
189
|
+
if (options?.publish) {
|
|
190
|
+
await this.permissions.canPublish(context, this.collectionName);
|
|
191
|
+
// Validate the updated draft data
|
|
192
|
+
const validationResult = await validateDocumentData(this._schema, document.draftData, document.draftData);
|
|
193
|
+
if (!validationResult.isValid) {
|
|
194
|
+
const errorMessage = validationResult.errors
|
|
195
|
+
.map((e) => `${e.field}: ${e.errors.join(', ')}`)
|
|
196
|
+
.join('; ');
|
|
197
|
+
throw new Error(`Cannot publish: validation errors - ${errorMessage}`);
|
|
198
|
+
}
|
|
199
|
+
const published = await this.databaseAdapter.publishDoc(context.organizationId, document.id);
|
|
200
|
+
if (published) {
|
|
201
|
+
return transformDocument(published, 'published');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return transformDocument(document, 'draft');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Delete a document
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const deleted = await api.collections.pages.delete(
|
|
212
|
+
* { organizationId: 'org_123', user },
|
|
213
|
+
* 'doc_123'
|
|
214
|
+
* );
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
async delete(context, id) {
|
|
218
|
+
// Permission check (unless overrideAccess)
|
|
219
|
+
await this.permissions.canDelete(context, this.collectionName);
|
|
220
|
+
return this.databaseAdapter.deleteDocById(context.organizationId, id);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Publish a document
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* const published = await api.collections.pages.publish(
|
|
228
|
+
* { organizationId: 'org_123', user },
|
|
229
|
+
* 'doc_123'
|
|
230
|
+
* );
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
async publish(context, id) {
|
|
234
|
+
// Permission check (unless overrideAccess)
|
|
235
|
+
await this.permissions.canPublish(context, this.collectionName);
|
|
236
|
+
// Get the document to access draft data for validation
|
|
237
|
+
const document = await this.databaseAdapter.findByDocId(context.organizationId, id);
|
|
238
|
+
if (!document || !document.draftData) {
|
|
239
|
+
throw new Error('Document not found or has no draft content to publish');
|
|
240
|
+
}
|
|
241
|
+
// Validate draft data before publishing
|
|
242
|
+
const validationResult = await validateDocumentData(this._schema, document.draftData, document.draftData);
|
|
243
|
+
if (!validationResult.isValid) {
|
|
244
|
+
const errorMessage = validationResult.errors
|
|
245
|
+
.map((e) => `${e.field}: ${e.errors.join(', ')}`)
|
|
246
|
+
.join('; ');
|
|
247
|
+
throw new Error(`Cannot publish: validation errors - ${errorMessage}`);
|
|
248
|
+
}
|
|
249
|
+
// Validation passed - proceed with publish
|
|
250
|
+
const publishedDocument = await this.databaseAdapter.publishDoc(context.organizationId, id);
|
|
251
|
+
if (!publishedDocument) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return transformDocument(publishedDocument, 'published');
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Unpublish a document
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const unpublished = await api.collections.pages.unpublish(
|
|
262
|
+
* { organizationId: 'org_123', user },
|
|
263
|
+
* 'doc_123'
|
|
264
|
+
* );
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
async unpublish(context, id) {
|
|
268
|
+
// Permission check (unless overrideAccess)
|
|
269
|
+
await this.permissions.canPublish(context, this.collectionName);
|
|
270
|
+
const document = await this.databaseAdapter.unpublishDoc(context.organizationId, id);
|
|
271
|
+
if (!document) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
return transformDocument(document, 'draft');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { CMSConfig } from '../types/config.js';
|
|
2
|
+
import type { DatabaseAdapter } from '../db/index.js';
|
|
3
|
+
import type { SchemaType } from '../types/schemas.js';
|
|
4
|
+
import { CollectionAPI } from './collection-api.js';
|
|
5
|
+
/**
|
|
6
|
+
* Collections map - provides type-safe access to all collections
|
|
7
|
+
* This interface is meant to be augmented by generated types
|
|
8
|
+
*/
|
|
9
|
+
export interface Collections {
|
|
10
|
+
[collectionName: string]: CollectionAPI<unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Local API - provides a unified, type-safe interface for all CMS operations
|
|
14
|
+
*
|
|
15
|
+
* This is the single source of truth for data operations in Aphex CMS.
|
|
16
|
+
* GraphQL and REST APIs should be thin wrappers around this Local API.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Initialize
|
|
21
|
+
* const api = await getLocalAPI(config);
|
|
22
|
+
*
|
|
23
|
+
* // Query documents
|
|
24
|
+
* const pages = await api.collections.pages.find(
|
|
25
|
+
* { organizationId: 'org_123', user },
|
|
26
|
+
* { where: { status: { equals: 'published' } } }
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* // Create document
|
|
30
|
+
* const newPage = await api.collections.pages.create(
|
|
31
|
+
* { organizationId: 'org_123', user },
|
|
32
|
+
* { title: 'Hello', slug: 'hello' }
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* // System operation (bypasses RLS)
|
|
36
|
+
* const allPages = await api.collections.pages.find(
|
|
37
|
+
* { organizationId: 'org_123', overrideAccess: true },
|
|
38
|
+
* { limit: 100 }
|
|
39
|
+
* );
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare class LocalAPI {
|
|
43
|
+
private config;
|
|
44
|
+
collections: Collections;
|
|
45
|
+
private userAdapter;
|
|
46
|
+
private systemAdapter;
|
|
47
|
+
private permissions;
|
|
48
|
+
private schemas;
|
|
49
|
+
constructor(config: CMSConfig, userAdapter: DatabaseAdapter, systemAdapter?: DatabaseAdapter);
|
|
50
|
+
/**
|
|
51
|
+
* Initialize collection APIs for all document schema types
|
|
52
|
+
*/
|
|
53
|
+
private initializeCollections;
|
|
54
|
+
/**
|
|
55
|
+
* Get the appropriate database adapter based on context
|
|
56
|
+
* Uses system adapter if overrideAccess is true, otherwise uses user adapter
|
|
57
|
+
*/
|
|
58
|
+
private getAdapter;
|
|
59
|
+
/**
|
|
60
|
+
* Get list of available collection names
|
|
61
|
+
*/
|
|
62
|
+
getCollectionNames(): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Check if a collection exists
|
|
65
|
+
*/
|
|
66
|
+
hasCollection(name: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get schema for a collection
|
|
69
|
+
*/
|
|
70
|
+
getCollectionSchema(name: string): SchemaType | undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create and initialize the Local API
|
|
74
|
+
*
|
|
75
|
+
* @param config - CMS configuration
|
|
76
|
+
* @param userAdapter - Standard database adapter (respects RLS)
|
|
77
|
+
* @param systemAdapter - Optional system adapter (bypasses RLS) for system operations
|
|
78
|
+
* @returns LocalAPI instance
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // Basic usage (single adapter)
|
|
83
|
+
* const api = createLocalAPI(config, userDb);
|
|
84
|
+
*
|
|
85
|
+
* // With system adapter for RLS bypass
|
|
86
|
+
* const api = createLocalAPI(config, userDb, systemDb);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function createLocalAPI(config: CMSConfig, userAdapter: DatabaseAdapter, systemAdapter?: DatabaseAdapter): LocalAPI;
|
|
90
|
+
/**
|
|
91
|
+
* Get the Local API instance
|
|
92
|
+
* Throws if Local API hasn't been initialized yet
|
|
93
|
+
*
|
|
94
|
+
* @returns LocalAPI instance
|
|
95
|
+
* @throws Error if Local API not initialized
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const api = getLocalAPI();
|
|
100
|
+
* const pages = await api.collections.pages.find(...);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function getLocalAPI(): LocalAPI;
|
|
104
|
+
export { CollectionAPI } from './collection-api.js';
|
|
105
|
+
export { PermissionChecker, PermissionError } from './permissions.js';
|
|
106
|
+
export type { LocalAPIContext, CreateOptions, UpdateOptions } from './types.js';
|
|
107
|
+
export { authToContext, requireAuth, systemContext } from './auth-helpers.js';
|
|
108
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIjD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE3B,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;CAGjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,QAAQ;IAQnB,OAAO,CAAC,MAAM;IAPR,WAAW,EAAE,WAAW,CAAM;IACrC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,OAAO,CAA0B;gBAGhC,MAAM,EAAE,SAAS,EACzB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe;IAmBhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqC7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;CAGzD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe,GAC7B,QAAQ,CAGV;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAKtC;AAGD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// local-api/index.ts
|
|
2
|
+
//
|
|
3
|
+
// Main Local API singleton and factory function
|
|
4
|
+
import { CollectionAPI } from './collection-api.js';
|
|
5
|
+
import { PermissionChecker } from './permissions.js';
|
|
6
|
+
/**
|
|
7
|
+
* Local API - provides a unified, type-safe interface for all CMS operations
|
|
8
|
+
*
|
|
9
|
+
* This is the single source of truth for data operations in Aphex CMS.
|
|
10
|
+
* GraphQL and REST APIs should be thin wrappers around this Local API.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Initialize
|
|
15
|
+
* const api = await getLocalAPI(config);
|
|
16
|
+
*
|
|
17
|
+
* // Query documents
|
|
18
|
+
* const pages = await api.collections.pages.find(
|
|
19
|
+
* { organizationId: 'org_123', user },
|
|
20
|
+
* { where: { status: { equals: 'published' } } }
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* // Create document
|
|
24
|
+
* const newPage = await api.collections.pages.create(
|
|
25
|
+
* { organizationId: 'org_123', user },
|
|
26
|
+
* { title: 'Hello', slug: 'hello' }
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* // System operation (bypasses RLS)
|
|
30
|
+
* const allPages = await api.collections.pages.find(
|
|
31
|
+
* { organizationId: 'org_123', overrideAccess: true },
|
|
32
|
+
* { limit: 100 }
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class LocalAPI {
|
|
37
|
+
config;
|
|
38
|
+
collections = {};
|
|
39
|
+
userAdapter;
|
|
40
|
+
systemAdapter;
|
|
41
|
+
permissions;
|
|
42
|
+
schemas;
|
|
43
|
+
constructor(config, userAdapter, systemAdapter) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
this.userAdapter = userAdapter;
|
|
46
|
+
this.systemAdapter = systemAdapter || null;
|
|
47
|
+
// Build schema map for quick lookups
|
|
48
|
+
this.schemas = new Map(config.schemaTypes
|
|
49
|
+
.filter((schema) => schema.type === 'document')
|
|
50
|
+
.map((schema) => [schema.name, schema]));
|
|
51
|
+
// Initialize permission checker
|
|
52
|
+
this.permissions = new PermissionChecker(config, this.schemas);
|
|
53
|
+
// Initialize collection APIs for all document types
|
|
54
|
+
this.initializeCollections();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Initialize collection APIs for all document schema types
|
|
58
|
+
*/
|
|
59
|
+
initializeCollections() {
|
|
60
|
+
const documentSchemas = this.config.schemaTypes.filter((s) => s.type === 'document');
|
|
61
|
+
for (const schema of documentSchemas) {
|
|
62
|
+
// Create a proxy that selects the correct adapter based on context
|
|
63
|
+
const collectionAPI = new Proxy(new CollectionAPI(schema.name, this.userAdapter, schema, this.permissions), {
|
|
64
|
+
get: (target, prop) => {
|
|
65
|
+
const method = target[prop];
|
|
66
|
+
if (typeof method === 'function') {
|
|
67
|
+
// Wrap method to dynamically select adapter
|
|
68
|
+
return async (...args) => {
|
|
69
|
+
const context = args[0];
|
|
70
|
+
const adapter = this.getAdapter(context);
|
|
71
|
+
// Create new CollectionAPI with the correct adapter
|
|
72
|
+
const api = new CollectionAPI(schema.name, adapter, schema, this.permissions);
|
|
73
|
+
// Call the method on the new instance
|
|
74
|
+
return api[prop].apply(api, args);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return method;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
this.collections[schema.name] = collectionAPI;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the appropriate database adapter based on context
|
|
85
|
+
* Uses system adapter if overrideAccess is true, otherwise uses user adapter
|
|
86
|
+
*/
|
|
87
|
+
getAdapter(context) {
|
|
88
|
+
if (context.overrideAccess && this.systemAdapter) {
|
|
89
|
+
return this.systemAdapter;
|
|
90
|
+
}
|
|
91
|
+
return this.userAdapter;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get list of available collection names
|
|
95
|
+
*/
|
|
96
|
+
getCollectionNames() {
|
|
97
|
+
return Array.from(this.schemas.keys());
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a collection exists
|
|
101
|
+
*/
|
|
102
|
+
hasCollection(name) {
|
|
103
|
+
return this.schemas.has(name);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get schema for a collection
|
|
107
|
+
*/
|
|
108
|
+
getCollectionSchema(name) {
|
|
109
|
+
return this.schemas.get(name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Global Local API instance
|
|
113
|
+
let localAPIInstance = null;
|
|
114
|
+
/**
|
|
115
|
+
* Create and initialize the Local API
|
|
116
|
+
*
|
|
117
|
+
* @param config - CMS configuration
|
|
118
|
+
* @param userAdapter - Standard database adapter (respects RLS)
|
|
119
|
+
* @param systemAdapter - Optional system adapter (bypasses RLS) for system operations
|
|
120
|
+
* @returns LocalAPI instance
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* // Basic usage (single adapter)
|
|
125
|
+
* const api = createLocalAPI(config, userDb);
|
|
126
|
+
*
|
|
127
|
+
* // With system adapter for RLS bypass
|
|
128
|
+
* const api = createLocalAPI(config, userDb, systemDb);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function createLocalAPI(config, userAdapter, systemAdapter) {
|
|
132
|
+
localAPIInstance = new LocalAPI(config, userAdapter, systemAdapter);
|
|
133
|
+
return localAPIInstance;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the Local API instance
|
|
137
|
+
* Throws if Local API hasn't been initialized yet
|
|
138
|
+
*
|
|
139
|
+
* @returns LocalAPI instance
|
|
140
|
+
* @throws Error if Local API not initialized
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const api = getLocalAPI();
|
|
145
|
+
* const pages = await api.collections.pages.find(...);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function getLocalAPI() {
|
|
149
|
+
if (!localAPIInstance) {
|
|
150
|
+
throw new Error('Local API not initialized. Call createLocalAPI() first.');
|
|
151
|
+
}
|
|
152
|
+
return localAPIInstance;
|
|
153
|
+
}
|
|
154
|
+
// Re-export types and classes for convenience
|
|
155
|
+
export { CollectionAPI } from './collection-api.js';
|
|
156
|
+
export { PermissionChecker, PermissionError } from './permissions.js';
|
|
157
|
+
export { authToContext, requireAuth, systemContext } from './auth-helpers.js';
|