@spfn/core 0.3.0-beta.3 → 0.3.0-beta.5
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 +84 -0
- package/dist/authz/index.js +1 -381
- package/dist/authz/index.js.map +1 -1
- package/dist/contract/index.d.ts +4 -3
- package/dist/env/loader.js +24 -1
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.js +1 -381
- package/dist/errors/index.js.map +1 -1
- package/dist/logger/index.js +0 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.js +6 -387
- package/dist/middleware/index.js.map +1 -1
- package/dist/ops/index.d.ts +64 -8
- package/dist/ops/index.js +330 -30
- package/dist/ops/index.js.map +1 -1
- package/dist/route/index.d.ts +4 -2
- package/dist/{router-Cy7rAfmj.d.ts → route-builder-2ani2jEI.d.ts} +2 -125
- package/dist/router-DJdpwuB6.d.ts +127 -0
- package/dist/server/index.d.ts +56 -2
- package/dist/server/index.js +414 -3
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CfYVhIQ9.d.ts → types-ClQVomgV.d.ts} +1 -1
- package/package.json +1 -1
package/dist/server/index.d.ts
CHANGED
|
@@ -10,8 +10,13 @@ import { b as EventRouterDef, E as EventDef } from '../token-manager-BT5EnUAR.js
|
|
|
10
10
|
import { d as SSEHandlerConfig, e as SSEAuthConfig } from '../types-ZQODsBft.js';
|
|
11
11
|
import { W as WSRouterDef, f as WSHandlerConfig, e as WSMessageHandlers, g as WSAuthConfig } from '../types-2AbaW4Ie.js';
|
|
12
12
|
import { DatabaseProvider, MigrationStatus, MigrationStatusDb } from '@spfn/core/db';
|
|
13
|
-
import '@sinclair/typebox';
|
|
13
|
+
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
14
|
+
import { Static } from '@sinclair/typebox';
|
|
15
|
+
import { R as RouteDef } from '../route-builder-2ani2jEI.js';
|
|
14
16
|
import 'pg-boss';
|
|
17
|
+
import '../define-middleware-CVKgqo8S.js';
|
|
18
|
+
import 'hono/utils/http-status';
|
|
19
|
+
import '../route/types.js';
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* @deprecated Use `loadEnv` from '@spfn/core/env/loader' instead.
|
|
@@ -22,6 +27,33 @@ import 'pg-boss';
|
|
|
22
27
|
*/
|
|
23
28
|
declare function loadEnvFiles(): void;
|
|
24
29
|
|
|
30
|
+
/** Stable operation identity used by separately deployed clients. */
|
|
31
|
+
declare const CORE_TIME_OPERATION_ID = "core.time";
|
|
32
|
+
/**
|
|
33
|
+
* Closed response shape for the server-time wire capability.
|
|
34
|
+
*
|
|
35
|
+
* Millisecond timestamps use integers throughout SPFN's external contracts.
|
|
36
|
+
*/
|
|
37
|
+
declare const ServerTimeResponseSchema: _sinclair_typebox.TObject<{
|
|
38
|
+
serverTimeMillis: _sinclair_typebox.TInteger;
|
|
39
|
+
}>;
|
|
40
|
+
type ServerTimeResponse = Static<typeof ServerTimeResponseSchema>;
|
|
41
|
+
/** Injectable source of Unix epoch milliseconds. */
|
|
42
|
+
interface ServerClock {
|
|
43
|
+
now(): number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the route from a clock so tests and alternate runtimes can supply the
|
|
47
|
+
* epoch source without replacing global time.
|
|
48
|
+
*/
|
|
49
|
+
declare function createCoreTimeRoute(clock?: ServerClock): RouteDef<{}, {}, {
|
|
50
|
+
serverTimeMillis: number;
|
|
51
|
+
}>;
|
|
52
|
+
/** The production route and the public wire-contract source of truth. */
|
|
53
|
+
declare const CORE_TIME_ROUTE: RouteDef<{}, {}, {
|
|
54
|
+
serverTimeMillis: number;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
25
57
|
/**
|
|
26
58
|
* Workflow router interface for @spfn/core integration
|
|
27
59
|
*
|
|
@@ -489,6 +521,15 @@ interface ServerConfig {
|
|
|
489
521
|
*/
|
|
490
522
|
detailed?: boolean;
|
|
491
523
|
};
|
|
524
|
+
/**
|
|
525
|
+
* Server-time capability dependencies.
|
|
526
|
+
*
|
|
527
|
+
* The endpoint is always enabled at `GET /_core/time`. Supplying a clock is
|
|
528
|
+
* primarily a deterministic test seam; production defaults to `Date.now()`.
|
|
529
|
+
*/
|
|
530
|
+
serverTime?: {
|
|
531
|
+
clock?: ServerClock;
|
|
532
|
+
};
|
|
492
533
|
/**
|
|
493
534
|
* Migration boot gate
|
|
494
535
|
*
|
|
@@ -757,6 +798,13 @@ declare const CORE_NAMESPACE = "/_core";
|
|
|
757
798
|
* balancer console — and a version bump migrates none of them.
|
|
758
799
|
*/
|
|
759
800
|
declare const CORE_HEALTH_PATH = "/_core/health";
|
|
801
|
+
/**
|
|
802
|
+
* Where clients obtain the server's current Unix epoch in milliseconds.
|
|
803
|
+
*
|
|
804
|
+
* This endpoint is registered before application routes and application auth
|
|
805
|
+
* middleware so a client can call it before it has a proof or session.
|
|
806
|
+
*/
|
|
807
|
+
declare const CORE_TIME_PATH = "/_core/time";
|
|
760
808
|
|
|
761
809
|
/**
|
|
762
810
|
* Start SPFN Server
|
|
@@ -1197,6 +1245,12 @@ declare class ServerConfigBuilder {
|
|
|
1197
1245
|
* Configure health check endpoint
|
|
1198
1246
|
*/
|
|
1199
1247
|
healthCheck(healthCheck: ServerConfig['healthCheck']): this;
|
|
1248
|
+
/**
|
|
1249
|
+
* Supply the clock used by the built-in `GET /_core/time` capability.
|
|
1250
|
+
* Production servers normally keep the default `Date.now()` clock; this is
|
|
1251
|
+
* exposed so tests can assert an exact wire value without replacing globals.
|
|
1252
|
+
*/
|
|
1253
|
+
serverTime(serverTime: ServerConfig['serverTime']): this;
|
|
1200
1254
|
/**
|
|
1201
1255
|
* Configure infrastructure initialization
|
|
1202
1256
|
*/
|
|
@@ -1273,4 +1327,4 @@ declare class ServerConfigBuilder {
|
|
|
1273
1327
|
*/
|
|
1274
1328
|
declare function defineServerConfig(): ServerConfigBuilder;
|
|
1275
1329
|
|
|
1276
|
-
export { type AppFactory, CORE_HEALTH_PATH, CORE_NAMESPACE, type MigrationSnapshot, PendingMigrationsError, type ServerConfig, type ServerInstance, type ShutdownHookOptions, createServer, createServerlessApp, defineServerConfig, getMigrationSnapshot, getShutdownManager, loadEnvFiles, provisionInfrastructure, resetMigrationSnapshot, resetServerlessApp, startServer };
|
|
1330
|
+
export { type AppFactory, CORE_HEALTH_PATH, CORE_NAMESPACE, CORE_TIME_OPERATION_ID, CORE_TIME_PATH, CORE_TIME_ROUTE, type MigrationSnapshot, PendingMigrationsError, type ServerClock, type ServerConfig, type ServerInstance, type ServerTimeResponse, ServerTimeResponseSchema, type ShutdownHookOptions, createCoreTimeRoute, createServer, createServerlessApp, defineServerConfig, getMigrationSnapshot, getShutdownManager, loadEnvFiles, provisionInfrastructure, resetMigrationSnapshot, resetServerlessApp, startServer };
|
package/dist/server/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { randomBytes, createHash } from 'crypto';
|
|
|
13
13
|
import { Agent, setGlobalDispatcher } from 'undici';
|
|
14
14
|
import { initDatabase, getDatabase, hasMigrationTargets, collectMigrationStatus, countPendingMigrations, pendingMigrationTargets, formatPendingMigrations, pendingMigrationsSummary, RUN_MIGRATIONS_HINT, closeDatabase, migrationTargets } from '@spfn/core/db';
|
|
15
15
|
import { initCache, getCache, closeCache } from '@spfn/core/cache';
|
|
16
|
+
import { FormatRegistry, Type } from '@sinclair/typebox';
|
|
16
17
|
import { serve } from '@hono/node-server';
|
|
17
18
|
import PgBoss from 'pg-boss';
|
|
18
19
|
import { networkInterfaces } from 'os';
|
|
@@ -354,9 +355,10 @@ function parseEnvFile(filePath) {
|
|
|
354
355
|
return parse(readFileSync(filePath, "utf-8"));
|
|
355
356
|
}
|
|
356
357
|
function loadEnv(options = {}) {
|
|
358
|
+
const nodeEnvAtEntry = process.env.NODE_ENV;
|
|
357
359
|
const {
|
|
358
360
|
cwd = process.cwd(),
|
|
359
|
-
nodeEnv =
|
|
361
|
+
nodeEnv = nodeEnvAtEntry || "local",
|
|
360
362
|
server = true,
|
|
361
363
|
debug = false,
|
|
362
364
|
override = false
|
|
@@ -386,8 +388,30 @@ function loadEnv(options = {}) {
|
|
|
386
388
|
envLogger.debug(`Loaded env files: ${loadedFiles.join(", ")}`);
|
|
387
389
|
envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);
|
|
388
390
|
}
|
|
391
|
+
warnIfEnvGuessCostSomething(cwd, nodeEnvAtEntry, options.nodeEnv, nodeEnv);
|
|
389
392
|
return { loadedFiles, loadedKeys };
|
|
390
393
|
}
|
|
394
|
+
function warnIfEnvGuessCostSomething(cwd, nodeEnvAtEntry, explicitNodeEnv, resolvedNodeEnv) {
|
|
395
|
+
if (nodeEnvAtEntry || explicitNodeEnv) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const declared = process.env.NODE_ENV;
|
|
399
|
+
if (!declared) {
|
|
400
|
+
envLogger.warn(
|
|
401
|
+
`NODE_ENV is not set anywhere; env files were resolved for "${resolvedNodeEnv}". Set NODE_ENV in the process environment to choose the file set.`
|
|
402
|
+
);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (declared === resolvedNodeEnv) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (!existsSync(resolve(cwd, `.env.${declared}`))) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
envLogger.warn(
|
|
412
|
+
`.env.${declared} was NOT loaded: NODE_ENV was not set when the process started, so env files were resolved for "${resolvedNodeEnv}" before a file declared NODE_ENV=${declared}. Set NODE_ENV in the process environment, not in a .env file.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
391
415
|
|
|
392
416
|
// src/server/dotenv-loader.ts
|
|
393
417
|
var warned = false;
|
|
@@ -1080,6 +1104,7 @@ async function runMigrationBootGate(config, cwd = process.cwd()) {
|
|
|
1080
1104
|
// src/server/namespace.ts
|
|
1081
1105
|
var CORE_NAMESPACE = "/_core";
|
|
1082
1106
|
var CORE_HEALTH_PATH = `${CORE_NAMESPACE}/health`;
|
|
1107
|
+
var CORE_TIME_PATH = `${CORE_NAMESPACE}/time`;
|
|
1083
1108
|
var LEGACY_HEALTH_PATH = "/health";
|
|
1084
1109
|
|
|
1085
1110
|
// src/server/shutdown-manager.ts
|
|
@@ -1480,6 +1505,378 @@ function buildStartupConfig(config, timeouts) {
|
|
|
1480
1505
|
};
|
|
1481
1506
|
}
|
|
1482
1507
|
|
|
1508
|
+
// src/route/route-builder.ts
|
|
1509
|
+
var RouteBuilder = class _RouteBuilder {
|
|
1510
|
+
_method;
|
|
1511
|
+
_path;
|
|
1512
|
+
_input;
|
|
1513
|
+
_interceptor;
|
|
1514
|
+
_middlewares;
|
|
1515
|
+
_skipMiddlewares;
|
|
1516
|
+
_contract;
|
|
1517
|
+
/**
|
|
1518
|
+
* Create a new RouteBuilder with copied properties and optional overrides
|
|
1519
|
+
*/
|
|
1520
|
+
clone(overrides) {
|
|
1521
|
+
const builder = new _RouteBuilder();
|
|
1522
|
+
builder._method = this._method;
|
|
1523
|
+
builder._path = this._path;
|
|
1524
|
+
builder._input = overrides?.input ?? this._input;
|
|
1525
|
+
builder._interceptor = overrides?.interceptor ?? this._interceptor;
|
|
1526
|
+
builder._middlewares = overrides?.middlewares ?? this._middlewares;
|
|
1527
|
+
builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;
|
|
1528
|
+
builder._contract = overrides?.contract ?? this._contract;
|
|
1529
|
+
return builder;
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Define input schemas
|
|
1533
|
+
*
|
|
1534
|
+
* @example
|
|
1535
|
+
* ```ts
|
|
1536
|
+
* route.get('/users/:id')
|
|
1537
|
+
* .input({
|
|
1538
|
+
* params: Type.Object({ id: Type.String() }),
|
|
1539
|
+
* query: Type.Object({ page: Type.Number() }),
|
|
1540
|
+
* headers: Type.Object({ authorization: Type.String() })
|
|
1541
|
+
* })
|
|
1542
|
+
* .handler(async (c) => {
|
|
1543
|
+
* const { params, query, headers } = await c.data();
|
|
1544
|
+
* // params = { id: string }
|
|
1545
|
+
* // query = { page: number }
|
|
1546
|
+
* // headers = { authorization: string }
|
|
1547
|
+
* })
|
|
1548
|
+
* ```
|
|
1549
|
+
*/
|
|
1550
|
+
input(input) {
|
|
1551
|
+
return this.clone({ input });
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Define fields injected by interceptors
|
|
1555
|
+
*
|
|
1556
|
+
* These fields are:
|
|
1557
|
+
* - Available in the handler (merged with input)
|
|
1558
|
+
* - Excluded from client types (codegen uses only input)
|
|
1559
|
+
* - Not validated by route input schema (injected by middleware)
|
|
1560
|
+
*
|
|
1561
|
+
* Use this when middleware/interceptors add fields to the request
|
|
1562
|
+
* before it reaches the handler.
|
|
1563
|
+
*
|
|
1564
|
+
* @example
|
|
1565
|
+
* ```ts
|
|
1566
|
+
* // Auth interceptor injects crypto key fields
|
|
1567
|
+
* route.post('/_auth/login')
|
|
1568
|
+
* .input({
|
|
1569
|
+
* body: Type.Object({
|
|
1570
|
+
* email: Type.String(),
|
|
1571
|
+
* password: Type.String()
|
|
1572
|
+
* })
|
|
1573
|
+
* })
|
|
1574
|
+
* .interceptor({
|
|
1575
|
+
* body: Type.Object({
|
|
1576
|
+
* publicKey: Type.String(),
|
|
1577
|
+
* keyId: Type.String(),
|
|
1578
|
+
* fingerprint: Type.String()
|
|
1579
|
+
* })
|
|
1580
|
+
* })
|
|
1581
|
+
* .handler(async (c) => {
|
|
1582
|
+
* const { body } = await c.data();
|
|
1583
|
+
* // body type: { email, password, publicKey, keyId, fingerprint }
|
|
1584
|
+
* // Client only sees: { email, password }
|
|
1585
|
+
* return loginService(body);
|
|
1586
|
+
* });
|
|
1587
|
+
* ```
|
|
1588
|
+
*/
|
|
1589
|
+
interceptor(interceptor) {
|
|
1590
|
+
return this.clone({ interceptor });
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Add middlewares to the route
|
|
1594
|
+
*
|
|
1595
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
1596
|
+
* Named middlewares that are already registered globally will be automatically
|
|
1597
|
+
* deduplicated to prevent double execution.
|
|
1598
|
+
*
|
|
1599
|
+
* @example
|
|
1600
|
+
* ```ts
|
|
1601
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
1602
|
+
*
|
|
1603
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
1604
|
+
* route.get('/users')
|
|
1605
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
1606
|
+
*
|
|
1607
|
+
* // With regular middleware handlers
|
|
1608
|
+
* route.get('/users')
|
|
1609
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
1610
|
+
* ```
|
|
1611
|
+
*/
|
|
1612
|
+
middleware(middlewares) {
|
|
1613
|
+
return this.clone({ middlewares });
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Add middlewares to the route (alias for `.middleware()`)
|
|
1617
|
+
*
|
|
1618
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
1619
|
+
* Named middlewares that are already registered globally will be automatically
|
|
1620
|
+
* deduplicated to prevent double execution.
|
|
1621
|
+
*
|
|
1622
|
+
* @example
|
|
1623
|
+
* ```ts
|
|
1624
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
1625
|
+
*
|
|
1626
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
1627
|
+
* route.get('/users')
|
|
1628
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
1629
|
+
*
|
|
1630
|
+
* // With regular middleware handlers
|
|
1631
|
+
* route.get('/users')
|
|
1632
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
1633
|
+
* ```
|
|
1634
|
+
*/
|
|
1635
|
+
use(middlewares) {
|
|
1636
|
+
return this.middleware(middlewares);
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Skip server-level named middlewares
|
|
1640
|
+
*
|
|
1641
|
+
* Useful for public endpoints that should bypass auth or rate limiting
|
|
1642
|
+
*
|
|
1643
|
+
* @param middlewareNames - Array of middleware names to skip, or '*' to skip all
|
|
1644
|
+
*
|
|
1645
|
+
* @example
|
|
1646
|
+
* ```ts
|
|
1647
|
+
* // Skip specific middlewares
|
|
1648
|
+
* route.get('/status')
|
|
1649
|
+
* .skip(['auth', 'rateLimit'])
|
|
1650
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
1651
|
+
*
|
|
1652
|
+
* // Skip only auth (still apply rate limiting)
|
|
1653
|
+
* route.get('/public-data')
|
|
1654
|
+
* .skip(['auth'])
|
|
1655
|
+
* .handler(async (c) => { ... });
|
|
1656
|
+
*
|
|
1657
|
+
* // Skip all middlewares
|
|
1658
|
+
* route.get('/public-health')
|
|
1659
|
+
* .skip('*')
|
|
1660
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
1661
|
+
* ```
|
|
1662
|
+
*/
|
|
1663
|
+
skip(middlewareNames) {
|
|
1664
|
+
return this.clone({ skipMiddlewares: middlewareNames });
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Publish this route as a versioned contract operation
|
|
1668
|
+
*
|
|
1669
|
+
* Marks the route as a promise to clients that are compiled and deployed
|
|
1670
|
+
* separately from the server — a mobile app, an external API consumer.
|
|
1671
|
+
* The `@spfn/core:contract` generator writes every contracted route into
|
|
1672
|
+
* `contracts/current.json`, and the build refuses a change that would break
|
|
1673
|
+
* an already-released client.
|
|
1674
|
+
*
|
|
1675
|
+
* Routes without `.contract()` are unaffected: they simply do not appear in
|
|
1676
|
+
* the contract. A web client needs nothing here — it derives its types from
|
|
1677
|
+
* the router in the same build.
|
|
1678
|
+
*
|
|
1679
|
+
* @example
|
|
1680
|
+
* ```ts
|
|
1681
|
+
* export const getUser = route.get('/users/:id')
|
|
1682
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
1683
|
+
* .contract({
|
|
1684
|
+
* since: '1.2.0',
|
|
1685
|
+
* auth: 'clientProofV1',
|
|
1686
|
+
* requiresSession: true,
|
|
1687
|
+
* response: Type.Object({
|
|
1688
|
+
* id: Type.String(),
|
|
1689
|
+
* name: Type.String(),
|
|
1690
|
+
* email: Type.Optional(Type.String()),
|
|
1691
|
+
* }),
|
|
1692
|
+
* })
|
|
1693
|
+
* .handler(async (c) => { ... });
|
|
1694
|
+
* ```
|
|
1695
|
+
*/
|
|
1696
|
+
contract(contract) {
|
|
1697
|
+
return this.clone({ contract });
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Define handler function
|
|
1701
|
+
*
|
|
1702
|
+
* Response type is automatically inferred from the return value.
|
|
1703
|
+
* Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
|
|
1704
|
+
*
|
|
1705
|
+
* @example
|
|
1706
|
+
* ```ts
|
|
1707
|
+
* // Direct return - type inferred from data
|
|
1708
|
+
* route.get('/users/:id')
|
|
1709
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
1710
|
+
* .handler(async (c) => {
|
|
1711
|
+
* const { params } = await c.data();
|
|
1712
|
+
* return await getUser(params.id); // Type: User
|
|
1713
|
+
* })
|
|
1714
|
+
*
|
|
1715
|
+
* // Using c.created() - returns data with 201 status, type preserved
|
|
1716
|
+
* route.post('/users')
|
|
1717
|
+
* .input({ body: Type.Object({ name: Type.String() }) })
|
|
1718
|
+
* .handler(async (c) => {
|
|
1719
|
+
* const { body } = await c.data();
|
|
1720
|
+
* return c.created(await createUser(body)); // Type: User
|
|
1721
|
+
* })
|
|
1722
|
+
*
|
|
1723
|
+
* // Using c.paginated() - returns PaginatedResult<T>
|
|
1724
|
+
* route.get('/users')
|
|
1725
|
+
* .handler(async (c) => {
|
|
1726
|
+
* const users = await getUsers();
|
|
1727
|
+
* return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
|
|
1728
|
+
* })
|
|
1729
|
+
*
|
|
1730
|
+
* // Using c.noContent() - returns void
|
|
1731
|
+
* route.delete('/users/:id')
|
|
1732
|
+
* .handler(async (c) => {
|
|
1733
|
+
* await deleteUser(params.id);
|
|
1734
|
+
* return c.noContent(); // Type: void
|
|
1735
|
+
* })
|
|
1736
|
+
*
|
|
1737
|
+
* // Using c.json() - returns Response (type inference lost)
|
|
1738
|
+
* // Use only when you need custom status codes not covered by helpers
|
|
1739
|
+
* route.get('/custom')
|
|
1740
|
+
* .handler(async (c) => {
|
|
1741
|
+
* return c.json({ data }, 418); // Type: Response
|
|
1742
|
+
* })
|
|
1743
|
+
* ```
|
|
1744
|
+
*/
|
|
1745
|
+
handler(fn) {
|
|
1746
|
+
return {
|
|
1747
|
+
method: this._method,
|
|
1748
|
+
path: this._path,
|
|
1749
|
+
input: this._input,
|
|
1750
|
+
interceptor: this._interceptor,
|
|
1751
|
+
middlewares: this._middlewares,
|
|
1752
|
+
skipMiddlewares: this._skipMiddlewares,
|
|
1753
|
+
contract: this._contract,
|
|
1754
|
+
handler: fn,
|
|
1755
|
+
_input: {},
|
|
1756
|
+
_interceptor: {},
|
|
1757
|
+
_response: {}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
function createMethodRoute(method) {
|
|
1762
|
+
return (path) => {
|
|
1763
|
+
const builder = new RouteBuilder();
|
|
1764
|
+
builder._method = method;
|
|
1765
|
+
builder._path = path;
|
|
1766
|
+
return builder;
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
var route = {
|
|
1770
|
+
get: createMethodRoute("GET"),
|
|
1771
|
+
post: createMethodRoute("POST"),
|
|
1772
|
+
put: createMethodRoute("PUT"),
|
|
1773
|
+
patch: createMethodRoute("PATCH"),
|
|
1774
|
+
delete: createMethodRoute("DELETE")
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// src/route/router.ts
|
|
1778
|
+
function createRouterInstance(routes, packageRouters = [], globalMiddlewares = [], contractVersion = null) {
|
|
1779
|
+
return {
|
|
1780
|
+
routes,
|
|
1781
|
+
_routes: routes,
|
|
1782
|
+
_packageRouters: packageRouters,
|
|
1783
|
+
_globalMiddlewares: globalMiddlewares,
|
|
1784
|
+
_contractVersion: contractVersion,
|
|
1785
|
+
packages(routers) {
|
|
1786
|
+
const newPackageRouters = [...this._packageRouters, ...routers];
|
|
1787
|
+
for (const pkgRouter of routers) {
|
|
1788
|
+
if (pkgRouter._packageRouters?.length > 0) {
|
|
1789
|
+
newPackageRouters.push(...pkgRouter._packageRouters);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return createRouterInstance(
|
|
1793
|
+
this.routes,
|
|
1794
|
+
newPackageRouters,
|
|
1795
|
+
this._globalMiddlewares,
|
|
1796
|
+
this._contractVersion
|
|
1797
|
+
);
|
|
1798
|
+
},
|
|
1799
|
+
use(middlewares) {
|
|
1800
|
+
return createRouterInstance(
|
|
1801
|
+
this.routes,
|
|
1802
|
+
this._packageRouters,
|
|
1803
|
+
[...this._globalMiddlewares, ...middlewares],
|
|
1804
|
+
this._contractVersion
|
|
1805
|
+
);
|
|
1806
|
+
},
|
|
1807
|
+
contractVersion(version) {
|
|
1808
|
+
assertContractVersion(version);
|
|
1809
|
+
return createRouterInstance(
|
|
1810
|
+
this.routes,
|
|
1811
|
+
this._packageRouters,
|
|
1812
|
+
this._globalMiddlewares,
|
|
1813
|
+
version
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
function assertContractVersion(version) {
|
|
1819
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/.test(version)) {
|
|
1820
|
+
throw new Error(
|
|
1821
|
+
`contractVersion("${version}") is not a version of the form major.minor.patch. The released snapshot is named from this value and releases are compared by it.`
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function defineRouter(routes) {
|
|
1826
|
+
return createRouterInstance(routes);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// src/route/validation.ts
|
|
1830
|
+
FormatRegistry.Set(
|
|
1831
|
+
"email",
|
|
1832
|
+
(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
1833
|
+
);
|
|
1834
|
+
FormatRegistry.Set(
|
|
1835
|
+
"uri",
|
|
1836
|
+
(value) => /^https?:\/\/.+/.test(value)
|
|
1837
|
+
);
|
|
1838
|
+
FormatRegistry.Set(
|
|
1839
|
+
"uuid",
|
|
1840
|
+
(value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
|
|
1841
|
+
);
|
|
1842
|
+
FormatRegistry.Set(
|
|
1843
|
+
"date",
|
|
1844
|
+
(value) => /^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
1845
|
+
);
|
|
1846
|
+
FormatRegistry.Set(
|
|
1847
|
+
"date-time",
|
|
1848
|
+
(value) => !isNaN(Date.parse(value))
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1851
|
+
// src/server/server-time.ts
|
|
1852
|
+
var CORE_TIME_OPERATION_ID = "core.time";
|
|
1853
|
+
var ServerTimeResponseSchema = Type.Object({
|
|
1854
|
+
serverTimeMillis: Type.Integer()
|
|
1855
|
+
}, { additionalProperties: false });
|
|
1856
|
+
var systemServerClock = {
|
|
1857
|
+
now: () => Date.now()
|
|
1858
|
+
};
|
|
1859
|
+
function createCoreTimeRoute(clock = systemServerClock) {
|
|
1860
|
+
return route.get(CORE_TIME_PATH).skip("*").contract({
|
|
1861
|
+
since: "0.3.0",
|
|
1862
|
+
auth: "none",
|
|
1863
|
+
requiresSession: false,
|
|
1864
|
+
response: ServerTimeResponseSchema
|
|
1865
|
+
}).handler(async (c) => {
|
|
1866
|
+
const response = {
|
|
1867
|
+
serverTimeMillis: clock.now()
|
|
1868
|
+
};
|
|
1869
|
+
c.raw.header("Cache-Control", "no-store");
|
|
1870
|
+
return response;
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
var CORE_TIME_ROUTE = createCoreTimeRoute();
|
|
1874
|
+
function createCoreTimeRouter(clock) {
|
|
1875
|
+
return defineRouter({
|
|
1876
|
+
[CORE_TIME_OPERATION_ID]: createCoreTimeRoute(clock)
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1483
1880
|
// src/server/create-server.ts
|
|
1484
1881
|
var rateLimitApplied = /* @__PURE__ */ new WeakSet();
|
|
1485
1882
|
async function createServer(config) {
|
|
@@ -1522,6 +1919,7 @@ async function createAutoConfiguredApp(config) {
|
|
|
1522
1919
|
applyDefaultMiddleware(app, config, enableLogger, enableCors);
|
|
1523
1920
|
await applyProxyGuard(app, config);
|
|
1524
1921
|
applyRateLimit(config);
|
|
1922
|
+
registerCoreTimeEndpoint(app, config);
|
|
1525
1923
|
if (Array.isArray(config?.use)) {
|
|
1526
1924
|
config.use.forEach((mw) => app.use("*", mw));
|
|
1527
1925
|
}
|
|
@@ -1571,7 +1969,7 @@ async function applyProxyGuard(app, config) {
|
|
|
1571
1969
|
serverLogger.warn("Proxy-guard nonce: cache module unavailable \u2014 using in-memory store (single instance only)");
|
|
1572
1970
|
}
|
|
1573
1971
|
}
|
|
1574
|
-
const autoSkip = [CORE_HEALTH_PATH, LEGACY_HEALTH_PATH];
|
|
1972
|
+
const autoSkip = [CORE_HEALTH_PATH, CORE_TIME_PATH, LEGACY_HEALTH_PATH];
|
|
1575
1973
|
if (config?.healthCheck?.path) {
|
|
1576
1974
|
autoSkip.push(config.healthCheck.path);
|
|
1577
1975
|
}
|
|
@@ -1628,6 +2026,10 @@ function resolveHealthCheck(config) {
|
|
|
1628
2026
|
detailed: healthCheckConfig.detailed ?? process.env.NODE_ENV === "development"
|
|
1629
2027
|
};
|
|
1630
2028
|
}
|
|
2029
|
+
function registerCoreTimeEndpoint(app, config) {
|
|
2030
|
+
registerRoutes(app, createCoreTimeRouter(config?.serverTime?.clock));
|
|
2031
|
+
serverLogger.debug(`Server time endpoint enabled at ${CORE_TIME_PATH}`);
|
|
2032
|
+
}
|
|
1631
2033
|
function registerCoreHealthEndpoint(app, config) {
|
|
1632
2034
|
const { enabled, path, detailed } = resolveHealthCheck(config);
|
|
1633
2035
|
if (!enabled) {
|
|
@@ -3231,6 +3633,15 @@ var ServerConfigBuilder = class {
|
|
|
3231
3633
|
this.config.healthCheck = healthCheck;
|
|
3232
3634
|
return this;
|
|
3233
3635
|
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Supply the clock used by the built-in `GET /_core/time` capability.
|
|
3638
|
+
* Production servers normally keep the default `Date.now()` clock; this is
|
|
3639
|
+
* exposed so tests can assert an exact wire value without replacing globals.
|
|
3640
|
+
*/
|
|
3641
|
+
serverTime(serverTime) {
|
|
3642
|
+
this.config.serverTime = serverTime;
|
|
3643
|
+
return this;
|
|
3644
|
+
}
|
|
3234
3645
|
/**
|
|
3235
3646
|
* Configure infrastructure initialization
|
|
3236
3647
|
*/
|
|
@@ -3327,6 +3738,6 @@ function defineServerConfig() {
|
|
|
3327
3738
|
return new ServerConfigBuilder();
|
|
3328
3739
|
}
|
|
3329
3740
|
|
|
3330
|
-
export { CORE_HEALTH_PATH, CORE_NAMESPACE, PendingMigrationsError, createServer, createServerlessApp, defineServerConfig, getMigrationSnapshot, getShutdownManager, loadEnv, loadEnvFiles, provisionInfrastructure, resetMigrationSnapshot, resetServerlessApp, startServer };
|
|
3741
|
+
export { CORE_HEALTH_PATH, CORE_NAMESPACE, CORE_TIME_OPERATION_ID, CORE_TIME_PATH, CORE_TIME_ROUTE, PendingMigrationsError, ServerTimeResponseSchema, createCoreTimeRoute, createServer, createServerlessApp, defineServerConfig, getMigrationSnapshot, getShutdownManager, loadEnv, loadEnvFiles, provisionInfrastructure, resetMigrationSnapshot, resetServerlessApp, startServer };
|
|
3331
3742
|
//# sourceMappingURL=index.js.map
|
|
3332
3743
|
//# sourceMappingURL=index.js.map
|