@xylex-group/athena 1.6.2 → 1.7.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # athena-js
2
2
 
3
- current version: `1.6.2`
3
+ current version: `1.7.0`
4
4
  `@xylex-group/athena` is a database driver and API gateway SDK that lets you interact with SQL backends over HTTP through a fluent builder API. It ships a typed query builder for Node.js / server environments plus Athena-native React hooks for client-side use.
5
5
 
6
6
  ## Install
@@ -43,6 +43,51 @@ if (error) {
43
43
  }
44
44
  ```
45
45
 
46
+ ### Auth client (Athena Auth server)
47
+
48
+ If your auth backend is now Athena Auth, you can keep core login/session flows in this SDK:
49
+
50
+ ```ts
51
+ import { createAuthClient } from "@xylex-group/athena";
52
+
53
+ const auth = createAuthClient({
54
+ baseUrl: "http://localhost:3001/api/auth",
55
+ // optional: bearer token if you are not using cookie-based sessions
56
+ bearerToken: process.env.AUTH_BEARER_TOKEN,
57
+ });
58
+
59
+ const login = await auth.signIn.email({
60
+ email: "demo@example.com",
61
+ password: "super-secret",
62
+ rememberMe: true,
63
+ });
64
+
65
+ const session = await auth.getSession();
66
+ const sessions = await auth.listSessions();
67
+
68
+ // clear one session
69
+ await auth.revokeSession({ token: "session_token_here" });
70
+ // or clear all sessions
71
+ await auth.revokeSessions();
72
+
73
+ await auth.signOut();
74
+
75
+ // additional core flows
76
+ await auth.forgetPassword({ email: "demo@example.com", redirectTo: "https://app/reset-password" });
77
+ await auth.resetPassword({ newPassword: "new-secret", token: "reset_token" });
78
+ await auth.verifyEmail({ token: "verify_token", callbackURL: "https://app/verified" });
79
+ await auth.changePassword({ currentPassword: "old-secret", newPassword: "new-secret" });
80
+ await auth.updateUser({ name: "Demo User" });
81
+
82
+ // escape hatch for any endpoint on your Athena Auth instance
83
+ await auth.request({
84
+ endpoint: "/ok",
85
+ query: { ping: "pong" },
86
+ });
87
+ ```
88
+
89
+ Auth responses follow the same envelope style: `{ ok, status, data, error, errorDetails, raw }`.
90
+
46
91
  ### Typed schema registry (model-first)
47
92
 
48
93
  You can keep `createClient(...).from<T>(...)` as-is, or opt into a typed registry:
@@ -101,6 +146,7 @@ Generator supports:
101
146
 
102
147
  - PostgreSQL direct introspection (`provider.mode = "direct"`, `provider.connectionString` from your `PG_URL`/`DATABASE_URL`)
103
148
  - PostgreSQL gateway-only introspection (`provider.mode = "gateway"` via Athena `POST /gateway/query`)
149
+ - Multiple schema syncs such as `public` plus `athena`, with schema-safe default output paths
104
150
  - Placeholder-driven output paths
105
151
  - Feature flags (`features.emitRegistry`, `features.emitRelations`)
106
152
 
@@ -291,6 +291,37 @@ function resolvePostgresColumnType(column) {
291
291
  }
292
292
  return wrapArrayType(baseType, column.arrayDimensions);
293
293
  }
294
+
295
+ // src/generator/schema-selection.ts
296
+ var DEFAULT_POSTGRES_SCHEMAS = ["public"];
297
+ function collectSchemaNames(input) {
298
+ if (!input) {
299
+ return [];
300
+ }
301
+ const values = typeof input === "string" ? [input] : input;
302
+ return values.flatMap((value) => String(value).split(","));
303
+ }
304
+ function normalizeSchemaSelection(input) {
305
+ const schemas = [];
306
+ const seen = /* @__PURE__ */ new Set();
307
+ for (const value of collectSchemaNames(input)) {
308
+ const schema = value.trim();
309
+ if (!schema || seen.has(schema)) {
310
+ continue;
311
+ }
312
+ seen.add(schema);
313
+ schemas.push(schema);
314
+ }
315
+ return schemas.length > 0 ? schemas : [...DEFAULT_POSTGRES_SCHEMAS];
316
+ }
317
+ function resolveProviderSchemas(providerConfig) {
318
+ if (providerConfig.kind === "postgres") {
319
+ return normalizeSchemaSelection(providerConfig.schemas);
320
+ }
321
+ return [...DEFAULT_POSTGRES_SCHEMAS];
322
+ }
323
+
324
+ // src/generator/config.ts
294
325
  var DEFAULT_CONFIG_CANDIDATES = [
295
326
  "athena.config.ts",
296
327
  "athena.config.js",
@@ -300,8 +331,8 @@ var DEFAULT_CONFIG_CANDIDATES = [
300
331
  ".athena.config.js"
301
332
  ];
302
333
  var DEFAULT_TARGETS = {
303
- model: "athena/models/{model_kebab}.ts",
304
- schema: "athena/schema.ts",
334
+ model: "athena/models/{schema_kebab}/{model_kebab}.ts",
335
+ schema: "athena/schemas/{schema_kebab}.ts",
305
336
  database: "athena/relations.ts",
306
337
  registry: "athena/config.ts"
307
338
  };
@@ -331,6 +362,15 @@ function normalizeOutputConfig(output) {
331
362
  }
332
363
  };
333
364
  }
365
+ function normalizeProviderConfig(provider) {
366
+ if (provider.kind === "postgres") {
367
+ return {
368
+ ...provider,
369
+ schemas: normalizeSchemaSelection(provider.schemas)
370
+ };
371
+ }
372
+ return provider;
373
+ }
334
374
  function isAthenaGeneratorConfig(value) {
335
375
  if (!value || typeof value !== "object") {
336
376
  return false;
@@ -340,7 +380,7 @@ function isAthenaGeneratorConfig(value) {
340
380
  }
341
381
  function normalizeGeneratorConfig(input) {
342
382
  return {
343
- provider: input.provider,
383
+ provider: normalizeProviderConfig(input.provider),
344
384
  output: normalizeOutputConfig(input.output),
345
385
  naming: {
346
386
  ...DEFAULT_NAMING,
@@ -550,12 +590,19 @@ export const ${registryConstName} = defineRegistry({
550
590
  };
551
591
  }
552
592
  function assertNoDuplicatePaths(files) {
553
- const seen = /* @__PURE__ */ new Set();
593
+ const seen = /* @__PURE__ */ new Map();
554
594
  for (const file of files) {
555
- if (seen.has(file.path)) {
556
- throw new Error(`Generator output collision detected for path: ${file.path}`);
595
+ const existing = seen.get(file.path);
596
+ if (existing) {
597
+ throw new Error(
598
+ [
599
+ `Generator output collision detected for path: ${file.path}`,
600
+ `Collision: ${existing.kind} and ${file.kind}.`,
601
+ "When syncing multiple schemas, include a schema placeholder such as {schema} or {schema_kebab} in model/schema output targets."
602
+ ].join(" ")
603
+ );
557
604
  }
558
- seen.add(file.path);
605
+ seen.set(file.path, file);
559
606
  }
560
607
  }
561
608
  var ArtifactComposer = class {
@@ -2111,8 +2158,21 @@ function coerceStringArray(value) {
2111
2158
  }
2112
2159
  return [];
2113
2160
  }
2161
+ function normalizePostgresCatalogSchemas(schemas) {
2162
+ const normalized = [];
2163
+ const seen = /* @__PURE__ */ new Set();
2164
+ for (const value of schemas ?? []) {
2165
+ const schema = value.trim();
2166
+ if (!schema || seen.has(schema)) {
2167
+ continue;
2168
+ }
2169
+ seen.add(schema);
2170
+ normalized.push(schema);
2171
+ }
2172
+ return normalized.length > 0 ? normalized : ["public"];
2173
+ }
2114
2174
  function buildSchemaArrayLiteral(schemas) {
2115
- const normalized = schemas.length > 0 ? schemas : ["public"];
2175
+ const normalized = normalizePostgresCatalogSchemas(schemas);
2116
2176
  const literals = normalized.map((schema) => `'${escapeSqlLiteral(schema)}'`).join(", ");
2117
2177
  return `ARRAY[${literals}]`;
2118
2178
  }
@@ -2311,12 +2371,14 @@ var PostgresIntrospectionProvider = class {
2311
2371
  backend = "postgresql";
2312
2372
  connectionString;
2313
2373
  database;
2374
+ schemas;
2314
2375
  constructor(options) {
2315
2376
  this.connectionString = options.connectionString;
2316
2377
  this.database = options.database ?? "postgres";
2378
+ this.schemas = normalizePostgresCatalogSchemas(options.schemas);
2317
2379
  }
2318
2380
  async inspect(options) {
2319
- const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : ["public"];
2381
+ const schemas = options?.schemas && options.schemas.length > 0 ? normalizePostgresCatalogSchemas(options.schemas) : this.schemas;
2320
2382
  const pool = new pg.Pool({
2321
2383
  connectionString: this.connectionString
2322
2384
  });
@@ -2388,11 +2450,13 @@ var AthenaGatewayPostgresIntrospectionProvider = class {
2388
2450
  type: this.config.backend ?? "postgresql"
2389
2451
  }
2390
2452
  });
2453
+ this.schemas = normalizeSchemaSelection(this.config.schemas);
2391
2454
  }
2392
2455
  backend = "postgresql";
2393
2456
  client;
2457
+ schemas;
2394
2458
  async inspect(options) {
2395
- const schemas = options?.schemas && options.schemas.length > 0 ? options.schemas : this.config.schemas && this.config.schemas.length > 0 ? this.config.schemas : ["public"];
2459
+ const schemas = options?.schemas && options.schemas.length > 0 ? normalizeSchemaSelection(options.schemas) : this.schemas;
2396
2460
  const catalogClient = new AthenaGatewayCatalogClient(this.client);
2397
2461
  const queries = buildGatewayCatalogQueries(schemas);
2398
2462
  const [columnRows, enumMap, primaryKeyRows, foreignKeyRows] = await Promise.all([
@@ -2428,7 +2492,8 @@ var ScyllaIntrospectionProvider = class {
2428
2492
  function createPostgresProvider(config) {
2429
2493
  return createPostgresIntrospectionProvider({
2430
2494
  connectionString: config.connectionString,
2431
- database: config.database
2495
+ database: config.database,
2496
+ schemas: normalizeSchemaSelection(config.schemas)
2432
2497
  });
2433
2498
  }
2434
2499
  function resolveGeneratorProvider(providerConfig, experimentalFlags) {
@@ -2450,12 +2515,6 @@ function resolveGeneratorProvider(providerConfig, experimentalFlags) {
2450
2515
  }
2451
2516
 
2452
2517
  // src/generator/pipeline.ts
2453
- function extractProviderSchemas(providerConfig) {
2454
- if (!("schemas" in providerConfig) || !providerConfig.schemas || providerConfig.schemas.length === 0) {
2455
- return void 0;
2456
- }
2457
- return providerConfig.schemas;
2458
- }
2459
2518
  async function writeArtifacts(files, cwd) {
2460
2519
  const writtenFiles = [];
2461
2520
  for (const file of files) {
@@ -2475,7 +2534,7 @@ async function runSchemaGenerator(options = {}) {
2475
2534
  const { configPath, config } = await loadGeneratorConfig(configOptions);
2476
2535
  const provider = options.provider ?? resolveGeneratorProvider(config.provider, config.experimental);
2477
2536
  const snapshot = await provider.inspect({
2478
- schemas: extractProviderSchemas(config.provider)
2537
+ schemas: resolveProviderSchemas(config.provider)
2479
2538
  });
2480
2539
  const generated = generateArtifactsFromSnapshot(snapshot, config);
2481
2540
  const writtenFiles = options.dryRun ? [] : await writeArtifacts(generated.files, cwd);