@apexkit/cli 1.0.1 → 1.0.3
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/package.json +1 -1
- package/src/index.js +128 -12
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -141,7 +141,7 @@ const TRIGGER_DEFINITIONS = [
|
|
|
141
141
|
{ id: "graphql", name: "graphql (Custom Resolver)", desc: "Dynamic Query/Mutation field resolver for GraphQL schema." }
|
|
142
142
|
];
|
|
143
143
|
|
|
144
|
-
// --- 4. WORKSPACE TYPES & CONFIG GENERATOR ---
|
|
144
|
+
// --- 4. ACCURATE WORKSPACE TYPES & CONFIG GENERATOR ---
|
|
145
145
|
async function generateWorkspaceFiles() {
|
|
146
146
|
console.log(" ⏳ Fetching collection schemas from DB...");
|
|
147
147
|
let collectionsData = [];
|
|
@@ -155,6 +155,34 @@ async function generateWorkspaceFiles() {
|
|
|
155
155
|
console.warn(` ⚠️ Could not connect to DB for schemas: ${err.message}`);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// Pre-calculate lookup maps for collection IDs and Names
|
|
159
|
+
const colIdToName = new Map();
|
|
160
|
+
const colNameToId = new Map();
|
|
161
|
+
collectionsData.forEach((col) => {
|
|
162
|
+
colIdToName.set(String(col.id), col.name);
|
|
163
|
+
colNameToId.set(col.name, col.id);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Calculate Reverse Relations: Target Collection Name -> Array of Reverse Link Definitions
|
|
167
|
+
const reverseRelationsMap = new Map();
|
|
168
|
+
collectionsData.forEach((originCol) => {
|
|
169
|
+
if (originCol.schema?.relations) {
|
|
170
|
+
for (const [relName, relDef] of Object.entries(originCol.schema.relations)) {
|
|
171
|
+
const rawTarget = relDef.target_collection;
|
|
172
|
+
const targetName = colIdToName.get(String(rawTarget)) || rawTarget;
|
|
173
|
+
|
|
174
|
+
if (!reverseRelationsMap.has(targetName)) {
|
|
175
|
+
reverseRelationsMap.set(targetName, []);
|
|
176
|
+
}
|
|
177
|
+
reverseRelationsMap.get(targetName).push({
|
|
178
|
+
originCollection: originCol.name,
|
|
179
|
+
relationFieldName: relName,
|
|
180
|
+
cardinality: relDef.relation_type, // 'many' or 'one'
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
158
186
|
let collectionTypesStr = `export interface Collections {\n`;
|
|
159
187
|
let collectionExpandsStr = `export interface CollectionExpands {\n`;
|
|
160
188
|
const collectionNames = [];
|
|
@@ -165,6 +193,7 @@ async function generateWorkspaceFiles() {
|
|
|
165
193
|
let expandsStr = "";
|
|
166
194
|
|
|
167
195
|
if (col.schema) {
|
|
196
|
+
// 1. STANDARD FIELDS
|
|
168
197
|
if (col.schema.fields) {
|
|
169
198
|
for (const [name, def] of Object.entries(col.schema.fields)) {
|
|
170
199
|
let tsType = "any";
|
|
@@ -183,6 +212,7 @@ async function generateWorkspaceFiles() {
|
|
|
183
212
|
break;
|
|
184
213
|
case "owner":
|
|
185
214
|
case "relation":
|
|
215
|
+
// ✅ Foreign keys and owners are ALWAYS scalar IDs on the record itself
|
|
186
216
|
tsType = "number | string";
|
|
187
217
|
break;
|
|
188
218
|
case "boolean":
|
|
@@ -206,21 +236,59 @@ async function generateWorkspaceFiles() {
|
|
|
206
236
|
}
|
|
207
237
|
const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
|
|
208
238
|
fieldsStr += ` ${safeName}${def.required ? "" : "?"}: ${tsType};\n`;
|
|
239
|
+
|
|
240
|
+
// If it's an Owner field, it expands to the User Profile map
|
|
241
|
+
if (def.type === "owner") {
|
|
242
|
+
expandsStr += ` ${safeName}?: { id: number | string; email: string; role: string; metadata?: Record<string, any> };\n`;
|
|
243
|
+
}
|
|
209
244
|
}
|
|
210
245
|
}
|
|
211
246
|
|
|
247
|
+
// 2. FORWARD RELATIONS (Directly on this collection)
|
|
212
248
|
if (col.schema.relations) {
|
|
213
249
|
for (const [name, rel] of Object.entries(col.schema.relations)) {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
250
|
+
const rawTarget = rel.target_collection;
|
|
251
|
+
const targetColName = colIdToName.get(String(rawTarget)) || rawTarget;
|
|
216
252
|
const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
|
|
217
|
-
fieldsStr += ` ${safeName}${rel.required ? "" : "?"}: ${tsType};\n`;
|
|
218
253
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
254
|
+
// ✅ BULLETPROOF RULE 1:
|
|
255
|
+
// A record holding the foreign key field stores a SCALAR foreign key ID, NEVER an array.
|
|
256
|
+
fieldsStr += ` ${safeName}${rel.required ? "" : "?"}: number | string;\n`;
|
|
257
|
+
|
|
258
|
+
// ✅ BULLETPROOF RULE 2:
|
|
259
|
+
// Forward expansion from the foreign key field points to ONE target record.
|
|
260
|
+
// It is ALWAYS a Map/Object, NEVER an array!
|
|
261
|
+
const targetRecordType = `Collections["${targetColName}"]`;
|
|
262
|
+
const expandSingleType = `{ id: number | string; data: ${targetRecordType}; created: string; updated: string; expand?: any }`;
|
|
263
|
+
|
|
264
|
+
expandsStr += ` ${safeName}?: ${expandSingleType};\n`;
|
|
222
265
|
}
|
|
223
266
|
}
|
|
267
|
+
|
|
268
|
+
// 3. REVERSE RELATIONS (Parent expanding children)
|
|
269
|
+
const reverseLinks = reverseRelationsMap.get(col.name) || [];
|
|
270
|
+
const fieldCountByName = new Map();
|
|
271
|
+
reverseLinks.forEach((rev) => {
|
|
272
|
+
fieldCountByName.set(rev.originCollection, (fieldCountByName.get(rev.originCollection) || 0) + 1);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
reverseLinks.forEach((rev) => {
|
|
276
|
+
const isDuplicate = (fieldCountByName.get(rev.originCollection) || 0) > 1;
|
|
277
|
+
const revKey = isDuplicate ? `${rev.originCollection}_via_${rev.relationFieldName}` : rev.originCollection;
|
|
278
|
+
const safeRevKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(revKey) ? revKey : `"${revKey}"`;
|
|
279
|
+
|
|
280
|
+
const originRecordType = `Collections["${rev.originCollection}"]`;
|
|
281
|
+
const childRecordType = `{ id: number | string; data: ${originRecordType}; created: string; updated: string; expand?: any }`;
|
|
282
|
+
|
|
283
|
+
// ✅ BULLETPROOF RULE 3:
|
|
284
|
+
// Only return an Array when reverse-expanded from parent AND cardinality is 'many'.
|
|
285
|
+
// If the reverse relation is unique ('one'), return a single Map/Object.
|
|
286
|
+
if (rev.cardinality === "one") {
|
|
287
|
+
expandsStr += ` ${safeRevKey}?: ${childRecordType};\n`;
|
|
288
|
+
} else {
|
|
289
|
+
expandsStr += ` ${safeRevKey}?: Array<${childRecordType}>;\n`;
|
|
290
|
+
}
|
|
291
|
+
});
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
collectionTypesStr += ` "${col.name}": {\n${fieldsStr} };\n`;
|
|
@@ -460,10 +528,58 @@ declare global {
|
|
|
460
528
|
base64EncodeBuffer(buffer: ArrayBuffer | Uint8Array): string;
|
|
461
529
|
base64Decode(text: string): string;
|
|
462
530
|
base64DecodeBuffer(text: string): ArrayBuffer;
|
|
531
|
+
textEncode(text: string): ArrayBuffer;
|
|
532
|
+
textDecode(data: ArrayBuffer | Uint8Array): string;
|
|
533
|
+
hexEncode(data: string | ArrayBuffer | Uint8Array): string;
|
|
534
|
+
hexDecode(hex: string): ArrayBuffer;
|
|
463
535
|
sleep(ms: number): Promise<void>;
|
|
464
536
|
randomHex(len?: number): string;
|
|
465
537
|
};
|
|
466
538
|
|
|
539
|
+
/** Standard Web APIs */
|
|
540
|
+
const crypto: {
|
|
541
|
+
randomUUID(): string;
|
|
542
|
+
getRandomValues<T extends Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | null>(array: T): T;
|
|
543
|
+
subtle: {
|
|
544
|
+
digest(algorithm: string | any, data: ArrayBuffer | Uint8Array): Promise<ArrayBuffer>;
|
|
545
|
+
importKey(format: string, keyData: any, algo: any, extractable: boolean, usages: string[]): Promise<any>;
|
|
546
|
+
sign(algorithm: string | any, key: any, data: ArrayBuffer | Uint8Array): Promise<ArrayBuffer>;
|
|
547
|
+
verify(algorithm: string | any, key: any, signature: ArrayBuffer | Uint8Array, data: ArrayBuffer | Uint8Array): Promise<boolean>;
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
class Blob {
|
|
552
|
+
constructor(sources?: any[], options?: any);
|
|
553
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
554
|
+
text(): Promise<string>;
|
|
555
|
+
readonly size: number;
|
|
556
|
+
readonly type: string;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
class ReadableStream {
|
|
560
|
+
constructor(underlyingSource?: any);
|
|
561
|
+
getReader(): any;
|
|
562
|
+
cancel(): Promise<void>;
|
|
563
|
+
readonly locked: boolean;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const btoa: (data: string) => string;
|
|
567
|
+
const atob: (data: string) => string;
|
|
568
|
+
|
|
569
|
+
class TextEncoder {
|
|
570
|
+
constructor(encoding?: string);
|
|
571
|
+
encode(input?: string): Uint8Array;
|
|
572
|
+
encodeInto(source: string, destination: Uint8Array): { read: number; written: number };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
class TextDecoder {
|
|
576
|
+
constructor(label?: string, options?: { fatal?: boolean; ignoreBOM?: boolean });
|
|
577
|
+
decode(input?: ArrayBuffer | Uint8Array, options?: { stream?: boolean }): string;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function setTimeout(callback: Function, ms: number): string;
|
|
581
|
+
function clearTimeout(id: string): void;
|
|
582
|
+
|
|
467
583
|
/** Environment & Configuration Secrets Accessor */
|
|
468
584
|
const $env: {
|
|
469
585
|
get(key: string): Promise<string>;
|
|
@@ -484,7 +600,7 @@ declare global {
|
|
|
484
600
|
export {};
|
|
485
601
|
`;
|
|
486
602
|
fs.writeFileSync("apexkit.d.ts", types.trim());
|
|
487
|
-
console.log(" 📄 Generated apexkit.d.ts with
|
|
603
|
+
console.log(" 📄 Generated apexkit.d.ts with Accurate Scaled & Relational Typings");
|
|
488
604
|
|
|
489
605
|
const jsconfig = {
|
|
490
606
|
compilerOptions: {
|
|
@@ -566,7 +682,7 @@ export default async function (context) {
|
|
|
566
682
|
<head>
|
|
567
683
|
<meta charset="UTF-8">
|
|
568
684
|
<title>{{ title }}</title>
|
|
569
|
-
<
|
|
685
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
570
686
|
<script src="/static/js/htmx.js"></script>
|
|
571
687
|
<script src="/static/js/alpine.js" defer></script>
|
|
572
688
|
</head>
|
|
@@ -575,10 +691,10 @@ export default async function (context) {
|
|
|
575
691
|
<h1 class="text-3xl font-bold text-indigo-400">{{ title }}</h1>
|
|
576
692
|
|
|
577
693
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
578
|
-
{% for
|
|
694
|
+
{% for post in posts %}
|
|
579
695
|
<div class="p-4 bg-slate-800 rounded-lg border border-slate-700 shadow">
|
|
580
|
-
<h2 class="text-xl font-semibold">{{
|
|
581
|
-
<p class="text-slate-400 text-sm mt-2">{{
|
|
696
|
+
<h2 class="text-xl font-semibold">{{ post.data.title | default(value="Item #" ~ post.id) }}</h2>
|
|
697
|
+
<p class="text-slate-400 text-sm mt-2">{{ post.created }}</p>
|
|
582
698
|
</div>
|
|
583
699
|
{% endfor %}
|
|
584
700
|
</div>
|