@tula-tech/contract 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,251 @@
1
+ // src/error-codes.ts
2
+ var ERROR_DEFINITIONS2 = {
3
+ "auth.invalid_credentials": { status: 401, message: "The email or password is incorrect." },
4
+ "auth.unauthenticated": { status: 401, message: "You need to sign in to do that." },
5
+ "auth.invalid_key": { status: 401, message: "The API key is missing, invalid or revoked." },
6
+ "auth.forbidden": { status: 403, message: "You do not have permission to do that." },
7
+ "auth.user_banned": { status: 403, message: "This account has been disabled." },
8
+ "auth.method_disabled": { status: 403, message: "This sign-in method is not available." },
9
+ "auth.step_up_required": {
10
+ status: 403,
11
+ message: "Confirm it is you to continue."
12
+ },
13
+ "flow.not_found": { status: 404, message: "This attempt does not exist or has expired." },
14
+ "flow.invalid_step": {
15
+ status: 409,
16
+ message: "That action is not valid at this step. Please start again."
17
+ },
18
+ "email.invalid": { status: 422, message: "Enter a valid email address." },
19
+ "email.already_set": { status: 409, message: "This account already has an email address." },
20
+ "password.too_short": { status: 422, message: "Password is too short." },
21
+ "password.too_long": { status: 422, message: "Password is too long." },
22
+ "password.missing_lowercase": { status: 422, message: "Add a lowercase letter." },
23
+ "password.missing_uppercase": { status: 422, message: "Add an uppercase letter." },
24
+ "password.missing_number": { status: 422, message: "Add a number." },
25
+ "password.missing_special": { status: 422, message: "Add a special character." },
26
+ "password.too_few_character_classes": {
27
+ status: 422,
28
+ message: "Use a wider mix of letters, numbers and symbols."
29
+ },
30
+ "password.contains_user_info": {
31
+ status: 422,
32
+ message: "Password must not contain your name or email."
33
+ },
34
+ "password.common": { status: 422, message: "This password is too common." },
35
+ "password.breached": {
36
+ status: 422,
37
+ message: "This password appeared in a data breach. Choose a different one."
38
+ },
39
+ "password.repeated_characters": {
40
+ status: 422,
41
+ message: "Avoid repeating the same character."
42
+ },
43
+ "password.sequence": { status: 422, message: 'Avoid sequences like "abcd" or "1234".' },
44
+ "password.reused": {
45
+ status: 422,
46
+ message: "You have used this password recently. Choose a different one."
47
+ },
48
+ "password.not_set": {
49
+ status: 409,
50
+ message: 'This account has no password yet. Use "forgot password" to set one.'
51
+ },
52
+ "verification.invalid_code": { status: 422, message: "That code is incorrect." },
53
+ "verification.expired": { status: 410, message: "That code has expired. Request a new one." },
54
+ "verification.too_many_attempts": {
55
+ status: 429,
56
+ message: "Too many incorrect codes. Request a new one."
57
+ },
58
+ "verification.different_browser": {
59
+ status: 409,
60
+ message: "Open this link in the browser where you started signing in, or enter the code from the email there."
61
+ },
62
+ "mfa.invalid_code": { status: 422, message: "That code is incorrect." },
63
+ "mfa.already_enabled": {
64
+ status: 409,
65
+ message: "Two-step verification is already on for this account."
66
+ },
67
+ "mfa.not_enabled": {
68
+ status: 409,
69
+ message: "Two-step verification is not on for this account."
70
+ },
71
+ "mfa.enrolment_expired": {
72
+ status: 410,
73
+ message: "This setup has expired. Start again."
74
+ },
75
+ "mfa.not_available": {
76
+ status: 403,
77
+ message: "Two-step verification is not available for this app."
78
+ },
79
+ "mfa.enrolment_needs_other_sign_in": {
80
+ status: 403,
81
+ message: "Sign in another way to set up two-step verification."
82
+ },
83
+ "mfa.needs_other_sign_in": {
84
+ status: 403,
85
+ message: "Sign in another way. A texted code cannot be both steps."
86
+ },
87
+ "mfa.sms_not_allowed": {
88
+ status: 409,
89
+ message: "This account already has a stronger second step than a texted code."
90
+ },
91
+ "mfa.phone_number_required": {
92
+ status: 409,
93
+ message: "Add a phone number to your account first."
94
+ },
95
+ "mfa.required_by_policy": {
96
+ status: 403,
97
+ message: "This app requires two-step verification. It cannot be turned off."
98
+ },
99
+ "oauth.access_denied": {
100
+ status: 403,
101
+ message: "Sign-in was cancelled. Try again or choose another way to sign in."
102
+ },
103
+ "oauth.provider_error": {
104
+ status: 502,
105
+ message: "The sign-in provider could not complete the request. Try again."
106
+ },
107
+ "oauth.state_invalid": {
108
+ status: 400,
109
+ message: "This sign-in could not be completed. Start again."
110
+ },
111
+ "oauth.ticket_invalid": {
112
+ status: 410,
113
+ message: "This sign-in has expired. Start again."
114
+ },
115
+ "oauth.different_browser": {
116
+ status: 409,
117
+ message: "Finish signing in in the browser where you started, or start again here."
118
+ },
119
+ "oauth.email_missing": {
120
+ status: 403,
121
+ message: "This provider did not share an email address. Allow access to your email and try again."
122
+ },
123
+ "oauth.email_unverified": {
124
+ status: 403,
125
+ message: "Your email address is not verified with this provider. Verify it there and try again."
126
+ },
127
+ "oauth.account_exists": {
128
+ status: 409,
129
+ message: "An account with this email already exists. Sign in the way you usually do, then connect this provider from your account."
130
+ },
131
+ "oauth.identity_in_use": {
132
+ status: 409,
133
+ message: "This provider account is already connected to another user."
134
+ },
135
+ "oauth.already_linked": {
136
+ status: 409,
137
+ message: "An account of this provider is already connected. Disconnect it first."
138
+ },
139
+ "identity.last_sign_in_method": {
140
+ status: 409,
141
+ message: "This is your only way to sign in. Add a password or connect another account first."
142
+ },
143
+ "webhook.url_not_allowed": {
144
+ status: 422,
145
+ message: "The server cannot deliver to that address."
146
+ },
147
+ "webhook.cannot_redeliver": {
148
+ status: 409,
149
+ message: "This delivery cannot be sent again."
150
+ },
151
+ "webhook.rotation_refused": {
152
+ status: 409,
153
+ message: "The signing secret cannot be changed now."
154
+ },
155
+ "hook.denied": { status: 403, message: "This was not allowed." },
156
+ "hook.unavailable": {
157
+ status: 503,
158
+ message: "This is unavailable right now. Try again later."
159
+ },
160
+ "hook.url_not_allowed": { status: 422, message: "The server cannot call that address." },
161
+ "passkey.registration_failed": {
162
+ status: 422,
163
+ message: "That passkey could not be saved. Try again."
164
+ },
165
+ "passkey.already_registered": {
166
+ status: 409,
167
+ message: "This passkey is already saved to an account."
168
+ },
169
+ "passkey.limit_reached": {
170
+ status: 409,
171
+ message: "You have reached the number of passkeys an account can have. Remove one first."
172
+ },
173
+ "passkey.last_sign_in_method": {
174
+ status: 409,
175
+ message: "This is your only way to sign in. Add a password or connect an account first."
176
+ },
177
+ "phone.invalid": {
178
+ status: 422,
179
+ message: "Enter a phone number with its country code, such as +14155550100."
180
+ },
181
+ "sms.disabled": { status: 403, message: "Text messages are not available." },
182
+ "sms.country_not_allowed": {
183
+ status: 422,
184
+ message: "Text messages cannot be sent to that country."
185
+ },
186
+ "sms.unavailable": {
187
+ status: 503,
188
+ message: "The text message could not be sent. Try again later."
189
+ },
190
+ "session.invalid_token": { status: 401, message: "Your session is invalid. Sign in again." },
191
+ "session.expired": { status: 401, message: "Your session has expired. Sign in again." },
192
+ "session.revoked": { status: 401, message: "Your session was signed out. Sign in again." },
193
+ "session.reuse_detected": {
194
+ status: 401,
195
+ message: "For your security this session was signed out. Sign in again."
196
+ },
197
+ "session.limit_reached": {
198
+ status: 403,
199
+ message: "You are signed in on too many devices. Sign out on another device, or reset your password to sign out everywhere, then try again."
200
+ },
201
+ "device.proof_invalid": {
202
+ status: 401,
203
+ message: "This session is bound to a device key, and the request did not prove that key."
204
+ },
205
+ "device.nonce_required": {
206
+ status: 400,
207
+ message: "The proof needs a fresh nonce. Send the request again with the nonce provided."
208
+ },
209
+ "device.binding_not_supported": {
210
+ status: 400,
211
+ message: "A session of this kind of client cannot be bound to a device key."
212
+ },
213
+ "device.binding_required": {
214
+ status: 400,
215
+ message: "Signing in here needs a device key."
216
+ },
217
+ rate_limited: { status: 429, message: "Too many requests. Try again shortly." },
218
+ "request.malformed": { status: 400, message: "The request could not be read." },
219
+ "request.too_large": { status: 413, message: "The request body is too large." },
220
+ "request.origin_not_allowed": {
221
+ status: 403,
222
+ message: "This origin is not allowed to sign in to this app."
223
+ },
224
+ "request.redirect_not_allowed": {
225
+ status: 400,
226
+ message: "This redirect URL is not allowed for this app."
227
+ },
228
+ "validation.failed": { status: 422, message: "Some fields are invalid." },
229
+ "resource.not_found": { status: 404, message: "The requested resource does not exist." },
230
+ "resource.conflict": { status: 409, message: "The resource conflicts with existing data." },
231
+ "precondition.required": {
232
+ status: 428,
233
+ message: "Send the revision you are changing in an If-Match header."
234
+ },
235
+ "precondition.failed": {
236
+ status: 412,
237
+ message: "The resource changed since you read it. Read it again and retry."
238
+ },
239
+ not_implemented: { status: 501, message: "This capability is not available yet." },
240
+ "service.unavailable": {
241
+ status: 503,
242
+ message: "The service is temporarily unavailable. Try again shortly."
243
+ },
244
+ internal: { status: 500, message: "Something went wrong on our side." }
245
+ };
246
+ var ERROR_CODES2 = Object.keys(ERROR_DEFINITIONS2);
247
+ function errorDefinition2(code) {
248
+ return ERROR_DEFINITIONS2[code];
249
+ }
250
+
251
+ export { ERROR_DEFINITIONS2, ERROR_CODES2, errorDefinition2 };
@@ -0,0 +1,195 @@
1
+ // src/theme.ts
2
+ function scheme(key, css, description, type = "color") {
3
+ return { key, scope: "scheme", type, cssVariable: `--tula-${css}`, description };
4
+ }
5
+ function shared(key, css, type, description) {
6
+ return { key, scope: "shared", type, cssVariable: `--tula-${css}`, description };
7
+ }
8
+ var THEME_TOKENS2 = [
9
+ scheme("background", "color-background", "Behind a component: the card, the menu, the dialog."),
10
+ scheme("surface", "color-surface", "A quieter panel: list rows, a hovered menu item."),
11
+ scheme("input", "color-input", "The inside of a text field."),
12
+ scheme("text", "color-text", "Body text and headings."),
13
+ scheme("textMuted", "color-text-muted", "Secondary text: hints, an unmet checklist rule."),
14
+ scheme("primary", "color-primary", "The brand colour: the main button."),
15
+ scheme("primaryHover", "color-primary-hover", "The main button while hovered or pressed."),
16
+ scheme("primaryText", "color-primary-text", "Text and icons on the primary colour."),
17
+ scheme("link", "color-link", "Links and text buttons."),
18
+ scheme("border", "color-border", "Hairlines: the card edge, dividers."),
19
+ scheme("borderStrong", "color-border-strong", "The edge of a text field or secondary button."),
20
+ scheme("danger", "color-danger", "Error text, invalid fields, destructive actions."),
21
+ scheme("dangerSurface", "color-danger-surface", "Behind an error message."),
22
+ scheme("success", "color-success", "A met checklist rule, a success message."),
23
+ scheme("focus", "color-focus", "The keyboard focus ring."),
24
+ scheme("overlay", "color-overlay", "Dims the page behind a dialog."),
25
+ scheme("shadow", "shadow", "The card shadow.", "shadow"),
26
+ shared("radius", "radius", "length", "Corner radius of cards, fields and buttons."),
27
+ shared("radiusSmall", "radius-small", "length", "Corner radius of badges and menu items."),
28
+ shared("fontFamily", "font-family", "fontFamily", "The typeface of everything."),
29
+ shared("fontFamilyMono", "font-family-mono", "fontFamily", "The typeface of codes."),
30
+ shared("fontSize", "font-size", "length", "Body text size."),
31
+ shared("fontSizeSmall", "font-size-small", "length", "Size of hints and the checklist."),
32
+ shared("fontSizeTitle", "font-size-title", "length", "Size of a card title."),
33
+ shared("spacing", "spacing", "length", "The spacing unit.")
34
+ ];
35
+ var DEFAULT_THEME2 = {
36
+ light: {
37
+ background: "#ffffff",
38
+ surface: "#f5f4f0",
39
+ input: "#ffffff",
40
+ text: "#17171c",
41
+ textMuted: "#5c5a55",
42
+ primary: "#5b4cf0",
43
+ primaryHover: "#4a3bdc",
44
+ primaryText: "#ffffff",
45
+ link: "#4a3bdc",
46
+ border: "#e3e0d8",
47
+ borderStrong: "#8a877e",
48
+ danger: "#b42318",
49
+ dangerSurface: "#fdecea",
50
+ success: "#1a6b3c",
51
+ focus: "#5b4cf0",
52
+ overlay: "rgba(23, 23, 28, 0.55)",
53
+ shadow: "0 1px 2px rgba(23, 23, 28, 0.06), 0 12px 32px rgba(23, 23, 28, 0.1)"
54
+ },
55
+ dark: {
56
+ background: "#17171c",
57
+ surface: "#202128",
58
+ input: "#101014",
59
+ text: "#f4f4f6",
60
+ textMuted: "#a9a9b4",
61
+ primary: "#8f84ff",
62
+ primaryHover: "#a59cff",
63
+ primaryText: "#121117",
64
+ link: "#aaa2ff",
65
+ border: "#2e2f39",
66
+ borderStrong: "#74758a",
67
+ danger: "#ff8a80",
68
+ dangerSurface: "#3a1a1c",
69
+ success: "#6fd49a",
70
+ focus: "#aaa2ff",
71
+ overlay: "rgba(0, 0, 0, 0.65)",
72
+ shadow: "0 1px 2px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.5)"
73
+ },
74
+ radius: "12px",
75
+ radiusSmall: "8px",
76
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
77
+ fontFamilyMono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',
78
+ fontSize: "0.9375rem",
79
+ fontSizeSmall: "0.8125rem",
80
+ fontSizeTitle: "1.5rem",
81
+ spacing: "1rem"
82
+ };
83
+ function darkCssVariable2(cssVariable) {
84
+ return `--tula-dark-${cssVariable.slice("--tula-".length)}`;
85
+ }
86
+ var MAX_VALUE_LENGTH = 200;
87
+ var FORBIDDEN = /[;{}<>\\@!\n\r\f\0]|\/\*|url\(|expression\(|var\(/i;
88
+ var HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
89
+ var FUNCTION_COLOR = /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch)\(\s*[0-9a-z.%+-]+(?:\s*[\s,/]\s*[0-9a-z.%+-]+){2,4}\s*\)$/i;
90
+ var NAMED_COLOR = /^[a-z]{3,30}$/i;
91
+ var LENGTH = /^(?:0|[+-]?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|ch|ex|pt|vw|vh|vmin|vmax))$/i;
92
+ var FONT_FAMILY = /^(?:"[\w .-]+"|'[\w .-]+'|-?[a-z_][\w-]*(?: [\w-]+)*)$/i;
93
+ function isColor(value) {
94
+ return HEX_COLOR.test(value) || FUNCTION_COLOR.test(value) || NAMED_COLOR.test(value);
95
+ }
96
+ function splitOutsideParentheses(value, separator) {
97
+ const parts = [];
98
+ let depth = 0;
99
+ let current = "";
100
+ for (const char of value) {
101
+ if (char === "(") {
102
+ depth += 1;
103
+ } else if (char === ")") {
104
+ depth -= 1;
105
+ if (depth < 0) {
106
+ return null;
107
+ }
108
+ }
109
+ if (depth === 0 && separator.test(char)) {
110
+ parts.push(current);
111
+ current = "";
112
+ } else {
113
+ current += char;
114
+ }
115
+ }
116
+ parts.push(current);
117
+ return depth === 0 ? parts : null;
118
+ }
119
+ function isShadowLayer(layer) {
120
+ const parts = splitOutsideParentheses(layer.trim(), /\s/)?.filter((part) => part !== "");
121
+ if (!parts) {
122
+ return false;
123
+ }
124
+ const rest = parts.filter((part) => part.toLowerCase() !== "inset");
125
+ const lengths = rest.filter((part) => LENGTH.test(part));
126
+ const colors = rest.filter((part) => !LENGTH.test(part));
127
+ return parts.length - rest.length <= 1 && lengths.length >= 2 && lengths.length <= 4 && colors.length <= 1 && colors.every(isColor);
128
+ }
129
+ function isValidThemeValue2(type, value) {
130
+ if (typeof value !== "string" || value === "" || value.length > MAX_VALUE_LENGTH) {
131
+ return false;
132
+ }
133
+ if (FORBIDDEN.test(value)) {
134
+ return false;
135
+ }
136
+ const trimmed = value.trim();
137
+ switch (type) {
138
+ case "color":
139
+ return isColor(trimmed);
140
+ case "length":
141
+ return LENGTH.test(trimmed);
142
+ case "fontFamily":
143
+ return (splitOutsideParentheses(trimmed, /,/) ?? [""]).every((family) => FONT_FAMILY.test(family.trim()));
144
+ case "shadow": {
145
+ const layers = splitOutsideParentheses(trimmed, /,/);
146
+ return trimmed.toLowerCase() === "none" || layers !== null && layers.every(isShadowLayer);
147
+ }
148
+ default:
149
+ return false;
150
+ }
151
+ }
152
+ function themeToCssVariables2(theme) {
153
+ const variables = {};
154
+ const own = (source, key, type) => {
155
+ const value = source && Object.hasOwn(source, key) ? source[key] : undefined;
156
+ return typeof value === "string" && isValidThemeValue2(type, value) ? value.trim() : undefined;
157
+ };
158
+ for (const token of THEME_TOKENS2) {
159
+ if (token.scope === "shared") {
160
+ const value = own(theme, token.key, token.type);
161
+ if (value !== undefined) {
162
+ variables[token.cssVariable] = value;
163
+ }
164
+ continue;
165
+ }
166
+ const light = own(theme.light, token.key, token.type);
167
+ if (light !== undefined) {
168
+ variables[token.cssVariable] = light;
169
+ }
170
+ const dark = own(theme.dark, token.key, token.type);
171
+ if (dark !== undefined) {
172
+ variables[darkCssVariable2(token.cssVariable)] = dark;
173
+ }
174
+ }
175
+ return variables;
176
+ }
177
+ function channel(value) {
178
+ const scaled = value / 255;
179
+ return scaled <= 0.04045 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
180
+ }
181
+ function luminance(hex) {
182
+ const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
183
+ if (!match) {
184
+ throw new TypeError(`contrastRatio: expected a #rrggbb colour, got "${hex}"`);
185
+ }
186
+ const [red, green, blue] = match.slice(1).map((part) => channel(Number.parseInt(part, 16)));
187
+ return 0.2126 * (red ?? 0) + 0.7152 * (green ?? 0) + 0.0722 * (blue ?? 0);
188
+ }
189
+ function contrastRatio2(foreground, background) {
190
+ const a = luminance(foreground);
191
+ const b = luminance(background);
192
+ return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
193
+ }
194
+
195
+ export { THEME_TOKENS2, DEFAULT_THEME2, darkCssVariable2, isValidThemeValue2, themeToCssVariables2, contrastRatio2 };
@@ -0,0 +1,19 @@
1
+ // src/headers.ts
2
+ var PUBLISHABLE_KEY_HEADER2 = "x-tula-publishable-key";
3
+ var CLIENT_HEADER2 = "x-tula-client";
4
+ var SESSION_PROFILE_HEADER2 = "x-tula-session-profile";
5
+ var FLOW_ATTEMPT_HEADER2 = "x-tula-attempt";
6
+ var CAN_STILL_SIGN_IN_HEADER2 = "x-tula-can-still-sign-in";
7
+ var CONFIG_MANAGED_BY_HEADER2 = "x-tula-managed-by";
8
+ var CONFIG_HASH_HEADER2 = "x-tula-config-hash";
9
+ var CONFIG_UNMANAGED2 = "none";
10
+ var DASHBOARD_HEADER2 = "x-tula-dashboard";
11
+ var DASHBOARD_HEADER_VALUE2 = "1";
12
+ var ENVIRONMENT_HEADER2 = "x-tula-environment";
13
+ var DASHBOARD_SESSION_COOKIE2 = "tula_dashboard";
14
+ var EMAIL_LINK_TOKEN_PARAM2 = "tula_link";
15
+ var EMAIL_LINK_ATTEMPT_PARAM2 = "tula_attempt";
16
+ var OAUTH_TICKET_PARAM2 = "tula_ticket";
17
+ var OAUTH_ERROR_PARAM2 = "tula_error";
18
+
19
+ export { PUBLISHABLE_KEY_HEADER2, CLIENT_HEADER2, SESSION_PROFILE_HEADER2, FLOW_ATTEMPT_HEADER2, CAN_STILL_SIGN_IN_HEADER2, CONFIG_MANAGED_BY_HEADER2, CONFIG_HASH_HEADER2, CONFIG_UNMANAGED2, DASHBOARD_HEADER2, DASHBOARD_HEADER_VALUE2, ENVIRONMENT_HEADER2, DASHBOARD_SESSION_COOKIE2, EMAIL_LINK_TOKEN_PARAM2, EMAIL_LINK_ATTEMPT_PARAM2, OAUTH_TICKET_PARAM2, OAUTH_ERROR_PARAM2 };