char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport FormData and multipart extension to typescriptc013baf

main
5.8 KiB176 linesraw
1import { markUncloneable, registerUncloneable } from "ext:web/serialization.js";
2import {
3  Array,
4  ArrayPrototypePush as push,
5  IteratorPrototype,
6  ObjectCreate,
7  ObjectDefineProperty,
8  ObjectSetPrototypeOf,
9  ReflectApply as apply,
10  SymbolToStringTag,
11  TypeError,
12  WeakMap,
13  WeakMapPrototypeGet as get,
14  WeakMapPrototypeSet as set,
15} from "ext:core/primordials";
16import { array, required, state, usv } from "ext:web/idl.js";
17import { blobs } from "ext:blob/blob.js";
18import { fileEntry } from "ext:blob/file.js";
19
20import type { File } from "ext:blob/file.js";
21
22type FormValue = string | File;
23interface FormEntry {
24  name: string;
25  value: FormValue;
26}
27interface FormState {
28  list: FormEntry[];
29}
30type IteratorValues = { 0: [string, FormValue]; 1: string; 2: FormValue };
31
32const forms = new WeakMap<object, FormState>(), arrayPrototype = Array.prototype;
33registerUncloneable(forms);
34function entry(rawName: unknown, value: unknown, rawFilename: unknown, count: number): FormEntry {
35  const name = usv(rawName);
36  const blob = get(blobs, value);
37  if (count >= 3 && !blob) {
38    throw new TypeError("Expected a Blob when filename is supplied");
39  }
40  if (!blob) return { name, value: usv(value) };
41  const filename = rawFilename === undefined ? undefined : usv(rawFilename);
42  return { name, value: fileEntry(value, filename) };
43}
44// Iterator inheritance is installed below, without introducing a super call.
45interface FormDataIterator<K extends keyof IteratorValues> extends IterableIterator<IteratorValues[K], undefined, unknown> {}
46class FormDataIterator<K extends keyof IteratorValues> {
47  #data: FormState;
48  #index = 0;
49  #kind: K;
50  constructor(data: FormState, kind: K) {
51    markUncloneable(this);
52    this.#data = data;
53    this.#kind = kind;
54  }
55  next(): IteratorResult<IteratorValues[K], undefined> {
56    const data = this.#data;
57    if (this.#index >= data.list.length) {
58      return { value: undefined, done: true };
59    }
60    const e = data.list[this.#index++];
61    return {
62      value: (this.#kind === 0
63        ? [e.name, e.value]
64        : this.#kind === 1
65        ? e.name
66        : e.value) as IteratorValues[K],
67      done: false,
68    };
69  }
70}
71delete (FormDataIterator.prototype as { constructor?: Function }).constructor;
72ObjectSetPrototypeOf(FormDataIterator.prototype, IteratorPrototype);
73ObjectDefineProperty(FormDataIterator.prototype, "next", { enumerable: true });
74ObjectDefineProperty(FormDataIterator.prototype, SymbolToStringTag, {
75  value: "FormData Iterator",
76  configurable: true,
77});
78interface FormData {
79  [Symbol.iterator](): FormDataIterator<0>;
80}
81class FormData {
82  constructor(form: unknown = undefined, submitter: unknown = null) {
83    if (form !== undefined || (submitter !== undefined && submitter !== null)) {
84      throw new TypeError("not yet implemented: DOM-backed FormData");
85    }
86    set(forms, this, { list: array<FormEntry>() });
87  }
88  append(name: unknown, value: unknown, filename: unknown = undefined): void {
89    const data = state(forms, this);
90    required(arguments.length, 2);
91    push(data.list, entry(name, value, filename, arguments.length));
92  }
93  set(name: unknown, value: unknown, filename: unknown = undefined): void {
94    const data = state(forms, this);
95    required(arguments.length, 2);
96    const e = entry(name, value, filename, arguments.length);
97    let found = false, write = 0;
98    for (let i = 0; i < data.list.length; i++) {
99      const current = data.list[i];
100      if (current.name !== e.name) data.list[write++] = current;
101      else if (!found) {
102        data.list[write++] = e;
103        found = true;
104      }
105    }
106    data.list.length = write;
107    if (!found) push(data.list, e);
108  }
109  delete(rawName: unknown): void {
110    const data = state(forms, this);
111    required(arguments.length, 1);
112    const name = usv(rawName);
113    let write = 0;
114    for (let i = 0; i < data.list.length; i++) {
115      const current = data.list[i];
116      if (current.name !== name) data.list[write++] = current;
117    }
118    data.list.length = write;
119  }
120  get(rawName: unknown): FormValue | null {
121    const data = state(forms, this);
122    required(arguments.length, 1);
123    const name = usv(rawName);
124    for (let i = 0; i < data.list.length; i++) {
125      if (data.list[i].name === name) return data.list[i].value;
126    }
127    return null;
128  }
129  getAll(rawName: unknown): FormValue[] {
130    const data = state(forms, this);
131    required(arguments.length, 1);
132    const name = usv(rawName);
133    const result = array<FormValue>();
134    for (let i = 0; i < data.list.length; i++) {
135      if (data.list[i].name === name) push(result, data.list[i].value);
136    }
137    ObjectSetPrototypeOf(result, arrayPrototype);
138    return result;
139  }
140  has(rawName: unknown): boolean {
141    const data = state(forms, this);
142    required(arguments.length, 1);
143    const name = usv(rawName);
144    for (let i = 0; i < data.list.length; i++) {
145      if (data.list[i].name === name) return true;
146    }
147    return false;
148  }
149  forEach(callback: (value: FormValue, key: string, parent: FormData) => void, thisArg: unknown = undefined): void {
150    const data = state(forms, this);
151    required(arguments.length, 1);
152    if (typeof callback !== "function") {
153      throw new TypeError("Expected a callback");
154    }
155    for (let i = 0; i < data.list.length; i++) {
156      const e = data.list[i];
157      apply(callback, thisArg, [e.value, e.name, this]);
158    }
159  }
160  entries(): FormDataIterator<0> {
161    return new FormDataIterator(state(forms, this), 0);
162  }
163  keys(): FormDataIterator<1> {
164    return new FormDataIterator(state(forms, this), 1);
165  }
166  values(): FormDataIterator<2> {
167    return new FormDataIterator(state(forms, this), 2);
168  }
169}
170function fromEntries(list: FormEntry[]): FormData {
171  const result: FormData = ObjectCreate(FormData.prototype);
172  set(forms, result, { list });
173  return result;
174}
175export { FormData, forms, fromEntries };
176export type { FormEntry };