@webstir-io/module-contract 0.1.69 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/client-nav.d.ts +19 -0
- package/dist/client-nav.js +19 -0
- package/dist/index.d.ts +98 -2
- package/dist/index.js +4 -0
- package/dist/render-execute.d.ts +36 -0
- package/dist/render-execute.js +269 -0
- package/dist/render-program.d.ts +95 -0
- package/dist/render-program.js +50 -0
- package/package.json +6 -1
- package/schema/module-manifest.schema.json +4 -0
- package/schema/route-definition.schema.json +4 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025
|
|
3
|
+
Copyright (c) 2025-2026 Sqware Logics, LLC and contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire protocol between the client-nav feature and the backend runtime. Constants only, so a
|
|
3
|
+
* browser bundle can import them without the rest of the contract.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CLIENT_NAV_HEADERS: {
|
|
6
|
+
/** Sent on every request client-nav makes itself. */
|
|
7
|
+
readonly request: 'x-webstir-client-nav';
|
|
8
|
+
/**
|
|
9
|
+
* Sent when client-nav can follow a redirect itself: the backend then answers a redirect with
|
|
10
|
+
* 204 and `location` instead of 3xx, so the destination keeps its #fragment.
|
|
11
|
+
*/
|
|
12
|
+
readonly acceptLocation: 'x-webstir-accept-location';
|
|
13
|
+
/** Where to go next, in place of a redirect's Location. */
|
|
14
|
+
readonly location: 'x-webstir-location';
|
|
15
|
+
/** Identifies one form submission, so the same post sent again gets the first answer. */
|
|
16
|
+
readonly submission: 'x-webstir-submission';
|
|
17
|
+
};
|
|
18
|
+
/** The form field carrying the submission id when client-nav falls back to a normal post. */
|
|
19
|
+
export declare const CLIENT_NAV_SUBMISSION_FIELD = "_webstir_submission";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire protocol between the client-nav feature and the backend runtime. Constants only, so a
|
|
3
|
+
* browser bundle can import them without the rest of the contract.
|
|
4
|
+
*/
|
|
5
|
+
export const CLIENT_NAV_HEADERS = {
|
|
6
|
+
/** Sent on every request client-nav makes itself. */
|
|
7
|
+
request: 'x-webstir-client-nav',
|
|
8
|
+
/**
|
|
9
|
+
* Sent when client-nav can follow a redirect itself: the backend then answers a redirect with
|
|
10
|
+
* 204 and `location` instead of 3xx, so the destination keeps its #fragment.
|
|
11
|
+
*/
|
|
12
|
+
acceptLocation: 'x-webstir-accept-location',
|
|
13
|
+
/** Where to go next, in place of a redirect's Location. */
|
|
14
|
+
location: 'x-webstir-location',
|
|
15
|
+
/** Identifies one form submission, so the same post sent again gets the first answer. */
|
|
16
|
+
submission: 'x-webstir-submission',
|
|
17
|
+
};
|
|
18
|
+
/** The form field carrying the submission id when client-nav falls back to a normal post. */
|
|
19
|
+
export const CLIENT_NAV_SUBMISSION_FIELD = '_webstir_submission';
|
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,24 @@ export interface RequestContext<TRequest = unknown, TReply = unknown, TAuth = un
|
|
|
85
85
|
readonly requestId: string;
|
|
86
86
|
readonly now: () => Date;
|
|
87
87
|
}
|
|
88
|
+
export type FormIssueCode = 'validation' | 'auth' | 'csrf';
|
|
89
|
+
export interface FormIssue {
|
|
90
|
+
readonly code?: FormIssueCode;
|
|
91
|
+
readonly field?: string;
|
|
92
|
+
readonly message: string;
|
|
93
|
+
}
|
|
94
|
+
export type FormValues = Readonly<Record<string, string | string[]>>;
|
|
95
|
+
/** A form's last failed submission, as a view loader reads it. Empty when there is none. */
|
|
96
|
+
export interface FormState {
|
|
97
|
+
readonly submitted: boolean;
|
|
98
|
+
readonly values: FormValues;
|
|
99
|
+
readonly issues: readonly FormIssue[];
|
|
100
|
+
/** The first message per field, plus `form` for issues without a field. */
|
|
101
|
+
readonly errors: Readonly<Record<string, string>>;
|
|
102
|
+
}
|
|
103
|
+
export interface FormStateReader {
|
|
104
|
+
read(formId: string): FormState;
|
|
105
|
+
}
|
|
88
106
|
export interface SSRContext<TParams = Record<string, string>, TAuth = unknown, TSession = unknown, TEnv extends EnvAccessor = EnvAccessor, TLogger extends Logger = Logger> {
|
|
89
107
|
readonly url: URL;
|
|
90
108
|
readonly params: TParams;
|
|
@@ -96,6 +114,7 @@ export interface SSRContext<TParams = Record<string, string>, TAuth = unknown, T
|
|
|
96
114
|
readonly logger: TLogger;
|
|
97
115
|
readonly requestId?: string;
|
|
98
116
|
readonly now: () => Date;
|
|
117
|
+
readonly forms: FormStateReader;
|
|
99
118
|
}
|
|
100
119
|
export interface AuthSession<TData = Record<string, unknown>> {
|
|
101
120
|
readonly id: string;
|
|
@@ -353,14 +372,17 @@ export type FlashPublishCondition = z.infer<typeof flashPublishConditionSchema>;
|
|
|
353
372
|
export declare const routeFlashMessageSchema: z.ZodObject<{
|
|
354
373
|
key: z.ZodString;
|
|
355
374
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
375
|
+
message: z.ZodOptional<z.ZodString>;
|
|
356
376
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
357
377
|
}, "strict", z.ZodTypeAny, {
|
|
358
378
|
key: string;
|
|
359
379
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
380
|
+
message?: string | undefined;
|
|
360
381
|
when?: "always" | "error" | "success" | undefined;
|
|
361
382
|
}, {
|
|
362
383
|
key: string;
|
|
363
384
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
385
|
+
message?: string | undefined;
|
|
364
386
|
when?: "always" | "error" | "success" | undefined;
|
|
365
387
|
}>;
|
|
366
388
|
export type RouteFlashMessageDefinition = z.infer<typeof routeFlashMessageSchema>;
|
|
@@ -369,14 +391,17 @@ export declare const routeFlashSchema: z.ZodObject<{
|
|
|
369
391
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
370
392
|
key: z.ZodString;
|
|
371
393
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
394
|
+
message: z.ZodOptional<z.ZodString>;
|
|
372
395
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
373
396
|
}, "strict", z.ZodTypeAny, {
|
|
374
397
|
key: string;
|
|
375
398
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
399
|
+
message?: string | undefined;
|
|
376
400
|
when?: "always" | "error" | "success" | undefined;
|
|
377
401
|
}, {
|
|
378
402
|
key: string;
|
|
379
403
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
404
|
+
message?: string | undefined;
|
|
380
405
|
when?: "always" | "error" | "success" | undefined;
|
|
381
406
|
}>, "many">>;
|
|
382
407
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -384,6 +409,7 @@ export declare const routeFlashSchema: z.ZodObject<{
|
|
|
384
409
|
publish?: {
|
|
385
410
|
key: string;
|
|
386
411
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
412
|
+
message?: string | undefined;
|
|
387
413
|
when?: "always" | "error" | "success" | undefined;
|
|
388
414
|
}[] | undefined;
|
|
389
415
|
}, {
|
|
@@ -391,6 +417,7 @@ export declare const routeFlashSchema: z.ZodObject<{
|
|
|
391
417
|
publish?: {
|
|
392
418
|
key: string;
|
|
393
419
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
420
|
+
message?: string | undefined;
|
|
394
421
|
when?: "always" | "error" | "success" | undefined;
|
|
395
422
|
}[] | undefined;
|
|
396
423
|
}>;
|
|
@@ -413,14 +440,17 @@ export declare const routeFormSchema: z.ZodObject<{
|
|
|
413
440
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
414
441
|
key: z.ZodString;
|
|
415
442
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
443
|
+
message: z.ZodOptional<z.ZodString>;
|
|
416
444
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
417
445
|
}, "strict", z.ZodTypeAny, {
|
|
418
446
|
key: string;
|
|
419
447
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
448
|
+
message?: string | undefined;
|
|
420
449
|
when?: "always" | "error" | "success" | undefined;
|
|
421
450
|
}, {
|
|
422
451
|
key: string;
|
|
423
452
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
453
|
+
message?: string | undefined;
|
|
424
454
|
when?: "always" | "error" | "success" | undefined;
|
|
425
455
|
}>, "many">>;
|
|
426
456
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -428,6 +458,7 @@ export declare const routeFormSchema: z.ZodObject<{
|
|
|
428
458
|
publish?: {
|
|
429
459
|
key: string;
|
|
430
460
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
461
|
+
message?: string | undefined;
|
|
431
462
|
when?: "always" | "error" | "success" | undefined;
|
|
432
463
|
}[] | undefined;
|
|
433
464
|
}, {
|
|
@@ -435,6 +466,7 @@ export declare const routeFormSchema: z.ZodObject<{
|
|
|
435
466
|
publish?: {
|
|
436
467
|
key: string;
|
|
437
468
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
469
|
+
message?: string | undefined;
|
|
438
470
|
when?: "always" | "error" | "success" | undefined;
|
|
439
471
|
}[] | undefined;
|
|
440
472
|
}>>;
|
|
@@ -450,6 +482,7 @@ export declare const routeFormSchema: z.ZodObject<{
|
|
|
450
482
|
publish?: {
|
|
451
483
|
key: string;
|
|
452
484
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
485
|
+
message?: string | undefined;
|
|
453
486
|
when?: "always" | "error" | "success" | undefined;
|
|
454
487
|
}[] | undefined;
|
|
455
488
|
} | undefined;
|
|
@@ -465,6 +498,7 @@ export declare const routeFormSchema: z.ZodObject<{
|
|
|
465
498
|
publish?: {
|
|
466
499
|
key: string;
|
|
467
500
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
501
|
+
message?: string | undefined;
|
|
468
502
|
when?: "always" | "error" | "success" | undefined;
|
|
469
503
|
}[] | undefined;
|
|
470
504
|
} | undefined;
|
|
@@ -1579,14 +1613,17 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1579
1613
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1580
1614
|
key: z.ZodString;
|
|
1581
1615
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
1616
|
+
message: z.ZodOptional<z.ZodString>;
|
|
1582
1617
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
1583
1618
|
}, "strict", z.ZodTypeAny, {
|
|
1584
1619
|
key: string;
|
|
1585
1620
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1621
|
+
message?: string | undefined;
|
|
1586
1622
|
when?: "always" | "error" | "success" | undefined;
|
|
1587
1623
|
}, {
|
|
1588
1624
|
key: string;
|
|
1589
1625
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1626
|
+
message?: string | undefined;
|
|
1590
1627
|
when?: "always" | "error" | "success" | undefined;
|
|
1591
1628
|
}>, "many">>;
|
|
1592
1629
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -1594,6 +1631,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1594
1631
|
publish?: {
|
|
1595
1632
|
key: string;
|
|
1596
1633
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1634
|
+
message?: string | undefined;
|
|
1597
1635
|
when?: "always" | "error" | "success" | undefined;
|
|
1598
1636
|
}[] | undefined;
|
|
1599
1637
|
}, {
|
|
@@ -1601,6 +1639,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1601
1639
|
publish?: {
|
|
1602
1640
|
key: string;
|
|
1603
1641
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1642
|
+
message?: string | undefined;
|
|
1604
1643
|
when?: "always" | "error" | "success" | undefined;
|
|
1605
1644
|
}[] | undefined;
|
|
1606
1645
|
}>>;
|
|
@@ -1622,14 +1661,17 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1622
1661
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1623
1662
|
key: z.ZodString;
|
|
1624
1663
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
1664
|
+
message: z.ZodOptional<z.ZodString>;
|
|
1625
1665
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
1626
1666
|
}, "strict", z.ZodTypeAny, {
|
|
1627
1667
|
key: string;
|
|
1628
1668
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1669
|
+
message?: string | undefined;
|
|
1629
1670
|
when?: "always" | "error" | "success" | undefined;
|
|
1630
1671
|
}, {
|
|
1631
1672
|
key: string;
|
|
1632
1673
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1674
|
+
message?: string | undefined;
|
|
1633
1675
|
when?: "always" | "error" | "success" | undefined;
|
|
1634
1676
|
}>, "many">>;
|
|
1635
1677
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -1637,6 +1679,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1637
1679
|
publish?: {
|
|
1638
1680
|
key: string;
|
|
1639
1681
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1682
|
+
message?: string | undefined;
|
|
1640
1683
|
when?: "always" | "error" | "success" | undefined;
|
|
1641
1684
|
}[] | undefined;
|
|
1642
1685
|
}, {
|
|
@@ -1644,6 +1687,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1644
1687
|
publish?: {
|
|
1645
1688
|
key: string;
|
|
1646
1689
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1690
|
+
message?: string | undefined;
|
|
1647
1691
|
when?: "always" | "error" | "success" | undefined;
|
|
1648
1692
|
}[] | undefined;
|
|
1649
1693
|
}>>;
|
|
@@ -1659,6 +1703,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1659
1703
|
publish?: {
|
|
1660
1704
|
key: string;
|
|
1661
1705
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1706
|
+
message?: string | undefined;
|
|
1662
1707
|
when?: "always" | "error" | "success" | undefined;
|
|
1663
1708
|
}[] | undefined;
|
|
1664
1709
|
} | undefined;
|
|
@@ -1674,6 +1719,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
1674
1719
|
publish?: {
|
|
1675
1720
|
key: string;
|
|
1676
1721
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
1722
|
+
message?: string | undefined;
|
|
1677
1723
|
when?: "always" | "error" | "success" | undefined;
|
|
1678
1724
|
}[] | undefined;
|
|
1679
1725
|
} | undefined;
|
|
@@ -2270,6 +2316,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
2270
2316
|
publish?: {
|
|
2271
2317
|
key: string;
|
|
2272
2318
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2319
|
+
message?: string | undefined;
|
|
2273
2320
|
when?: "always" | "error" | "success" | undefined;
|
|
2274
2321
|
}[] | undefined;
|
|
2275
2322
|
} | undefined;
|
|
@@ -2285,6 +2332,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
2285
2332
|
publish?: {
|
|
2286
2333
|
key: string;
|
|
2287
2334
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2335
|
+
message?: string | undefined;
|
|
2288
2336
|
when?: "always" | "error" | "success" | undefined;
|
|
2289
2337
|
}[] | undefined;
|
|
2290
2338
|
} | undefined;
|
|
@@ -2431,6 +2479,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
2431
2479
|
publish?: {
|
|
2432
2480
|
key: string;
|
|
2433
2481
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2482
|
+
message?: string | undefined;
|
|
2434
2483
|
when?: "always" | "error" | "success" | undefined;
|
|
2435
2484
|
}[] | undefined;
|
|
2436
2485
|
} | undefined;
|
|
@@ -2446,6 +2495,7 @@ export declare const routeDefinitionSchema: z.ZodObject<{
|
|
|
2446
2495
|
publish?: {
|
|
2447
2496
|
key: string;
|
|
2448
2497
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2498
|
+
message?: string | undefined;
|
|
2449
2499
|
when?: "always" | "error" | "success" | undefined;
|
|
2450
2500
|
}[] | undefined;
|
|
2451
2501
|
} | undefined;
|
|
@@ -2604,11 +2654,32 @@ export interface RouteFragmentResponse<TResponse extends z.ZodTypeAny> {
|
|
|
2604
2654
|
};
|
|
2605
2655
|
readonly headers?: Record<string, string>;
|
|
2606
2656
|
}
|
|
2657
|
+
export interface RouteFlashMessage {
|
|
2658
|
+
readonly key?: string;
|
|
2659
|
+
readonly level?: FlashLevel;
|
|
2660
|
+
readonly message: string;
|
|
2661
|
+
}
|
|
2607
2662
|
export interface RouteRedirectResponse {
|
|
2608
2663
|
readonly status?: RedirectStatus;
|
|
2609
2664
|
readonly redirect: {
|
|
2610
2665
|
readonly location: string;
|
|
2611
2666
|
};
|
|
2667
|
+
/** Messages the next rendered page receives in its `flash`. */
|
|
2668
|
+
readonly flash?: readonly RouteFlashMessage[];
|
|
2669
|
+
readonly headers?: Record<string, string>;
|
|
2670
|
+
}
|
|
2671
|
+
/** Renders a view in place of the action's response, with the failed form in its loader. */
|
|
2672
|
+
export interface RouteRerenderResponse {
|
|
2673
|
+
readonly status?: number;
|
|
2674
|
+
readonly rerender: {
|
|
2675
|
+
readonly view: string;
|
|
2676
|
+
readonly params?: Record<string, string>;
|
|
2677
|
+
readonly form: {
|
|
2678
|
+
readonly id: string;
|
|
2679
|
+
readonly values: FormValues;
|
|
2680
|
+
readonly issues: readonly FormIssue[];
|
|
2681
|
+
};
|
|
2682
|
+
};
|
|
2612
2683
|
readonly headers?: Record<string, string>;
|
|
2613
2684
|
}
|
|
2614
2685
|
export interface RouteErrorResponse {
|
|
@@ -2617,8 +2688,8 @@ export interface RouteErrorResponse {
|
|
|
2617
2688
|
readonly headers?: Record<string, string>;
|
|
2618
2689
|
}
|
|
2619
2690
|
export type RouteNavigationResult<TResponse extends z.ZodTypeAny> = RouteBodyResponse<TResponse> | RouteRedirectResponse | RouteErrorResponse;
|
|
2620
|
-
export type RouteMutationResult<TResponse extends z.ZodTypeAny> = RouteBodyResponse<TResponse> | RouteFragmentResponse<TResponse> | RouteRedirectResponse | RouteErrorResponse;
|
|
2621
|
-
export type RouteHandlerResult<TResponse extends z.ZodTypeAny> = RouteBodyResponse<TResponse> | RouteFragmentResponse<TResponse> | RouteRedirectResponse | RouteErrorResponse;
|
|
2691
|
+
export type RouteMutationResult<TResponse extends z.ZodTypeAny> = RouteBodyResponse<TResponse> | RouteFragmentResponse<TResponse> | RouteRedirectResponse | RouteRerenderResponse | RouteErrorResponse;
|
|
2692
|
+
export type RouteHandlerResult<TResponse extends z.ZodTypeAny> = RouteBodyResponse<TResponse> | RouteFragmentResponse<TResponse> | RouteRedirectResponse | RouteRerenderResponse | RouteErrorResponse;
|
|
2622
2693
|
export type RouteHandler<TContext extends RequestContext, TParams extends z.ZodTypeAny | undefined, TQuery extends z.ZodTypeAny | undefined, TBody extends z.ZodTypeAny | undefined, TResponse extends z.ZodTypeAny> = (context: RouteHandlerContext<TContext, TParams, TQuery, TBody>) => Promise<RouteHandlerResult<TResponse>> | RouteHandlerResult<TResponse>;
|
|
2623
2694
|
export interface RouteSpec<TContext extends RequestContext = RequestContext, TParams extends z.ZodTypeAny | undefined = undefined, TQuery extends z.ZodTypeAny | undefined = undefined, TBody extends z.ZodTypeAny | undefined = undefined, TResponse extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
2624
2695
|
readonly definition: RouteDefinition;
|
|
@@ -2843,14 +2914,17 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2843
2914
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
2844
2915
|
key: z.ZodString;
|
|
2845
2916
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
2917
|
+
message: z.ZodOptional<z.ZodString>;
|
|
2846
2918
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
2847
2919
|
}, "strict", z.ZodTypeAny, {
|
|
2848
2920
|
key: string;
|
|
2849
2921
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2922
|
+
message?: string | undefined;
|
|
2850
2923
|
when?: "always" | "error" | "success" | undefined;
|
|
2851
2924
|
}, {
|
|
2852
2925
|
key: string;
|
|
2853
2926
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2927
|
+
message?: string | undefined;
|
|
2854
2928
|
when?: "always" | "error" | "success" | undefined;
|
|
2855
2929
|
}>, "many">>;
|
|
2856
2930
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -2858,6 +2932,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2858
2932
|
publish?: {
|
|
2859
2933
|
key: string;
|
|
2860
2934
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2935
|
+
message?: string | undefined;
|
|
2861
2936
|
when?: "always" | "error" | "success" | undefined;
|
|
2862
2937
|
}[] | undefined;
|
|
2863
2938
|
}, {
|
|
@@ -2865,6 +2940,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2865
2940
|
publish?: {
|
|
2866
2941
|
key: string;
|
|
2867
2942
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2943
|
+
message?: string | undefined;
|
|
2868
2944
|
when?: "always" | "error" | "success" | undefined;
|
|
2869
2945
|
}[] | undefined;
|
|
2870
2946
|
}>>;
|
|
@@ -2886,14 +2962,17 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2886
2962
|
publish: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
2887
2963
|
key: z.ZodString;
|
|
2888
2964
|
level: z.ZodOptional<z.ZodEnum<["info", "success", "warning", "error"]>>;
|
|
2965
|
+
message: z.ZodOptional<z.ZodString>;
|
|
2889
2966
|
when: z.ZodOptional<z.ZodEnum<["always", "success", "error"]>>;
|
|
2890
2967
|
}, "strict", z.ZodTypeAny, {
|
|
2891
2968
|
key: string;
|
|
2892
2969
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2970
|
+
message?: string | undefined;
|
|
2893
2971
|
when?: "always" | "error" | "success" | undefined;
|
|
2894
2972
|
}, {
|
|
2895
2973
|
key: string;
|
|
2896
2974
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2975
|
+
message?: string | undefined;
|
|
2897
2976
|
when?: "always" | "error" | "success" | undefined;
|
|
2898
2977
|
}>, "many">>;
|
|
2899
2978
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -2901,6 +2980,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2901
2980
|
publish?: {
|
|
2902
2981
|
key: string;
|
|
2903
2982
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2983
|
+
message?: string | undefined;
|
|
2904
2984
|
when?: "always" | "error" | "success" | undefined;
|
|
2905
2985
|
}[] | undefined;
|
|
2906
2986
|
}, {
|
|
@@ -2908,6 +2988,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2908
2988
|
publish?: {
|
|
2909
2989
|
key: string;
|
|
2910
2990
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
2991
|
+
message?: string | undefined;
|
|
2911
2992
|
when?: "always" | "error" | "success" | undefined;
|
|
2912
2993
|
}[] | undefined;
|
|
2913
2994
|
}>>;
|
|
@@ -2923,6 +3004,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2923
3004
|
publish?: {
|
|
2924
3005
|
key: string;
|
|
2925
3006
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3007
|
+
message?: string | undefined;
|
|
2926
3008
|
when?: "always" | "error" | "success" | undefined;
|
|
2927
3009
|
}[] | undefined;
|
|
2928
3010
|
} | undefined;
|
|
@@ -2938,6 +3020,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
2938
3020
|
publish?: {
|
|
2939
3021
|
key: string;
|
|
2940
3022
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3023
|
+
message?: string | undefined;
|
|
2941
3024
|
when?: "always" | "error" | "success" | undefined;
|
|
2942
3025
|
}[] | undefined;
|
|
2943
3026
|
} | undefined;
|
|
@@ -3534,6 +3617,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
3534
3617
|
publish?: {
|
|
3535
3618
|
key: string;
|
|
3536
3619
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3620
|
+
message?: string | undefined;
|
|
3537
3621
|
when?: "always" | "error" | "success" | undefined;
|
|
3538
3622
|
}[] | undefined;
|
|
3539
3623
|
} | undefined;
|
|
@@ -3549,6 +3633,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
3549
3633
|
publish?: {
|
|
3550
3634
|
key: string;
|
|
3551
3635
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3636
|
+
message?: string | undefined;
|
|
3552
3637
|
when?: "always" | "error" | "success" | undefined;
|
|
3553
3638
|
}[] | undefined;
|
|
3554
3639
|
} | undefined;
|
|
@@ -3695,6 +3780,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
3695
3780
|
publish?: {
|
|
3696
3781
|
key: string;
|
|
3697
3782
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3783
|
+
message?: string | undefined;
|
|
3698
3784
|
when?: "always" | "error" | "success" | undefined;
|
|
3699
3785
|
}[] | undefined;
|
|
3700
3786
|
} | undefined;
|
|
@@ -3710,6 +3796,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
3710
3796
|
publish?: {
|
|
3711
3797
|
key: string;
|
|
3712
3798
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
3799
|
+
message?: string | undefined;
|
|
3713
3800
|
when?: "always" | "error" | "success" | undefined;
|
|
3714
3801
|
}[] | undefined;
|
|
3715
3802
|
} | undefined;
|
|
@@ -4019,6 +4106,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
4019
4106
|
publish?: {
|
|
4020
4107
|
key: string;
|
|
4021
4108
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
4109
|
+
message?: string | undefined;
|
|
4022
4110
|
when?: "always" | "error" | "success" | undefined;
|
|
4023
4111
|
}[] | undefined;
|
|
4024
4112
|
} | undefined;
|
|
@@ -4034,6 +4122,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
4034
4122
|
publish?: {
|
|
4035
4123
|
key: string;
|
|
4036
4124
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
4125
|
+
message?: string | undefined;
|
|
4037
4126
|
when?: "always" | "error" | "success" | undefined;
|
|
4038
4127
|
}[] | undefined;
|
|
4039
4128
|
} | undefined;
|
|
@@ -4239,6 +4328,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
4239
4328
|
publish?: {
|
|
4240
4329
|
key: string;
|
|
4241
4330
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
4331
|
+
message?: string | undefined;
|
|
4242
4332
|
when?: "always" | "error" | "success" | undefined;
|
|
4243
4333
|
}[] | undefined;
|
|
4244
4334
|
} | undefined;
|
|
@@ -4254,6 +4344,7 @@ export declare const moduleManifestSchema: z.ZodObject<{
|
|
|
4254
4344
|
publish?: {
|
|
4255
4345
|
key: string;
|
|
4256
4346
|
level?: "error" | "info" | "success" | "warning" | undefined;
|
|
4347
|
+
message?: string | undefined;
|
|
4257
4348
|
when?: "always" | "error" | "success" | undefined;
|
|
4258
4349
|
}[] | undefined;
|
|
4259
4350
|
} | undefined;
|
|
@@ -4459,3 +4550,8 @@ export declare function defineView<TContext extends SSRContext, TParams extends
|
|
|
4459
4550
|
export declare function createModule<TRequestContext extends RequestContext, TSSRContext extends SSRContext, TRoutes extends readonly LooseRouteSpec<TRequestContext>[] = readonly LooseRouteSpec<TRequestContext>[], TViews extends readonly LooseViewSpec<TSSRContext>[] = readonly LooseViewSpec<TSSRContext>[]>(definition: ModuleDefinition<TRequestContext, TSSRContext, TRoutes, TViews>): ModuleDefinition<TRequestContext, TSSRContext, TRoutes, TViews>;
|
|
4460
4551
|
export { fromTsRestRoute, fromTsRestRouter } from './adapters/ts-rest.js';
|
|
4461
4552
|
export type { FromTsRestRouteOptions, FromTsRestRouterOptions, RouterRouteConfig, } from './adapters/ts-rest.js';
|
|
4553
|
+
export { RENDER_PROGRAM_FILE, RENDER_PROGRAM_VERSION, renderFlashMessageSchema, renderFlashSchema, renderNodeSchema, renderPathSchema, renderProgramSchema, renderSourceLocationSchema, } from './render-program.js';
|
|
4554
|
+
export type { RenderFlashMessage, RenderNode, RenderPath, RenderProgram, RenderSourceLocation, } from './render-program.js';
|
|
4555
|
+
export { executeRenderProgram, programUsesCsrf, readRenderProgram, RENDER_CSRF_FIELD, RenderProgramError, prepareViewData, schemaDeclaresField, } from './render-execute.js';
|
|
4556
|
+
export type { ExecuteRenderProgramOptions, PreparedViewData } from './render-execute.js';
|
|
4557
|
+
export { CLIENT_NAV_HEADERS, CLIENT_NAV_SUBMISSION_FIELD } from './client-nav.js';
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,7 @@ export const routeFlashMessageSchema = z
|
|
|
84
84
|
.object({
|
|
85
85
|
key: z.string().min(1),
|
|
86
86
|
level: flashLevelSchema.optional(),
|
|
87
|
+
message: z.string().min(1).optional(),
|
|
87
88
|
when: flashPublishConditionSchema.optional(),
|
|
88
89
|
})
|
|
89
90
|
.strict();
|
|
@@ -231,3 +232,6 @@ export function createModule(definition) {
|
|
|
231
232
|
return definition;
|
|
232
233
|
}
|
|
233
234
|
export { fromTsRestRoute, fromTsRestRouter } from './adapters/ts-rest.js';
|
|
235
|
+
export { RENDER_PROGRAM_FILE, RENDER_PROGRAM_VERSION, renderFlashMessageSchema, renderFlashSchema, renderNodeSchema, renderPathSchema, renderProgramSchema, renderSourceLocationSchema, } from './render-program.js';
|
|
236
|
+
export { executeRenderProgram, programUsesCsrf, readRenderProgram, RENDER_CSRF_FIELD, RenderProgramError, prepareViewData, schemaDeclaresField, } from './render-execute.js';
|
|
237
|
+
export { CLIENT_NAV_HEADERS, CLIENT_NAV_SUBMISSION_FIELD } from './client-nav.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type RenderProgram, type RenderSourceLocation } from './render-program.js';
|
|
2
|
+
export declare const RENDER_CSRF_FIELD = "_csrf";
|
|
3
|
+
export declare class RenderProgramError extends Error {
|
|
4
|
+
readonly loc: RenderSourceLocation;
|
|
5
|
+
constructor(loc: RenderSourceLocation, message: string);
|
|
6
|
+
}
|
|
7
|
+
export interface ExecuteRenderProgramOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The session's token for POST forms. `false` renders static output, which has no session:
|
|
10
|
+
* POST forms render without a token field.
|
|
11
|
+
*/
|
|
12
|
+
readonly csrfToken?: string | false;
|
|
13
|
+
}
|
|
14
|
+
export declare function readRenderProgram(value: unknown, source: string): RenderProgram;
|
|
15
|
+
export declare function programUsesCsrf(program: RenderProgram): boolean;
|
|
16
|
+
export declare function executeRenderProgram(program: RenderProgram, data: unknown, options?: ExecuteRenderProgramOptions): string;
|
|
17
|
+
export type PreparedViewData = {
|
|
18
|
+
readonly ok: true;
|
|
19
|
+
readonly data: unknown;
|
|
20
|
+
} | {
|
|
21
|
+
readonly ok: false;
|
|
22
|
+
readonly error: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Checks a loader's data against its view schema and supplies `flash`, which the framework
|
|
26
|
+
* owns: the session's messages come first, then any the loader returned. Whether the schema
|
|
27
|
+
* wants `flash` can depend on the branch a union picks, so the data is parsed with it and,
|
|
28
|
+
* failing that, without it, starting with what the schema declares.
|
|
29
|
+
*/
|
|
30
|
+
export declare function prepareViewData(schema: unknown, loaded: unknown, flash: readonly unknown[]): PreparedViewData;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a zod-style schema's underlying object declares `field`, looking through wrappers
|
|
33
|
+
* such as optional, default, refine and transform, and into unions and intersections. A schema
|
|
34
|
+
* with no object inside declares nothing.
|
|
35
|
+
*/
|
|
36
|
+
export declare function schemaDeclaresField(schema: unknown, field: string): boolean;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { RENDER_PROGRAM_VERSION, } from './render-program.js';
|
|
2
|
+
export const RENDER_CSRF_FIELD = '_csrf';
|
|
3
|
+
const SAFE_URL_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);
|
|
4
|
+
const BLOCKED_URL = 'about:invalid';
|
|
5
|
+
// ARIA attributes whose values include the tokens "true" and "false" (WAI-ARIA 1.2). The rest take
|
|
6
|
+
// text or ids, where false still leaves the attribute out.
|
|
7
|
+
const ARIA_TRUE_FALSE = new Set([
|
|
8
|
+
'aria-atomic',
|
|
9
|
+
'aria-busy',
|
|
10
|
+
'aria-checked',
|
|
11
|
+
'aria-current',
|
|
12
|
+
'aria-disabled',
|
|
13
|
+
'aria-expanded',
|
|
14
|
+
'aria-grabbed',
|
|
15
|
+
'aria-haspopup',
|
|
16
|
+
'aria-hidden',
|
|
17
|
+
'aria-invalid',
|
|
18
|
+
'aria-modal',
|
|
19
|
+
'aria-multiline',
|
|
20
|
+
'aria-multiselectable',
|
|
21
|
+
'aria-pressed',
|
|
22
|
+
'aria-readonly',
|
|
23
|
+
'aria-required',
|
|
24
|
+
'aria-selected',
|
|
25
|
+
]);
|
|
26
|
+
export class RenderProgramError extends Error {
|
|
27
|
+
loc;
|
|
28
|
+
constructor(loc, message) {
|
|
29
|
+
super(`${loc.file}:${loc.line}: ${message}`);
|
|
30
|
+
this.name = 'RenderProgramError';
|
|
31
|
+
this.loc = loc;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function readRenderProgram(value, source) {
|
|
35
|
+
if (!value ||
|
|
36
|
+
typeof value !== 'object' ||
|
|
37
|
+
value.version !== RENDER_PROGRAM_VERSION ||
|
|
38
|
+
!Array.isArray(value.nodes)) {
|
|
39
|
+
throw new Error(`Render program ${source} is not a version ${RENDER_PROGRAM_VERSION} program. Rebuild the frontend.`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function programUsesCsrf(program) {
|
|
44
|
+
return nodesUseCsrf(program.nodes);
|
|
45
|
+
}
|
|
46
|
+
export function executeRenderProgram(program, data, options = {}) {
|
|
47
|
+
const out = [];
|
|
48
|
+
run(program.nodes, data, [], options, out);
|
|
49
|
+
return out.join('');
|
|
50
|
+
}
|
|
51
|
+
function run(nodes, data, scopes, options, out) {
|
|
52
|
+
for (const node of nodes) {
|
|
53
|
+
if (typeof node === 'string') {
|
|
54
|
+
out.push(node);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
switch (node.op) {
|
|
58
|
+
case 'csrf':
|
|
59
|
+
if (options.csrfToken === false) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (!options.csrfToken) {
|
|
63
|
+
throw new Error('A POST form needs a CSRF token, but none was supplied.');
|
|
64
|
+
}
|
|
65
|
+
out.push(`<input type="hidden" name="${RENDER_CSRF_FIELD}" value="${escapeAttribute(options.csrfToken)}">`);
|
|
66
|
+
break;
|
|
67
|
+
case 'text': {
|
|
68
|
+
const value = read(node.path, data, scopes);
|
|
69
|
+
if (value !== null && value !== undefined) {
|
|
70
|
+
out.push(escapeText(toText(value, node.loc, `data-text="${node.path.source}"`)));
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'attr': {
|
|
75
|
+
const value = read(node.path, data, scopes);
|
|
76
|
+
// ARIA states are the words "true" and "false", not an attribute's presence.
|
|
77
|
+
if (typeof value === 'boolean' && ARIA_TRUE_FALSE.has(node.name.toLowerCase())) {
|
|
78
|
+
out.push(` ${node.name}="${value}"`);
|
|
79
|
+
}
|
|
80
|
+
else if (value === true) {
|
|
81
|
+
out.push(` ${node.name}`);
|
|
82
|
+
}
|
|
83
|
+
else if (value !== false && value !== null && value !== undefined) {
|
|
84
|
+
const label = `data-attr-${node.name}="${node.path.source}"`;
|
|
85
|
+
const text = toText(value, node.loc, label);
|
|
86
|
+
out.push(` ${node.name}="${escapeAttribute(node.url ? safeAttributeUrl(node.name, text) : text)}"`);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'if':
|
|
91
|
+
if (isTruthy(read(node.path, data, scopes)) !== node.negate) {
|
|
92
|
+
run(node.body, data, scopes, options, out);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case 'each': {
|
|
96
|
+
const items = read(node.path, data, scopes);
|
|
97
|
+
if (items === null || items === undefined) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(items)) {
|
|
101
|
+
throw new RenderProgramError(node.loc, `data-each="${node.path.source} as ${node.as}" needs an array, got ${describe(items)}`);
|
|
102
|
+
}
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
run(node.body, data, [...scopes, item], options, out);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function read(path, data, scopes) {
|
|
112
|
+
let current = path.scope === -1 ? data : scopes[path.scope];
|
|
113
|
+
for (const key of path.keys) {
|
|
114
|
+
if (current === null || typeof current !== 'object' || !Object.hasOwn(current, key)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
current = current[key];
|
|
118
|
+
}
|
|
119
|
+
return current;
|
|
120
|
+
}
|
|
121
|
+
function isTruthy(value) {
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
return value.length > 0;
|
|
124
|
+
}
|
|
125
|
+
return Boolean(value);
|
|
126
|
+
}
|
|
127
|
+
function toText(value, loc, label) {
|
|
128
|
+
if (typeof value === 'string') {
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
throw new RenderProgramError(loc, `${label} needs a string or number, got ${describe(value)}`);
|
|
135
|
+
}
|
|
136
|
+
/** Every URL in the value is checked: `srcset` lists candidates, `ping` lists URLs. */
|
|
137
|
+
function safeAttributeUrl(name, value) {
|
|
138
|
+
if (name === 'srcset' || name === 'imagesrcset') {
|
|
139
|
+
return value
|
|
140
|
+
.split(',')
|
|
141
|
+
.map((candidate) => {
|
|
142
|
+
const [url = '', ...descriptors] = candidate.trim().split(/\s+/);
|
|
143
|
+
return [safeUrl(url), ...descriptors].join(' ');
|
|
144
|
+
})
|
|
145
|
+
.join(', ');
|
|
146
|
+
}
|
|
147
|
+
if (name === 'ping') {
|
|
148
|
+
return value.split(/\s+/).filter(Boolean).map(safeUrl).join(' ');
|
|
149
|
+
}
|
|
150
|
+
return safeUrl(value);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Checks a loader's data against its view schema and supplies `flash`, which the framework
|
|
154
|
+
* owns: the session's messages come first, then any the loader returned. Whether the schema
|
|
155
|
+
* wants `flash` can depend on the branch a union picks, so the data is parsed with it and,
|
|
156
|
+
* failing that, without it, starting with what the schema declares.
|
|
157
|
+
*/
|
|
158
|
+
export function prepareViewData(schema, loaded, flash) {
|
|
159
|
+
const withFlash = mergeFlash(loaded, flash);
|
|
160
|
+
if (!schema || typeof schema.safeParse !== 'function') {
|
|
161
|
+
return { ok: true, data: withFlash };
|
|
162
|
+
}
|
|
163
|
+
const attempts = schemaDeclaresField(schema, 'flash') ? [withFlash, loaded] : [loaded, withFlash];
|
|
164
|
+
let firstError;
|
|
165
|
+
for (const [index, input] of attempts.entries()) {
|
|
166
|
+
const parsed = schema.safeParse(input);
|
|
167
|
+
if (parsed.success) {
|
|
168
|
+
return { ok: true, data: ensureFlash(parsed.data, flash) };
|
|
169
|
+
}
|
|
170
|
+
if (index === 0) {
|
|
171
|
+
firstError = parsed.error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { ok: false, error: describeSchemaError(firstError) };
|
|
175
|
+
}
|
|
176
|
+
function mergeFlash(value, flash) {
|
|
177
|
+
if (!isPlainObject(value)) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
const own = value.flash;
|
|
181
|
+
if (own !== undefined && !Array.isArray(own)) {
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
return { ...value, flash: [...flash, ...(own ?? [])] };
|
|
185
|
+
}
|
|
186
|
+
function ensureFlash(value, flash) {
|
|
187
|
+
return isPlainObject(value) && !('flash' in value) ? { ...value, flash: [...flash] } : value;
|
|
188
|
+
}
|
|
189
|
+
function isPlainObject(value) {
|
|
190
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
191
|
+
}
|
|
192
|
+
function describeSchemaError(error) {
|
|
193
|
+
const issues = error
|
|
194
|
+
?.issues;
|
|
195
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
196
|
+
return error instanceof Error ? error.message : String(error);
|
|
197
|
+
}
|
|
198
|
+
return issues
|
|
199
|
+
.map((issue) => `${(issue.path ?? []).join('.') || '(root)'}: ${issue.message ?? 'invalid'}`)
|
|
200
|
+
.join('; ');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Whether a zod-style schema's underlying object declares `field`, looking through wrappers
|
|
204
|
+
* such as optional, default, refine and transform, and into unions and intersections. A schema
|
|
205
|
+
* with no object inside declares nothing.
|
|
206
|
+
*/
|
|
207
|
+
export function schemaDeclaresField(schema, field) {
|
|
208
|
+
return declares(schema, field, new Set());
|
|
209
|
+
}
|
|
210
|
+
function declares(schema, field, seen) {
|
|
211
|
+
if (!schema || typeof schema !== 'object' || seen.has(schema)) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
seen.add(schema);
|
|
215
|
+
const shape = schema.shape;
|
|
216
|
+
if (shape && typeof shape === 'object') {
|
|
217
|
+
return field in shape;
|
|
218
|
+
}
|
|
219
|
+
const def = schema._def;
|
|
220
|
+
if (!def) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const inner = [def.innerType, def.schema, def.in, def.type, def.left, def.right];
|
|
224
|
+
if (typeof def.getter === 'function') {
|
|
225
|
+
inner.push(def.getter());
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(def.options)) {
|
|
228
|
+
inner.push(...def.options);
|
|
229
|
+
}
|
|
230
|
+
return inner.some((candidate) => declares(candidate, field, seen));
|
|
231
|
+
}
|
|
232
|
+
function safeUrl(value) {
|
|
233
|
+
const normalized = Array.from(value)
|
|
234
|
+
.filter((character) => {
|
|
235
|
+
const code = character.charCodeAt(0);
|
|
236
|
+
return code > 0x20 && code !== 0x7f;
|
|
237
|
+
})
|
|
238
|
+
.join('')
|
|
239
|
+
.toLowerCase();
|
|
240
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(normalized);
|
|
241
|
+
if (scheme && !SAFE_URL_SCHEMES.has(scheme[1])) {
|
|
242
|
+
return BLOCKED_URL;
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function nodesUseCsrf(nodes) {
|
|
247
|
+
return nodes.some((node) => typeof node !== 'string' &&
|
|
248
|
+
(node.op === 'csrf' || ((node.op === 'if' || node.op === 'each') && nodesUseCsrf(node.body))));
|
|
249
|
+
}
|
|
250
|
+
function describe(value) {
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
return 'an array';
|
|
253
|
+
}
|
|
254
|
+
if (value === null) {
|
|
255
|
+
return 'null';
|
|
256
|
+
}
|
|
257
|
+
return typeof value === 'object' ? 'an object' : `a ${typeof value}`;
|
|
258
|
+
}
|
|
259
|
+
function escapeText(value) {
|
|
260
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
261
|
+
}
|
|
262
|
+
function escapeAttribute(value) {
|
|
263
|
+
return value
|
|
264
|
+
.replaceAll('&', '&')
|
|
265
|
+
.replaceAll('"', '"')
|
|
266
|
+
.replaceAll("'", ''')
|
|
267
|
+
.replaceAll('<', '<')
|
|
268
|
+
.replaceAll('>', '>');
|
|
269
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const RENDER_PROGRAM_VERSION = 1;
|
|
3
|
+
export declare const RENDER_PROGRAM_FILE = "index.program.json";
|
|
4
|
+
export declare const renderSourceLocationSchema: z.ZodObject<{
|
|
5
|
+
file: z.ZodString;
|
|
6
|
+
line: z.ZodNumber;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
}, {
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
}>;
|
|
14
|
+
export type RenderSourceLocation = z.infer<typeof renderSourceLocationSchema>;
|
|
15
|
+
export declare const renderPathSchema: z.ZodObject<{
|
|
16
|
+
source: z.ZodString;
|
|
17
|
+
scope: z.ZodNumber;
|
|
18
|
+
keys: z.ZodArray<z.ZodString, "many">;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
source: string;
|
|
21
|
+
scope: number;
|
|
22
|
+
keys: string[];
|
|
23
|
+
}, {
|
|
24
|
+
source: string;
|
|
25
|
+
scope: number;
|
|
26
|
+
keys: string[];
|
|
27
|
+
}>;
|
|
28
|
+
export type RenderPath = z.infer<typeof renderPathSchema>;
|
|
29
|
+
export type RenderNode = string | {
|
|
30
|
+
readonly op: 'text';
|
|
31
|
+
readonly path: RenderPath;
|
|
32
|
+
readonly loc: RenderSourceLocation;
|
|
33
|
+
} | {
|
|
34
|
+
readonly op: 'attr';
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly url: boolean;
|
|
37
|
+
readonly path: RenderPath;
|
|
38
|
+
readonly loc: RenderSourceLocation;
|
|
39
|
+
} | {
|
|
40
|
+
readonly op: 'if';
|
|
41
|
+
readonly negate: boolean;
|
|
42
|
+
readonly path: RenderPath;
|
|
43
|
+
readonly loc: RenderSourceLocation;
|
|
44
|
+
readonly body: readonly RenderNode[];
|
|
45
|
+
} | {
|
|
46
|
+
readonly op: 'each';
|
|
47
|
+
readonly as: string;
|
|
48
|
+
readonly path: RenderPath;
|
|
49
|
+
readonly loc: RenderSourceLocation;
|
|
50
|
+
readonly body: readonly RenderNode[];
|
|
51
|
+
} | {
|
|
52
|
+
readonly op: 'csrf';
|
|
53
|
+
};
|
|
54
|
+
export declare const renderNodeSchema: z.ZodType<RenderNode>;
|
|
55
|
+
export declare const renderProgramSchema: z.ZodObject<{
|
|
56
|
+
version: z.ZodLiteral<1>;
|
|
57
|
+
page: z.ZodString;
|
|
58
|
+
source: z.ZodString;
|
|
59
|
+
bindings: z.ZodNumber;
|
|
60
|
+
nodes: z.ZodArray<z.ZodType<RenderNode, z.ZodTypeDef, RenderNode>, "many">;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
version: 1;
|
|
63
|
+
page: string;
|
|
64
|
+
source: string;
|
|
65
|
+
bindings: number;
|
|
66
|
+
nodes: RenderNode[];
|
|
67
|
+
}, {
|
|
68
|
+
version: 1;
|
|
69
|
+
page: string;
|
|
70
|
+
source: string;
|
|
71
|
+
bindings: number;
|
|
72
|
+
nodes: RenderNode[];
|
|
73
|
+
}>;
|
|
74
|
+
export type RenderProgram = z.infer<typeof renderProgramSchema>;
|
|
75
|
+
export declare const renderFlashMessageSchema: z.ZodObject<{
|
|
76
|
+
level: z.ZodEnum<["info", "success", "warning", "error"]>;
|
|
77
|
+
message: z.ZodString;
|
|
78
|
+
}, "strip", z.ZodTypeAny, {
|
|
79
|
+
level: "error" | "info" | "success" | "warning";
|
|
80
|
+
message: string;
|
|
81
|
+
}, {
|
|
82
|
+
level: "error" | "info" | "success" | "warning";
|
|
83
|
+
message: string;
|
|
84
|
+
}>;
|
|
85
|
+
export type RenderFlashMessage = z.infer<typeof renderFlashMessageSchema>;
|
|
86
|
+
export declare const renderFlashSchema: z.ZodArray<z.ZodObject<{
|
|
87
|
+
level: z.ZodEnum<["info", "success", "warning", "error"]>;
|
|
88
|
+
message: z.ZodString;
|
|
89
|
+
}, "strip", z.ZodTypeAny, {
|
|
90
|
+
level: "error" | "info" | "success" | "warning";
|
|
91
|
+
message: string;
|
|
92
|
+
}, {
|
|
93
|
+
level: "error" | "info" | "success" | "warning";
|
|
94
|
+
message: string;
|
|
95
|
+
}>, "many">;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const RENDER_PROGRAM_VERSION = 1;
|
|
3
|
+
export const RENDER_PROGRAM_FILE = 'index.program.json';
|
|
4
|
+
export const renderSourceLocationSchema = z.object({
|
|
5
|
+
file: z.string().min(1),
|
|
6
|
+
line: z.number().int().positive(),
|
|
7
|
+
});
|
|
8
|
+
export const renderPathSchema = z.object({
|
|
9
|
+
source: z.string().min(1),
|
|
10
|
+
scope: z.number().int().min(-1),
|
|
11
|
+
keys: z.array(z.string().min(1)),
|
|
12
|
+
});
|
|
13
|
+
export const renderNodeSchema = z.lazy(() => z.union([
|
|
14
|
+
z.string(),
|
|
15
|
+
z.object({ op: z.literal('text'), path: renderPathSchema, loc: renderSourceLocationSchema }),
|
|
16
|
+
z.object({
|
|
17
|
+
op: z.literal('attr'),
|
|
18
|
+
name: z.string().min(1),
|
|
19
|
+
url: z.boolean(),
|
|
20
|
+
path: renderPathSchema,
|
|
21
|
+
loc: renderSourceLocationSchema,
|
|
22
|
+
}),
|
|
23
|
+
z.object({
|
|
24
|
+
op: z.literal('if'),
|
|
25
|
+
negate: z.boolean(),
|
|
26
|
+
path: renderPathSchema,
|
|
27
|
+
loc: renderSourceLocationSchema,
|
|
28
|
+
body: z.array(renderNodeSchema),
|
|
29
|
+
}),
|
|
30
|
+
z.object({
|
|
31
|
+
op: z.literal('each'),
|
|
32
|
+
as: z.string().min(1),
|
|
33
|
+
path: renderPathSchema,
|
|
34
|
+
loc: renderSourceLocationSchema,
|
|
35
|
+
body: z.array(renderNodeSchema),
|
|
36
|
+
}),
|
|
37
|
+
z.object({ op: z.literal('csrf') }),
|
|
38
|
+
]));
|
|
39
|
+
export const renderProgramSchema = z.object({
|
|
40
|
+
version: z.literal(RENDER_PROGRAM_VERSION),
|
|
41
|
+
page: z.string().min(1),
|
|
42
|
+
source: z.string().min(1),
|
|
43
|
+
bindings: z.number().int().min(0),
|
|
44
|
+
nodes: z.array(renderNodeSchema),
|
|
45
|
+
});
|
|
46
|
+
export const renderFlashMessageSchema = z.object({
|
|
47
|
+
level: z.enum(['info', 'success', 'warning', 'error']),
|
|
48
|
+
message: z.string(),
|
|
49
|
+
});
|
|
50
|
+
export const renderFlashSchema = z.array(renderFlashMessageSchema);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstir-io/module-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Public contract definitions for Webstir frontend and backend module providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./client-nav": {
|
|
15
|
+
"types": "./dist/client-nav.d.ts",
|
|
16
|
+
"import": "./dist/client-nav.js",
|
|
17
|
+
"default": "./dist/client-nav.js"
|
|
18
|
+
},
|
|
14
19
|
"./ts-rest": {
|
|
15
20
|
"types": "./dist/adapters/ts-rest.d.ts",
|
|
16
21
|
"import": "./dist/adapters/ts-rest.js",
|