@c9up/atlas 0.3.5 → 0.3.7

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 (50) hide show
  1. package/db.win32-x64-msvc.node +0 -0
  2. package/dist/AtlasProvider.d.ts +53 -16
  3. package/dist/AtlasProvider.d.ts.map +1 -1
  4. package/dist/AtlasProvider.js +10 -3
  5. package/dist/AtlasProvider.js.map +1 -1
  6. package/dist/BaseRepository.d.ts.map +1 -1
  7. package/dist/BaseRepository.js +53 -0
  8. package/dist/BaseRepository.js.map +1 -1
  9. package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
  10. package/dist/adapters/NapiDbAdapter.js +10 -2
  11. package/dist/adapters/NapiDbAdapter.js.map +1 -1
  12. package/dist/configure.d.ts +1 -0
  13. package/dist/configure.d.ts.map +1 -1
  14. package/dist/configure.js +12 -1
  15. package/dist/configure.js.map +1 -1
  16. package/dist/console/index.d.ts +39 -0
  17. package/dist/console/index.d.ts.map +1 -0
  18. package/dist/console/index.js +144 -0
  19. package/dist/console/index.js.map +1 -0
  20. package/dist/console/migrationCommands.d.ts +12 -0
  21. package/dist/console/migrationCommands.d.ts.map +1 -1
  22. package/dist/console/migrationCommands.js +5 -0
  23. package/dist/console/migrationCommands.js.map +1 -1
  24. package/dist/console/schemaCheckCommand.d.ts +7 -5
  25. package/dist/console/schemaCheckCommand.d.ts.map +1 -1
  26. package/dist/console/schemaCheckCommand.js +16 -5
  27. package/dist/console/schemaCheckCommand.js.map +1 -1
  28. package/dist/decorators/entity.d.ts +38 -5
  29. package/dist/decorators/entity.d.ts.map +1 -1
  30. package/dist/decorators/entity.js +40 -9
  31. package/dist/decorators/entity.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/services/db.d.ts +24 -1
  36. package/dist/services/db.d.ts.map +1 -1
  37. package/dist/services/db.js +66 -0
  38. package/dist/services/db.js.map +1 -1
  39. package/index.win32-x64-msvc.node +0 -0
  40. package/package.json +5 -1
  41. package/src/AtlasProvider.ts +66 -19
  42. package/src/BaseRepository.ts +57 -0
  43. package/src/adapters/NapiDbAdapter.ts +10 -2
  44. package/src/configure.ts +13 -1
  45. package/src/console/index.ts +202 -0
  46. package/src/console/migrationCommands.ts +17 -0
  47. package/src/console/schemaCheckCommand.ts +19 -6
  48. package/src/decorators/entity.ts +53 -12
  49. package/src/index.ts +1 -0
  50. package/src/services/db.ts +84 -1
@@ -61,6 +61,18 @@ export interface MigrationCommandOptions {
61
61
  * loads it once migrations exist.
62
62
  */
63
63
  schemaPath?: string;
64
+ /**
65
+ * Migration bookkeeping table — Adonis Lucid `migrations.tableName`. Must
66
+ * match what the application boots with: a command tracking migrations in a
67
+ * different table than the app would re-apply every migration it cannot see.
68
+ * Defaults to the runner's `"ream_migrations"`.
69
+ */
70
+ tableName?: string;
71
+ /**
72
+ * Refuse rollback/reset/refresh/fresh/wipe in production unless `--force`
73
+ * (Adonis Lucid `migrations.disableRollbacksInProduction`). On by default.
74
+ */
75
+ disableRollbacksInProduction?: boolean;
64
76
  }
65
77
 
66
78
  /**
@@ -105,6 +117,11 @@ function resolveRunner(
105
117
  dialect: db.dialect,
106
118
  naturalSort: options.naturalSort,
107
119
  disableTransactions: options.disableTransactions,
120
+ // Without the table name the runner falls back to its default while the
121
+ // application tracks migrations in the configured one — the command would
122
+ // then see an empty history and re-apply everything.
123
+ tableName: options.tableName,
124
+ disableRollbacksInProduction: options.disableRollbacksInProduction,
108
125
  });
109
126
  }
110
127
 
@@ -21,10 +21,12 @@ type Constructor = new (...args: unknown[]) => unknown;
21
21
  export type { AtlasCommandClass } from "./contract.js";
22
22
 
23
23
  /**
24
- * Build the `atlas:check` command for the given models. Register it in
25
- * `reamrc.commands` (atlas has no global entity registry — list your models,
26
- * as in Lucid). Run it as `ream atlas:check`; `--warn` reports drift without a
27
- * non-zero exit (useful for an advisory CI step).
24
+ * Build the `atlas:check` command for the given models.
25
+ *
26
+ * Most applications do not call this: `@c9up/atlas/commands` ships the command
27
+ * already, reading the models from `verifySchema.entities`. Call it directly to
28
+ * verify a different set — pass a function when the list is only known once the
29
+ * application has booted.
28
30
  *
29
31
  * @example
30
32
  * // commands/atlas-check.ts
@@ -35,7 +37,7 @@ export type { AtlasCommandClass } from "./contract.js";
35
37
  * // run: ream atlas:check --warn
36
38
  */
37
39
  export function schemaCheckCommand(
38
- entities: readonly Constructor[],
40
+ entities: readonly Constructor[] | (() => readonly Constructor[]),
39
41
  ): AtlasCommandClass {
40
42
  return class SchemaCheck {
41
43
  static commandName = "atlas:check";
@@ -58,7 +60,18 @@ export function schemaCheckCommand(
58
60
  process.exitCode = 1;
59
61
  return;
60
62
  }
61
- const code = await runSchemaCheck(entities, db, getAtlasDialect());
63
+ // Resolved here, not at registration: the shipped command reads the
64
+ // models from `verifySchema.entities`, which only exists once the
65
+ // application has booted its config.
66
+ const models = typeof entities === "function" ? entities() : entities;
67
+ if (models.length === 0) {
68
+ console.error(
69
+ "[atlas:check] no models to verify — list them under `verifySchema.entities` in config/database.ts.",
70
+ );
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ const code = await runSchemaCheck(models, db, getAtlasDialect());
62
75
  // `--warn` downgrades drift to advisory (exit 0); default fails CI.
63
76
  if (code !== 0 && !this.warn) process.exitCode = code;
64
77
  }
@@ -214,7 +214,7 @@ export function Entity(tableName: string): ClassDecorator {
214
214
  }
215
215
 
216
216
  /** @Column() — marks a property as a database column. */
217
- export function Column(options?: ColumnOptions): PropertyDecorator {
217
+ function ColumnBase(options?: ColumnOptions): PropertyDecorator {
218
218
  return (target, propertyKey) => {
219
219
  const columns: ColumnMetadata[] =
220
220
  Reflect.getOwnMetadata(COLUMNS_KEY, target.constructor) ?? [];
@@ -362,23 +362,64 @@ function columnDateTime(options?: DateTimeColumnOptions): PropertyDecorator {
362
362
  };
363
363
  }
364
364
 
365
- /** Namespace access so users write `@column.date()` / `@column.dateTime()`. */
366
- const columnWithSubs = Column as typeof Column & {
367
- date: typeof columnDate;
368
- dateTime: typeof columnDateTime;
369
- };
370
- columnWithSubs.date = columnDate;
371
- columnWithSubs.dateTime = columnDateTime;
365
+ /** Options for `@Column.json()`. */
366
+ export interface JsonColumnOptions extends ColumnOptions {
367
+ /**
368
+ * The SQL type the column is declared with. `jsonb` by default — the binary
369
+ * form, which is what a Postgres schema almost always wants. Pass `'json'`
370
+ * for the textual one.
371
+ */
372
+ type?: "json" | "jsonb";
373
+ }
372
374
 
373
375
  /**
374
- * `Column` exposed with its sub-decorators (`Column.date()`, `Column.dateTime()`).
375
- * Alias exports so TS users can `import { column } from '@c9up/atlas'` for a
376
- * Lucid-style lowercase naming when they prefer.
376
+ * `@Column.json()` a column holding a JSON document.
377
+ *
378
+ * The property keeps its JavaScript shape on both sides: assign an object or an
379
+ * array, read an object or an array back. atlas serialises the value when it
380
+ * writes the row and parses it when it reads one, so nothing in the application
381
+ * converts by hand, and every dialect behaves the same — Postgres decodes JSON
382
+ * itself, while SQLite and MySQL store text.
383
+ *
384
+ * @Column.json() declare metadata: Record<string, unknown> | null
385
+ * @Column.json({ type: 'json' }) declare tags: string[] | null
386
+ *
387
+ * A `prepare` or `consume` given here still wins: the column is yours to
388
+ * encode differently when the document needs it.
389
+ *
390
+ * Declaring `@Column({ type: 'jsonb' })` does the same thing — the type is what
391
+ * atlas reads — but this says it in one place and cannot be mistyped.
377
392
  */
378
- export const column = Column as typeof Column & {
393
+ function columnJson(options?: JsonColumnOptions): PropertyDecorator {
394
+ return (target, propertyKey) => {
395
+ Column({ ...options, type: options?.type ?? "jsonb" })(target, propertyKey);
396
+ };
397
+ }
398
+
399
+ /**
400
+ * `@Column()` with its sub-decorators attached: `Column.date()`,
401
+ * `Column.dateTime()`, `Column.json()`.
402
+ *
403
+ * The type carries them too — attaching them to the plain function left
404
+ * `Column.date` working at runtime but unknown to TypeScript, so only the
405
+ * lowercase alias type-checked.
406
+ */
407
+ const ColumnWithSubs = ColumnBase as typeof ColumnBase & {
379
408
  date: typeof columnDate;
380
409
  dateTime: typeof columnDateTime;
410
+ json: typeof columnJson;
381
411
  };
412
+ ColumnWithSubs.date = columnDate;
413
+ ColumnWithSubs.dateTime = columnDateTime;
414
+ ColumnWithSubs.json = columnJson;
415
+
416
+ export const Column = ColumnWithSubs;
417
+
418
+ /**
419
+ * Also exported lowercase, so `import { column } from '@c9up/atlas'` reads the
420
+ * way a schema definition usually does. The same decorator, either spelling.
421
+ */
422
+ export const column = ColumnWithSubs;
382
423
 
383
424
  /** Read the date-column configuration map for an entity class (walks prototype chain). */
384
425
  export function getDateColumnConfig(
package/src/index.ts CHANGED
@@ -72,6 +72,7 @@ export type {
72
72
  DateColumnConfig,
73
73
  DateTimeColumnOptions,
74
74
  EntityMetadata,
75
+ JsonColumnOptions,
75
76
  ManyToManyOptions,
76
77
  RelationMetadata,
77
78
  } from "./decorators/entity.js";
@@ -13,7 +13,10 @@
13
13
  * container hooks).
14
14
  */
15
15
 
16
- import type { ConnectionConfig } from "../AtlasProvider.js";
16
+ import type {
17
+ AtlasDatabaseConfig,
18
+ ConnectionConfig,
19
+ } from "../AtlasProvider.js";
17
20
  import type { AsyncDatabaseConnection } from "../adapters/NapiDbAdapter.js";
18
21
  import { ConnectionManager } from "../ConnectionManager.js";
19
22
  import {
@@ -171,6 +174,86 @@ export function getDb(): AsyncDatabaseConnection | undefined {
171
174
  return instance;
172
175
  }
173
176
 
177
+ // The config the provider booted with. Commands need it: `migration:run` has to
178
+ // know where the migration files are, and Lucid answers that from the config
179
+ // too (`connection.config.migrations.paths`, read through `lucid.db`). Atlas has
180
+ // no framework container to read, so the provider records it here.
181
+ let databaseConfig:
182
+ | { config: AtlasDatabaseConfig; defaultName: string }
183
+ | undefined;
184
+
185
+ /** @internal Record the booted config (called by AtlasProvider). */
186
+ export function setDatabaseConfig(
187
+ config: AtlasDatabaseConfig,
188
+ defaultName: string,
189
+ ): void {
190
+ databaseConfig = { config, defaultName };
191
+ }
192
+
193
+ /**
194
+ * @internal Forget the config IF it is still the one `config` describes.
195
+ * Ownership-guarded for the same reason {@link clearDb} is: a second provider
196
+ * may have rebound it, and the older provider's shutdown must not clear the
197
+ * newer binding.
198
+ */
199
+ export function clearDatabaseConfig(config: AtlasDatabaseConfig): void {
200
+ if (databaseConfig?.config === config) databaseConfig = undefined;
201
+ }
202
+
203
+ /** The config the application booted with, or `undefined` before boot. */
204
+ export function getDatabaseConfig(): AtlasDatabaseConfig | undefined {
205
+ return databaseConfig?.config;
206
+ }
207
+
208
+ /** The default connection's name — Lucid `db.primaryConnectionName`. */
209
+ export function primaryConnectionName(): string {
210
+ return databaseConfig?.defaultName ?? "primary";
211
+ }
212
+
213
+ /**
214
+ * One connection's config, by name (default connection when unnamed).
215
+ *
216
+ * Read from the manager's node first — that is where Lucid keeps `migrations`,
217
+ * `seeders` and `schemaGeneration`, and the shape we mirror. The top level is
218
+ * the fallback, because the single-connection form IS the connection config,
219
+ * and because an app that set `migrations` there before these keys were
220
+ * per-connection must keep working.
221
+ */
222
+ export function connectionConfigFor(name?: string): ConnectionConfig {
223
+ const resolved = name ?? primaryConnectionName();
224
+ const top = databaseConfig?.config;
225
+ // The manager knows a connection once the provider has registered it; before
226
+ // that — a command running against a config nobody opened yet — the declared
227
+ // `connections` entry is the same information.
228
+ const declared =
229
+ manager.get(resolved)?.config ?? top?.connections?.[resolved];
230
+ if (top === undefined) return declared ?? {};
231
+ if (declared === undefined) return top;
232
+ return {
233
+ ...top,
234
+ ...declared,
235
+ // These three are merged a level down rather than replaced: the top level
236
+ // is the single-connection form of the same config, so a connection that
237
+ // names only its paths must not drop a `tableName` set beside it.
238
+ migrations: mergeOrUndefined(top.migrations, declared.migrations),
239
+ seeders: mergeOrUndefined(top.seeders, declared.seeders),
240
+ schemaGeneration: mergeOrUndefined(
241
+ top.schemaGeneration,
242
+ declared.schemaGeneration,
243
+ ),
244
+ };
245
+ }
246
+
247
+ /** `{...a, ...b}`, or `undefined` when neither side has anything to merge. */
248
+ function mergeOrUndefined<T extends object>(
249
+ base: T | undefined,
250
+ override: T | undefined,
251
+ ): T | undefined {
252
+ if (base === undefined) return override;
253
+ if (override === undefined) return base;
254
+ return { ...base, ...override };
255
+ }
256
+
174
257
  // The shared connection manager (Lucid `db.manager`) — the single owner of named
175
258
  // connections. Backs `BaseModel.connection = 'analytics'` so a model resolves a
176
259
  // non-default connection from a plain import (AdonisJS `static connection`).