@effect-auth/core 0.1.0-alpha.18 → 0.1.0-alpha.19
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 +4 -5
- package/dist/DevEmail.d.ts +44 -0
- package/dist/DevEmail.d.ts.map +1 -0
- package/dist/DevEmail.js +115 -0
- package/dist/DevEmail.js.map +1 -0
- package/dist/DevelopmentSeed.d.ts +47 -0
- package/dist/DevelopmentSeed.d.ts.map +1 -0
- package/dist/DevelopmentSeed.js +337 -0
- package/dist/DevelopmentSeed.js.map +1 -0
- package/dist/DrizzleBunSqliteStorage.d.ts +1 -1
- package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -1
- package/dist/DrizzleD1SqliteStorage.d.ts +1 -1
- package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -1
- package/dist/DrizzleEffectSqliteStorage.d.ts +1 -1
- package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -1
- package/dist/DrizzleNodeSqliteStorage.d.ts +1 -1
- package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -1
- package/dist/DrizzlePostgresStorage.d.ts +26 -0
- package/dist/DrizzlePostgresStorage.d.ts.map +1 -0
- package/dist/DrizzlePostgresStorage.js +14 -0
- package/dist/DrizzlePostgresStorage.js.map +1 -0
- package/dist/EffectQbPostgresStorage.d.ts +8 -1
- package/dist/EffectQbPostgresStorage.d.ts.map +1 -1
- package/dist/EffectQbPostgresStorage.js +359 -2
- package/dist/EffectQbPostgresStorage.js.map +1 -1
- package/dist/EffectQbSqliteStorage.d.ts +42 -2
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.js +337 -1
- package/dist/EffectQbSqliteStorage.js.map +1 -1
- package/dist/HttpApi/Api.d.ts +151 -98
- package/dist/HttpApi/Api.d.ts.map +1 -1
- package/dist/HttpApi/Api.js +122 -72
- package/dist/HttpApi/Api.js.map +1 -1
- package/dist/HttpApi/ClientApi.d.ts +136 -136
- package/dist/HttpApi/Endpoints.d.ts +33 -1
- package/dist/HttpApi/Endpoints.d.ts.map +1 -1
- package/dist/HttpApi/Endpoints.js +31 -2
- package/dist/HttpApi/Endpoints.js.map +1 -1
- package/dist/HttpApi/Errors.d.ts +11 -3
- package/dist/HttpApi/Errors.d.ts.map +1 -1
- package/dist/HttpApi/Errors.js +16 -0
- package/dist/HttpApi/Errors.js.map +1 -1
- package/dist/HttpApi/Middleware.d.ts +12 -0
- package/dist/HttpApi/Middleware.d.ts.map +1 -1
- package/dist/HttpApi/Middleware.js +8 -0
- package/dist/HttpApi/Middleware.js.map +1 -1
- package/dist/HttpApi/OAuthDeviceAuthorization.d.ts +106 -0
- package/dist/HttpApi/OAuthDeviceAuthorization.d.ts.map +1 -0
- package/dist/HttpApi/OAuthDeviceAuthorization.js +415 -0
- package/dist/HttpApi/OAuthDeviceAuthorization.js.map +1 -0
- package/dist/HttpApi/OAuthToken.d.ts +1 -1
- package/dist/HttpApi/OAuthToken.d.ts.map +1 -1
- package/dist/HttpApi/OAuthToken.js +1 -1
- package/dist/HttpApi/OAuthToken.js.map +1 -1
- package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts +23 -0
- package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts.map +1 -0
- package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js +7 -0
- package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js.map +1 -0
- package/dist/HttpApi/Operations/OAuthToken.d.ts +10 -0
- package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -1
- package/dist/HttpApi/Operations/OAuthToken.js +4 -0
- package/dist/HttpApi/Operations/OAuthToken.js.map +1 -1
- package/dist/HttpApi/Operations/index.d.ts +1 -0
- package/dist/HttpApi/Operations/index.d.ts.map +1 -1
- package/dist/HttpApi/Operations/index.js +1 -0
- package/dist/HttpApi/Operations/index.js.map +1 -1
- package/dist/HttpApi/PasswordContract.d.ts +9 -9
- package/dist/HttpApi/Schemas.d.ts +38 -0
- package/dist/HttpApi/Schemas.d.ts.map +1 -1
- package/dist/HttpApi/Schemas.js +36 -1
- package/dist/HttpApi/Schemas.js.map +1 -1
- package/dist/HttpApi/index.d.ts +3 -1
- package/dist/HttpApi/index.d.ts.map +1 -1
- package/dist/HttpApi/index.js +3 -1
- package/dist/HttpApi/index.js.map +1 -1
- package/dist/OAuth.d.ts +46 -1
- package/dist/OAuth.d.ts.map +1 -1
- package/dist/OAuth.js +118 -4
- package/dist/OAuth.js.map +1 -1
- package/dist/OAuthDeviceAuthorization.d.ts +378 -0
- package/dist/OAuthDeviceAuthorization.d.ts.map +1 -0
- package/dist/OAuthDeviceAuthorization.js +860 -0
- package/dist/OAuthDeviceAuthorization.js.map +1 -0
- package/dist/OAuthDeviceClient.d.ts +76 -0
- package/dist/OAuthDeviceClient.d.ts.map +1 -0
- package/dist/OAuthDeviceClient.js +362 -0
- package/dist/OAuthDeviceClient.js.map +1 -0
- package/dist/PostgresStorageMigrations.d.ts +2 -1
- package/dist/PostgresStorageMigrations.d.ts.map +1 -1
- package/dist/PostgresStorageMigrations.js +54 -0
- package/dist/PostgresStorageMigrations.js.map +1 -1
- package/dist/Retention.d.ts +6 -1
- package/dist/Retention.d.ts.map +1 -1
- package/dist/Retention.js +49 -12
- package/dist/Retention.js.map +1 -1
- package/dist/SqliteStorageSchemaData.d.ts +2 -0
- package/dist/SqliteStorageSchemaData.d.ts.map +1 -0
- package/dist/SqliteStorageSchemaData.js +508 -0
- package/dist/SqliteStorageSchemaData.js.map +1 -0
- package/dist/StorageMigrations.d.ts +2 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +32 -0
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/StorageSchemaGenerator.d.ts +70 -12
- package/dist/StorageSchemaGenerator.d.ts.map +1 -1
- package/dist/StorageSchemaGenerator.js +478 -82
- package/dist/StorageSchemaGenerator.js.map +1 -1
- package/migrations/0029_auth_oauth_device_authorization.sql +28 -0
- package/migrations/postgres/0010_auth_oauth_device_authorization.sql +50 -0
- package/package.json +24 -16
- package/dist/DrizzleDurableObjectSqliteStorage.d.ts +0 -19
- package/dist/DrizzleDurableObjectSqliteStorage.d.ts.map +0 -1
- package/dist/DrizzleDurableObjectSqliteStorage.js +0 -14
- package/dist/DrizzleDurableObjectSqliteStorage.js.map +0 -1
- package/dist/DrizzleWasmSqliteStorage.d.ts +0 -23
- package/dist/DrizzleWasmSqliteStorage.d.ts.map +0 -1
- package/dist/DrizzleWasmSqliteStorage.js +0 -15
- package/dist/DrizzleWasmSqliteStorage.js.map +0 -1
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
import { postgresAuthStorageMigrations } from "./PostgresStorageMigrations.js";
|
|
2
|
+
import { sqliteAuthStorageSchema } from "./SqliteStorageSchemaData.js";
|
|
2
3
|
export const storageSchemaGeneratorApiVersion = "effect-auth.storage-schema-generator/v1";
|
|
3
|
-
export const storageSchemaGeneratorVersion = "1.
|
|
4
|
-
export const storageSchemaVersion = "
|
|
5
|
-
export const
|
|
4
|
+
export const storageSchemaGeneratorVersion = "1.5.0";
|
|
5
|
+
export const storageSchemaVersion = "0010_auth_oauth_device_authorization";
|
|
6
|
+
export const storageSchemaVersions = {
|
|
7
|
+
postgres: storageSchemaVersion,
|
|
8
|
+
sqlite: "0029_auth_oauth_device_authorization",
|
|
9
|
+
};
|
|
10
|
+
export const supportedStorageDatabases = ["postgres", "sqlite"];
|
|
6
11
|
export const supportedStorageAdapters = ["effect-qb", "drizzle"];
|
|
7
12
|
export const supportedStorageGeneratorModes = ["fresh"];
|
|
13
|
+
export const supportedDevelopmentSeedTargets = [
|
|
14
|
+
"alchemy-d1",
|
|
15
|
+
"bun-sqlite",
|
|
16
|
+
"node-sqlite",
|
|
17
|
+
"postgres",
|
|
18
|
+
];
|
|
19
|
+
export const supportedDevelopmentSeedComplexities = ["medium", "low"];
|
|
20
|
+
export const storageDatabaseAdapters = {
|
|
21
|
+
postgres: supportedStorageAdapters,
|
|
22
|
+
sqlite: supportedStorageAdapters,
|
|
23
|
+
};
|
|
24
|
+
export const developmentSeedTargetsByDatabase = {
|
|
25
|
+
postgres: ["postgres"],
|
|
26
|
+
sqlite: ["alchemy-d1", "bun-sqlite", "node-sqlite"],
|
|
27
|
+
};
|
|
28
|
+
export const developmentSeedTargetsByDatabaseAdapter = {
|
|
29
|
+
postgres: {
|
|
30
|
+
"effect-qb": ["postgres"],
|
|
31
|
+
drizzle: ["postgres"],
|
|
32
|
+
},
|
|
33
|
+
sqlite: {
|
|
34
|
+
"effect-qb": ["alchemy-d1"],
|
|
35
|
+
drizzle: ["alchemy-d1", "bun-sqlite", "node-sqlite"],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
8
38
|
export const storageModuleCatalog = [
|
|
9
39
|
{
|
|
10
40
|
id: "core",
|
|
@@ -122,6 +152,7 @@ export const storageModuleCatalog = [
|
|
|
122
152
|
"auth_oauth_client",
|
|
123
153
|
"auth_oauth_consent",
|
|
124
154
|
"auth_oauth_authorization_code",
|
|
155
|
+
"auth_oauth_device_authorization",
|
|
125
156
|
"auth_oauth_client_secret",
|
|
126
157
|
"auth_oauth_provider_mode_token",
|
|
127
158
|
],
|
|
@@ -261,16 +292,22 @@ export const storageSchemaGeneratorCatalog = {
|
|
|
261
292
|
apiVersion: storageSchemaGeneratorApiVersion,
|
|
262
293
|
generatorVersion: storageSchemaGeneratorVersion,
|
|
263
294
|
schemaVersion: storageSchemaVersion,
|
|
295
|
+
schemaVersions: storageSchemaVersions,
|
|
264
296
|
databases: supportedStorageDatabases,
|
|
265
297
|
adapters: supportedStorageAdapters,
|
|
298
|
+
databaseAdapters: storageDatabaseAdapters,
|
|
266
299
|
adapterOutputs: {
|
|
267
|
-
"effect-qb": ["migration-sql"],
|
|
268
|
-
drizzle: ["migration-sql", "drizzle-schema"],
|
|
300
|
+
"effect-qb": ["migration-sql", "development-seed"],
|
|
301
|
+
drizzle: ["migration-sql", "drizzle-schema", "development-seed"],
|
|
269
302
|
},
|
|
270
303
|
modes: supportedStorageGeneratorModes,
|
|
271
|
-
outputKinds: ["migration-sql", "drizzle-schema"],
|
|
304
|
+
outputKinds: ["migration-sql", "drizzle-schema", "development-seed"],
|
|
272
305
|
sqlLayouts: ["single", "module"],
|
|
273
306
|
drizzleLayouts: ["single", "module", "table"],
|
|
307
|
+
developmentSeedTargets: supportedDevelopmentSeedTargets,
|
|
308
|
+
developmentSeedTargetsByDatabase,
|
|
309
|
+
developmentSeedTargetsByDatabaseAdapter,
|
|
310
|
+
developmentSeedComplexities: supportedDevelopmentSeedComplexities,
|
|
274
311
|
features: storageFeatureCatalog,
|
|
275
312
|
modules: storageModuleCatalog,
|
|
276
313
|
};
|
|
@@ -343,20 +380,20 @@ const splitSqlStatements = (sql) => {
|
|
|
343
380
|
}
|
|
344
381
|
return statements;
|
|
345
382
|
};
|
|
346
|
-
const
|
|
383
|
+
const buildCanonicalTables = (database, sqlSources) => {
|
|
347
384
|
const creates = new Map();
|
|
348
385
|
const indexes = new Map();
|
|
349
386
|
let order = 0;
|
|
350
|
-
for (const
|
|
351
|
-
for (const statement of splitSqlStatements(
|
|
352
|
-
const createMatch = /^create table ([a-z0-9_]+)\s*\(/i.exec(statement);
|
|
387
|
+
for (const sql of sqlSources) {
|
|
388
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
389
|
+
const createMatch = /^create table (?:if not exists )?([a-z0-9_]+)\s*\(/i.exec(statement);
|
|
353
390
|
if (createMatch?.[1] !== undefined) {
|
|
354
391
|
creates.set(createMatch[1], { sql: statement, order });
|
|
355
392
|
indexes.set(createMatch[1], []);
|
|
356
393
|
order += 1;
|
|
357
394
|
continue;
|
|
358
395
|
}
|
|
359
|
-
const indexMatch = /^create (?:unique )?index [a-z0-9_]+\s+on ([a-z0-9_]+)\s*\(/i.exec(statement);
|
|
396
|
+
const indexMatch = /^create (?:unique )?index (?:if not exists )?[a-z0-9_]+\s+on ([a-z0-9_]+)\s*\(/i.exec(statement);
|
|
360
397
|
if (indexMatch?.[1] !== undefined) {
|
|
361
398
|
const tableIndexes = indexes.get(indexMatch[1]);
|
|
362
399
|
if (tableIndexes === undefined) {
|
|
@@ -366,6 +403,16 @@ const canonicalTables = (() => {
|
|
|
366
403
|
}
|
|
367
404
|
}
|
|
368
405
|
}
|
|
406
|
+
const oauthClient = creates.get("auth_oauth_client");
|
|
407
|
+
if (database === "postgres" && oauthClient !== undefined) {
|
|
408
|
+
const previous = '@ != "client_credentials"))\'))';
|
|
409
|
+
const replacement = '@ != "client_credentials" && @ != "urn:ietf:params:oauth:grant-type:device_code"))\'))';
|
|
410
|
+
const sql = oauthClient.sql.replace(previous, replacement);
|
|
411
|
+
if (sql === oauthClient.sql) {
|
|
412
|
+
throw new Error("Unable to apply the OAuth device-code grant constraint");
|
|
413
|
+
}
|
|
414
|
+
creates.set("auth_oauth_client", { ...oauthClient, sql });
|
|
415
|
+
}
|
|
369
416
|
const result = [];
|
|
370
417
|
for (const [name, create] of creates) {
|
|
371
418
|
const moduleId = tableModule.get(name);
|
|
@@ -387,7 +434,12 @@ const canonicalTables = (() => {
|
|
|
387
434
|
}
|
|
388
435
|
// oxlint-disable-next-line unicorn/no-array-sort -- ES2023 toSorted is outside the supported target.
|
|
389
436
|
return result.sort((left, right) => left.order - right.order);
|
|
390
|
-
}
|
|
437
|
+
};
|
|
438
|
+
const canonicalTablesByDatabase = {
|
|
439
|
+
postgres: buildCanonicalTables("postgres", postgresAuthStorageMigrations.map((migration) => migration.sql)),
|
|
440
|
+
sqlite: buildCanonicalTables("sqlite", [sqliteAuthStorageSchema]),
|
|
441
|
+
};
|
|
442
|
+
const canonicalTables = canonicalTablesByDatabase.postgres;
|
|
391
443
|
const splitTopLevel = (value) => {
|
|
392
444
|
const parts = [];
|
|
393
445
|
let depth = 0;
|
|
@@ -429,8 +481,9 @@ const splitTopLevel = (value) => {
|
|
|
429
481
|
parts.push(value.slice(start).trim());
|
|
430
482
|
return parts.filter((part) => part.length > 0);
|
|
431
483
|
};
|
|
484
|
+
// oxlint-disable-next-line eslint/complexity -- one pass handles both dialects' column and constraint syntax
|
|
432
485
|
const parseCanonicalTable = (canonical) => {
|
|
433
|
-
const tableMatch = /^create table [a-z0-9_]+\s*\(([\s\S]*)\);$/i.exec(canonical.createTable);
|
|
486
|
+
const tableMatch = /^create table (?:if not exists )?[a-z0-9_]+\s*\(([\s\S]*)\);$/i.exec(canonical.createTable);
|
|
434
487
|
if (tableMatch?.[1] === undefined) {
|
|
435
488
|
throw new Error(`Unable to parse canonical table ${canonical.name}`);
|
|
436
489
|
}
|
|
@@ -453,6 +506,24 @@ const parseCanonicalTable = (canonical) => {
|
|
|
453
506
|
});
|
|
454
507
|
continue;
|
|
455
508
|
}
|
|
509
|
+
const primaryKeyMatch = /^primary key\s*\(([\s\S]*)\)$/i.exec(definition);
|
|
510
|
+
if (primaryKeyMatch?.[1] !== undefined) {
|
|
511
|
+
constraints.push({
|
|
512
|
+
name: `${canonical.name}_pkey`,
|
|
513
|
+
kind: "primary-key",
|
|
514
|
+
columns: splitTopLevel(primaryKeyMatch[1]).map((column) => column.trim().split(/\s+/u)[0].toLowerCase()),
|
|
515
|
+
});
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const tableCheckMatch = /^check\s*\(([\s\S]*)\)$/i.exec(definition);
|
|
519
|
+
if (tableCheckMatch?.[1] !== undefined) {
|
|
520
|
+
constraints.push({
|
|
521
|
+
name: `${canonical.name}_state_check`,
|
|
522
|
+
kind: "check",
|
|
523
|
+
expression: tableCheckMatch[1].trim(),
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
456
527
|
const columnMatch = /^([a-z0-9_]+)\s+(text|bigint|integer|boolean|jsonb)\b([\s\S]*)$/i.exec(definition);
|
|
457
528
|
if (columnMatch === null) {
|
|
458
529
|
throw new Error(`Unable to parse column definition in ${canonical.name}: ${definition}`);
|
|
@@ -461,32 +532,58 @@ const parseCanonicalTable = (canonical) => {
|
|
|
461
532
|
if (name === undefined || rawType === undefined) {
|
|
462
533
|
throw new Error(`Unable to parse column in ${canonical.name}`);
|
|
463
534
|
}
|
|
464
|
-
const inlinePrimaryKey =
|
|
465
|
-
|
|
535
|
+
const inlinePrimaryKey = /\bprimary key\b/i.test(modifiers);
|
|
536
|
+
const namedPrimaryKey = /constraint ([a-z0-9_]+) primary key/i.exec(modifiers)?.[1];
|
|
537
|
+
if (inlinePrimaryKey) {
|
|
466
538
|
constraints.push({
|
|
467
|
-
name:
|
|
539
|
+
name: namedPrimaryKey ?? `${canonical.name}_pkey`,
|
|
468
540
|
kind: "primary-key",
|
|
469
541
|
columns: [name],
|
|
470
542
|
});
|
|
471
543
|
}
|
|
544
|
+
if (/\bunique\b/i.test(modifiers)) {
|
|
545
|
+
constraints.push({
|
|
546
|
+
name: `${canonical.name}_${name}_key`,
|
|
547
|
+
kind: "unique",
|
|
548
|
+
columns: [name],
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const inlineCheck = /\bcheck\s*\(([\s\S]*)\)\s*$/i.exec(modifiers);
|
|
552
|
+
if (inlineCheck?.[1] !== undefined) {
|
|
553
|
+
constraints.push({
|
|
554
|
+
name: `${canonical.name}_${name}_check`,
|
|
555
|
+
kind: "check",
|
|
556
|
+
expression: inlineCheck[1].trim(),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
472
559
|
const defaultMatch = /\bdefault\s+(false|true|-?\d+|'(?:''|[^'])*')/i.exec(modifiers);
|
|
473
560
|
const defaultValue = defaultMatch?.[1];
|
|
474
561
|
columns.push({
|
|
475
562
|
name,
|
|
476
563
|
type: rawType.toLowerCase(),
|
|
477
|
-
notNull: /\bnot null\b/i.test(modifiers) || inlinePrimaryKey
|
|
564
|
+
notNull: /\bnot null\b/i.test(modifiers) || inlinePrimaryKey,
|
|
478
565
|
defaultValue: defaultValue === undefined
|
|
479
566
|
? undefined
|
|
480
567
|
: /^(?:false|true)$/iu.test(defaultValue)
|
|
481
568
|
? defaultValue.toLowerCase()
|
|
482
569
|
: defaultValue,
|
|
483
570
|
identity: /\bgenerated always as identity\b/i.test(modifiers),
|
|
571
|
+
autoincrement: /\bautoincrement\b/i.test(modifiers),
|
|
484
572
|
collateC: /\bcollate "C"/i.test(modifiers),
|
|
485
573
|
});
|
|
486
574
|
}
|
|
487
575
|
return { canonical, columns, constraints };
|
|
488
576
|
};
|
|
489
|
-
const
|
|
577
|
+
const parsedTableByDatabase = {
|
|
578
|
+
postgres: new Map(canonicalTablesByDatabase.postgres.map((table) => [
|
|
579
|
+
table.name,
|
|
580
|
+
parseCanonicalTable(table),
|
|
581
|
+
])),
|
|
582
|
+
sqlite: new Map(canonicalTablesByDatabase.sqlite.map((table) => [
|
|
583
|
+
table.name,
|
|
584
|
+
parseCanonicalTable(table),
|
|
585
|
+
])),
|
|
586
|
+
};
|
|
490
587
|
const compareByOrder = (order) => (left, right) => (order.get(left) ?? Number.MAX_SAFE_INTEGER) -
|
|
491
588
|
(order.get(right) ?? Number.MAX_SAFE_INTEGER);
|
|
492
589
|
const resolveModules = (directModules) => {
|
|
@@ -535,6 +632,21 @@ export const resolveStorageFeatures = (features) => {
|
|
|
535
632
|
impliedModules: modules.filter((moduleId) => !directModules.has(moduleId)),
|
|
536
633
|
};
|
|
537
634
|
};
|
|
635
|
+
const includeDevelopmentSeedRequirements = (selection) => {
|
|
636
|
+
if (selection.modules.includes("core")) {
|
|
637
|
+
return selection;
|
|
638
|
+
}
|
|
639
|
+
const modules = resolveModules(new Set([...selection.modules, "core"]));
|
|
640
|
+
const selectedModules = new Set(modules);
|
|
641
|
+
return {
|
|
642
|
+
...selection,
|
|
643
|
+
modules,
|
|
644
|
+
tables: canonicalTables
|
|
645
|
+
.filter((table) => selectedModules.has(table.module))
|
|
646
|
+
.map((table) => table.name),
|
|
647
|
+
impliedModules: [...selection.impliedModules, "core"],
|
|
648
|
+
};
|
|
649
|
+
};
|
|
538
650
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
539
651
|
const assertKnownKeys = (value, keys, path, code) => {
|
|
540
652
|
const allowed = new Set(keys);
|
|
@@ -551,9 +663,10 @@ const normalizeRequest = (input) => {
|
|
|
551
663
|
}
|
|
552
664
|
const request = input;
|
|
553
665
|
assertKnownKeys(request, ["database", "adapter", "mode", "features", "outputs"], "$", "INVALID_REQUEST");
|
|
554
|
-
if (request.database !== "postgres") {
|
|
555
|
-
throw new StorageSchemaGeneratorError("UNSUPPORTED_DATABASE", "database", `Unsupported database ${JSON.stringify(request.database)}.`,
|
|
666
|
+
if (request.database !== "postgres" && request.database !== "sqlite") {
|
|
667
|
+
throw new StorageSchemaGeneratorError("UNSUPPORTED_DATABASE", "database", `Unsupported database ${JSON.stringify(request.database)}.`, supportedStorageDatabases);
|
|
556
668
|
}
|
|
669
|
+
const { database } = request;
|
|
557
670
|
if (request.adapter !== "effect-qb" && request.adapter !== "drizzle") {
|
|
558
671
|
throw new StorageSchemaGeneratorError("UNSUPPORTED_ADAPTER", "adapter", `Unsupported adapter ${JSON.stringify(request.adapter)}.`, supportedStorageAdapters);
|
|
559
672
|
}
|
|
@@ -573,7 +686,7 @@ const normalizeRequest = (input) => {
|
|
|
573
686
|
features.push(feature);
|
|
574
687
|
}
|
|
575
688
|
if (!Array.isArray(request.outputs) || request.outputs.length === 0) {
|
|
576
|
-
throw new StorageSchemaGeneratorError("INVALID_REQUEST", "outputs", "At least one output is required.", ["migration-sql", "drizzle-schema"]);
|
|
689
|
+
throw new StorageSchemaGeneratorError("INVALID_REQUEST", "outputs", "At least one output is required.", ["migration-sql", "drizzle-schema", "development-seed"]);
|
|
577
690
|
}
|
|
578
691
|
const outputs = [];
|
|
579
692
|
const outputKinds = new Set();
|
|
@@ -582,14 +695,15 @@ const normalizeRequest = (input) => {
|
|
|
582
695
|
if (!isRecord(output)) {
|
|
583
696
|
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", path, "Each output must be an object.");
|
|
584
697
|
}
|
|
585
|
-
assertKnownKeys(output, ["kind", "layout", "includeRelations"], path, "INVALID_OUTPUT");
|
|
586
698
|
if (output.kind === "migration-sql") {
|
|
699
|
+
assertKnownKeys(output, ["kind", "layout"], path, "INVALID_OUTPUT");
|
|
587
700
|
if (output.layout !== "single" && output.layout !== "module") {
|
|
588
701
|
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.layout`, `Invalid migration SQL layout ${JSON.stringify(output.layout)}.`, ["single", "module"]);
|
|
589
702
|
}
|
|
590
703
|
outputs.push({ kind: "migration-sql", layout: output.layout });
|
|
591
704
|
}
|
|
592
705
|
else if (output.kind === "drizzle-schema") {
|
|
706
|
+
assertKnownKeys(output, ["kind", "layout", "includeRelations"], path, "INVALID_OUTPUT");
|
|
593
707
|
if (output.layout !== "single" &&
|
|
594
708
|
output.layout !== "module" &&
|
|
595
709
|
output.layout !== "table") {
|
|
@@ -605,8 +719,29 @@ const normalizeRequest = (input) => {
|
|
|
605
719
|
includeRelations: output.includeRelations === true,
|
|
606
720
|
});
|
|
607
721
|
}
|
|
722
|
+
else if (output.kind === "development-seed") {
|
|
723
|
+
assertKnownKeys(output, ["kind", "target", "complexity"], path, "INVALID_OUTPUT");
|
|
724
|
+
if (typeof output.target !== "string" ||
|
|
725
|
+
!supportedDevelopmentSeedTargets.includes(output.target)) {
|
|
726
|
+
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.target`, `Invalid development seed target ${JSON.stringify(output.target)}.`, supportedDevelopmentSeedTargets);
|
|
727
|
+
}
|
|
728
|
+
if (!developmentSeedTargetsByDatabaseAdapter[database][adapter].includes(output.target)) {
|
|
729
|
+
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.target`, `Development seed target ${JSON.stringify(output.target)} is not available for ${database}.`, developmentSeedTargetsByDatabaseAdapter[database][adapter]);
|
|
730
|
+
}
|
|
731
|
+
if (output.complexity !== undefined &&
|
|
732
|
+
output.complexity !== "medium" &&
|
|
733
|
+
output.complexity !== "low") {
|
|
734
|
+
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.complexity`, `Invalid development seed complexity ${JSON.stringify(output.complexity)}.`, supportedDevelopmentSeedComplexities);
|
|
735
|
+
}
|
|
736
|
+
outputs.push({
|
|
737
|
+
kind: "development-seed",
|
|
738
|
+
target: output.target,
|
|
739
|
+
complexity: output.complexity ??
|
|
740
|
+
"medium",
|
|
741
|
+
});
|
|
742
|
+
}
|
|
608
743
|
else {
|
|
609
|
-
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.kind`, `Unsupported output kind ${JSON.stringify(output.kind)}.`, ["migration-sql", "drizzle-schema"]);
|
|
744
|
+
throw new StorageSchemaGeneratorError("INVALID_OUTPUT", `${path}.kind`, `Unsupported output kind ${JSON.stringify(output.kind)}.`, ["migration-sql", "drizzle-schema", "development-seed"]);
|
|
610
745
|
}
|
|
611
746
|
if (outputKinds.has(output.kind)) {
|
|
612
747
|
throw new StorageSchemaGeneratorError("DUPLICATE_OUTPUT", `${path}.kind`, `Only one ${String(output.kind)} output may be requested.`, ["Combine the requested files by choosing one layout."]);
|
|
@@ -614,20 +749,28 @@ const normalizeRequest = (input) => {
|
|
|
614
749
|
outputKinds.add(output.kind);
|
|
615
750
|
}
|
|
616
751
|
if (adapter === "effect-qb") {
|
|
617
|
-
const unsupportedOutputIndex = outputs.findIndex((output) => output.kind
|
|
752
|
+
const unsupportedOutputIndex = outputs.findIndex((output) => output.kind === "drizzle-schema");
|
|
618
753
|
if (unsupportedOutputIndex !== -1) {
|
|
619
|
-
throw new StorageSchemaGeneratorError("UNSUPPORTED_OUTPUT_FOR_ADAPTER", `outputs[${unsupportedOutputIndex}].kind`, "The effect-qb adapter owns its table declarations and
|
|
754
|
+
throw new StorageSchemaGeneratorError("UNSUPPORTED_OUTPUT_FOR_ADAPTER", `outputs[${unsupportedOutputIndex}].kind`, "The effect-qb adapter owns its table declarations and does not generate a Drizzle schema.", ["migration-sql", "development-seed"]);
|
|
620
755
|
}
|
|
621
756
|
}
|
|
622
757
|
// oxlint-disable-next-line unicorn/no-array-sort -- ES2023 toSorted is outside the supported target.
|
|
623
758
|
outputs.sort((left, right) => {
|
|
624
759
|
if (left.kind !== right.kind) {
|
|
625
|
-
|
|
760
|
+
const order = new Map([
|
|
761
|
+
["migration-sql", 0],
|
|
762
|
+
["drizzle-schema", 1],
|
|
763
|
+
["development-seed", 2],
|
|
764
|
+
]);
|
|
765
|
+
return (order.get(left.kind) ?? 3) - (order.get(right.kind) ?? 3);
|
|
766
|
+
}
|
|
767
|
+
if (left.kind === "development-seed") {
|
|
768
|
+
return left.target.localeCompare(right.target);
|
|
626
769
|
}
|
|
627
770
|
return left.layout.localeCompare(right.layout);
|
|
628
771
|
});
|
|
629
772
|
return {
|
|
630
|
-
database
|
|
773
|
+
database,
|
|
631
774
|
adapter,
|
|
632
775
|
mode: "fresh",
|
|
633
776
|
// oxlint-disable-next-line unicorn/no-array-sort -- ES2023 toSorted is outside the supported target.
|
|
@@ -639,9 +782,9 @@ const renderCanonicalSql = (tables) => `${tables
|
|
|
639
782
|
.flatMap((table) => [table.createTable, ...table.indexes])
|
|
640
783
|
.join("\n\n")}
|
|
641
784
|
`;
|
|
642
|
-
const sqlFiles = (layout, selection) => {
|
|
785
|
+
const sqlFiles = (database, layout, selection) => {
|
|
643
786
|
const selectedTables = new Set(selection.tables);
|
|
644
|
-
const tables =
|
|
787
|
+
const tables = canonicalTablesByDatabase[database].filter((table) => selectedTables.has(table.name));
|
|
645
788
|
if (layout === "single") {
|
|
646
789
|
return [
|
|
647
790
|
{
|
|
@@ -660,34 +803,42 @@ const sqlFiles = (layout, selection) => {
|
|
|
660
803
|
const camelCase = (value) => value.replaceAll(/_([a-z0-9])/g, (_, character) => character.toUpperCase());
|
|
661
804
|
const kebabCase = (value) => value.replaceAll("_", "-");
|
|
662
805
|
const escapeTemplate = (value) => value.replaceAll("`", "\\`").replaceAll("${", "\\${");
|
|
663
|
-
const renderColumn = (column) => {
|
|
806
|
+
const renderColumn = (column, database) => {
|
|
664
807
|
let expression;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
808
|
+
if (database === "sqlite") {
|
|
809
|
+
expression = `${column.type === "integer" ? "integer" : "text"}(${JSON.stringify(column.name)})`;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
switch (column.type) {
|
|
813
|
+
case "bigint": {
|
|
814
|
+
expression = `bigint(${JSON.stringify(column.name)}, { mode: "number" })`;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
case "boolean": {
|
|
818
|
+
expression = `boolean(${JSON.stringify(column.name)})`;
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "integer": {
|
|
822
|
+
expression = `integer(${JSON.stringify(column.name)})`;
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
case "jsonb": {
|
|
826
|
+
expression = `jsonb(${JSON.stringify(column.name)})`;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
case "text": {
|
|
830
|
+
expression = `${column.collateC ? "textC" : "text"}(${JSON.stringify(column.name)})`;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
685
833
|
}
|
|
686
834
|
}
|
|
687
|
-
if (column.
|
|
835
|
+
if (column.autoincrement) {
|
|
836
|
+
expression += ".primaryKey({ autoIncrement: true })";
|
|
837
|
+
}
|
|
838
|
+
else if (column.identity) {
|
|
688
839
|
expression += ".generatedAlwaysAsIdentity()";
|
|
689
840
|
}
|
|
690
|
-
if (column.notNull && !column.identity) {
|
|
841
|
+
if (column.notNull && !column.identity && !column.autoincrement) {
|
|
691
842
|
expression += ".notNull()";
|
|
692
843
|
}
|
|
693
844
|
if (column.defaultValue !== undefined) {
|
|
@@ -710,7 +861,7 @@ const renderIndexColumn = (value) => {
|
|
|
710
861
|
: `t.${column}`;
|
|
711
862
|
};
|
|
712
863
|
const renderIndex = (statement) => {
|
|
713
|
-
const match = /^create (unique )?index ([a-z0-9_]+)\s+on [a-z0-9_]+\s*\(([\s\S]*?)\)(?:\s+where\s+([\s\S]+?))?;$/i.exec(statement);
|
|
864
|
+
const match = /^create (unique )?index (?:if not exists )?([a-z0-9_]+)\s+on [a-z0-9_]+\s*\(([\s\S]*?)\)(?:\s+where\s+([\s\S]+?))?;$/i.exec(statement);
|
|
714
865
|
if (match === null || match[2] === undefined || match[3] === undefined) {
|
|
715
866
|
throw new Error(`Unable to render canonical index: ${statement}`);
|
|
716
867
|
}
|
|
@@ -738,16 +889,27 @@ const renderConstraint = (constraint) => {
|
|
|
738
889
|
}
|
|
739
890
|
}
|
|
740
891
|
};
|
|
741
|
-
const
|
|
742
|
-
|
|
892
|
+
const isAutoincrementPrimaryKey = (table, constraint) => constraint.kind === "primary-key" &&
|
|
893
|
+
constraint.columns?.some((columnName) => table.columns.some((column) => column.name === columnName && column.autoincrement)) === true;
|
|
894
|
+
const drizzleImports = (database, tables) => {
|
|
895
|
+
const imports = new Set([
|
|
896
|
+
database === "postgres" ? "pgTable" : "sqliteTable",
|
|
897
|
+
]);
|
|
743
898
|
for (const table of tables) {
|
|
744
899
|
for (const column of table.columns) {
|
|
745
|
-
imports.add(
|
|
746
|
-
|
|
900
|
+
imports.add(database === "sqlite"
|
|
901
|
+
? column.type === "integer"
|
|
902
|
+
? "integer"
|
|
903
|
+
: "text"
|
|
904
|
+
: column.type);
|
|
905
|
+
if (database === "postgres" && column.collateC) {
|
|
747
906
|
imports.add("customType");
|
|
748
907
|
}
|
|
749
908
|
}
|
|
750
909
|
for (const constraint of table.constraints) {
|
|
910
|
+
if (isAutoincrementPrimaryKey(table, constraint)) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
751
913
|
imports.add(constraint.kind === "primary-key"
|
|
752
914
|
? "primaryKey"
|
|
753
915
|
: constraint.kind === "unique"
|
|
@@ -767,31 +929,34 @@ const drizzleImports = (tables) => {
|
|
|
767
929
|
// oxlint-disable-next-line unicorn/no-array-sort -- ES2023 toSorted is outside the supported target.
|
|
768
930
|
return [...imports].sort();
|
|
769
931
|
};
|
|
770
|
-
const renderTable = (table) => {
|
|
932
|
+
const renderTable = (database, table) => {
|
|
771
933
|
const variable = camelCase(table.canonical.name);
|
|
772
934
|
const extras = [
|
|
773
|
-
...table.constraints
|
|
935
|
+
...table.constraints
|
|
936
|
+
.filter((constraint) => !isAutoincrementPrimaryKey(table, constraint))
|
|
937
|
+
.map(renderConstraint),
|
|
774
938
|
...table.canonical.indexes.map(renderIndex),
|
|
775
939
|
];
|
|
776
|
-
return `export const ${variable} = pgTable(
|
|
940
|
+
return `export const ${variable} = ${database === "postgres" ? "pgTable" : "sqliteTable"}(
|
|
777
941
|
${JSON.stringify(table.canonical.name)},
|
|
778
942
|
{
|
|
779
|
-
${table.columns.map(renderColumn).join("\n")}
|
|
943
|
+
${table.columns.map((column) => renderColumn(column, database)).join("\n")}
|
|
780
944
|
},
|
|
781
945
|
(t) => [
|
|
782
946
|
${extras.join("\n")}
|
|
783
947
|
]
|
|
784
948
|
);`;
|
|
785
949
|
};
|
|
786
|
-
const renderDrizzleTables = (tables) => {
|
|
787
|
-
const imports = drizzleImports(tables);
|
|
950
|
+
const renderDrizzleTables = (database, tables) => {
|
|
951
|
+
const imports = drizzleImports(database, tables);
|
|
788
952
|
const usesSql = imports.includes("sql");
|
|
789
|
-
const
|
|
953
|
+
const databaseImports = imports.filter((name) => name !== "sql");
|
|
790
954
|
const importLines = [
|
|
791
955
|
...(usesSql ? ['import { sql } from "drizzle-orm";'] : []),
|
|
792
|
-
`import { ${
|
|
956
|
+
`import { ${databaseImports.join(", ")} } from "drizzle-orm/${database === "postgres" ? "pg-core" : "sqlite-core"}";`,
|
|
793
957
|
];
|
|
794
|
-
const collatedText =
|
|
958
|
+
const collatedText = database === "postgres" &&
|
|
959
|
+
tables.some((table) => table.columns.some((column) => column.collateC))
|
|
795
960
|
? `
|
|
796
961
|
const textC = customType<{ data: string }>({
|
|
797
962
|
dataType: () => 'text collate "C"',
|
|
@@ -799,7 +964,7 @@ const textC = customType<{ data: string }>({
|
|
|
799
964
|
: "";
|
|
800
965
|
return `${importLines.join("\n")}${collatedText}
|
|
801
966
|
|
|
802
|
-
${tables.map(renderTable).join("\n\n")}
|
|
967
|
+
${tables.map((table) => renderTable(database, table)).join("\n\n")}
|
|
803
968
|
`;
|
|
804
969
|
};
|
|
805
970
|
// oxlint-disable-next-line eslint/complexity -- each branch describes one canonical relation family
|
|
@@ -844,6 +1009,7 @@ const logicalRelations = (tables) => {
|
|
|
844
1009
|
for (const tableName of [
|
|
845
1010
|
"auth_oauth_consent",
|
|
846
1011
|
"auth_oauth_authorization_code",
|
|
1012
|
+
"auth_oauth_device_authorization",
|
|
847
1013
|
"auth_oauth_client_secret",
|
|
848
1014
|
"auth_oauth_provider_mode_token",
|
|
849
1015
|
]) {
|
|
@@ -886,7 +1052,7 @@ const logicalRelations = (tables) => {
|
|
|
886
1052
|
addRelation("auth_role_permission", "permission_id", "auth_permission_definition", "id", "permission");
|
|
887
1053
|
return relations;
|
|
888
1054
|
};
|
|
889
|
-
const renderRelations = (tables, schemaExpression, importPath) => {
|
|
1055
|
+
const renderRelations = (database, tables, schemaExpression, importPath) => {
|
|
890
1056
|
const relations = logicalRelations(tables);
|
|
891
1057
|
const grouped = new Map();
|
|
892
1058
|
for (const relation of relations) {
|
|
@@ -916,7 +1082,7 @@ ${tableRelations
|
|
|
916
1082
|
];
|
|
917
1083
|
return `${imports.join("\n")}
|
|
918
1084
|
|
|
919
|
-
// Drizzle relations are application-level only and do not create PostgreSQL foreign keys.
|
|
1085
|
+
// Drizzle relations are application-level only and do not create ${database === "postgres" ? "PostgreSQL" : "SQLite"} foreign keys.
|
|
920
1086
|
export const relations = defineRelations(${schemaExpression}, (r) => ({
|
|
921
1087
|
${entries.join("\n")}
|
|
922
1088
|
}));
|
|
@@ -924,18 +1090,18 @@ ${entries.join("\n")}
|
|
|
924
1090
|
};
|
|
925
1091
|
const exportFile = (paths) => `${paths.map((path) => `export * from ${JSON.stringify(path)};`).join("\n")}
|
|
926
1092
|
`;
|
|
927
|
-
const drizzleFiles = (layout, includeRelations, selection) => {
|
|
1093
|
+
const drizzleFiles = (database, layout, includeRelations, selection) => {
|
|
928
1094
|
const selected = new Set(selection.tables);
|
|
929
|
-
const tables =
|
|
1095
|
+
const tables = canonicalTablesByDatabase[database]
|
|
930
1096
|
.filter((table) => selected.has(table.name))
|
|
931
|
-
.map((table) =>
|
|
1097
|
+
.map((table) => parsedTableByDatabase[database].get(table.name));
|
|
932
1098
|
if (layout === "single") {
|
|
933
|
-
let content = renderDrizzleTables(tables);
|
|
1099
|
+
let content = renderDrizzleTables(database, tables);
|
|
934
1100
|
if (includeRelations) {
|
|
935
1101
|
const schema = `{ ${tables
|
|
936
1102
|
.map((table) => camelCase(table.canonical.name))
|
|
937
1103
|
.join(", ")} }`;
|
|
938
|
-
content += `\n${renderRelations(tables, schema)}`;
|
|
1104
|
+
content += `\n${renderRelations(database, tables, schema)}`;
|
|
939
1105
|
}
|
|
940
1106
|
return [{ path: "schema.ts", mediaType: "text/typescript", content }];
|
|
941
1107
|
}
|
|
@@ -952,7 +1118,7 @@ const drizzleFiles = (layout, includeRelations, selection) => {
|
|
|
952
1118
|
const files = groups.map((group) => ({
|
|
953
1119
|
path: `${directory}/${group.name}.ts`,
|
|
954
1120
|
mediaType: "text/typescript",
|
|
955
|
-
content: renderDrizzleTables(group.tables),
|
|
1121
|
+
content: renderDrizzleTables(database, group.tables),
|
|
956
1122
|
}));
|
|
957
1123
|
files.push({
|
|
958
1124
|
path: `${directory}/index.ts`,
|
|
@@ -963,7 +1129,7 @@ const drizzleFiles = (layout, includeRelations, selection) => {
|
|
|
963
1129
|
files.push({
|
|
964
1130
|
path: "relations.ts",
|
|
965
1131
|
mediaType: "text/typescript",
|
|
966
|
-
content: renderRelations(tables, "schema", `./${directory}/index.js`),
|
|
1132
|
+
content: renderRelations(database, tables, "schema", `./${directory}/index.js`),
|
|
967
1133
|
});
|
|
968
1134
|
}
|
|
969
1135
|
files.push({
|
|
@@ -976,6 +1142,224 @@ const drizzleFiles = (layout, includeRelations, selection) => {
|
|
|
976
1142
|
});
|
|
977
1143
|
return files;
|
|
978
1144
|
};
|
|
1145
|
+
const developmentSeedCoreImports = (includePassword) => `import {
|
|
1146
|
+
DevelopmentSeed,
|
|
1147
|
+
DevelopmentSeedLive,
|
|
1148
|
+
} from "@effect-auth/core/DevelopmentSeed";
|
|
1149
|
+
import { IdentityKindRegistryDefaultLive } from "@effect-auth/core/Identity";
|
|
1150
|
+
${includePassword
|
|
1151
|
+
? `import { WebCryptoLive } from "@effect-auth/core/Crypto";
|
|
1152
|
+
import { Pbkdf2PasswordHasherLive } from "@effect-auth/core/Password";
|
|
1153
|
+
`
|
|
1154
|
+
: ""}`;
|
|
1155
|
+
const developmentSeedDependenciesSource = (includePassword) => includePassword
|
|
1156
|
+
? `const DevelopmentSeedSupportLive = Layer.mergeAll(
|
|
1157
|
+
IdentityKindRegistryDefaultLive,
|
|
1158
|
+
Pbkdf2PasswordHasherLive().pipe(Layer.provide(WebCryptoLive())),
|
|
1159
|
+
);
|
|
1160
|
+
`
|
|
1161
|
+
: `const DevelopmentSeedSupportLive = IdentityKindRegistryDefaultLive;
|
|
1162
|
+
`;
|
|
1163
|
+
const developmentSeedProgramSource = (includePassword) => `const developmentSeedProgram = Effect.gen(function* () {
|
|
1164
|
+
const seed = yield* DevelopmentSeed;
|
|
1165
|
+
const result = yield* seed.${includePassword ? "seedPasswordUsers" : "seedUsers"};
|
|
1166
|
+
yield* Effect.logInfo(
|
|
1167
|
+
\`Development auth seed complete: \${result.created} created, \${result.existing} existing.\`,
|
|
1168
|
+
);
|
|
1169
|
+
return result;
|
|
1170
|
+
});
|
|
1171
|
+
`;
|
|
1172
|
+
const developmentSeedConfigurationErrorSource = `export class DevelopmentSeedConfigurationError extends Data.TaggedError(
|
|
1173
|
+
"DevelopmentSeedConfigurationError",
|
|
1174
|
+
)<{ readonly message: string }> {}
|
|
1175
|
+
`;
|
|
1176
|
+
const alchemyD1SeedSource = (adapter, includePassword, complexity) => {
|
|
1177
|
+
const drizzle = adapter === "drizzle";
|
|
1178
|
+
const storageModule = drizzle
|
|
1179
|
+
? "DrizzleD1SqliteStorage"
|
|
1180
|
+
: "EffectQbSqliteStorage";
|
|
1181
|
+
const storageType = drizzle
|
|
1182
|
+
? "DrizzleD1DatabaseLike"
|
|
1183
|
+
: "D1EffectQbDatabaseLike";
|
|
1184
|
+
const storageLayer = drizzle
|
|
1185
|
+
? "DrizzleD1SqliteAuthStorageLive"
|
|
1186
|
+
: "D1EffectQbSqliteAuthStorageLive";
|
|
1187
|
+
const medium = complexity === "medium";
|
|
1188
|
+
return `import type { ${storageType} } from "@effect-auth/core/${storageModule}";
|
|
1189
|
+
import { ${storageLayer} } from "@effect-auth/core/${storageModule}";
|
|
1190
|
+
${developmentSeedCoreImports(includePassword)}import { Data, Effect, Layer } from "effect";
|
|
1191
|
+
|
|
1192
|
+
${developmentSeedConfigurationErrorSource}
|
|
1193
|
+
${developmentSeedDependenciesSource(includePassword)}
|
|
1194
|
+
${developmentSeedProgramSource(includePassword)}
|
|
1195
|
+
${medium ? "export " : ""}const AppDevelopmentSeedLive = (database: ${storageType}) =>
|
|
1196
|
+
DevelopmentSeedLive.pipe(
|
|
1197
|
+
Layer.provideMerge(
|
|
1198
|
+
Layer.mergeAll(
|
|
1199
|
+
${storageLayer}(database),
|
|
1200
|
+
DevelopmentSeedSupportLive,
|
|
1201
|
+
),
|
|
1202
|
+
),
|
|
1203
|
+
);
|
|
1204
|
+
|
|
1205
|
+
${medium ? "export const runDevelopmentSeed" : "export const seedDevelopmentAuth"} = (
|
|
1206
|
+
database: ${storageType},
|
|
1207
|
+
isDevelopment: boolean,
|
|
1208
|
+
) =>
|
|
1209
|
+
Effect.gen(function* () {
|
|
1210
|
+
if (!isDevelopment) {
|
|
1211
|
+
return yield* new DevelopmentSeedConfigurationError({
|
|
1212
|
+
message: "Refusing to seed Effect Auth outside Alchemy development mode.",
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return yield* developmentSeedProgram.pipe(
|
|
1216
|
+
Effect.provide(AppDevelopmentSeedLive(database)),
|
|
1217
|
+
);
|
|
1218
|
+
});
|
|
1219
|
+
${medium
|
|
1220
|
+
? ""
|
|
1221
|
+
: `
|
|
1222
|
+
// In alchemy.run.ts, after migrations:
|
|
1223
|
+
// const alchemy = yield* Alchemy.AlchemyContext;
|
|
1224
|
+
// yield* Database;
|
|
1225
|
+
// const client = yield* Cloudflare.D1.QueryDatabase(Database);
|
|
1226
|
+
// yield* seedDevelopmentAuth(yield* client.raw, alchemy.dev);
|
|
1227
|
+
`}`;
|
|
1228
|
+
};
|
|
1229
|
+
const standaloneConfigurationSource = (value) => {
|
|
1230
|
+
const readValue = value === "sqlite"
|
|
1231
|
+
? 'const filename = process.env.EFFECT_AUTH_SQLITE_FILE ?? "./data/auth.sqlite";'
|
|
1232
|
+
: `const databaseUrl = process.env.DATABASE_URL;
|
|
1233
|
+
if (databaseUrl === undefined || databaseUrl.length === 0) {
|
|
1234
|
+
return yield* new DevelopmentSeedConfigurationError({
|
|
1235
|
+
message: "DATABASE_URL is required.",
|
|
1236
|
+
});
|
|
1237
|
+
}`;
|
|
1238
|
+
const returned = value === "sqlite" ? "filename" : "databaseUrl";
|
|
1239
|
+
return `const developmentSeedConfiguration = Effect.gen(function* () {
|
|
1240
|
+
if (
|
|
1241
|
+
process.env.NODE_ENV === "production" ||
|
|
1242
|
+
process.env.EFFECT_AUTH_DEVELOPMENT_SEED !== "true"
|
|
1243
|
+
) {
|
|
1244
|
+
return yield* new DevelopmentSeedConfigurationError({
|
|
1245
|
+
message: "Set EFFECT_AUTH_DEVELOPMENT_SEED=true to run the development seed.",
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
${readValue}
|
|
1249
|
+
return ${returned};
|
|
1250
|
+
});
|
|
1251
|
+
`;
|
|
1252
|
+
};
|
|
1253
|
+
const sqliteSeedSource = (runtime, includePassword, complexity) => {
|
|
1254
|
+
const storageModule = runtime === "bun" ? "DrizzleBunSqliteStorage" : "DrizzleNodeSqliteStorage";
|
|
1255
|
+
const storageLayer = runtime === "bun"
|
|
1256
|
+
? "DrizzleBunSqliteAuthStorageLive"
|
|
1257
|
+
: "DrizzleNodeSqliteAuthStorageLive";
|
|
1258
|
+
const medium = complexity === "medium";
|
|
1259
|
+
return `import { ${storageLayer} } from "@effect-auth/core/${storageModule}";
|
|
1260
|
+
${developmentSeedCoreImports(includePassword)}import { Data, Effect, Layer } from "effect";
|
|
1261
|
+
|
|
1262
|
+
${developmentSeedConfigurationErrorSource}
|
|
1263
|
+
${developmentSeedDependenciesSource(includePassword)}
|
|
1264
|
+
${developmentSeedProgramSource(includePassword)}
|
|
1265
|
+
${standaloneConfigurationSource("sqlite")}
|
|
1266
|
+
${medium ? "export " : ""}const AppDevelopmentSeedLive = (filename: string) =>
|
|
1267
|
+
DevelopmentSeedLive.pipe(
|
|
1268
|
+
Layer.provideMerge(
|
|
1269
|
+
Layer.mergeAll(
|
|
1270
|
+
${storageLayer}({ filename }),
|
|
1271
|
+
DevelopmentSeedSupportLive,
|
|
1272
|
+
),
|
|
1273
|
+
),
|
|
1274
|
+
);
|
|
1275
|
+
|
|
1276
|
+
${medium ? "export " : ""}const runDevelopmentSeed = Effect.gen(function* () {
|
|
1277
|
+
const filename = yield* developmentSeedConfiguration;
|
|
1278
|
+
return yield* developmentSeedProgram.pipe(
|
|
1279
|
+
Effect.provide(AppDevelopmentSeedLive(filename)),
|
|
1280
|
+
Effect.scoped,
|
|
1281
|
+
);
|
|
1282
|
+
});
|
|
1283
|
+
${medium ? "" : "\nawait Effect.runPromise(runDevelopmentSeed);\n"}`;
|
|
1284
|
+
};
|
|
1285
|
+
const postgresSeedSource = (adapter, includePassword, complexity) => {
|
|
1286
|
+
const drizzle = adapter === "drizzle";
|
|
1287
|
+
const medium = complexity === "medium";
|
|
1288
|
+
const storageImports = drizzle
|
|
1289
|
+
? 'import { DrizzlePostgresAuthStorageLive } from "@effect-auth/core/DrizzlePostgresStorage";\n'
|
|
1290
|
+
: `import * as PgClient from "@effect/sql-pg/PgClient";
|
|
1291
|
+
import { EffectQbPostgresCoreStoresLive } from "@effect-auth/core/EffectQbPostgresStorage";
|
|
1292
|
+
`;
|
|
1293
|
+
const layerSource = drizzle
|
|
1294
|
+
? `${medium ? "export " : ""}const AppDevelopmentSeedLive = (databaseUrl: string) =>
|
|
1295
|
+
DevelopmentSeedLive.pipe(
|
|
1296
|
+
Layer.provideMerge(
|
|
1297
|
+
Layer.mergeAll(
|
|
1298
|
+
DrizzlePostgresAuthStorageLive({ url: Redacted.make(databaseUrl) }),
|
|
1299
|
+
DevelopmentSeedSupportLive,
|
|
1300
|
+
),
|
|
1301
|
+
),
|
|
1302
|
+
);`
|
|
1303
|
+
: `${medium ? "export " : ""}const AppDevelopmentSeedLive = (sql: PgClient.PgClient) =>
|
|
1304
|
+
DevelopmentSeedLive.pipe(
|
|
1305
|
+
Layer.provideMerge(
|
|
1306
|
+
Layer.mergeAll(
|
|
1307
|
+
EffectQbPostgresCoreStoresLive(sql),
|
|
1308
|
+
DevelopmentSeedSupportLive,
|
|
1309
|
+
),
|
|
1310
|
+
),
|
|
1311
|
+
);`;
|
|
1312
|
+
const runSource = drizzle
|
|
1313
|
+
? `const databaseUrl = yield* developmentSeedConfiguration;
|
|
1314
|
+
return yield* developmentSeedProgram.pipe(
|
|
1315
|
+
Effect.provide(AppDevelopmentSeedLive(databaseUrl)),
|
|
1316
|
+
Effect.scoped,
|
|
1317
|
+
);`
|
|
1318
|
+
: `const databaseUrl = yield* developmentSeedConfiguration;
|
|
1319
|
+
const PostgresClientLive = PgClient.layer({
|
|
1320
|
+
url: Redacted.make(databaseUrl),
|
|
1321
|
+
});
|
|
1322
|
+
return yield* Effect.gen(function* () {
|
|
1323
|
+
const sql = yield* PgClient.PgClient;
|
|
1324
|
+
return yield* developmentSeedProgram.pipe(
|
|
1325
|
+
Effect.provide(AppDevelopmentSeedLive(sql)),
|
|
1326
|
+
);
|
|
1327
|
+
}).pipe(Effect.provide(PostgresClientLive), Effect.scoped);`;
|
|
1328
|
+
return `${storageImports}${developmentSeedCoreImports(includePassword)}import { Data, Effect, Layer, Redacted } from "effect";
|
|
1329
|
+
|
|
1330
|
+
${developmentSeedConfigurationErrorSource}
|
|
1331
|
+
${developmentSeedDependenciesSource(includePassword)}
|
|
1332
|
+
${developmentSeedProgramSource(includePassword)}
|
|
1333
|
+
${standaloneConfigurationSource("postgres")}
|
|
1334
|
+
${layerSource}
|
|
1335
|
+
|
|
1336
|
+
${medium ? "export " : ""}const runDevelopmentSeed = Effect.gen(function* () {
|
|
1337
|
+
${runSource}
|
|
1338
|
+
});
|
|
1339
|
+
${medium ? "" : "\nawait Effect.runPromise(runDevelopmentSeed);\n"}`;
|
|
1340
|
+
};
|
|
1341
|
+
const developmentSeedFiles = (adapter, target, includePassword, complexity) => {
|
|
1342
|
+
let content;
|
|
1343
|
+
switch (target) {
|
|
1344
|
+
case "alchemy-d1": {
|
|
1345
|
+
content = alchemyD1SeedSource(adapter, includePassword, complexity);
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
case "bun-sqlite": {
|
|
1349
|
+
content = sqliteSeedSource("bun", includePassword, complexity);
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
case "node-sqlite": {
|
|
1353
|
+
content = sqliteSeedSource("node", includePassword, complexity);
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
case "postgres": {
|
|
1357
|
+
content = postgresSeedSource(adapter, includePassword, complexity);
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return [{ path: "seed-auth.ts", mediaType: "text/typescript", content }];
|
|
1362
|
+
};
|
|
979
1363
|
const fingerprint = (value) => {
|
|
980
1364
|
let hash = 2_166_136_261;
|
|
981
1365
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -988,21 +1372,33 @@ const fingerprint = (value) => {
|
|
|
988
1372
|
export const parseStorageSchemaGeneratorRequest = (request) => normalizeRequest(request);
|
|
989
1373
|
export const generateStorageArtifacts = (request) => {
|
|
990
1374
|
const normalized = normalizeRequest(request);
|
|
991
|
-
const
|
|
1375
|
+
const featureSelection = resolveStorageFeatures(normalized.features);
|
|
1376
|
+
const resolved = normalized.outputs.some(({ kind }) => kind === "development-seed")
|
|
1377
|
+
? includeDevelopmentSeedRequirements(featureSelection)
|
|
1378
|
+
: featureSelection;
|
|
1379
|
+
const schemaVersion = storageSchemaVersions[normalized.database];
|
|
992
1380
|
const files = [];
|
|
993
1381
|
const warnings = [];
|
|
994
1382
|
for (const output of normalized.outputs) {
|
|
995
1383
|
if (output.kind === "migration-sql") {
|
|
996
|
-
files.push(...sqlFiles(output.layout, resolved));
|
|
1384
|
+
files.push(...sqlFiles(normalized.database, output.layout, resolved));
|
|
997
1385
|
}
|
|
998
|
-
else {
|
|
999
|
-
files.push(...drizzleFiles(output.layout, output.includeRelations, resolved));
|
|
1386
|
+
else if (output.kind === "drizzle-schema") {
|
|
1387
|
+
files.push(...drizzleFiles(normalized.database, output.layout, output.includeRelations, resolved));
|
|
1000
1388
|
if (output.includeRelations) {
|
|
1001
|
-
warnings.push(
|
|
1389
|
+
warnings.push(`Drizzle relations are application-level only and do not create ${normalized.database === "postgres" ? "PostgreSQL" : "SQLite"} foreign keys.`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
files.push(...developmentSeedFiles(normalized.adapter, output.target, normalized.features.includes("password"), output.complexity));
|
|
1394
|
+
warnings.push("The generated development seed creates public demo credentials and must never run against production data.");
|
|
1395
|
+
if (!normalized.features.includes("password")) {
|
|
1396
|
+
warnings.push("The selected features do not include password authentication, so the development seed does not create password credentials.");
|
|
1002
1397
|
}
|
|
1003
1398
|
}
|
|
1004
1399
|
}
|
|
1005
1400
|
const fingerprintInput = JSON.stringify({
|
|
1401
|
+
schemaVersion,
|
|
1006
1402
|
request: normalized,
|
|
1007
1403
|
resolved,
|
|
1008
1404
|
files,
|
|
@@ -1011,7 +1407,7 @@ export const generateStorageArtifacts = (request) => {
|
|
|
1011
1407
|
return {
|
|
1012
1408
|
apiVersion: storageSchemaGeneratorApiVersion,
|
|
1013
1409
|
generatorVersion: storageSchemaGeneratorVersion,
|
|
1014
|
-
schemaVersion
|
|
1410
|
+
schemaVersion,
|
|
1015
1411
|
fingerprint: fingerprint(fingerprintInput),
|
|
1016
1412
|
request: normalized,
|
|
1017
1413
|
resolved,
|