@rebasepro/codegen 0.19.1 → 0.19.2-canary.g09316f6

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/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug } from "@rebasepro/common";
1
+ import { effectiveAccess, fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug } from "@rebasepro/common";
2
2
  //#region src/utils.ts
3
3
  /**
4
4
  * Utility functions for the SDK generator
@@ -215,7 +215,7 @@ function line(key, type, optional) {
215
215
  return ` ${emitKey(key)}${optional ? "?" : ""}: ${type};`;
216
216
  }
217
217
  /**
218
- * The keys `excludeFromApi` takes off the API surface — in *both* directions.
218
+ * The keys nobody can reach take off the API surface — in *both* directions.
219
219
  *
220
220
  * `excludeFromApi` means one thing: the API surface does not mention this
221
221
  * property. `Row` already honoured that; `Insert` and `Update` deliberately did
@@ -225,15 +225,23 @@ function line(key, type, optional) {
225
225
  * *accepts* such a field on a write — this describes the surface, it does not
226
226
  * add an enforcement point — but nothing generated advertises it.
227
227
  *
228
+ * Read through `effectiveAccess`, so the flag and its longhand
229
+ * `access: { read: [], write: [] }` produce the same file. A *role* rule is
230
+ * deliberately not honoured here and `Row` is unchanged by one: a generated type
231
+ * is one shape for every caller, and there is no `Row` that is right for both a
232
+ * reader who holds `hr` and one who does not. The server is the enforcement
233
+ * point; the types describe the surface a caller may name.
234
+ *
228
235
  * Keyed by the property name *and* by its column name, the same pair the
229
- * server's `stripExcluded` deletes, so a foreign key or a relation addressed
236
+ * server's `stripUnreadable` deletes, so a foreign key or a relation addressed
230
237
  * under the column name cannot put the property back.
231
238
  */
232
239
  function excludedApiKeys(properties) {
233
240
  const excluded = /* @__PURE__ */ new Set();
234
241
  for (const [key, rawProp] of Object.entries(properties)) {
235
242
  const prop = rawProp;
236
- if (!prop?.excludeFromApi) continue;
243
+ const access = effectiveAccess(prop);
244
+ if (access?.read?.length !== 0 || access?.write?.length !== 0) continue;
237
245
  excluded.add(key);
238
246
  if (prop.columnName) excluded.add(prop.columnName);
239
247
  }
@@ -323,6 +331,13 @@ function generateTypedefs(input) {
323
331
  }
324
332
  emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);
325
333
  lines.push(" };");
334
+ lines.push(" Relations: {");
335
+ for (const [key, relation] of Object.entries(resolvedRelations)) {
336
+ const slug = resolveTargetCollection(relation)?.slug ?? relation.targetSlug;
337
+ const accessor = slug ? accessors.get(slug) : void 0;
338
+ lines.push(` ${emitKey(key)}: ${accessor ? emitString(accessor) : "never"};`);
339
+ }
340
+ lines.push(" };");
326
341
  lines.push(" };");
327
342
  }
328
343
  lines.push("}");
@@ -335,9 +350,97 @@ function generateTypedefs(input) {
335
350
  lines.push("");
336
351
  lines.push("export type CollectionsDictionary = typeof collectionsDictionary;");
337
352
  lines.push("");
353
+ lines.push(...includeHelperLines());
338
354
  return lines.join("\n");
339
355
  }
340
356
  /**
357
+ * The `include` type machinery, emitted into the generated file.
358
+ *
359
+ * It lives here rather than in `@rebasepro/types` because it is the *graph*
360
+ * that makes it work, and the graph only exists once a project's collections
361
+ * have been generated. `IncludeSpec` in `@rebasepro/types` is deliberately
362
+ * unconstrained — a hand-written row type has no relations in it to check
363
+ * against — and these narrow it for a project that does have them.
364
+ *
365
+ * Two things a caller gets:
366
+ *
367
+ * - `IncludeFor<"posts">` — an include whose keys are relations that exist,
368
+ * recursively, so `include: { comments: { include: { authr: true } } }` is a
369
+ * compile error rather than a 400 at runtime.
370
+ * - `RowWith<"posts", I>` — the row a read with that include returns, where
371
+ * every included relation is **present** rather than optional. `Row` types a
372
+ * relation as optional because it is absent from every read that did not ask
373
+ * for it; once a read has asked, `row.author.name` should not need a `?.`.
374
+ *
375
+ * The depth bound matches the server's (`MAX_INCLUDE_DEPTH`), and is spelled as
376
+ * a decrementing tuple because TypeScript has no arithmetic — `Prev[3]` is `2`.
377
+ * Without a bound, a self-referencing relation makes the type infinite and the
378
+ * compiler gives up with "type instantiation is excessively deep".
379
+ */
380
+ function includeHelperLines() {
381
+ return [
382
+ "/** Which collection each of `A`'s relations reaches. */",
383
+ "export type RelationsOf<A extends CollectionName> =",
384
+ " Database[A] extends { Relations: infer R } ? R : Record<string, never>;",
385
+ "",
386
+ "/** The names of `A`'s relations. */",
387
+ "export type RelationKeys<A extends CollectionName> = keyof RelationsOf<A> & string;",
388
+ "",
389
+ "/** The collection a relation reaches, or `never` when it left this project. */",
390
+ "export type RelationTarget<A extends CollectionName, K extends RelationKeys<A>> =",
391
+ " RelationsOf<A>[K] extends CollectionName ? RelationsOf<A>[K] : never;",
392
+ "",
393
+ "/** Counts `IncludeFor` down; TypeScript has no arithmetic. */",
394
+ "type Prev = [never, 0, 1, 2, 3];",
395
+ "",
396
+ "/**",
397
+ " * Per-relation options, minus `include` — which `IncludeFor` supplies at",
398
+ " * the next depth so the nested keys are checked against the *target's*",
399
+ " * relations rather than this collection's.",
400
+ " */",
401
+ "export interface IncludeOptionsFor<A extends CollectionName, K extends RelationKeys<A>, D extends number> {",
402
+ " limit?: number;",
403
+ " where?: Record<string, unknown>;",
404
+ " logical?: unknown;",
405
+ " orderBy?: unknown;",
406
+ " fields?: string[];",
407
+ " include?: RelationTarget<A, K> extends CollectionName",
408
+ " ? IncludeFor<RelationTarget<A, K>, Prev[D]>",
409
+ " : never;",
410
+ "}",
411
+ "",
412
+ "/**",
413
+ " * An `include` for collection `A`: its relation names, dotted paths, or the",
414
+ " * parametrised tree — with every key checked against the relations that",
415
+ " * actually exist, at every level.",
416
+ " */",
417
+ "export type IncludeFor<A extends CollectionName, D extends number = 3> =",
418
+ " D extends 0",
419
+ " ? never",
420
+ " : | readonly (RelationKeys<A> | \"*\")[]",
421
+ " | { [K in RelationKeys<A>]?: true | IncludeOptionsFor<A, K, D> };",
422
+ "",
423
+ "/** The relation names an include asks for at the top level. */",
424
+ "type IncludedKeys<A extends CollectionName, I> =",
425
+ " I extends readonly (infer K)[]",
426
+ " ? Extract<K, RelationKeys<A>>",
427
+ " : Extract<keyof I, RelationKeys<A>>;",
428
+ "",
429
+ "/**",
430
+ " * The row a read with include `I` returns: `Row`, with every included",
431
+ " * relation made **required**.",
432
+ " *",
433
+ " * `Row` types a relation as optional because it is absent from every read",
434
+ " * that did not name it. Once a read has named it, it is there — and having",
435
+ " * to write `row.author?.name` after asking for the author is the type",
436
+ " * describing a possibility the query already ruled out.",
437
+ " */",
438
+ "export type RowWith<A extends CollectionName, I> =",
439
+ " Database[A][\"Row\"] & Required<Pick<Database[A][\"Row\"], IncludedKeys<A, I> & keyof Database[A][\"Row\"]>>;",
440
+ ""
441
+ ];
442
+ }
443
+ /**
341
444
  * The two ways a write can name a `belongsTo` target, both of which the server
342
445
  * accepts: the foreign key under its own wire name (`{ authorId: 5 }`, which
343
446
  * passes through to the `author_id` column untouched) and the relation
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/utils.ts","../src/generate-types.ts","../src/index.ts"],"sourcesContent":["/**\n * Utility functions for the SDK generator\n */\n\n/**\n * Convert a slug/snake_case string to PascalCase\n * e.g. \"private_notes\" → \"PrivateNotes\"\n *\n * Capitals already inside a word are meaningful and are kept: lowercasing the\n * tail of every chunk turned \"TestEntities\" into \"Testentities\", which is what\n * ended up in the generated type names. SHOUTING_CASE is the one shape where\n * the tail is not meaningful, so it is folded down.\n */\nexport function toPascalCase(str: string): string {\n return str\n .split(/[_\\-\\s]+/)\n .filter(Boolean)\n .map(word => {\n const rest = /^[A-Z0-9]+$/.test(word) ? word.slice(1).toLowerCase() : word.slice(1);\n return word.charAt(0).toUpperCase() + rest;\n })\n .join(\"\");\n}\n\n/**\n * Convert a slug/snake_case string to camelCase\n * e.g. \"private_notes\" → \"privateNotes\"\n */\nexport function toCamelCase(str: string): string {\n if (!/[_\\-\\s]/.test(str)) {\n return str.charAt(0).toLowerCase() + str.slice(1);\n }\n const pascal = toPascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Convert a slug to a safe JS identifier\n * e.g. \"private-notes\" → \"privateNotes\"\n */\nexport function toSafeIdentifier(str: string): string {\n return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, \"_\"));\n}\n\n/**\n * Indent a block of text by a given number of spaces\n */\nexport function indent(text: string, spaces: number): string {\n const pad = \" \".repeat(spaces);\n return text\n .split(\"\\n\")\n .map(line => (line.trim() ? pad + line : line))\n .join(\"\\n\");\n}\n","import { CollectionConfig, Property, Properties, MapProperty, ArrayProperty, StringProperty, NumberProperty, ResolvedRelation } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { toSafeIdentifier } from \"./utils\";\n\n/**\n * A schema that cannot be expressed as a valid TypeScript file.\n *\n * Thrown rather than emitted. The generator used to concatenate whatever it was\n * given, so a slug that collided with another one, or that was not an\n * identifier, produced a file that either failed to compile or — worse —\n * compiled while quietly routing one collection to another's slug.\n */\nexport class CodegenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CodegenError\";\n }\n}\n\nconst IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A property name for the emitted TypeScript: verbatim when it is a valid\n * identifier, quoted otherwise.\n *\n * Every key that reaches the output goes through here. Column names are not\n * required to be identifiers — `\"order\"`, `\"user id\"`, a quoted Postgres\n * identifier — and the previous behaviour of camel-casing them into shape\n * renamed the column in the type while the wire kept the original, so the\n * generated `Row` described fields that did not exist.\n */\nfunction emitKey(key: string): string {\n return IDENTIFIER.test(key) ? key : JSON.stringify(key);\n}\n\n/**\n * A string literal, escaped.\n *\n * `\"${value}\"` was the previous form. A value containing a quote closed the\n * literal early, which at best broke the file and at worst let a slug from a\n * remote contract inject top-level statements into a file the developer\n * compiles and bundles.\n */\nfunction emitString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** The `id`s of an enum declared as an array, an array of `{ id }`, or an object map. */\nfunction enumIds(raw: unknown): (string | number)[] {\n if (Array.isArray(raw)) {\n return raw.map((entry: string | number | { id: string | number }) =>\n entry && typeof entry === \"object\" ? entry.id : entry);\n }\n if (raw && typeof raw === \"object\") return Object.keys(raw);\n return [];\n}\n\nfunction propertyToTypeScriptType(prop: Property): string {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) {\n const ids = enumIds(sp.enum);\n if (ids.length === 0) return \"string\";\n return ids.map(v => emitString(String(v))).join(\" | \");\n }\n return \"string\";\n }\n case \"number\": {\n const np = prop as NumberProperty;\n if (np.enum) {\n const ids = enumIds(np.enum);\n const numbers = ids.map(Number);\n // A numeric enum carrying something that is not a number cannot\n // be written as a union of numeric literals. Widening to\n // `number` is imprecise; emitting `NaN | undefined` is invalid.\n if (ids.length === 0 || numbers.some(n => !Number.isFinite(n))) return \"number\";\n return numbers.map(n => String(n)).join(\" | \");\n }\n return \"number\";\n }\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"string\"; // ISO 8601 string over the wire\n case \"geopoint\":\n return \"{ latitude: number; longitude: number; }\";\n case \"reference\":\n return \"string | number\";\n case \"relation\":\n return \"string | number\";\n case \"map\": {\n const mapProp = prop as MapProperty;\n if (mapProp.properties) {\n const inner = Object.entries(mapProp.properties)\n .map(([k, v]) => {\n const child = v as Property;\n // Nested fields carry validation like any other. Emitting\n // them all required claimed a shape the payload does not\n // have to satisfy.\n const optional = !child.validation?.required;\n const type = propertyToTypeScriptType(child);\n return `${emitKey(k)}${optional ? \"?\" : \"\"}: ${optional ? `${type} | null` : type};`;\n })\n .join(\" \");\n return `{ ${inner} }`;\n }\n return \"Record<string, unknown>\";\n }\n case \"array\": {\n const arrProp = prop as ArrayProperty;\n if (arrProp.of) {\n return `Array<${propertyToTypeScriptType(arrProp.of as Property)}>`;\n }\n return \"Array<unknown>\";\n }\n case \"vector\":\n return \"number[]\";\n case \"binary\":\n return \"string\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Unwrap a relation target that arrived as a module namespace rather than the\n * collection itself — `target: () => import(\"./authors\")` is a common slip.\n */\nfunction resolveTargetCollection(relation: ResolvedRelation): CollectionConfig | undefined {\n try {\n let target = relation.target() as CollectionConfig & { default?: CollectionConfig; __esModule?: boolean };\n if (target && (target.default || target.__esModule)) {\n target = (target.default ?? target) as typeof target;\n }\n return target;\n } catch {\n return undefined;\n }\n}\n\n/** The TypeScript type of a foreign key: whatever the target's primary key is. */\nfunction foreignKeyType(relation: ResolvedRelation): string {\n const target = resolveTargetCollection(relation);\n if (!target?.properties) return \"string | number\";\n const idProp = Object.entries(target.properties).find(([_, p]) => (p as Record<string, unknown>).isId);\n if (!idProp) return \"string | number\";\n return (idProp[1] as Property).type === \"number\" ? \"number\" : \"string\";\n}\n\n/** Whether a property is the collection's primary key. */\nfunction isPrimaryKey(prop: Property): boolean {\n return Boolean((prop as unknown as Record<string, unknown>).isId);\n}\n\n/**\n * Whether the server assigns this primary key, so a write does not have to.\n * `true` and `\"manual\"` both mean the caller supplies it.\n */\nfunction isAutoAssignedId(prop: Property): boolean {\n const isId = (prop as unknown as Record<string, unknown>).isId;\n return Boolean(isId) && isId !== \"manual\" && isId !== true;\n}\n\n/**\n * The type an *included* relation arrives as: the target's own row, inlined.\n *\n * This is what the read pipeline actually serves — `toRestRow` puts the\n * target's flat columns where the relation was, and the SDK and the HTTP API\n * both go through it. It is deliberately *not* a `{ __type: \"relation\" }`\n * envelope: that shape is the admin's view-model and never reaches a\n * developer's `find()`.\n *\n * Falls back to an open record when the target is not part of this generation\n * run, since there is no `Row` to point at.\n */\nfunction includedRelationType(\n relation: ResolvedRelation,\n accessors: Map<string, string>\n): string {\n const target = resolveTargetCollection(relation);\n const slug = target?.slug ?? relation.targetSlug;\n const accessor = slug ? accessors.get(slug) : undefined;\n const rowType = accessor\n ? `Database[${emitString(accessor)}][\"Row\"]`\n : \"Record<string, unknown>\";\n return relation.cardinality === \"many\" ? `Array<${rowType}>` : rowType;\n}\n\n/**\n * Map every slug to the property name it is reachable under on `client.data`.\n *\n * The accessor is a safe identifier because `client.data.myNotes` is the point\n * of generating this at all, and `collectionsDictionary` maps it back to the\n * slug the wire uses. Two slugs that safe down to the same identifier cannot\n * both have it: the interface would not compile, and the dictionary — an object\n * literal — would silently keep only the last, routing one collection's reads\n * to the other's table. There is no defensible way to pick, so this refuses.\n */\nfunction buildAccessors(collections: CollectionConfig[]): Map<string, string> {\n const accessors = new Map<string, string>();\n const bySafeName = new Map<string, string>();\n\n for (const collection of collections) {\n const slug = collection.slug;\n if (typeof slug !== \"string\" || slug.length === 0) {\n throw new CodegenError(\n \"A collection has no slug, so it has no name to generate a type for. \" +\n \"Every collection needs a unique `slug`.\"\n );\n }\n\n const safe = toSafeIdentifier(slug);\n if (safe.length === 0) {\n throw new CodegenError(\n `The slug ${emitString(slug)} has no characters that can form a property name, ` +\n \"so it cannot be reached as `client.data.<name>`. Use a slug containing \" +\n \"letters, digits, underscores or dashes.\"\n );\n }\n\n const existing = bySafeName.get(safe);\n if (existing !== undefined) {\n throw new CodegenError(\n `The collections ${emitString(existing)} and ${emitString(slug)} both generate the ` +\n `accessor \"${safe}\", so only one of them could be reached from the generated client ` +\n \"and the other's reads would silently go to the wrong table. Rename one of the slugs.\"\n );\n }\n\n bySafeName.set(safe, slug);\n accessors.set(slug, safe);\n }\n\n return accessors;\n}\n\n/** One emitted `key: type;` line, already indented. */\nfunction line(key: string, type: string, optional: boolean): string {\n return ` ${emitKey(key)}${optional ? \"?\" : \"\"}: ${type};`;\n}\n\n/**\n * The keys `excludeFromApi` takes off the API surface — in *both* directions.\n *\n * `excludeFromApi` means one thing: the API surface does not mention this\n * property. `Row` already honoured that; `Insert` and `Update` deliberately did\n * not, on the reading that the column is stripped from responses rather than\n * from writes. That left the generated types as the one place a password hash\n * was still named, and it invited a client to send one. The server still\n * *accepts* such a field on a write — this describes the surface, it does not\n * add an enforcement point — but nothing generated advertises it.\n *\n * Keyed by the property name *and* by its column name, the same pair the\n * server's `stripExcluded` deletes, so a foreign key or a relation addressed\n * under the column name cannot put the property back.\n */\nfunction excludedApiKeys(properties: Properties): Set<string> {\n const excluded = new Set<string>();\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (!prop?.excludeFromApi) continue;\n excluded.add(key);\n if (prop.columnName) excluded.add(prop.columnName);\n }\n return excluded;\n}\n\nexport function generateTypedefs(input: CollectionConfig[]): string {\n // Sorted here rather than only in `generate-sdk`: the output is\n // order-dependent and `rebase doctor` regenerates it in memory to diff\n // against the file on disk. While only the writer sorted, a project whose\n // file order differed from its slug order was reported permanently stale.\n const collections = sortCollectionsBySlug(input);\n const accessors = buildAccessors(collections);\n const lines: string[] = [\n \"/**\",\n \" * This file was auto-generated by Rebase.\",\n \" * Do not make direct changes to the file.\",\n \" */\",\n \"\",\n \"export interface Database {\"\n ];\n\n for (const collection of collections) {\n const properties = (collection.properties ?? {}) as Properties;\n\n // Resolve relations\n let resolvedRelations: Record<string, ResolvedRelation> = {};\n try {\n resolvedRelations = resolveCollectionRelations(collection);\n } catch (e) {\n // Swallowed before, which made this the quietest way to ship a\n // wrong type. The foreign-key columns are emitted from the resolved\n // relations rather than from the properties, so losing them drops\n // both the relation fields *and* columns that exist in the\n // database — and the resulting error surfaces in the user's code,\n // typechecking against a `Database` that is missing `author_id`,\n // with nothing pointing back at generation.\n //\n // A target thunk usually throws because of a circular import; the\n // boot-time relation validator names the same cause.\n console.warn(\n `[rebase] Could not resolve the relations of \"${collection.slug}\", so its generated ` +\n \"type has no relation fields and none of their foreign-key columns. This is usually a \" +\n \"circular import in the collection files — make sure the target is `() => otherCollection` \" +\n `and not evaluated at module load.\\n ${e instanceof Error ? e.message : String(e)}`\n );\n }\n\n // Subcollections are collections in their own right and are addressed\n // over a nested path, not as `client.data.<name>`. Generating them here\n // would invent an accessor the client does not serve, so they are\n // skipped — loudly, because doing it silently is how a developer\n // concludes the generator is broken.\n const subcollections = (collection as unknown as { subcollections?: unknown[] }).subcollections;\n if (Array.isArray(subcollections) && subcollections.length > 0) {\n console.warn(\n `[rebase] \"${collection.slug}\" declares ${subcollections.length} subcollection(s), which are ` +\n \"not part of the generated Database: they are reached over a nested path \" +\n `(\\`data/${collection.slug}/<id>/<relation>\\`), not as a top-level accessor. Register a ` +\n \"subcollection as a collection of its own if you want a typed accessor for it.\"\n );\n }\n\n lines.push(` ${emitKey(accessors.get(collection.slug)!)}: {`);\n\n // ── Row Type ──\n //\n // What a read serves. There is no field selection in the query API, so\n // every column of a row comes back on every read; a column is optional\n // here only because the value may be absent or null, never because the\n // caller might not have asked for it.\n lines.push(\" Row: {\");\n const emittedKeys = new Set<string>();\n\n // Off the surface entirely — see `excludedApiKeys`. Seeding the emitted\n // set means every later pass (foreign keys, relations, unresolved\n // relations) skips them too, since each of those already refuses to\n // emit a key twice.\n const excluded = excludedApiKeys(properties);\n for (const key of excluded) emittedKeys.add(key);\n\n // 1. Direct properties\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (excluded.has(key)) continue;\n\n const tsType = propertyToTypeScriptType(prop);\n // A primary key is on every row a read can return, whether or not\n // anyone wrote `validation: { required: true }` next to it —\n // introspection never does, so `row.id` was `string | undefined`\n // for every baas project.\n const isRequired = Boolean(prop.validation?.required) || isPrimaryKey(prop);\n lines.push(line(key, isRequired ? tsType : `${tsType} | null`, !isRequired));\n emittedKeys.add(key);\n }\n\n // 2. FK columns from relations.\n //\n // Emitted under the relation's *wire* name, which is what\n // `fieldKeyForColumn` answers: `localKey` is the database column\n // (`author_id`) and the row arrives keyed `authorId`. Emitting the\n // column, which this used to do, described a key the JSON does not\n // carry and hid the one it does — including from `where` and `orderBy`,\n // which are keyed off this type. The column name is not a second name\n // for the field: it is the name of a different thing.\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n const fkKey = fieldKeyForColumn(collection, relation.localKey);\n if (emittedKeys.has(fkKey)) continue;\n\n const fkType = foreignKeyType(relation);\n\n // A relation addressed by the same name as its own foreign key\n // is served *over* that column when the read includes it: the\n // query nests the target under the relation name, and the\n // scalar it shadows is gone. Both outcomes are real, so the\n // column is typed as both — which is what stops a plain\n // `const id: string = row.author_id` from compiling.\n const shadowedByInclude = relKey === fkKey;\n const tsType = shadowedByInclude\n ? `${fkType} | ${includedRelationType(relation, accessors)}`\n : fkType;\n\n const isRequired = isRelationRequired(collection, relation) && !shadowedByInclude;\n lines.push(line(fkKey, isRequired ? tsType : `${tsType} | null`, !isRequired));\n emittedKeys.add(fkKey);\n }\n }\n\n // 3. Relation fields — the target's own row, inlined.\n //\n // Optional throughout: a relation is only loaded when the read names it\n // in `include`, so it is absent from every other read.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (emittedKeys.has(key)) continue;\n lines.push(line(key, includedRelationType(relation, accessors), true));\n emittedKeys.add(key);\n }\n\n // A `relation` property whose relation could not be resolved — an\n // engine without relation support, or a target that did not load. It is\n // still a column on the row, so it is still typed, just not precisely.\n for (const [key, rawProp] of Object.entries(properties)) {\n if ((rawProp as Property).type !== \"relation\") continue;\n if (emittedKeys.has(key)) continue;\n lines.push(line(key, \"Record<string, unknown>\", true));\n emittedKeys.add(key);\n }\n lines.push(\" };\");\n\n // ── Insert Type ──\n //\n // What `create()` accepts, minus the `excludeFromApi` columns: the\n // property is off the API surface in both directions, so a generated\n // client never names it.\n lines.push(\" Insert: {\");\n emittedKeys.clear();\n for (const key of excluded) emittedKeys.add(key);\n\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (excluded.has(key)) continue;\n const tsType = propertyToTypeScriptType(prop);\n const isOptional = !prop.validation?.required || isAutoAssignedId(prop);\n lines.push(line(key, tsType, isOptional));\n emittedKeys.add(key);\n }\n\n emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);\n lines.push(\" };\");\n\n // ── Update Type ──\n //\n // Everything optional, and the primary key left out: an update\n // addresses a row by id, it does not reassign one. Accepting `id` here\n // typechecked `update(id, { id: someoneElses })`.\n lines.push(\" Update: {\");\n emittedKeys.clear();\n for (const key of excluded) emittedKeys.add(key);\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (isPrimaryKey(prop)) continue;\n if (excluded.has(key)) continue;\n lines.push(line(key, propertyToTypeScriptType(prop), true));\n emittedKeys.add(key);\n }\n emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);\n lines.push(\" };\");\n\n lines.push(\" };\");\n }\n\n lines.push(\"}\");\n lines.push(\"\");\n lines.push(\"export type CollectionName = keyof Database;\");\n lines.push(\"\");\n lines.push(\"export const collectionsDictionary = {\");\n for (const collection of collections) {\n lines.push(` ${emitKey(accessors.get(collection.slug)!)}: ${emitString(collection.slug)},`);\n }\n lines.push(\"} as const;\");\n lines.push(\"\");\n // Describes the const above rather than restating its keys. The previous\n // `{ [K in CollectionName]: K }` said every value equalled its key, which is\n // false for any slug that is not already an identifier — `myNotes` maps to\n // `\"my-notes\"` — so the export the CLI tells people to pass did not satisfy\n // its own published type.\n lines.push(\"export type CollectionsDictionary = typeof collectionsDictionary;\");\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * The two ways a write can name a `belongsTo` target, both of which the server\n * accepts: the foreign key under its own wire name (`{ authorId: 5 }`, which\n * passes through to the `author_id` column untouched) and the relation\n * *property* (`{ author: 5 }`, which the write transformer maps onto that\n * column).\n *\n * Only the first was generated, so the documented and idiomatic write shape was\n * a type error.\n *\n * The second form is emitted under the **property key**, not the resolved\n * relation name, because that is what the transformer keys off: it looks the\n * payload key up in `properties` and only treats it as a relation if what it\n * finds there is one. A relation whose `relationName` differs from its property\n * key is reachable as the property and not as the name, so emitting the name\n * would have offered a key that writes to a column that does not exist.\n */\nfunction emitWritableRelations(\n lines: string[],\n collection: CollectionConfig,\n properties: Properties,\n resolvedRelations: Record<string, ResolvedRelation>,\n emittedKeys: Set<string>,\n allOptional: boolean\n): void {\n // The target's primary key type is the same one `Row` uses. A hardcoded\n // `string | number` here accepted a string for a numeric-keyed target.\n const emit = (key: string, relation: ResolvedRelation): void => {\n if (emittedKeys.has(key)) return;\n const optional = allOptional || !isRelationRequired(collection, relation);\n lines.push(line(key, foreignKeyType(relation), optional));\n emittedKeys.add(key);\n };\n\n for (const relation of Object.values(resolvedRelations)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n emit(fieldKeyForColumn(collection, relation.localKey), relation);\n }\n }\n\n for (const [key, rawProp] of Object.entries(properties)) {\n if ((rawProp as Property).type !== \"relation\") continue;\n const relation = findRelation(resolvedRelations, key);\n if (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n }\n}\n","/**\n * @rebasepro/codegen\n *\n * Generates a purely typed Typescript database definition.\n */\n\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { generateTypedefs } from \"./generate-types\";\n\nexport { generateTypedefs, CodegenError } from \"./generate-types\";\nexport { toPascalCase, toCamelCase, toSafeIdentifier, indent } from \"./utils\";\n\n// ─── Public API ────────────────────────────────────────────────────\n\nexport interface GeneratedFile {\n /** Relative file path within the output directory */\n path: string;\n /** File content */\n content: string;\n}\n\nexport interface GenerateSDKOptions {\n /** Whether to include a README file (default: true) */\n includeReadme?: boolean;\n}\n\nexport function generateSDK(\n collections: CollectionConfig[],\n options: GenerateSDKOptions = {}\n): GeneratedFile[] {\n const files: GeneratedFile[] = [];\n\n files.push({\n path: \"database.types.ts\",\n content: generateTypedefs(collections)\n });\n\n if (options.includeReadme !== false) {\n files.push({\n path: \"README.md\",\n content: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import { collectionsDictionary, type Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // Maps each accessor back to the slug the wire uses. Without it a\n // hyphenated slug is not resolvable from the property name alone.\n collections: collectionsDictionary,\n });\n\n // Property access is the typed surface: rows, filters and sorts are all\n // checked against the generated Database.\n const { data: users } = await rebase.data.users.find();\n console.log(users[0].email); // flat access — no .values wrapper\n \\`\\`\\`\n\n## Field names are the ones the API serves\n\nThe generated \\`Row\\` uses each field's **wire** name — the key it arrives under in\nJSON — and nothing here renames anything.\n\n- **A declared property is its key in the collection.** A property keyed\n \\`createdAt\\` is \\`row.createdAt\\`, whatever \\`columnName\\` says. A column name is\n the name of a different thing: where the value lives, not what the API calls it.\n- **A foreign key derived from a relation is camelCase**, because that is what\n the wire carries. A \\`belongsTo\\` named \\`author\\` gives you \\`row.authorId\\`, not\n the column spelling.\n- **A collection accessor is camelCase too** (\\`my-notes\\` → \\`rebase.data.myNotes\\`),\n which is what \\`collectionsDictionary\\` maps back to the slug.\n\n\\`where\\` and \\`orderBy\\` are keyed off the same type, so what compiles is what the\nbackend answers to.\n\n## \\`Row\\` vs \\`Insert\\` vs \\`Update\\`\n\n| Type | What it describes |\n|---|---|\n| \\`Row\\` | What a read serves. Nullable columns are \\`T \\\\| null\\`; relations appear only when \\`include\\` names them. |\n| \\`Insert\\` | What \\`create()\\` accepts. Server-assigned ids are optional; a \\`belongsTo\\` target may be named either way (\\`{ author: 5 }\\` or \\`{ authorId: 5 }\\`). |\n| \\`Update\\` | What \\`update()\\` accepts. Everything optional, and the primary key is not settable. |\n\nA property marked \\`excludeFromApi\\` is absent from all three: the API surface\ndoes not mention it, in either direction. The server holds the same line — a\nread never serves the column and a write naming it is refused — so this is a\nguarantee rather than a description, and nothing generated names a password\nhash.\n\nIf you need an untyped escape hatch, \\`rebase.data.collection(slug)\\` still works —\nbut it is generic over \\`Record<string, unknown>\\` and gives up everything above.\n`\n });\n }\n\n return files;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/utils.ts","../src/generate-types.ts","../src/index.ts"],"sourcesContent":["/**\n * Utility functions for the SDK generator\n */\n\n/**\n * Convert a slug/snake_case string to PascalCase\n * e.g. \"private_notes\" → \"PrivateNotes\"\n *\n * Capitals already inside a word are meaningful and are kept: lowercasing the\n * tail of every chunk turned \"TestEntities\" into \"Testentities\", which is what\n * ended up in the generated type names. SHOUTING_CASE is the one shape where\n * the tail is not meaningful, so it is folded down.\n */\nexport function toPascalCase(str: string): string {\n return str\n .split(/[_\\-\\s]+/)\n .filter(Boolean)\n .map(word => {\n const rest = /^[A-Z0-9]+$/.test(word) ? word.slice(1).toLowerCase() : word.slice(1);\n return word.charAt(0).toUpperCase() + rest;\n })\n .join(\"\");\n}\n\n/**\n * Convert a slug/snake_case string to camelCase\n * e.g. \"private_notes\" → \"privateNotes\"\n */\nexport function toCamelCase(str: string): string {\n if (!/[_\\-\\s]/.test(str)) {\n return str.charAt(0).toLowerCase() + str.slice(1);\n }\n const pascal = toPascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Convert a slug to a safe JS identifier\n * e.g. \"private-notes\" → \"privateNotes\"\n */\nexport function toSafeIdentifier(str: string): string {\n return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, \"_\"));\n}\n\n/**\n * Indent a block of text by a given number of spaces\n */\nexport function indent(text: string, spaces: number): string {\n const pad = \" \".repeat(spaces);\n return text\n .split(\"\\n\")\n .map(line => (line.trim() ? pad + line : line))\n .join(\"\\n\");\n}\n","import { CollectionConfig, Property, Properties, MapProperty, ArrayProperty, StringProperty, NumberProperty, ResolvedRelation } from \"@rebasepro/types\";\nimport { effectiveAccess, fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { toSafeIdentifier } from \"./utils\";\n\n/**\n * A schema that cannot be expressed as a valid TypeScript file.\n *\n * Thrown rather than emitted. The generator used to concatenate whatever it was\n * given, so a slug that collided with another one, or that was not an\n * identifier, produced a file that either failed to compile or — worse —\n * compiled while quietly routing one collection to another's slug.\n */\nexport class CodegenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CodegenError\";\n }\n}\n\nconst IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A property name for the emitted TypeScript: verbatim when it is a valid\n * identifier, quoted otherwise.\n *\n * Every key that reaches the output goes through here. Column names are not\n * required to be identifiers — `\"order\"`, `\"user id\"`, a quoted Postgres\n * identifier — and the previous behaviour of camel-casing them into shape\n * renamed the column in the type while the wire kept the original, so the\n * generated `Row` described fields that did not exist.\n */\nfunction emitKey(key: string): string {\n return IDENTIFIER.test(key) ? key : JSON.stringify(key);\n}\n\n/**\n * A string literal, escaped.\n *\n * `\"${value}\"` was the previous form. A value containing a quote closed the\n * literal early, which at best broke the file and at worst let a slug from a\n * remote contract inject top-level statements into a file the developer\n * compiles and bundles.\n */\nfunction emitString(value: string): string {\n return JSON.stringify(value);\n}\n\n/** The `id`s of an enum declared as an array, an array of `{ id }`, or an object map. */\nfunction enumIds(raw: unknown): (string | number)[] {\n if (Array.isArray(raw)) {\n return raw.map((entry: string | number | { id: string | number }) =>\n entry && typeof entry === \"object\" ? entry.id : entry);\n }\n if (raw && typeof raw === \"object\") return Object.keys(raw);\n return [];\n}\n\nfunction propertyToTypeScriptType(prop: Property): string {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) {\n const ids = enumIds(sp.enum);\n if (ids.length === 0) return \"string\";\n return ids.map(v => emitString(String(v))).join(\" | \");\n }\n return \"string\";\n }\n case \"number\": {\n const np = prop as NumberProperty;\n if (np.enum) {\n const ids = enumIds(np.enum);\n const numbers = ids.map(Number);\n // A numeric enum carrying something that is not a number cannot\n // be written as a union of numeric literals. Widening to\n // `number` is imprecise; emitting `NaN | undefined` is invalid.\n if (ids.length === 0 || numbers.some(n => !Number.isFinite(n))) return \"number\";\n return numbers.map(n => String(n)).join(\" | \");\n }\n return \"number\";\n }\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"string\"; // ISO 8601 string over the wire\n case \"geopoint\":\n return \"{ latitude: number; longitude: number; }\";\n case \"reference\":\n return \"string | number\";\n case \"relation\":\n return \"string | number\";\n case \"map\": {\n const mapProp = prop as MapProperty;\n if (mapProp.properties) {\n const inner = Object.entries(mapProp.properties)\n .map(([k, v]) => {\n const child = v as Property;\n // Nested fields carry validation like any other. Emitting\n // them all required claimed a shape the payload does not\n // have to satisfy.\n const optional = !child.validation?.required;\n const type = propertyToTypeScriptType(child);\n return `${emitKey(k)}${optional ? \"?\" : \"\"}: ${optional ? `${type} | null` : type};`;\n })\n .join(\" \");\n return `{ ${inner} }`;\n }\n return \"Record<string, unknown>\";\n }\n case \"array\": {\n const arrProp = prop as ArrayProperty;\n if (arrProp.of) {\n return `Array<${propertyToTypeScriptType(arrProp.of as Property)}>`;\n }\n return \"Array<unknown>\";\n }\n case \"vector\":\n return \"number[]\";\n case \"binary\":\n return \"string\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Unwrap a relation target that arrived as a module namespace rather than the\n * collection itself — `target: () => import(\"./authors\")` is a common slip.\n */\nfunction resolveTargetCollection(relation: ResolvedRelation): CollectionConfig | undefined {\n try {\n let target = relation.target() as CollectionConfig & { default?: CollectionConfig; __esModule?: boolean };\n if (target && (target.default || target.__esModule)) {\n target = (target.default ?? target) as typeof target;\n }\n return target;\n } catch {\n return undefined;\n }\n}\n\n/** The TypeScript type of a foreign key: whatever the target's primary key is. */\nfunction foreignKeyType(relation: ResolvedRelation): string {\n const target = resolveTargetCollection(relation);\n if (!target?.properties) return \"string | number\";\n const idProp = Object.entries(target.properties).find(([_, p]) => (p as Record<string, unknown>).isId);\n if (!idProp) return \"string | number\";\n return (idProp[1] as Property).type === \"number\" ? \"number\" : \"string\";\n}\n\n/** Whether a property is the collection's primary key. */\nfunction isPrimaryKey(prop: Property): boolean {\n return Boolean((prop as unknown as Record<string, unknown>).isId);\n}\n\n/**\n * Whether the server assigns this primary key, so a write does not have to.\n * `true` and `\"manual\"` both mean the caller supplies it.\n */\nfunction isAutoAssignedId(prop: Property): boolean {\n const isId = (prop as unknown as Record<string, unknown>).isId;\n return Boolean(isId) && isId !== \"manual\" && isId !== true;\n}\n\n/**\n * The type an *included* relation arrives as: the target's own row, inlined.\n *\n * This is what the read pipeline actually serves — `toRestRow` puts the\n * target's flat columns where the relation was, and the SDK and the HTTP API\n * both go through it. It is deliberately *not* a `{ __type: \"relation\" }`\n * envelope: that shape is the admin's view-model and never reaches a\n * developer's `find()`.\n *\n * Falls back to an open record when the target is not part of this generation\n * run, since there is no `Row` to point at.\n */\nfunction includedRelationType(\n relation: ResolvedRelation,\n accessors: Map<string, string>\n): string {\n const target = resolveTargetCollection(relation);\n const slug = target?.slug ?? relation.targetSlug;\n const accessor = slug ? accessors.get(slug) : undefined;\n const rowType = accessor\n ? `Database[${emitString(accessor)}][\"Row\"]`\n : \"Record<string, unknown>\";\n return relation.cardinality === \"many\" ? `Array<${rowType}>` : rowType;\n}\n\n/**\n * Map every slug to the property name it is reachable under on `client.data`.\n *\n * The accessor is a safe identifier because `client.data.myNotes` is the point\n * of generating this at all, and `collectionsDictionary` maps it back to the\n * slug the wire uses. Two slugs that safe down to the same identifier cannot\n * both have it: the interface would not compile, and the dictionary — an object\n * literal — would silently keep only the last, routing one collection's reads\n * to the other's table. There is no defensible way to pick, so this refuses.\n */\nfunction buildAccessors(collections: CollectionConfig[]): Map<string, string> {\n const accessors = new Map<string, string>();\n const bySafeName = new Map<string, string>();\n\n for (const collection of collections) {\n const slug = collection.slug;\n if (typeof slug !== \"string\" || slug.length === 0) {\n throw new CodegenError(\n \"A collection has no slug, so it has no name to generate a type for. \" +\n \"Every collection needs a unique `slug`.\"\n );\n }\n\n const safe = toSafeIdentifier(slug);\n if (safe.length === 0) {\n throw new CodegenError(\n `The slug ${emitString(slug)} has no characters that can form a property name, ` +\n \"so it cannot be reached as `client.data.<name>`. Use a slug containing \" +\n \"letters, digits, underscores or dashes.\"\n );\n }\n\n const existing = bySafeName.get(safe);\n if (existing !== undefined) {\n throw new CodegenError(\n `The collections ${emitString(existing)} and ${emitString(slug)} both generate the ` +\n `accessor \"${safe}\", so only one of them could be reached from the generated client ` +\n \"and the other's reads would silently go to the wrong table. Rename one of the slugs.\"\n );\n }\n\n bySafeName.set(safe, slug);\n accessors.set(slug, safe);\n }\n\n return accessors;\n}\n\n/** One emitted `key: type;` line, already indented. */\nfunction line(key: string, type: string, optional: boolean): string {\n return ` ${emitKey(key)}${optional ? \"?\" : \"\"}: ${type};`;\n}\n\n/**\n * The keys nobody can reach take off the API surface — in *both* directions.\n *\n * `excludeFromApi` means one thing: the API surface does not mention this\n * property. `Row` already honoured that; `Insert` and `Update` deliberately did\n * not, on the reading that the column is stripped from responses rather than\n * from writes. That left the generated types as the one place a password hash\n * was still named, and it invited a client to send one. The server still\n * *accepts* such a field on a write — this describes the surface, it does not\n * add an enforcement point — but nothing generated advertises it.\n *\n * Read through `effectiveAccess`, so the flag and its longhand\n * `access: { read: [], write: [] }` produce the same file. A *role* rule is\n * deliberately not honoured here and `Row` is unchanged by one: a generated type\n * is one shape for every caller, and there is no `Row` that is right for both a\n * reader who holds `hr` and one who does not. The server is the enforcement\n * point; the types describe the surface a caller may name.\n *\n * Keyed by the property name *and* by its column name, the same pair the\n * server's `stripUnreadable` deletes, so a foreign key or a relation addressed\n * under the column name cannot put the property back.\n */\nfunction excludedApiKeys(properties: Properties): Set<string> {\n const excluded = new Set<string>();\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n const access = effectiveAccess(prop);\n if (access?.read?.length !== 0 || access?.write?.length !== 0) continue;\n excluded.add(key);\n if (prop.columnName) excluded.add(prop.columnName);\n }\n return excluded;\n}\n\nexport function generateTypedefs(input: CollectionConfig[]): string {\n // Sorted here rather than only in `generate-sdk`: the output is\n // order-dependent and `rebase doctor` regenerates it in memory to diff\n // against the file on disk. While only the writer sorted, a project whose\n // file order differed from its slug order was reported permanently stale.\n const collections = sortCollectionsBySlug(input);\n const accessors = buildAccessors(collections);\n const lines: string[] = [\n \"/**\",\n \" * This file was auto-generated by Rebase.\",\n \" * Do not make direct changes to the file.\",\n \" */\",\n \"\",\n \"export interface Database {\"\n ];\n\n for (const collection of collections) {\n const properties = (collection.properties ?? {}) as Properties;\n\n // Resolve relations\n let resolvedRelations: Record<string, ResolvedRelation> = {};\n try {\n resolvedRelations = resolveCollectionRelations(collection);\n } catch (e) {\n // Swallowed before, which made this the quietest way to ship a\n // wrong type. The foreign-key columns are emitted from the resolved\n // relations rather than from the properties, so losing them drops\n // both the relation fields *and* columns that exist in the\n // database — and the resulting error surfaces in the user's code,\n // typechecking against a `Database` that is missing `author_id`,\n // with nothing pointing back at generation.\n //\n // A target thunk usually throws because of a circular import; the\n // boot-time relation validator names the same cause.\n console.warn(\n `[rebase] Could not resolve the relations of \"${collection.slug}\", so its generated ` +\n \"type has no relation fields and none of their foreign-key columns. This is usually a \" +\n \"circular import in the collection files — make sure the target is `() => otherCollection` \" +\n `and not evaluated at module load.\\n ${e instanceof Error ? e.message : String(e)}`\n );\n }\n\n // Subcollections are collections in their own right and are addressed\n // over a nested path, not as `client.data.<name>`. Generating them here\n // would invent an accessor the client does not serve, so they are\n // skipped — loudly, because doing it silently is how a developer\n // concludes the generator is broken.\n const subcollections = (collection as unknown as { subcollections?: unknown[] }).subcollections;\n if (Array.isArray(subcollections) && subcollections.length > 0) {\n console.warn(\n `[rebase] \"${collection.slug}\" declares ${subcollections.length} subcollection(s), which are ` +\n \"not part of the generated Database: they are reached over a nested path \" +\n `(\\`data/${collection.slug}/<id>/<relation>\\`), not as a top-level accessor. Register a ` +\n \"subcollection as a collection of its own if you want a typed accessor for it.\"\n );\n }\n\n lines.push(` ${emitKey(accessors.get(collection.slug)!)}: {`);\n\n // ── Row Type ──\n //\n // What a read serves.\n //\n // ## A `belongsTo` has three shapes, and all three are typed\n //\n // One relation, three places it appears, and the wire is not symmetric\n // about it — so the types are not either:\n //\n // 1. **Write** — `Insert`/`Update` accept *either* the foreign key\n // under its own wire name (`{ authorId: 5 }`) or the relation\n // property (`{ author: 5 }`), because the write transformer maps\n // the second onto the first. Emitted by `emitWritableRelations`.\n // 2. **Read** — a row carries `authorId`. Always: it is a column.\n // 3. **Read with `include`** — the target's own row arrives under\n // `author`. Emitted below as optional, because it is absent from\n // every read that did not name it; the generated `RowWith` makes it\n // required for a read that did.\n //\n // The three collapse in exactly one case — a relation named identically\n // to its own foreign key, where 3 is served *over* 2 — handled below.\n //\n // `fields` narrows which columns a read returns, but a column is\n // optional here only because the value may be absent or null, never\n // because the caller might not have asked for it: a projection is a\n // choice made at one call site, and typing every column optional to\n // describe it would make every row unusable everywhere else.\n lines.push(\" Row: {\");\n const emittedKeys = new Set<string>();\n\n // Off the surface entirely — see `excludedApiKeys`. Seeding the emitted\n // set means every later pass (foreign keys, relations, unresolved\n // relations) skips them too, since each of those already refuses to\n // emit a key twice.\n const excluded = excludedApiKeys(properties);\n for (const key of excluded) emittedKeys.add(key);\n\n // 1. Direct properties\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (excluded.has(key)) continue;\n\n const tsType = propertyToTypeScriptType(prop);\n // A primary key is on every row a read can return, whether or not\n // anyone wrote `validation: { required: true }` next to it —\n // introspection never does, so `row.id` was `string | undefined`\n // for every baas project.\n const isRequired = Boolean(prop.validation?.required) || isPrimaryKey(prop);\n lines.push(line(key, isRequired ? tsType : `${tsType} | null`, !isRequired));\n emittedKeys.add(key);\n }\n\n // 2. FK columns from relations.\n //\n // Emitted under the relation's *wire* name, which is what\n // `fieldKeyForColumn` answers: `localKey` is the database column\n // (`author_id`) and the row arrives keyed `authorId`. Emitting the\n // column, which this used to do, described a key the JSON does not\n // carry and hid the one it does — including from `where` and `orderBy`,\n // which are keyed off this type. The column name is not a second name\n // for the field: it is the name of a different thing.\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n const fkKey = fieldKeyForColumn(collection, relation.localKey);\n if (emittedKeys.has(fkKey)) continue;\n\n const fkType = foreignKeyType(relation);\n\n // A relation addressed by the same name as its own foreign key\n // is served *over* that column when the read includes it: the\n // query nests the target under the relation name, and the\n // scalar it shadows is gone. Both outcomes are real, so the\n // column is typed as both — which is what stops a plain\n // `const id: string = row.author_id` from compiling.\n const shadowedByInclude = relKey === fkKey;\n const tsType = shadowedByInclude\n ? `${fkType} | ${includedRelationType(relation, accessors)}`\n : fkType;\n\n const isRequired = isRelationRequired(collection, relation) && !shadowedByInclude;\n lines.push(line(fkKey, isRequired ? tsType : `${tsType} | null`, !isRequired));\n emittedKeys.add(fkKey);\n }\n }\n\n // 3. Relation fields — the target's own row, inlined.\n //\n // Optional throughout: a relation is only loaded when the read names it\n // in `include`, so it is absent from every other read.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n if (emittedKeys.has(key)) continue;\n lines.push(line(key, includedRelationType(relation, accessors), true));\n emittedKeys.add(key);\n }\n\n // A `relation` property whose relation could not be resolved — an\n // engine without relation support, or a target that did not load. It is\n // still a column on the row, so it is still typed, just not precisely.\n for (const [key, rawProp] of Object.entries(properties)) {\n if ((rawProp as Property).type !== \"relation\") continue;\n if (emittedKeys.has(key)) continue;\n lines.push(line(key, \"Record<string, unknown>\", true));\n emittedKeys.add(key);\n }\n lines.push(\" };\");\n\n // ── Insert Type ──\n //\n // What `create()` accepts, minus the `excludeFromApi` columns: the\n // property is off the API surface in both directions, so a generated\n // client never names it.\n lines.push(\" Insert: {\");\n emittedKeys.clear();\n for (const key of excluded) emittedKeys.add(key);\n\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (excluded.has(key)) continue;\n const tsType = propertyToTypeScriptType(prop);\n const isOptional = !prop.validation?.required || isAutoAssignedId(prop);\n lines.push(line(key, tsType, isOptional));\n emittedKeys.add(key);\n }\n\n emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);\n lines.push(\" };\");\n\n // ── Update Type ──\n //\n // Everything optional, and the primary key left out: an update\n // addresses a row by id, it does not reassign one. Accepting `id` here\n // typechecked `update(id, { id: someoneElses })`.\n lines.push(\" Update: {\");\n emittedKeys.clear();\n for (const key of excluded) emittedKeys.add(key);\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue;\n if (isPrimaryKey(prop)) continue;\n if (excluded.has(key)) continue;\n lines.push(line(key, propertyToTypeScriptType(prop), true));\n emittedKeys.add(key);\n }\n emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);\n lines.push(\" };\");\n\n // ── Relations ──\n //\n // Which relations this collection has, and which collection each one\n // reaches. Not a shape a read ever returns — it is the *graph*, and\n // `include` is the one parameter that needs it: `IncludeFor` below\n // walks it to constrain an include's keys to relations that exist, at\n // every level, and `RowWith` reads it to make an included relation\n // non-optional on the row that comes back.\n //\n // Emitted as the accessor NAME rather than the target's `Row`, so a\n // self-referencing or mutually-referencing relation is a finite string\n // instead of a type that expands forever.\n lines.push(\" Relations: {\");\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n const target = resolveTargetCollection(relation);\n const slug = target?.slug ?? relation.targetSlug;\n const accessor = slug ? accessors.get(slug) : undefined;\n // A target outside this generation run has no accessor to name, so\n // the relation is typed as reaching nothing rather than as reaching\n // something that does not exist.\n lines.push(` ${emitKey(key)}: ${accessor ? emitString(accessor) : \"never\"};`);\n }\n lines.push(\" };\");\n\n lines.push(\" };\");\n }\n\n lines.push(\"}\");\n lines.push(\"\");\n lines.push(\"export type CollectionName = keyof Database;\");\n lines.push(\"\");\n lines.push(\"export const collectionsDictionary = {\");\n for (const collection of collections) {\n lines.push(` ${emitKey(accessors.get(collection.slug)!)}: ${emitString(collection.slug)},`);\n }\n lines.push(\"} as const;\");\n lines.push(\"\");\n // Describes the const above rather than restating its keys. The previous\n // `{ [K in CollectionName]: K }` said every value equalled its key, which is\n // false for any slug that is not already an identifier — `myNotes` maps to\n // `\"my-notes\"` — so the export the CLI tells people to pass did not satisfy\n // its own published type.\n lines.push(\"export type CollectionsDictionary = typeof collectionsDictionary;\");\n lines.push(\"\");\n lines.push(...includeHelperLines());\n\n return lines.join(\"\\n\");\n}\n\n/**\n * The `include` type machinery, emitted into the generated file.\n *\n * It lives here rather than in `@rebasepro/types` because it is the *graph*\n * that makes it work, and the graph only exists once a project's collections\n * have been generated. `IncludeSpec` in `@rebasepro/types` is deliberately\n * unconstrained — a hand-written row type has no relations in it to check\n * against — and these narrow it for a project that does have them.\n *\n * Two things a caller gets:\n *\n * - `IncludeFor<\"posts\">` — an include whose keys are relations that exist,\n * recursively, so `include: { comments: { include: { authr: true } } }` is a\n * compile error rather than a 400 at runtime.\n * - `RowWith<\"posts\", I>` — the row a read with that include returns, where\n * every included relation is **present** rather than optional. `Row` types a\n * relation as optional because it is absent from every read that did not ask\n * for it; once a read has asked, `row.author.name` should not need a `?.`.\n *\n * The depth bound matches the server's (`MAX_INCLUDE_DEPTH`), and is spelled as\n * a decrementing tuple because TypeScript has no arithmetic — `Prev[3]` is `2`.\n * Without a bound, a self-referencing relation makes the type infinite and the\n * compiler gives up with \"type instantiation is excessively deep\".\n */\nfunction includeHelperLines(): string[] {\n return [\n \"/** Which collection each of `A`'s relations reaches. */\",\n \"export type RelationsOf<A extends CollectionName> =\",\n \" Database[A] extends { Relations: infer R } ? R : Record<string, never>;\",\n \"\",\n \"/** The names of `A`'s relations. */\",\n \"export type RelationKeys<A extends CollectionName> = keyof RelationsOf<A> & string;\",\n \"\",\n \"/** The collection a relation reaches, or `never` when it left this project. */\",\n \"export type RelationTarget<A extends CollectionName, K extends RelationKeys<A>> =\",\n \" RelationsOf<A>[K] extends CollectionName ? RelationsOf<A>[K] : never;\",\n \"\",\n \"/** Counts `IncludeFor` down; TypeScript has no arithmetic. */\",\n \"type Prev = [never, 0, 1, 2, 3];\",\n \"\",\n \"/**\",\n \" * Per-relation options, minus `include` — which `IncludeFor` supplies at\",\n \" * the next depth so the nested keys are checked against the *target's*\",\n \" * relations rather than this collection's.\",\n \" */\",\n \"export interface IncludeOptionsFor<A extends CollectionName, K extends RelationKeys<A>, D extends number> {\",\n \" limit?: number;\",\n \" where?: Record<string, unknown>;\",\n \" logical?: unknown;\",\n \" orderBy?: unknown;\",\n \" fields?: string[];\",\n \" include?: RelationTarget<A, K> extends CollectionName\",\n \" ? IncludeFor<RelationTarget<A, K>, Prev[D]>\",\n \" : never;\",\n \"}\",\n \"\",\n \"/**\",\n \" * An `include` for collection `A`: its relation names, dotted paths, or the\",\n \" * parametrised tree — with every key checked against the relations that\",\n \" * actually exist, at every level.\",\n \" */\",\n \"export type IncludeFor<A extends CollectionName, D extends number = 3> =\",\n \" D extends 0\",\n \" ? never\",\n \" : | readonly (RelationKeys<A> | \\\"*\\\")[]\",\n \" | { [K in RelationKeys<A>]?: true | IncludeOptionsFor<A, K, D> };\",\n \"\",\n \"/** The relation names an include asks for at the top level. */\",\n \"type IncludedKeys<A extends CollectionName, I> =\",\n \" I extends readonly (infer K)[]\",\n \" ? Extract<K, RelationKeys<A>>\",\n \" : Extract<keyof I, RelationKeys<A>>;\",\n \"\",\n \"/**\",\n \" * The row a read with include `I` returns: `Row`, with every included\",\n \" * relation made **required**.\",\n \" *\",\n \" * `Row` types a relation as optional because it is absent from every read\",\n \" * that did not name it. Once a read has named it, it is there — and having\",\n \" * to write `row.author?.name` after asking for the author is the type\",\n \" * describing a possibility the query already ruled out.\",\n \" */\",\n \"export type RowWith<A extends CollectionName, I> =\",\n \" Database[A][\\\"Row\\\"] & Required<Pick<Database[A][\\\"Row\\\"], IncludedKeys<A, I> & keyof Database[A][\\\"Row\\\"]>>;\",\n \"\"\n ];\n}\n\n/**\n * The two ways a write can name a `belongsTo` target, both of which the server\n * accepts: the foreign key under its own wire name (`{ authorId: 5 }`, which\n * passes through to the `author_id` column untouched) and the relation\n * *property* (`{ author: 5 }`, which the write transformer maps onto that\n * column).\n *\n * Only the first was generated, so the documented and idiomatic write shape was\n * a type error.\n *\n * The second form is emitted under the **property key**, not the resolved\n * relation name, because that is what the transformer keys off: it looks the\n * payload key up in `properties` and only treats it as a relation if what it\n * finds there is one. A relation whose `relationName` differs from its property\n * key is reachable as the property and not as the name, so emitting the name\n * would have offered a key that writes to a column that does not exist.\n */\nfunction emitWritableRelations(\n lines: string[],\n collection: CollectionConfig,\n properties: Properties,\n resolvedRelations: Record<string, ResolvedRelation>,\n emittedKeys: Set<string>,\n allOptional: boolean\n): void {\n // The target's primary key type is the same one `Row` uses. A hardcoded\n // `string | number` here accepted a string for a numeric-keyed target.\n const emit = (key: string, relation: ResolvedRelation): void => {\n if (emittedKeys.has(key)) return;\n const optional = allOptional || !isRelationRequired(collection, relation);\n lines.push(line(key, foreignKeyType(relation), optional));\n emittedKeys.add(key);\n };\n\n for (const relation of Object.values(resolvedRelations)) {\n if (relation.kind === \"belongsTo\" && relation.localKey) {\n emit(fieldKeyForColumn(collection, relation.localKey), relation);\n }\n }\n\n for (const [key, rawProp] of Object.entries(properties)) {\n if ((rawProp as Property).type !== \"relation\") continue;\n const relation = findRelation(resolvedRelations, key);\n if (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n }\n}\n","/**\n * @rebasepro/codegen\n *\n * Generates a purely typed Typescript database definition.\n */\n\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { generateTypedefs } from \"./generate-types\";\n\nexport { generateTypedefs, CodegenError } from \"./generate-types\";\nexport { toPascalCase, toCamelCase, toSafeIdentifier, indent } from \"./utils\";\n\n// ─── Public API ────────────────────────────────────────────────────\n\nexport interface GeneratedFile {\n /** Relative file path within the output directory */\n path: string;\n /** File content */\n content: string;\n}\n\nexport interface GenerateSDKOptions {\n /** Whether to include a README file (default: true) */\n includeReadme?: boolean;\n}\n\nexport function generateSDK(\n collections: CollectionConfig[],\n options: GenerateSDKOptions = {}\n): GeneratedFile[] {\n const files: GeneratedFile[] = [];\n\n files.push({\n path: \"database.types.ts\",\n content: generateTypedefs(collections)\n });\n\n if (options.includeReadme !== false) {\n files.push({\n path: \"README.md\",\n content: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import { collectionsDictionary, type Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // Maps each accessor back to the slug the wire uses. Without it a\n // hyphenated slug is not resolvable from the property name alone.\n collections: collectionsDictionary,\n });\n\n // Property access is the typed surface: rows, filters and sorts are all\n // checked against the generated Database.\n const { data: users } = await rebase.data.users.find();\n console.log(users[0].email); // flat access — no .values wrapper\n \\`\\`\\`\n\n## Field names are the ones the API serves\n\nThe generated \\`Row\\` uses each field's **wire** name — the key it arrives under in\nJSON — and nothing here renames anything.\n\n- **A declared property is its key in the collection.** A property keyed\n \\`createdAt\\` is \\`row.createdAt\\`, whatever \\`columnName\\` says. A column name is\n the name of a different thing: where the value lives, not what the API calls it.\n- **A foreign key derived from a relation is camelCase**, because that is what\n the wire carries. A \\`belongsTo\\` named \\`author\\` gives you \\`row.authorId\\`, not\n the column spelling.\n- **A collection accessor is camelCase too** (\\`my-notes\\` → \\`rebase.data.myNotes\\`),\n which is what \\`collectionsDictionary\\` maps back to the slug.\n\n\\`where\\` and \\`orderBy\\` are keyed off the same type, so what compiles is what the\nbackend answers to.\n\n## \\`Row\\` vs \\`Insert\\` vs \\`Update\\`\n\n| Type | What it describes |\n|---|---|\n| \\`Row\\` | What a read serves. Nullable columns are \\`T \\\\| null\\`; relations appear only when \\`include\\` names them. |\n| \\`Insert\\` | What \\`create()\\` accepts. Server-assigned ids are optional; a \\`belongsTo\\` target may be named either way (\\`{ author: 5 }\\` or \\`{ authorId: 5 }\\`). |\n| \\`Update\\` | What \\`update()\\` accepts. Everything optional, and the primary key is not settable. |\n\nA property marked \\`excludeFromApi\\` is absent from all three: the API surface\ndoes not mention it, in either direction. The server holds the same line — a\nread never serves the column and a write naming it is refused — so this is a\nguarantee rather than a description, and nothing generated names a password\nhash.\n\nIf you need an untyped escape hatch, \\`rebase.data.collection(slug)\\` still works —\nbut it is generic over \\`Record<string, unknown>\\` and gives up everything above.\n`\n });\n }\n\n return files;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/codegen",
3
- "version": "0.19.1",
3
+ "version": "0.19.2-canary.g09316f6",
4
4
  "description": "Generate a typed JS SDK from Rebase collection definitions",
5
5
  "keywords": [
6
6
  "sdk",
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "peerDependencies": {
36
- "@rebasepro/types": "0.19.1"
36
+ "@rebasepro/types": "0.19.2-canary.g09316f6"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@jest/globals": "^30.4.1",
@@ -43,7 +43,7 @@
43
43
  "ts-jest": "^29.4.12",
44
44
  "typescript": "^6.0.3",
45
45
  "vite": "^8.1.5",
46
- "@rebasepro/types": "0.19.1"
46
+ "@rebasepro/types": "0.19.2-canary.g09316f6"
47
47
  },
48
48
  "exports": {
49
49
  ".": {
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
56
56
  "dependencies": {
57
- "@rebasepro/common": "0.19.1"
57
+ "@rebasepro/common": "0.19.2-canary.g09316f6"
58
58
  },
59
59
  "scripts": {
60
60
  "test": "jest --config jest.config.cjs",