@vesk/runtime 0.0.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.
Files changed (81) hide show
  1. package/README.md +23 -0
  2. package/dist/action.d.ts +43 -0
  3. package/dist/action.d.ts.map +1 -0
  4. package/dist/action.js +84 -0
  5. package/dist/bindings.d.ts +4 -0
  6. package/dist/bindings.d.ts.map +1 -0
  7. package/dist/bindings.js +137 -0
  8. package/dist/context.d.ts +15 -0
  9. package/dist/context.d.ts.map +1 -0
  10. package/dist/context.js +34 -0
  11. package/dist/experiment.d.ts +17 -0
  12. package/dist/experiment.d.ts.map +1 -0
  13. package/dist/experiment.js +71 -0
  14. package/dist/form.d.ts +36 -0
  15. package/dist/form.d.ts.map +1 -0
  16. package/dist/form.js +278 -0
  17. package/dist/hmr-client.d.ts +1 -0
  18. package/dist/hmr-client.d.ts.map +1 -0
  19. package/dist/hmr-client.js +273 -0
  20. package/dist/hydrate.d.ts +34 -0
  21. package/dist/hydrate.d.ts.map +1 -0
  22. package/dist/hydrate.js +248 -0
  23. package/dist/image.d.ts +19 -0
  24. package/dist/image.d.ts.map +1 -0
  25. package/dist/image.js +51 -0
  26. package/dist/index-client.d.ts +16 -0
  27. package/dist/index-client.d.ts.map +1 -0
  28. package/dist/index-client.js +15 -0
  29. package/dist/index-server.d.ts +15 -0
  30. package/dist/index-server.d.ts.map +1 -0
  31. package/dist/index-server.js +14 -0
  32. package/dist/isr.d.ts +27 -0
  33. package/dist/isr.d.ts.map +1 -0
  34. package/dist/isr.js +143 -0
  35. package/dist/md.d.ts +17 -0
  36. package/dist/md.d.ts.map +1 -0
  37. package/dist/md.js +507 -0
  38. package/dist/package.json +23 -0
  39. package/dist/portal.d.ts +8 -0
  40. package/dist/portal.d.ts.map +1 -0
  41. package/dist/portal.js +20 -0
  42. package/dist/reconcile.d.ts +3 -0
  43. package/dist/reconcile.d.ts.map +1 -0
  44. package/dist/reconcile.js +66 -0
  45. package/dist/request.d.ts +188 -0
  46. package/dist/request.d.ts.map +1 -0
  47. package/dist/request.js +624 -0
  48. package/dist/resource.d.ts +47 -0
  49. package/dist/resource.d.ts.map +1 -0
  50. package/dist/resource.js +502 -0
  51. package/dist/ripple-blocks.d.ts +18 -0
  52. package/dist/ripple-blocks.d.ts.map +1 -0
  53. package/dist/ripple-blocks.js +184 -0
  54. package/dist/ripple-constants.d.ts +42 -0
  55. package/dist/ripple-constants.d.ts.map +1 -0
  56. package/dist/ripple-constants.js +41 -0
  57. package/dist/ripple-runtime.d.ts +125 -0
  58. package/dist/ripple-runtime.d.ts.map +1 -0
  59. package/dist/ripple-runtime.js +891 -0
  60. package/dist/ripple-utils.d.ts +12 -0
  61. package/dist/ripple-utils.d.ts.map +1 -0
  62. package/dist/ripple-utils.js +10 -0
  63. package/dist/router-components.d.ts +83 -0
  64. package/dist/router-components.d.ts.map +1 -0
  65. package/dist/router-components.js +320 -0
  66. package/dist/router-match.d.ts +40 -0
  67. package/dist/router-match.d.ts.map +1 -0
  68. package/dist/router-match.js +158 -0
  69. package/dist/router.d.ts +56 -0
  70. package/dist/router.d.ts.map +1 -0
  71. package/dist/router.js +1330 -0
  72. package/dist/seo.d.ts +94 -0
  73. package/dist/seo.d.ts.map +1 -0
  74. package/dist/seo.js +138 -0
  75. package/dist/suspense.d.ts +1 -0
  76. package/dist/suspense.d.ts.map +1 -0
  77. package/dist/suspense.js +14 -0
  78. package/dist/track.d.ts +20 -0
  79. package/dist/track.d.ts.map +1 -0
  80. package/dist/track.js +124 -0
  81. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @vesk/runtime
2
+
3
+ Vesk runtime — reactivity engine, block lifecycle, DOM operations, hydration, routing, and server APIs.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @vesk/runtime
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ // Client
15
+ import { track, effect, derived } from '@vesk/runtime';
16
+
17
+ // Server
18
+ import { renderPage, parseCookies } from '@vesk/runtime/server';
19
+ ```
20
+
21
+ ## License
22
+
23
+ MIT
@@ -0,0 +1,43 @@
1
+ import type { ValidationRule } from './form';
2
+ export interface ActionIssue {
3
+ field: string;
4
+ message: string;
5
+ }
6
+ export type ActionInputSchema = Record<string, ValidationRule | ValidationRule[]>;
7
+ export interface ActionContext {
8
+ request: Request;
9
+ params: Record<string, string>;
10
+ url: string;
11
+ headers: () => Map<string, string>;
12
+ cookies: () => Record<string, string>;
13
+ locals: () => Record<string, unknown>;
14
+ redirect: (url: string, status?: number) => Response;
15
+ }
16
+ export interface ActionConfig {
17
+ input?: ActionInputSchema;
18
+ execute: (input: Record<string, unknown>, ctx: ActionContext) => unknown | Promise<unknown>;
19
+ }
20
+ export interface ActionDefinition extends ActionConfig {
21
+ id: string;
22
+ url: string;
23
+ }
24
+ export interface ActionStub {
25
+ __veskAction: true;
26
+ id: string;
27
+ url: string;
28
+ }
29
+ export type FormAction = string | ActionDefinition | ActionStub;
30
+ /**
31
+ * Define a server action from a page component.
32
+ *
33
+ * Generated server code calls `defineAction("<stable-id>", { input, execute })`
34
+ * so both the client stub and the server registration share one id. When called
35
+ * directly (no compiler transform), the id is derived from the `execute` source.
36
+ */
37
+ export declare function defineAction(config: ActionConfig | string, config2?: ActionConfig): ActionDefinition;
38
+ export declare function getAction(id: string): ActionConfig | null;
39
+ export declare function clearActions(): void;
40
+ export declare function isFormAction(action: unknown): action is ActionDefinition | ActionStub;
41
+ export declare function validateActionInput(def: ActionConfig, input: Record<string, unknown>): ActionIssue[];
42
+ export declare function issuesToFieldMap(issues: ActionIssue[]): Record<string, string>;
43
+ //# sourceMappingURL=action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC,CAAC;AAElF,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC;CACrD;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5F;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IAC1B,YAAY,EAAE,IAAI,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,gBAAgB,GAAG,UAAU,CAAC;AA2BhE;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,gBAAgB,CAgBpG;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAEzD;AAED,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,gBAAgB,GAAG,UAAU,CAQrF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,EAAE,CAYpG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM9E"}
package/dist/action.js ADDED
@@ -0,0 +1,84 @@
1
+ const registryKey = '__vesk_actions';
2
+ function getActionRegistry() {
3
+ const g = globalThis;
4
+ if (!g[registryKey])
5
+ g[registryKey] = {};
6
+ return g[registryKey];
7
+ }
8
+ function hashString(str) {
9
+ let h1 = 0x811c9dc5;
10
+ let h2 = 0x01000193;
11
+ for (let i = 0; i < str.length; i++) {
12
+ const c = str.charCodeAt(i);
13
+ h1 ^= c;
14
+ h1 = Math.imul(h1, 0x01000193) >>> 0;
15
+ h2 ^= c;
16
+ h2 = Math.imul(h2, 0x01000193) >>> 0;
17
+ }
18
+ return (h1 ^ h2).toString(36).slice(0, 12);
19
+ }
20
+ function computeActionId(def) {
21
+ return hashString(String(def.execute || ''));
22
+ }
23
+ /**
24
+ * Define a server action from a page component.
25
+ *
26
+ * Generated server code calls `defineAction("<stable-id>", { input, execute })`
27
+ * so both the client stub and the server registration share one id. When called
28
+ * directly (no compiler transform), the id is derived from the `execute` source.
29
+ */
30
+ export function defineAction(config, config2) {
31
+ let def;
32
+ let id = null;
33
+ if (typeof config === 'string') {
34
+ id = config;
35
+ def = config2;
36
+ }
37
+ else {
38
+ def = config;
39
+ }
40
+ if (!def || typeof def.execute !== 'function') {
41
+ throw new Error('defineAction requires an object with an `execute` function');
42
+ }
43
+ if (!id)
44
+ id = computeActionId(def);
45
+ const registry = getActionRegistry();
46
+ if (!registry[id])
47
+ registry[id] = def;
48
+ return { id, url: `/_vesk/action/${id}`, input: def.input, execute: def.execute };
49
+ }
50
+ export function getAction(id) {
51
+ return getActionRegistry()[id] || null;
52
+ }
53
+ export function clearActions() {
54
+ const g = globalThis;
55
+ g[registryKey] = {};
56
+ }
57
+ export function isFormAction(action) {
58
+ return (typeof action === 'object' &&
59
+ action !== null &&
60
+ (action.__veskAction === true ||
61
+ (typeof action.id === 'string' &&
62
+ typeof action.url === 'string')));
63
+ }
64
+ export function validateActionInput(def, input) {
65
+ const issues = [];
66
+ for (const [field, ruleOrRules] of Object.entries(def.input || {})) {
67
+ const rules = Array.isArray(ruleOrRules) ? ruleOrRules : [ruleOrRules];
68
+ for (const rule of rules) {
69
+ if (rule && !rule.validate(input[field])) {
70
+ issues.push({ field, message: rule.message });
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ return issues;
76
+ }
77
+ export function issuesToFieldMap(issues) {
78
+ const map = {};
79
+ for (const issue of issues) {
80
+ if (!(issue.field in map))
81
+ map[issue.field] = issue.message;
82
+ }
83
+ return map;
84
+ }
@@ -0,0 +1,4 @@
1
+ export declare function bindValue(maybe_tracked: unknown, set_func?: (value: unknown) => void): (node: HTMLElement) => () => void;
2
+ export declare function bindChecked(maybe_tracked: unknown, set_func?: (value: unknown) => void): (input: HTMLInputElement) => () => void;
3
+ export declare function bindGroup(maybe_tracked: unknown, set_func?: (value: unknown) => void): (input: HTMLInputElement) => () => void;
4
+ //# sourceMappingURL=bindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"AAsDA,wBAAgB,SAAS,CACxB,aAAa,EAAE,OAAO,EACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,IAAI,CAoDnC;AAED,wBAAgB,WAAW,CAC1B,aAAa,EAAE,OAAO,EACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,CAAC,KAAK,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAiBzC;AAED,wBAAgB,SAAS,CACxB,aAAa,EAAE,OAAO,EACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACjC,CAAC,KAAK,EAAE,gBAAgB,KAAK,MAAM,IAAI,CA2CzC"}
@@ -0,0 +1,137 @@
1
+ import { effect, render } from '@vesk/runtime/src/ripple-blocks';
2
+ import { get, set } from '@vesk/runtime/src/ripple-runtime';
3
+ import { is_ripple_object } from '@vesk/runtime/src/ripple-utils';
4
+ function not_tracked_type_error(name) {
5
+ return new TypeError(`${name} argument is not a tracked object`);
6
+ }
7
+ function not_set_function_type_error(name) {
8
+ return new TypeError(`${name} second argument must be a set function when first argument is a get function`);
9
+ }
10
+ function get_bind_get_set(name, maybe_tracked, set_func) {
11
+ if (typeof maybe_tracked === 'function') {
12
+ if (typeof set_func !== 'function') {
13
+ throw not_set_function_type_error(name);
14
+ }
15
+ return {
16
+ getter: maybe_tracked,
17
+ setter: set_func,
18
+ };
19
+ }
20
+ else {
21
+ if (!is_ripple_object(maybe_tracked)) {
22
+ throw not_tracked_type_error(name);
23
+ }
24
+ const obj = maybe_tracked;
25
+ return {
26
+ getter: () => get(obj),
27
+ setter: (value) => set(obj, value),
28
+ };
29
+ }
30
+ }
31
+ function is_numberlike_input(input) {
32
+ const type = input.type;
33
+ return type === 'number' || type === 'range';
34
+ }
35
+ function to_number(value) {
36
+ return value === '' ? null : +value;
37
+ }
38
+ export function bindValue(maybe_tracked, set_func) {
39
+ const { getter, setter } = get_bind_get_set('bindValue()', maybe_tracked, set_func);
40
+ return (node) => {
41
+ if (node.tagName === 'SELECT') {
42
+ const select = node;
43
+ const onChange = () => {
44
+ const value = select.multiple
45
+ ? [].map.call(select.querySelectorAll(':checked'), (o) => o.value)
46
+ : select.value;
47
+ setter(value);
48
+ };
49
+ select.addEventListener('change', onChange);
50
+ effect(() => {
51
+ const value = getter();
52
+ if (select.multiple) {
53
+ for (const option of select.options) {
54
+ option.selected = (value || []).includes(option.value);
55
+ }
56
+ }
57
+ else {
58
+ select.value = value ?? '';
59
+ }
60
+ });
61
+ return () => select.removeEventListener('change', onChange);
62
+ }
63
+ else {
64
+ const input = node;
65
+ const onInput = () => {
66
+ let value = input.value;
67
+ value = is_numberlike_input(input) ? to_number(value) : value;
68
+ setter(value);
69
+ };
70
+ input.addEventListener('input', onInput);
71
+ render(() => {
72
+ const value = getter();
73
+ if (is_numberlike_input(input) && value === to_number(input.value)) {
74
+ return;
75
+ }
76
+ if (value !== input.value) {
77
+ input.value = value ?? '';
78
+ }
79
+ });
80
+ return () => input.removeEventListener('input', onInput);
81
+ }
82
+ };
83
+ }
84
+ export function bindChecked(maybe_tracked, set_func) {
85
+ const { getter, setter } = get_bind_get_set('bindChecked()', maybe_tracked, set_func);
86
+ return (input) => {
87
+ const onChange = () => {
88
+ setter(input.checked);
89
+ };
90
+ input.addEventListener('change', onChange);
91
+ effect(() => {
92
+ const value = getter();
93
+ input.checked = Boolean(value);
94
+ });
95
+ return () => input.removeEventListener('change', onChange);
96
+ };
97
+ }
98
+ export function bindGroup(maybe_tracked, set_func) {
99
+ const { getter, setter } = get_bind_get_set('bindGroup()', maybe_tracked, set_func);
100
+ return (input) => {
101
+ const is_checkbox = input.getAttribute('type') === 'checkbox';
102
+ const onChange = () => {
103
+ const value = input.value;
104
+ let result;
105
+ if (is_checkbox) {
106
+ const list = getter() || [];
107
+ if (input.checked) {
108
+ if (!list.includes(value)) {
109
+ result = [...list, value];
110
+ }
111
+ else {
112
+ result = list;
113
+ }
114
+ }
115
+ else {
116
+ result = list.filter((v) => v !== value);
117
+ }
118
+ }
119
+ else {
120
+ result = input.value;
121
+ }
122
+ setter(result);
123
+ };
124
+ input.addEventListener('change', onChange);
125
+ effect(() => {
126
+ let value = getter();
127
+ if (is_checkbox) {
128
+ value = value || [];
129
+ input.checked = value.includes(input.value);
130
+ }
131
+ else {
132
+ input.checked = value === input.value;
133
+ }
134
+ });
135
+ return () => input.removeEventListener('change', onChange);
136
+ };
137
+ }
@@ -0,0 +1,15 @@
1
+ interface Component {
2
+ p: Component | null;
3
+ c: Map<Context<unknown>, unknown> | null;
4
+ }
5
+ export declare function getActiveComponent(): Component | null;
6
+ export declare function setActiveComponent(value: Component | null): void;
7
+ export declare class Context<T> {
8
+ _v: T;
9
+ constructor(value: T);
10
+ get(): T;
11
+ set(value: T): void;
12
+ }
13
+ export declare function createContext<T>(value: T): Context<T>;
14
+ export {};
15
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAIA,UAAU,SAAS;IAClB,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACpB,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC;AAID,wBAAgB,kBAAkB,IAAI,SAAS,GAAG,IAAI,CAErD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAEhE;AAED,qBAAa,OAAO,CAAC,CAAC;IACrB,EAAE,EAAE,CAAC,CAAC;gBACM,KAAK,EAAE,CAAC;IAGpB,GAAG,IAAI,CAAC;IAQR,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;CAOnB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAErD"}
@@ -0,0 +1,34 @@
1
+ const ctx = { current: null };
2
+ export function getActiveComponent() {
3
+ return ctx.current ?? globalThis.__vesk_ctx ?? null;
4
+ }
5
+ export function setActiveComponent(value) {
6
+ ctx.current = value;
7
+ }
8
+ export class Context {
9
+ _v;
10
+ constructor(value) {
11
+ this._v = value;
12
+ }
13
+ get() {
14
+ let current = getActiveComponent();
15
+ while (current) {
16
+ if (current.c?.has(this))
17
+ return current.c.get(this);
18
+ current = current.p;
19
+ }
20
+ return this._v;
21
+ }
22
+ set(value) {
23
+ const component = getActiveComponent();
24
+ if (component === null)
25
+ throw new Error('No active component found, cannot set context');
26
+ let map = component.c;
27
+ if (map === null)
28
+ map = component.c = new Map();
29
+ map.set(this, value);
30
+ }
31
+ }
32
+ export function createContext(value) {
33
+ return new Context(value);
34
+ }
@@ -0,0 +1,17 @@
1
+ interface ExperimentVariant {
2
+ name?: string;
3
+ weight?: number;
4
+ children?: unknown;
5
+ content?: unknown;
6
+ [k: string]: unknown;
7
+ }
8
+ interface ExperimentProps {
9
+ name: string;
10
+ variants?: ExperimentVariant[];
11
+ default?: unknown;
12
+ sticky?: boolean;
13
+ track?: boolean;
14
+ }
15
+ export declare function Experiment(props: ExperimentProps): unknown;
16
+ export {};
17
+ //# sourceMappingURL=experiment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"experiment.d.ts","sourceRoot":"","sources":["../src/experiment.ts"],"names":[],"mappings":"AAAA,UAAU,iBAAiB;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrB;AAED,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AA4CD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CA0C1D"}
@@ -0,0 +1,71 @@
1
+ function getUserId() {
2
+ if (typeof document === 'undefined')
3
+ return '';
4
+ let id = sessionStorage.getItem('vsk_exp_user');
5
+ if (!id) {
6
+ id = Math.random().toString(36).slice(2, 10);
7
+ sessionStorage.setItem('vsk_exp_user', id);
8
+ }
9
+ return id;
10
+ }
11
+ function selectVariant(variants, seed) {
12
+ let hash = 0;
13
+ for (let i = 0; i < seed.length; i++) {
14
+ const char = seed.charCodeAt(i);
15
+ hash = ((hash << 5) - hash) + char;
16
+ hash = hash & hash;
17
+ }
18
+ const r = (Math.abs(hash) % 10000) / 10000;
19
+ const totalWeight = variants.reduce((s, v) => s + (v.weight || 1), 0);
20
+ let cumulative = 0;
21
+ for (const v of variants) {
22
+ cumulative += (v.weight || 1) / totalWeight;
23
+ if (r <= cumulative)
24
+ return v;
25
+ }
26
+ return variants[variants.length - 1];
27
+ }
28
+ function isSSR() {
29
+ return typeof document === 'undefined';
30
+ }
31
+ function getCookie(name) {
32
+ if (isSSR())
33
+ return null;
34
+ const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
35
+ return match ? decodeURIComponent(match[2]) : null;
36
+ }
37
+ function setCookie(name, value, maxAge = 86400) {
38
+ if (isSSR())
39
+ return;
40
+ document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=${maxAge};SameSite=Lax`;
41
+ }
42
+ export function Experiment(props) {
43
+ const { name, variants = [], default: defaultContent = null, sticky = true, track = true, } = props;
44
+ const cookieName = `vsk_exp_${name}`;
45
+ let assignedVariant;
46
+ if (isSSR()) {
47
+ const seed = name;
48
+ assignedVariant = selectVariant(variants, seed);
49
+ return assignedVariant ? assignedVariant.children || assignedVariant.content || null : defaultContent;
50
+ }
51
+ const userId = getUserId();
52
+ const seed = name + userId;
53
+ if (sticky) {
54
+ const stored = getCookie(cookieName);
55
+ if (stored) {
56
+ assignedVariant = variants.find(v => v.name === stored);
57
+ }
58
+ }
59
+ if (!assignedVariant) {
60
+ assignedVariant = selectVariant(variants, seed);
61
+ if (sticky && assignedVariant) {
62
+ setCookie(cookieName, assignedVariant.name || 'default', 86400 * 30);
63
+ }
64
+ }
65
+ if (track && assignedVariant && typeof window !== 'undefined') {
66
+ const w = window;
67
+ const existing = w.__vsk_experiments || (w.__vsk_experiments = []);
68
+ existing.push({ experiment: name, variant: assignedVariant.name || 'default' });
69
+ }
70
+ return assignedVariant ? assignedVariant.children || assignedVariant.content || null : defaultContent;
71
+ }
package/dist/form.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { HydrateWalker } from '@vesk/runtime/src/hydrate';
2
+ export interface ValidationRule {
3
+ validate: (v: unknown) => boolean;
4
+ message: string;
5
+ }
6
+ interface FieldProps {
7
+ name: string;
8
+ label?: string;
9
+ rules?: ValidationRule[];
10
+ children?: string | Node;
11
+ errorClass?: string;
12
+ class?: string;
13
+ style?: string;
14
+ [k: string]: unknown;
15
+ }
16
+ interface FormProps {
17
+ children?: string | Node;
18
+ onSubmit?: (data: Record<string, unknown>, form: HTMLFormElement) => void | Promise<void>;
19
+ onError?: (err: unknown) => void;
20
+ onSuccess?: (res: Response) => void;
21
+ action?: string | Record<string, unknown>;
22
+ method?: string;
23
+ class?: string;
24
+ style?: string;
25
+ [k: string]: unknown;
26
+ }
27
+ export declare function required(msg?: string): ValidationRule;
28
+ export declare function email(msg?: string): ValidationRule;
29
+ export declare function minLength(n: number, msg?: string): ValidationRule;
30
+ export declare function maxLength(n: number, msg?: string): ValidationRule;
31
+ export declare function pattern(re: RegExp, msg?: string): ValidationRule;
32
+ export declare function custom(fn: (v: unknown) => boolean, msg?: string): ValidationRule;
33
+ export declare function Field(props: FieldProps, registry?: Map<string, unknown>, hydrate?: HydrateWalker): Node | string;
34
+ export declare function Form(props: FormProps, registry?: Map<string, unknown>, hydrate?: HydrateWalker): Node | string;
35
+ export {};
36
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrB;AAED,UAAU,SAAS;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrB;AAsCD,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAErD;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAElD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAEjE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAEjE;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAEhE;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAEhF;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAoDhH;AA+GD,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CA8C9G"}