alepha 0.19.2 → 0.19.4
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/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/dist/api/audits/index.d.ts +8 -8
- package/dist/api/invitations/index.d.ts +790 -0
- package/dist/api/invitations/index.d.ts.map +1 -0
- package/dist/api/invitations/index.js +665 -0
- package/dist/api/invitations/index.js.map +1 -0
- package/dist/api/jobs/index.browser.js +8 -9
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +90 -34
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +267 -44
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +0 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +0 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +112 -1
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +90 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +79 -12
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/{billing → api/payments}/index.d.ts +67 -49
- package/dist/api/payments/index.d.ts.map +1 -0
- package/dist/{billing → api/payments}/index.js +108 -74
- package/dist/api/payments/index.js.map +1 -0
- package/dist/api/subscriptions/index.d.ts +1692 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1870 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +27 -21
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +167 -34
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/workflows/index.browser.js +246 -0
- package/dist/api/workflows/index.browser.js.map +1 -0
- package/dist/api/workflows/index.d.ts +1618 -0
- package/dist/api/workflows/index.d.ts.map +1 -0
- package/dist/api/workflows/index.js +1504 -0
- package/dist/api/workflows/index.js.map +1 -0
- package/dist/cli/config/index.d.ts +6 -28
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +5 -10
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +11669 -208
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +60 -69
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +5 -0
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +4 -0
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +69 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +6 -2
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +38 -10
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +85 -26
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +21 -2
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +33 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +25 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +25 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +25 -2
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js +24 -8
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +0 -18
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +25 -73
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +10 -32
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +25 -73
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +3 -3
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +3 -3
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/router/index.browser.js +25 -3
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +16 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +25 -3
- package/dist/react/router/index.js.map +1 -1
- package/dist/security/index.d.ts +28 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +28 -0
- package/dist/security/index.js.map +1 -1
- package/package.json +37 -20
- package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
- package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
- package/src/api/invitations/controllers/InvitationController.ts +84 -0
- package/src/api/invitations/entities/invitations.ts +33 -0
- package/src/api/invitations/index.ts +65 -0
- package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
- package/src/api/invitations/providers/InvitationProvider.ts +45 -0
- package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
- package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
- package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
- package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
- package/src/api/invitations/services/InvitationService.ts +556 -0
- package/src/api/jobs/__tests__/$job.spec.ts +876 -0
- package/src/api/jobs/controllers/AdminJobController.ts +44 -0
- package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
- package/src/api/jobs/index.ts +0 -3
- package/src/api/jobs/primitives/$job.ts +22 -11
- package/src/api/jobs/providers/JobProvider.ts +239 -25
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
- package/src/api/jobs/services/JobService.ts +51 -12
- package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
- package/src/api/parameters/index.browser.ts +12 -0
- package/src/api/parameters/primitives/$parameter.ts +20 -3
- package/src/api/parameters/services/ParameterProvider.ts +48 -7
- package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
- package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
- package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
- package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
- package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
- package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
- package/src/{billing → api/payments}/index.ts +31 -25
- package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
- package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
- package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
- package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +144 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
- package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
- package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
- package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
- package/src/api/users/__tests__/SessionService.spec.ts +142 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
- package/src/api/users/controllers/UserController.ts +3 -8
- package/src/api/users/notifications/UserNotifications.ts +23 -0
- package/src/api/users/schemas/loginSchema.ts +1 -1
- package/src/api/users/services/CredentialService.ts +51 -4
- package/src/api/users/services/RegistrationService.ts +38 -9
- package/src/api/users/services/SessionService.ts +62 -9
- package/src/api/users/services/UserService.ts +21 -12
- package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
- package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
- package/src/api/workflows/entities/workflowExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
- package/src/api/workflows/index.browser.ts +22 -0
- package/src/api/workflows/index.ts +124 -0
- package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
- package/src/api/workflows/primitives/$workflow.ts +202 -0
- package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
- package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
- package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
- package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
- package/src/api/workflows/services/WorkflowService.ts +382 -0
- package/src/cli/config/defineConfig.ts +17 -46
- package/src/cli/core/providers/ViteDevServerProvider.ts +45 -3
- package/src/cli/core/services/PackageManagerUtils.ts +3 -1
- package/src/cli/core/services/ProjectScaffolder.ts +5 -5
- package/src/cli/core/templates/agentMd.ts +14 -5
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/devtools/index.ts +21 -1
- package/src/cli/platform/index.ts +23 -2
- package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
- package/src/cli/vendor/index.ts +20 -3
- package/src/cli/vendor/services/VendorService.ts +126 -27
- package/src/core/Alepha.ts +10 -0
- package/src/core/__tests__/TypeProvider.spec.ts +4 -2
- package/src/core/providers/SchemaValidator.ts +1 -1
- package/src/core/providers/TypeProvider.ts +46 -3
- package/src/logger/index.ts +6 -1
- package/src/orm/__tests__/enums.spec.ts +22 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
- package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
- package/src/orm/core/providers/DrizzleKitProvider.ts +56 -105
- package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
- package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
- package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
- package/src/security/primitives/$secure.ts +28 -0
- package/tsconfig.base.json +0 -1
- package/dist/billing/index.d.ts.map +0 -1
- package/dist/billing/index.js.map +0 -1
- package/src/billing/__tests__/BillingService.spec.ts +0 -136
- /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
- /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
- /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
|
@@ -29,10 +29,13 @@ export class DrizzleKitProvider {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (this.alepha.isTest()) {
|
|
32
|
-
// In test mode, we want to generate migrations from scratch (no snapshots)
|
|
33
|
-
// to ensure the generated SQL is correct and can be applied cleanly.
|
|
34
32
|
const { statements } = await this.generateMigration(provider);
|
|
35
|
-
await this.
|
|
33
|
+
await this.executeStatements(
|
|
34
|
+
statements.map((s) =>
|
|
35
|
+
s.replace(/^CREATE SCHEMA /i, "CREATE SCHEMA IF NOT EXISTS "),
|
|
36
|
+
),
|
|
37
|
+
provider,
|
|
38
|
+
);
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -52,14 +55,18 @@ export class DrizzleKitProvider {
|
|
|
52
55
|
await this.pushPostgres(kit, models, provider);
|
|
53
56
|
}
|
|
54
57
|
} catch (error) {
|
|
55
|
-
// Fallback: generate migrations from scratch (no snapshots)
|
|
56
|
-
// Covers drivers that don't support introspection (e.g. PgLite, sqlite-proxy)
|
|
58
|
+
// Fallback: generate migrations from scratch (no snapshots).
|
|
59
|
+
// Covers drivers that don't support introspection (e.g. PgLite, sqlite-proxy).
|
|
60
|
+
//
|
|
61
|
+
// If push partially executed (e.g. interactive rename applied then errored),
|
|
62
|
+
// the fallback would re-create tables that already exist. Guard against this
|
|
63
|
+
// by attempting the statements individually and ignoring "already exists" errors.
|
|
57
64
|
this.log.debug(
|
|
58
65
|
"Push sync not available, falling back to migration generation",
|
|
59
66
|
{ error },
|
|
60
67
|
);
|
|
61
68
|
const { statements } = await this.generateMigration(provider);
|
|
62
|
-
await this.
|
|
69
|
+
await this.executeStatementsLenient(statements, provider);
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
this.log.info(
|
|
@@ -127,7 +134,6 @@ export class DrizzleKitProvider {
|
|
|
127
134
|
public getModels(provider: DatabaseProvider): Record<string, unknown> {
|
|
128
135
|
const models: Record<string, unknown> = {};
|
|
129
136
|
|
|
130
|
-
// Required for pushSchema with Postgres and POSTGRES_SCHEMA
|
|
131
137
|
for (const [key, value] of provider.schemas.entries()) {
|
|
132
138
|
models[`__schema_${key}`] = value;
|
|
133
139
|
}
|
|
@@ -232,14 +238,10 @@ export class DrizzleKitProvider {
|
|
|
232
238
|
};
|
|
233
239
|
|
|
234
240
|
if (provider.dialect === "sqlite") {
|
|
235
|
-
result = await
|
|
236
|
-
kit.pushSQLiteSchema(models, provider.db as any),
|
|
237
|
-
);
|
|
241
|
+
result = await kit.pushSQLiteSchema(models, provider.db as any);
|
|
238
242
|
} else {
|
|
239
243
|
const wrappedDb = this.wrapDbForDrizzleKit(provider.db);
|
|
240
|
-
result = await
|
|
241
|
-
kit.pushSchema(models, wrappedDb, [provider.schema]),
|
|
242
|
-
);
|
|
244
|
+
result = await kit.pushSchema(models, wrappedDb, [provider.schema]);
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
return {
|
|
@@ -256,11 +258,11 @@ export class DrizzleKitProvider {
|
|
|
256
258
|
models: Record<string, unknown>,
|
|
257
259
|
provider: DatabaseProvider,
|
|
258
260
|
): Promise<void> {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
+
const { statementsToExecute } = await kit.pushSQLiteSchema(
|
|
262
|
+
models,
|
|
263
|
+
provider.db as any,
|
|
261
264
|
);
|
|
262
|
-
|
|
263
|
-
await this.runPushResult(result, provider);
|
|
265
|
+
await this.executeStatements(statementsToExecute, provider);
|
|
264
266
|
}
|
|
265
267
|
|
|
266
268
|
/**
|
|
@@ -275,78 +277,61 @@ export class DrizzleKitProvider {
|
|
|
275
277
|
await this.createSchemaIfNotExists(provider, provider.schema);
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
// Drizzle Kit's pushSchema
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
// This assumes node-postgres (pg) format where execute() returns { rows: [...] }.
|
|
283
|
-
// But postgres.js (used by Alepha) returns a Result that extends Array — no .rows property.
|
|
284
|
-
// We wrap the db instance so execute() returns { rows: [...] } as expected.
|
|
280
|
+
// Drizzle Kit's pushSchema expects execute() to return { rows: T[] }
|
|
281
|
+
// (node-postgres/pg format), but postgres.js returns a Result that
|
|
282
|
+
// extends Array directly — no .rows property.
|
|
285
283
|
const wrappedDb = this.wrapDbForDrizzleKit(provider.db);
|
|
286
284
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
await this.runPushResult(result, provider);
|
|
285
|
+
const { statementsToExecute } = await kit.pushSchema(models, wrappedDb, [
|
|
286
|
+
provider.schema,
|
|
287
|
+
]);
|
|
288
|
+
await this.executeStatements(statementsToExecute, provider);
|
|
292
289
|
}
|
|
293
290
|
|
|
294
291
|
/**
|
|
295
|
-
*
|
|
292
|
+
* Execute a list of SQL statements against the provider.
|
|
296
293
|
*/
|
|
297
|
-
protected async
|
|
298
|
-
|
|
299
|
-
statementsToExecute: string[];
|
|
300
|
-
warnings: string[];
|
|
301
|
-
hasDataLoss: boolean;
|
|
302
|
-
},
|
|
294
|
+
protected async executeStatements(
|
|
295
|
+
statements: string[],
|
|
303
296
|
provider: DatabaseProvider,
|
|
304
|
-
) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (upper.startsWith("DROP SCHEMA") || upper.startsWith("DROP TABLE")) {
|
|
309
|
-
this.log.warn("Skipping destructive statement", { statement: s });
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
return true;
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
if (result.hasDataLoss) {
|
|
316
|
-
this.log.warn("Push would cause data loss", {
|
|
317
|
-
warnings: result.warnings,
|
|
318
|
-
statements: result.statementsToExecute,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
if (statements.length > 0) {
|
|
299
|
+
this.log.debug(`Executing ${statements.length} statements ...`, {
|
|
300
|
+
statements,
|
|
319
301
|
});
|
|
320
302
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
this.log.debug(`Pushing ${safe.length} statements ...`, {
|
|
324
|
-
statements: safe,
|
|
325
|
-
});
|
|
326
|
-
for (const statement of safe) {
|
|
327
|
-
await provider.execute(sql.raw(statement));
|
|
328
|
-
}
|
|
303
|
+
for (const statement of statements) {
|
|
304
|
+
await provider.execute(sql.raw(statement));
|
|
329
305
|
}
|
|
330
306
|
}
|
|
331
307
|
|
|
332
308
|
/**
|
|
333
|
-
* Execute
|
|
334
|
-
*
|
|
309
|
+
* Execute SQL statements, ignoring "already exists" errors.
|
|
310
|
+
*
|
|
311
|
+
* Used by the fallback migration path where push may have partially
|
|
312
|
+
* applied changes before erroring, leaving some objects already created.
|
|
335
313
|
*/
|
|
336
|
-
protected async
|
|
314
|
+
protected async executeStatementsLenient(
|
|
337
315
|
statements: string[],
|
|
338
316
|
provider: DatabaseProvider,
|
|
339
317
|
): Promise<void> {
|
|
318
|
+
if (statements.length > 0) {
|
|
319
|
+
this.log.debug(
|
|
320
|
+
`Executing ${statements.length} statements (lenient) ...`,
|
|
321
|
+
{ statements },
|
|
322
|
+
);
|
|
323
|
+
}
|
|
340
324
|
for (const statement of statements) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
325
|
+
try {
|
|
326
|
+
await provider.execute(sql.raw(statement));
|
|
327
|
+
} catch (error: any) {
|
|
328
|
+
const message = error?.message ?? "";
|
|
329
|
+
if (message.includes("already exists")) {
|
|
330
|
+
this.log.debug(`Skipped (already exists): ${statement.slice(0, 80)}`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
348
334
|
}
|
|
349
|
-
await provider.execute(sql.raw(statement));
|
|
350
335
|
}
|
|
351
336
|
}
|
|
352
337
|
|
|
@@ -375,7 +360,7 @@ export class DrizzleKitProvider {
|
|
|
375
360
|
|
|
376
361
|
// -------------------------------------------------------------------------------------------------------------------
|
|
377
362
|
|
|
378
|
-
// TODO: remove
|
|
363
|
+
// TODO: remove when Drizzle Kit fixes postgres.js compatibility
|
|
379
364
|
|
|
380
365
|
/**
|
|
381
366
|
* Wrap a Drizzle PgDatabase instance for compatibility with Drizzle Kit.
|
|
@@ -401,40 +386,6 @@ export class DrizzleKitProvider {
|
|
|
401
386
|
});
|
|
402
387
|
}
|
|
403
388
|
|
|
404
|
-
/**
|
|
405
|
-
* Suppress Drizzle Kit's spinner output during a callback.
|
|
406
|
-
*
|
|
407
|
-
* Drizzle Kit uses hanji's renderWithTask with a setInterval-based spinner.
|
|
408
|
-
* If the wrapped task throws, the interval is never cleared and leaks
|
|
409
|
-
* spinner frames to stdout. We keep the filter active until the next
|
|
410
|
-
* tick after the promise settles to catch any straggling writes.
|
|
411
|
-
*/
|
|
412
|
-
protected async muteSpinner<T>(fn: () => Promise<T>): Promise<T> {
|
|
413
|
-
const originalWrite = process.stdout.write;
|
|
414
|
-
const filter = (chunk: any, ...args: any[]) => {
|
|
415
|
-
const str =
|
|
416
|
-
typeof chunk === "string" ? chunk : (chunk?.toString?.() ?? "");
|
|
417
|
-
if (str.includes("Pulling schema from database")) {
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
|
-
if (str.includes("\x1B[1A")) {
|
|
421
|
-
return true;
|
|
422
|
-
}
|
|
423
|
-
return (originalWrite as any).call(process.stdout, chunk, ...args);
|
|
424
|
-
};
|
|
425
|
-
process.stdout.write = filter as any;
|
|
426
|
-
try {
|
|
427
|
-
return await fn();
|
|
428
|
-
} finally {
|
|
429
|
-
// Delay restore to catch orphaned setInterval spinner writes
|
|
430
|
-
// that fire after the promise rejects but before cleanup.
|
|
431
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
432
|
-
process.stdout.write = originalWrite;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// -------------------------------------------------------------------------------------------------------------------
|
|
437
|
-
|
|
438
389
|
/**
|
|
439
390
|
* Try to load the official Drizzle Kit API.
|
|
440
391
|
*/
|
|
@@ -4,14 +4,12 @@ import {
|
|
|
4
4
|
type FromSchema,
|
|
5
5
|
ModelBuilder,
|
|
6
6
|
PG_CREATED_AT,
|
|
7
|
-
PG_ENUM,
|
|
8
7
|
PG_GENERATED,
|
|
9
8
|
PG_IDENTITY,
|
|
10
9
|
PG_PRIMARY_KEY,
|
|
11
10
|
PG_REF,
|
|
12
11
|
PG_SERIAL,
|
|
13
12
|
PG_UPDATED_AT,
|
|
14
|
-
type PgEnumOptions,
|
|
15
13
|
type PgGeneratedOptions,
|
|
16
14
|
type PgIdentityOptions,
|
|
17
15
|
type PgRefOptions,
|
|
@@ -384,10 +382,9 @@ export class PostgresModelBuilder extends ModelBuilder {
|
|
|
384
382
|
);
|
|
385
383
|
}
|
|
386
384
|
|
|
387
|
-
// SQL Enum
|
|
388
|
-
if (
|
|
389
|
-
const
|
|
390
|
-
const enumName = options.name ?? `${tableName}_${key}_enum`;
|
|
385
|
+
// SQL Enum (default for t.enum unless mode: "text")
|
|
386
|
+
if ((value as any).mode !== "text") {
|
|
387
|
+
const enumName = (value as any).enumName ?? `${tableName}_${key}_enum`;
|
|
391
388
|
|
|
392
389
|
if (enums.has(enumName)) {
|
|
393
390
|
const values = (
|
|
@@ -882,4 +882,161 @@ describe("$page browser tests", () => {
|
|
|
882
882
|
});
|
|
883
883
|
});
|
|
884
884
|
});
|
|
885
|
+
|
|
886
|
+
describe("transition supersession", () => {
|
|
887
|
+
it("should not commit a stale slow transition when a newer navigation already won", async () => {
|
|
888
|
+
let resolvePageA: ((value: { data: string }) => void) | undefined;
|
|
889
|
+
const pageARendered = vi.fn();
|
|
890
|
+
const pageBRendered = vi.fn();
|
|
891
|
+
|
|
892
|
+
class App {
|
|
893
|
+
pageA = $page({
|
|
894
|
+
path: "/page-a",
|
|
895
|
+
loader: () =>
|
|
896
|
+
new Promise<{ data: string }>((resolve) => {
|
|
897
|
+
resolvePageA = resolve;
|
|
898
|
+
}),
|
|
899
|
+
component: ({ data }: { data: string }) => {
|
|
900
|
+
pageARendered();
|
|
901
|
+
return <div data-testid="page-a">A: {data}</div>;
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
pageB = $page({
|
|
906
|
+
path: "/page-b",
|
|
907
|
+
component: () => {
|
|
908
|
+
pageBRendered();
|
|
909
|
+
return <div data-testid="page-b">B</div>;
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
alepha = Alepha.create().with(AlephaReact).with(App);
|
|
915
|
+
await alepha.start();
|
|
916
|
+
|
|
917
|
+
const router = alepha.inject(ReactRouter);
|
|
918
|
+
|
|
919
|
+
// Start navigating to /page-a — its loader hangs, push() will not
|
|
920
|
+
// resolve until we manually resolve the loader below.
|
|
921
|
+
const pushAPromise = router.push("/page-a");
|
|
922
|
+
|
|
923
|
+
// Yield so the in-flight transition for /page-a actually starts and
|
|
924
|
+
// reaches the awaited loader before pushB enters the race.
|
|
925
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
926
|
+
|
|
927
|
+
// Navigate to /page-b before /page-a finishes loading. This should
|
|
928
|
+
// supersede the in-flight /page-a transition.
|
|
929
|
+
await act(async () => {
|
|
930
|
+
await router.push("/page-b");
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// /page-b should now be the committed router state.
|
|
934
|
+
expect(router.state.name).toBe("pageB");
|
|
935
|
+
expect(router.state.url.pathname).toBe("/page-b");
|
|
936
|
+
|
|
937
|
+
// Now resolve /page-a's loader: this is the race window where the
|
|
938
|
+
// stale /page-a transition could overwrite /page-b.
|
|
939
|
+
resolvePageA?.({ data: "loaded" });
|
|
940
|
+
await act(async () => {
|
|
941
|
+
await pushAPromise;
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// Stale /page-a must NOT have committed.
|
|
945
|
+
expect(router.state.name).toBe("pageB");
|
|
946
|
+
expect(router.state.url.pathname).toBe("/page-b");
|
|
947
|
+
// /page-a's component must never have been instantiated.
|
|
948
|
+
expect(pageARendered).not.toHaveBeenCalled();
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it("should not fire onEnter for a stale superseded page", async () => {
|
|
952
|
+
let resolvePageA: (() => void) | undefined;
|
|
953
|
+
const pageAOnEnter = vi.fn();
|
|
954
|
+
const pageBOnEnter = vi.fn();
|
|
955
|
+
|
|
956
|
+
class App {
|
|
957
|
+
pageA = $page({
|
|
958
|
+
path: "/page-a",
|
|
959
|
+
loader: () =>
|
|
960
|
+
new Promise<void>((resolve) => {
|
|
961
|
+
resolvePageA = resolve;
|
|
962
|
+
}),
|
|
963
|
+
onEnter: pageAOnEnter,
|
|
964
|
+
component: () => <div data-testid="page-a">A</div>,
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
pageB = $page({
|
|
968
|
+
path: "/page-b",
|
|
969
|
+
onEnter: pageBOnEnter,
|
|
970
|
+
component: () => <div data-testid="page-b">B</div>,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
alepha = Alepha.create().with(AlephaReact).with(App);
|
|
975
|
+
await alepha.start();
|
|
976
|
+
|
|
977
|
+
const router = alepha.inject(ReactRouter);
|
|
978
|
+
|
|
979
|
+
const pushAPromise = router.push("/page-a");
|
|
980
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
981
|
+
|
|
982
|
+
await act(async () => {
|
|
983
|
+
await router.push("/page-b");
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
expect(router.state.name).toBe("pageB");
|
|
987
|
+
expect(pageBOnEnter).toHaveBeenCalledTimes(1);
|
|
988
|
+
expect(pageAOnEnter).not.toHaveBeenCalled();
|
|
989
|
+
|
|
990
|
+
// Resolve the stale loader: it must remain a no-op.
|
|
991
|
+
resolvePageA?.();
|
|
992
|
+
await act(async () => {
|
|
993
|
+
await pushAPromise;
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
expect(router.state.name).toBe("pageB");
|
|
997
|
+
expect(pageAOnEnter).not.toHaveBeenCalled();
|
|
998
|
+
expect(pageBOnEnter).toHaveBeenCalledTimes(1);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it("should not call pushState for a stale superseded transition", async () => {
|
|
1002
|
+
let resolvePageA: (() => void) | undefined;
|
|
1003
|
+
|
|
1004
|
+
class App {
|
|
1005
|
+
pageA = $page({
|
|
1006
|
+
path: "/page-a",
|
|
1007
|
+
loader: () =>
|
|
1008
|
+
new Promise<void>((resolve) => {
|
|
1009
|
+
resolvePageA = resolve;
|
|
1010
|
+
}),
|
|
1011
|
+
component: () => <div data-testid="page-a">A</div>,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
pageB = $page({
|
|
1015
|
+
path: "/page-b",
|
|
1016
|
+
component: () => <div data-testid="page-b">B</div>,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
alepha = Alepha.create().with(AlephaReact).with(App);
|
|
1021
|
+
await alepha.start();
|
|
1022
|
+
|
|
1023
|
+
const router = alepha.inject(ReactRouter);
|
|
1024
|
+
|
|
1025
|
+
const pushAPromise = router.push("/page-a");
|
|
1026
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1027
|
+
|
|
1028
|
+
await act(async () => {
|
|
1029
|
+
await router.push("/page-b");
|
|
1030
|
+
});
|
|
1031
|
+
expect(window.location.pathname).toBe("/page-b");
|
|
1032
|
+
|
|
1033
|
+
resolvePageA?.();
|
|
1034
|
+
await act(async () => {
|
|
1035
|
+
await pushAPromise;
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// The stale /page-a transition must not have rewritten the URL bar.
|
|
1039
|
+
expect(window.location.pathname).toBe("/page-b");
|
|
1040
|
+
});
|
|
1041
|
+
});
|
|
885
1042
|
});
|
|
@@ -79,6 +79,17 @@ export class ReactBrowserProvider {
|
|
|
79
79
|
from?: string;
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Monotonic counter used to detect stale (superseded) transitions.
|
|
84
|
+
*
|
|
85
|
+
* Each call to `render()` captures `++this.transitionId` and any
|
|
86
|
+
* subsequent `render()` invalidates older in-flight transitions.
|
|
87
|
+
* This prevents a slow page from racing past a newer navigation
|
|
88
|
+
* (e.g. user clicks /pageA which has a 2s loader, then clicks /pageB
|
|
89
|
+
* — pageB must remain the committed page).
|
|
90
|
+
*/
|
|
91
|
+
protected transitionId = 0;
|
|
92
|
+
|
|
82
93
|
public get state(): ReactRouterState {
|
|
83
94
|
return this.alepha.store.get("alepha.react.router.state")!;
|
|
84
95
|
}
|
|
@@ -167,12 +178,21 @@ export class ReactBrowserProvider {
|
|
|
167
178
|
options,
|
|
168
179
|
});
|
|
169
180
|
|
|
181
|
+
const myTransitionId = ++this.transitionId;
|
|
182
|
+
|
|
170
183
|
await this.render({
|
|
171
184
|
url,
|
|
172
185
|
previous: options.force ? [] : this.state.layers,
|
|
173
186
|
meta: options.meta,
|
|
187
|
+
transitionId: myTransitionId,
|
|
174
188
|
});
|
|
175
189
|
|
|
190
|
+
// A newer navigation has superseded us — bail out without touching
|
|
191
|
+
// history, otherwise we'd push a duplicate/stale entry.
|
|
192
|
+
if (myTransitionId !== this.transitionId) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
176
196
|
// when redirecting in browser
|
|
177
197
|
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
178
198
|
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
@@ -183,6 +203,7 @@ export class ReactBrowserProvider {
|
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
protected async render(options: RouterRenderOptions = {}): Promise<void> {
|
|
206
|
+
const myTransitionId = options.transitionId ?? ++this.transitionId;
|
|
186
207
|
const previous = options.previous ?? this.state.layers;
|
|
187
208
|
const url = options.url ?? this.url;
|
|
188
209
|
const start = this.dateTimeProvider.now();
|
|
@@ -196,12 +217,25 @@ export class ReactBrowserProvider {
|
|
|
196
217
|
to: url,
|
|
197
218
|
});
|
|
198
219
|
|
|
220
|
+
const isStale = () => this.transitionId !== myTransitionId;
|
|
221
|
+
|
|
199
222
|
const redirect = await this.router.transition(
|
|
200
223
|
new URL(`http://localhost${url}`),
|
|
201
224
|
previous,
|
|
202
225
|
options.meta,
|
|
226
|
+
isStale,
|
|
203
227
|
);
|
|
204
228
|
|
|
229
|
+
// A newer navigation has superseded us between the time we awaited
|
|
230
|
+
// transition() and now. Drop everything: don't follow redirects, don't
|
|
231
|
+
// log success, don't clear `transitioning` (the newer render owns it).
|
|
232
|
+
if (isStale()) {
|
|
233
|
+
this.log.debug("Transition superseded — discarding stale result", {
|
|
234
|
+
to: url,
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
205
239
|
if (redirect) {
|
|
206
240
|
this.log.info("Redirecting to", {
|
|
207
241
|
redirect,
|
|
@@ -307,4 +341,9 @@ export interface RouterRenderOptions {
|
|
|
307
341
|
url?: string;
|
|
308
342
|
previous?: PreviousLayerData[];
|
|
309
343
|
meta?: Record<string, any>;
|
|
344
|
+
/**
|
|
345
|
+
* Transition id used to detect supersession by a newer navigation.
|
|
346
|
+
* When omitted, render() allocates a fresh id internally.
|
|
347
|
+
*/
|
|
348
|
+
transitionId?: number;
|
|
310
349
|
}
|
|
@@ -51,6 +51,7 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
51
51
|
url: URL,
|
|
52
52
|
previous: PreviousLayerData[] = [],
|
|
53
53
|
meta = {},
|
|
54
|
+
isStale: () => boolean = () => false,
|
|
54
55
|
): Promise<string | void> {
|
|
55
56
|
const { pathname, search } = url;
|
|
56
57
|
|
|
@@ -101,6 +102,13 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
101
102
|
state,
|
|
102
103
|
previous,
|
|
103
104
|
);
|
|
105
|
+
// A newer navigation already won — bail before committing or
|
|
106
|
+
// emitting any further events. The caller (ReactBrowserProvider)
|
|
107
|
+
// also re-checks staleness, but stopping here avoids running
|
|
108
|
+
// success hooks for a transition the user no longer wants.
|
|
109
|
+
if (isStale()) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
104
112
|
if (redirect) {
|
|
105
113
|
return redirect;
|
|
106
114
|
}
|
|
@@ -120,6 +128,13 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
120
128
|
});
|
|
121
129
|
await this.alepha.events.emit("react:transition:success", { state });
|
|
122
130
|
} catch (e) {
|
|
131
|
+
// If we were superseded mid-flight, swallow the error: the user has
|
|
132
|
+
// already moved on, and an error UI for an abandoned page would
|
|
133
|
+
// overwrite the newer page they actually want.
|
|
134
|
+
if (isStale()) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
this.log.error("Transition has failed", e);
|
|
124
139
|
|
|
125
140
|
let element: ReactNode | undefined;
|
|
@@ -151,6 +166,13 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
151
166
|
});
|
|
152
167
|
}
|
|
153
168
|
|
|
169
|
+
// Final supersession check before any side effects (onLeave/onEnter,
|
|
170
|
+
// store mutation, head rewrite). Stale transitions must be a complete
|
|
171
|
+
// no-op from this point on.
|
|
172
|
+
if (isStale()) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
154
176
|
// [feature]: local hook for leaving a page
|
|
155
177
|
if (previous) {
|
|
156
178
|
for (let i = 0; i < previous.length; i++) {
|