@aphexcms/cms-core 0.1.12 → 0.1.13

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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/documents.d.ts +1 -0
  3. package/dist/api/documents.d.ts.map +1 -1
  4. package/dist/api/documents.js +9 -1
  5. package/dist/api/types.d.ts +24 -2
  6. package/dist/api/types.d.ts.map +1 -1
  7. package/dist/auth/auth-hooks.d.ts.map +1 -1
  8. package/dist/auth/auth-hooks.js +18 -4
  9. package/dist/cli/generate-types.js +218 -0
  10. package/dist/cli/index.js +86 -0
  11. package/dist/components/AdminApp.svelte +26 -55
  12. package/dist/components/AdminApp.svelte.d.ts +3 -5
  13. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  14. package/dist/components/admin/DocumentEditor.svelte +60 -14
  15. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  16. package/dist/components/admin/fields/ReferenceField.svelte +2 -3
  17. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  18. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
  19. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
  20. package/dist/db/interfaces/asset.d.ts +22 -0
  21. package/dist/db/interfaces/asset.d.ts.map +1 -1
  22. package/dist/db/interfaces/document.d.ts +25 -0
  23. package/dist/db/interfaces/document.d.ts.map +1 -1
  24. package/dist/field-validation/utils.d.ts +19 -1
  25. package/dist/field-validation/utils.d.ts.map +1 -1
  26. package/dist/field-validation/utils.js +33 -0
  27. package/dist/hooks.d.ts +2 -0
  28. package/dist/hooks.d.ts.map +1 -1
  29. package/dist/hooks.js +80 -12
  30. package/dist/lib/auth/provider.js +1 -0
  31. package/dist/lib/db/index.js +4 -0
  32. package/dist/lib/db/interfaces/asset.js +1 -0
  33. package/dist/lib/db/interfaces/document.js +1 -0
  34. package/dist/lib/db/interfaces/index.js +1 -0
  35. package/dist/lib/db/interfaces/organization.js +1 -0
  36. package/dist/lib/db/interfaces/schema.js +1 -0
  37. package/dist/lib/db/interfaces/user.js +1 -0
  38. package/dist/lib/email/index.js +4 -0
  39. package/dist/lib/email/interfaces/email.js +1 -0
  40. package/dist/lib/field-validation/rule.js +221 -0
  41. package/dist/lib/field-validation/utils.js +99 -0
  42. package/dist/lib/storage/interfaces/index.js +2 -0
  43. package/dist/lib/storage/interfaces/storage.js +1 -0
  44. package/dist/lib/types/asset.js +2 -0
  45. package/dist/lib/types/auth.js +41 -0
  46. package/dist/lib/types/config.js +1 -0
  47. package/dist/lib/types/document.js +1 -0
  48. package/dist/lib/types/filters.js +5 -0
  49. package/dist/lib/types/index.js +9 -0
  50. package/dist/lib/types/organization.js +3 -0
  51. package/dist/lib/types/schemas.js +1 -0
  52. package/dist/lib/types/sidebar.js +1 -0
  53. package/dist/lib/types/user.js +1 -0
  54. package/dist/local-api/auth-helpers.d.ts +65 -0
  55. package/dist/local-api/auth-helpers.d.ts.map +1 -0
  56. package/dist/local-api/auth-helpers.js +102 -0
  57. package/dist/local-api/collection-api.d.ts +138 -0
  58. package/dist/local-api/collection-api.d.ts.map +1 -0
  59. package/dist/local-api/collection-api.js +276 -0
  60. package/dist/local-api/index.d.ts +108 -0
  61. package/dist/local-api/index.d.ts.map +1 -0
  62. package/dist/local-api/index.js +157 -0
  63. package/dist/local-api/permissions.d.ts +45 -0
  64. package/dist/local-api/permissions.d.ts.map +1 -0
  65. package/dist/local-api/permissions.js +117 -0
  66. package/dist/local-api/types.d.ts +65 -0
  67. package/dist/local-api/types.d.ts.map +1 -0
  68. package/dist/local-api/types.js +4 -0
  69. package/dist/routes/documents-by-id.d.ts.map +1 -1
  70. package/dist/routes/documents-by-id.js +84 -63
  71. package/dist/routes/documents-publish.d.ts.map +1 -1
  72. package/dist/routes/documents-publish.js +57 -72
  73. package/dist/routes/documents-query.d.ts +24 -0
  74. package/dist/routes/documents-query.d.ts.map +1 -0
  75. package/dist/routes/documents-query.js +95 -0
  76. package/dist/routes/documents.d.ts.map +1 -1
  77. package/dist/routes/documents.js +80 -75
  78. package/dist/routes/index.d.ts +2 -0
  79. package/dist/routes/index.d.ts.map +1 -1
  80. package/dist/routes/index.js +2 -0
  81. package/dist/server/index.d.ts +1 -0
  82. package/dist/server/index.d.ts.map +1 -1
  83. package/dist/server/index.js +2 -0
  84. package/dist/types/config.d.ts +18 -1
  85. package/dist/types/config.d.ts.map +1 -1
  86. package/dist/types/document.d.ts +1 -0
  87. package/dist/types/document.d.ts.map +1 -1
  88. package/dist/types/filters.d.ts +173 -0
  89. package/dist/types/filters.d.ts.map +1 -0
  90. package/dist/types/filters.js +5 -0
  91. package/dist/types/index.d.ts +1 -0
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/dist/types/index.js +1 -0
  94. package/dist/types/schemas.d.ts +0 -5
  95. package/dist/types/schemas.d.ts.map +1 -1
  96. 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';