@octanejs/remix-router 0.1.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/package.json +53 -0
  4. package/src/dom.ts +16 -0
  5. package/src/index.ts +267 -0
  6. package/src/internal.ts +37 -0
  7. package/src/lib/Await.tsrx +145 -0
  8. package/src/lib/Await.tsrx.d.ts +6 -0
  9. package/src/lib/DefaultErrorComponent.tsrx +47 -0
  10. package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
  11. package/src/lib/RenderErrorBoundary.tsrx +117 -0
  12. package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
  13. package/src/lib/actions.ts +90 -0
  14. package/src/lib/components/MemoryRouter.tsrx +42 -0
  15. package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
  16. package/src/lib/components/Navigate.ts +60 -0
  17. package/src/lib/components/Router.tsrx +88 -0
  18. package/src/lib/components/Router.tsrx.d.ts +13 -0
  19. package/src/lib/components/RouterProvider.tsrx +320 -0
  20. package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
  21. package/src/lib/components/Routes.tsrx +53 -0
  22. package/src/lib/components/Routes.tsrx.d.ts +7 -0
  23. package/src/lib/components/routes-collector.ts +317 -0
  24. package/src/lib/components/utils.ts +171 -0
  25. package/src/lib/components/with-props.ts +72 -0
  26. package/src/lib/context.ts +129 -0
  27. package/src/lib/dom/Form.tsrx +76 -0
  28. package/src/lib/dom/Form.tsrx.d.ts +22 -0
  29. package/src/lib/dom/Link.tsrx +91 -0
  30. package/src/lib/dom/Link.tsrx.d.ts +22 -0
  31. package/src/lib/dom/NavLink.tsrx +118 -0
  32. package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
  33. package/src/lib/dom/dom.ts +344 -0
  34. package/src/lib/dom/hooks.ts +93 -0
  35. package/src/lib/dom/lib.ts +822 -0
  36. package/src/lib/dom/routers.tsrx +104 -0
  37. package/src/lib/dom/routers.tsrx.d.ts +23 -0
  38. package/src/lib/dom/server.tsrx +350 -0
  39. package/src/lib/dom/server.tsrx.d.ts +25 -0
  40. package/src/lib/errors.ts +84 -0
  41. package/src/lib/framework-stubs.ts +103 -0
  42. package/src/lib/hooks.ts +1797 -0
  43. package/src/lib/href.ts +71 -0
  44. package/src/lib/react-types.ts +10 -0
  45. package/src/lib/router/history.ts +763 -0
  46. package/src/lib/router/instrumentation.ts +496 -0
  47. package/src/lib/router/links.ts +192 -0
  48. package/src/lib/router/router.ts +7165 -0
  49. package/src/lib/router/server-runtime-types.ts +12 -0
  50. package/src/lib/router/url.ts +8 -0
  51. package/src/lib/router/utils.ts +2179 -0
  52. package/src/lib/server-runtime/cookies.ts +240 -0
  53. package/src/lib/server-runtime/crypto.ts +55 -0
  54. package/src/lib/server-runtime/mode.ts +16 -0
  55. package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
  56. package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
  57. package/src/lib/server-runtime/sessions.ts +291 -0
  58. package/src/lib/server-runtime/warnings.ts +10 -0
  59. package/src/lib/types/future.ts +16 -0
  60. package/src/lib/types/params.ts +8 -0
  61. package/src/lib/types/register.ts +39 -0
  62. package/src/lib/types/route-module.ts +17 -0
  63. package/src/lib/types/utils.ts +37 -0
@@ -0,0 +1,240 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/server-runtime/cookies.ts — unmodified.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ import type { ParseOptions, SerializeOptions } from 'cookie';
4
+ import { parse, serialize } from 'cookie';
5
+
6
+ import { sign, unsign } from './crypto';
7
+ import { warnOnce } from './warnings';
8
+
9
+ export type { ParseOptions as CookieParseOptions, SerializeOptions as CookieSerializeOptions };
10
+
11
+ export interface CookieSignatureOptions {
12
+ /**
13
+ * An array of secrets that may be used to sign/unsign the value of a cookie.
14
+ *
15
+ * The array makes it easy to rotate secrets. New secrets should be added to
16
+ * the beginning of the array. `cookie.serialize()` will always use the first
17
+ * value in the array, but `cookie.parse()` may use any of them so that
18
+ * cookies that were signed with older secrets still work.
19
+ */
20
+ secrets?: string[];
21
+ }
22
+
23
+ export type CookieOptions = ParseOptions & SerializeOptions & CookieSignatureOptions;
24
+
25
+ /**
26
+ * A HTTP cookie.
27
+ *
28
+ * A Cookie is a logical container for metadata about a HTTP cookie; its name
29
+ * and options. But it doesn't contain a value. Instead, it has `parse()` and
30
+ * `serialize()` methods that allow a single instance to be reused for
31
+ * parsing/encoding multiple different values.
32
+ *
33
+ * @see https://remix.run/utils/cookies#cookie-api
34
+ */
35
+ export interface Cookie {
36
+ /**
37
+ * The name of the cookie, used in the `Cookie` and `Set-Cookie` headers.
38
+ */
39
+ readonly name: string;
40
+
41
+ /**
42
+ * True if this cookie uses one or more secrets for verification.
43
+ */
44
+ readonly isSigned: boolean;
45
+
46
+ /**
47
+ * The Date this cookie expires.
48
+ *
49
+ * Note: This is calculated at access time using `maxAge` when no `expires`
50
+ * option is provided to `createCookie()`.
51
+ */
52
+ readonly expires?: Date;
53
+
54
+ /**
55
+ * Parses a raw `Cookie` header and returns the value of this cookie or
56
+ * `null` if it's not present.
57
+ */
58
+ parse(cookieHeader: string | null, options?: ParseOptions): Promise<any>;
59
+
60
+ /**
61
+ * Serializes the given value to a string and returns the `Set-Cookie`
62
+ * header.
63
+ */
64
+ serialize(value: any, options?: SerializeOptions): Promise<string>;
65
+ }
66
+
67
+ /**
68
+ * Creates a logical container for managing a browser cookie from the server.
69
+ */
70
+ export const createCookie = (name: string, cookieOptions: CookieOptions = {}): Cookie => {
71
+ let { secrets = [], ...options } = {
72
+ path: '/',
73
+ sameSite: 'lax' as const,
74
+ ...cookieOptions,
75
+ };
76
+
77
+ warnOnceAboutExpiresCookie(name, options.expires);
78
+
79
+ return {
80
+ get name() {
81
+ return name;
82
+ },
83
+ get isSigned() {
84
+ return secrets.length > 0;
85
+ },
86
+ get expires() {
87
+ // Max-Age takes precedence over Expires
88
+ return typeof options.maxAge !== 'undefined'
89
+ ? new Date(Date.now() + options.maxAge * 1000)
90
+ : options.expires;
91
+ },
92
+ async parse(cookieHeader, parseOptions) {
93
+ if (!cookieHeader) return null;
94
+ let cookies = parse(cookieHeader, { ...options, ...parseOptions });
95
+ if (name in cookies) {
96
+ let value = cookies[name];
97
+ if (typeof value === 'string' && value !== '') {
98
+ let decoded = await decodeCookieValue(value, secrets);
99
+ return decoded;
100
+ } else {
101
+ return '';
102
+ }
103
+ } else {
104
+ return null;
105
+ }
106
+ },
107
+ async serialize(value, serializeOptions) {
108
+ return serialize(name, value === '' ? '' : await encodeCookieValue(value, secrets), {
109
+ ...options,
110
+ ...serializeOptions,
111
+ });
112
+ },
113
+ };
114
+ };
115
+
116
+ export type IsCookieFunction = (object: any) => object is Cookie;
117
+
118
+ /**
119
+ * Returns true if an object is a Remix cookie container.
120
+ *
121
+ * @see https://remix.run/utils/cookies#iscookie
122
+ */
123
+ export const isCookie: IsCookieFunction = (object): object is Cookie => {
124
+ return (
125
+ object != null &&
126
+ typeof object.name === 'string' &&
127
+ typeof object.isSigned === 'boolean' &&
128
+ typeof object.parse === 'function' &&
129
+ typeof object.serialize === 'function'
130
+ );
131
+ };
132
+
133
+ async function encodeCookieValue(value: any, secrets: string[]): Promise<string> {
134
+ let encoded = encodeData(value);
135
+
136
+ if (secrets.length > 0) {
137
+ encoded = await sign(encoded, secrets[0]);
138
+ }
139
+
140
+ return encoded;
141
+ }
142
+
143
+ async function decodeCookieValue(value: string, secrets: string[]): Promise<any> {
144
+ if (secrets.length > 0) {
145
+ for (let secret of secrets) {
146
+ let unsignedValue = await unsign(value, secret);
147
+ if (unsignedValue !== false) {
148
+ return decodeData(unsignedValue);
149
+ }
150
+ }
151
+
152
+ return null;
153
+ }
154
+
155
+ return decodeData(value);
156
+ }
157
+
158
+ function encodeData(value: any): string {
159
+ return btoa(myUnescape(encodeURIComponent(JSON.stringify(value))));
160
+ }
161
+
162
+ function decodeData(value: string): any {
163
+ try {
164
+ return JSON.parse(decodeURIComponent(myEscape(atob(value))));
165
+ } catch (
166
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
167
+ e
168
+ ) {
169
+ return {};
170
+ }
171
+ }
172
+
173
+ // See: https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.escape.js
174
+ function myEscape(value: string): string {
175
+ let str = value.toString();
176
+ let result = '';
177
+ let index = 0;
178
+ let chr, code;
179
+ while (index < str.length) {
180
+ chr = str.charAt(index++);
181
+ if (/[\w*+\-./@]/.exec(chr)) {
182
+ result += chr;
183
+ } else {
184
+ code = chr.charCodeAt(0);
185
+ if (code < 256) {
186
+ result += '%' + hex(code, 2);
187
+ } else {
188
+ result += '%u' + hex(code, 4).toUpperCase();
189
+ }
190
+ }
191
+ }
192
+ return result;
193
+ }
194
+
195
+ function hex(code: number, length: number): string {
196
+ let result = code.toString(16);
197
+ while (result.length < length) result = '0' + result;
198
+ return result;
199
+ }
200
+
201
+ // See: https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.unescape.js
202
+ function myUnescape(value: string): string {
203
+ let str = value.toString();
204
+ let result = '';
205
+ let index = 0;
206
+ let chr, part;
207
+ while (index < str.length) {
208
+ chr = str.charAt(index++);
209
+ if (chr === '%') {
210
+ if (str.charAt(index) === 'u') {
211
+ part = str.slice(index + 1, index + 5);
212
+ if (/^[\da-f]{4}$/i.exec(part)) {
213
+ result += String.fromCharCode(parseInt(part, 16));
214
+ index += 5;
215
+ continue;
216
+ }
217
+ } else {
218
+ part = str.slice(index, index + 2);
219
+ if (/^[\da-f]{2}$/i.exec(part)) {
220
+ result += String.fromCharCode(parseInt(part, 16));
221
+ index += 2;
222
+ continue;
223
+ }
224
+ }
225
+ }
226
+ result += chr;
227
+ }
228
+ return result;
229
+ }
230
+
231
+ function warnOnceAboutExpiresCookie(name: string, expires?: Date) {
232
+ warnOnce(
233
+ !expires,
234
+ `The "${name}" cookie has an "expires" property set. ` +
235
+ `This will cause the expires value to not be updated when the session is committed. ` +
236
+ `Instead, you should set the expires value when serializing the cookie. ` +
237
+ `You can use \`commitSession(session, { expires })\` if using a session storage object, ` +
238
+ `or \`cookie.serialize("value", { expires })\` if you're using the cookie directly.`,
239
+ );
240
+ }
@@ -0,0 +1,55 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/server-runtime/crypto.ts — unmodified except: type-only: bare Uint8Array return widens to Uint8Array<ArrayBufferLike> under TS 5.7+ typed-array generics, which crypto.subtle.verify's BufferSource rejects.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ const encoder = /* @__PURE__ */ new TextEncoder();
4
+
5
+ export const sign = async (value: string, secret: string): Promise<string> => {
6
+ let data = encoder.encode(value);
7
+ let key = await createKey(secret, ['sign']);
8
+ let signature = await crypto.subtle.sign('HMAC', key, data);
9
+ let hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/=+$/, '');
10
+
11
+ return value + '.' + hash;
12
+ };
13
+
14
+ export const unsign = async (cookie: string, secret: string): Promise<string | false> => {
15
+ let index = cookie.lastIndexOf('.');
16
+ let value = cookie.slice(0, index);
17
+ let hash = cookie.slice(index + 1);
18
+
19
+ let data = encoder.encode(value);
20
+
21
+ let key = await createKey(secret, ['verify']);
22
+ try {
23
+ let signature = byteStringToUint8Array(atob(hash));
24
+ let valid = await crypto.subtle.verify('HMAC', key, signature, data);
25
+
26
+ return valid ? value : false;
27
+ } catch (
28
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
29
+ e
30
+ ) {
31
+ // atob will throw a DOMException with name === 'InvalidCharacterError'
32
+ // if the signature contains a non-base64 character, which should just
33
+ // be treated as an invalid signature.
34
+ return false;
35
+ }
36
+ };
37
+
38
+ const createKey = async (secret: string, usages: CryptoKey['usages']): Promise<CryptoKey> =>
39
+ crypto.subtle.importKey(
40
+ 'raw',
41
+ encoder.encode(secret),
42
+ { name: 'HMAC', hash: 'SHA-256' },
43
+ false,
44
+ usages,
45
+ );
46
+
47
+ function byteStringToUint8Array(byteString: string): Uint8Array<ArrayBuffer> {
48
+ let array = new Uint8Array(byteString.length);
49
+
50
+ for (let i = 0; i < byteString.length; i++) {
51
+ array[i] = byteString.charCodeAt(i);
52
+ }
53
+
54
+ return array;
55
+ }
@@ -0,0 +1,16 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/server-runtime/mode.ts — unmodified.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ /**
4
+ * The mode to use when running the server.
5
+ */
6
+ export enum ServerMode {
7
+ Development = 'development',
8
+ Production = 'production',
9
+ Test = 'test',
10
+ }
11
+
12
+ export function isServerMode(value: any): value is ServerMode {
13
+ return (
14
+ value === ServerMode.Development || value === ServerMode.Production || value === ServerMode.Test
15
+ );
16
+ }
@@ -0,0 +1,54 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/server-runtime/sessions/cookieStorage.ts — unmodified.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ import { createCookie, isCookie } from '../cookies';
4
+ import type { SessionStorage, SessionIdStorageStrategy, SessionData } from '../sessions';
5
+ import { warnOnceAboutSigningSessionCookie, createSession } from '../sessions';
6
+
7
+ interface CookieSessionStorageOptions {
8
+ /**
9
+ * The Cookie used to store the session data on the client, or options used
10
+ * to automatically create one.
11
+ */
12
+ cookie?: SessionIdStorageStrategy['cookie'];
13
+ }
14
+
15
+ /**
16
+ * Creates and returns a SessionStorage object that stores all session data
17
+ * directly in the session cookie itself.
18
+ *
19
+ * This has the advantage that no database or other backend services are
20
+ * needed, and can help to simplify some load-balanced scenarios. However, it
21
+ * also has the limitation that serialized session data may not exceed the
22
+ * browser's maximum cookie size. Trade-offs!
23
+ */
24
+ export function createCookieSessionStorage<Data = SessionData, FlashData = Data>({
25
+ cookie: cookieArg,
26
+ }: CookieSessionStorageOptions = {}): SessionStorage<Data, FlashData> {
27
+ let cookie = isCookie(cookieArg)
28
+ ? cookieArg
29
+ : createCookie(cookieArg?.name || '__session', cookieArg);
30
+
31
+ warnOnceAboutSigningSessionCookie(cookie);
32
+
33
+ return {
34
+ async getSession(cookieHeader, options) {
35
+ return createSession((cookieHeader && (await cookie.parse(cookieHeader, options))) || {});
36
+ },
37
+ async commitSession(session, options) {
38
+ let serializedCookie = await cookie.serialize(session.data, options);
39
+ if (serializedCookie.length > 4096) {
40
+ throw new Error(
41
+ 'Cookie length will exceed browser maximum. Length: ' + serializedCookie.length,
42
+ );
43
+ }
44
+ return serializedCookie;
45
+ },
46
+ async destroySession(_session, options) {
47
+ return cookie.serialize('', {
48
+ ...options,
49
+ maxAge: undefined,
50
+ expires: new Date(0),
51
+ });
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,59 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/server-runtime/sessions/memoryStorage.ts — unmodified.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ import type {
4
+ SessionData,
5
+ SessionStorage,
6
+ SessionIdStorageStrategy,
7
+ FlashSessionData,
8
+ } from '../sessions';
9
+ import { createSessionStorage } from '../sessions';
10
+
11
+ interface MemorySessionStorageOptions {
12
+ /**
13
+ * The Cookie used to store the session id on the client, or options used
14
+ * to automatically create one.
15
+ */
16
+ cookie?: SessionIdStorageStrategy['cookie'];
17
+ }
18
+
19
+ /**
20
+ * Creates and returns a simple in-memory SessionStorage object, mostly useful
21
+ * for testing and as a reference implementation.
22
+ *
23
+ * Note: This storage does not scale beyond a single process, so it is not
24
+ * suitable for most production scenarios.
25
+ */
26
+ export function createMemorySessionStorage<Data = SessionData, FlashData = Data>({
27
+ cookie,
28
+ }: MemorySessionStorageOptions = {}): SessionStorage<Data, FlashData> {
29
+ let map = new Map<string, { data: FlashSessionData<Data, FlashData>; expires?: Date }>();
30
+
31
+ return createSessionStorage({
32
+ cookie,
33
+ async createData(data, expires) {
34
+ let id = Math.random().toString(36).substring(2, 10);
35
+ map.set(id, { data, expires });
36
+ return id;
37
+ },
38
+ async readData(id) {
39
+ if (map.has(id)) {
40
+ let { data, expires } = map.get(id)!;
41
+
42
+ if (!expires || expires > new Date()) {
43
+ return data;
44
+ }
45
+
46
+ // Remove expired session data.
47
+ if (expires) map.delete(id);
48
+ }
49
+
50
+ return null;
51
+ },
52
+ async updateData(id, data, expires) {
53
+ map.set(id, { data, expires });
54
+ },
55
+ async deleteData(id) {
56
+ map.delete(id);
57
+ },
58
+ });
59
+ }