@stainless-api/playgrounds 0.0.1-beta.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 (45) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +23 -0
  3. package/eslint.config.js +2 -0
  4. package/package.json +69 -0
  5. package/src/Logs.tsx +216 -0
  6. package/src/Panel.tsx +21 -0
  7. package/src/PlaygroundPanelWrapper.tsx +5 -0
  8. package/src/build-py-types.ts +152 -0
  9. package/src/build-ts-types.ts +70 -0
  10. package/src/build.ts +97 -0
  11. package/src/codemirror/comlink.ts +698 -0
  12. package/src/codemirror/curl/curlconverter.vendor.js +7959 -0
  13. package/src/codemirror/curl.ts +108 -0
  14. package/src/codemirror/deps.ts +12 -0
  15. package/src/codemirror/fix-lsp-markdown.ts +50 -0
  16. package/src/codemirror/lsp.ts +87 -0
  17. package/src/codemirror/python/anser.ts +398 -0
  18. package/src/codemirror/python/pyodide.ts +180 -0
  19. package/src/codemirror/python.ts +160 -0
  20. package/src/codemirror/react.tsx +615 -0
  21. package/src/codemirror/sanitize-html.ts +12 -0
  22. package/src/codemirror/shiki.ts +65 -0
  23. package/src/codemirror/typescript/cdn-typescript.d.ts +1 -0
  24. package/src/codemirror/typescript/cdn-typescript.js +1 -0
  25. package/src/codemirror/typescript/console.ts +590 -0
  26. package/src/codemirror/typescript/get-signature.ts +94 -0
  27. package/src/codemirror/typescript/prettier-plugin-external-typescript.vendor.js +4968 -0
  28. package/src/codemirror/typescript/runner.ts +396 -0
  29. package/src/codemirror/typescript/special-info.ts +171 -0
  30. package/src/codemirror/typescript/worker.ts +292 -0
  31. package/src/codemirror/typescript.tsx +198 -0
  32. package/src/create.tsx +44 -0
  33. package/src/icon.tsx +21 -0
  34. package/src/index.ts +6 -0
  35. package/src/logs-context.ts +5 -0
  36. package/src/playground.css +359 -0
  37. package/src/sandbox-worker/in-frame.js +179 -0
  38. package/src/sandbox-worker/index.ts +202 -0
  39. package/src/use-storage.ts +54 -0
  40. package/src/util.ts +29 -0
  41. package/src/virtual-module.d.ts +45 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/test/get-signature.test.ts +73 -0
  44. package/test/use-storage.test.ts +60 -0
  45. package/tsconfig.json +11 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * @module
3
+ * This worker runs user code and handles environment setup, processes console logs,
4
+ * and implements the structured object logging api so users can expand and collapse
5
+ * logged objects.
6
+ */
7
+ import { expose } from '../comlink.ts';
8
+ import { createConsole } from './console.ts';
9
+ import process from 'unenv/runtime/node/process/index';
10
+ import { Entries, isProxy, specialInfo } from './special-info.ts';
11
+ import type { Logger, Part } from '../../Logs.tsx';
12
+ import { getSignature } from './get-signature.ts';
13
+
14
+ // add the process polyfill to the global
15
+ globalThis.process = process;
16
+ // unenv uses a weird proxy thing for process.env, just use an object instead.
17
+ globalThis.process.env = {};
18
+
19
+ let hideSpecial = false;
20
+
21
+ // WebIDL types like Request/Response/URL/etc. use getters and setters instead of value
22
+ // properties, but we don't want to trigger user side effects when logging.
23
+ // To fix this, keep a set of getters that are safe to call.
24
+ const safeGetters = new Set(
25
+ Reflect.ownKeys(globalThis)
26
+ .map((e) => {
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ return (globalThis as any)[e].prototype;
30
+ } catch {
31
+ // ignore errors
32
+ }
33
+ })
34
+ .filter((e) => e && e !== Function.prototype && e !== Object.prototype)
35
+ .flatMap((e) => Object.values(Object.getOwnPropertyDescriptors(e)))
36
+ .flatMap((e) => e?.get ?? []),
37
+ );
38
+
39
+ function* prototypeChain(
40
+ obj: object | ((..._: never[]) => unknown),
41
+ ): Generator<object | ((..._: never[]) => unknown)> {
42
+ while (obj) {
43
+ yield obj;
44
+ obj = Object.getPrototypeOf(obj);
45
+ }
46
+ }
47
+
48
+ type Properties = {
49
+ key: unknown;
50
+ configurable: boolean;
51
+ enumerable: boolean;
52
+ value: unknown;
53
+ get: unknown;
54
+ set: unknown;
55
+ }[];
56
+
57
+ async function properties(value: unknown): Promise<{
58
+ properties: Properties;
59
+ specials: { name: string; value: unknown }[];
60
+ }> {
61
+ const specials = hideSpecial
62
+ ? []
63
+ : (await specialInfo(value)).map(({ name, value }) => ({
64
+ name,
65
+ value: value,
66
+ }));
67
+ const isObject = (typeof value === 'object' || typeof value === 'function') && value !== null;
68
+ if (isObject && isProxy(value)) {
69
+ return {
70
+ properties: [],
71
+ specials,
72
+ };
73
+ }
74
+ return {
75
+ properties: isObject
76
+ ? [
77
+ ...Reflect.ownKeys(value)
78
+ .map((key): Properties[number] | undefined => {
79
+ let desc: PropertyDescriptor | undefined;
80
+ try {
81
+ desc = Object.getOwnPropertyDescriptor(value, key);
82
+ } catch {
83
+ try {
84
+ desc = {
85
+ configurable: true,
86
+ enumerable: true,
87
+ value: Reflect.get(value, key, value),
88
+ };
89
+ } catch {
90
+ // ignore error
91
+ }
92
+ }
93
+ if (desc) {
94
+ return {
95
+ key: key,
96
+ configurable: !!desc.configurable,
97
+ enumerable: !!desc.enumerable,
98
+ value: desc.value,
99
+ get: desc.get,
100
+ set: desc.set,
101
+ };
102
+ }
103
+ })
104
+ .filter((e): e is Properties[number] => !!e),
105
+ ...[...prototypeChain(value)].flatMap((e) =>
106
+ Object.entries(Object.getOwnPropertyDescriptors(e)).flatMap(([k, v]) => {
107
+ try {
108
+ if (!safeGetters.has(v.get!)) return [];
109
+ const result = (value as unknown as Record<PropertyKey, unknown>)[k];
110
+ if (result instanceof Promise) {
111
+ result.catch(() => {});
112
+ }
113
+ return {
114
+ key: k,
115
+ configurable: !!v.configurable,
116
+ enumerable: !!v.enumerable,
117
+ value: result,
118
+ get: undefined,
119
+ set: undefined,
120
+ };
121
+ } catch {
122
+ return [];
123
+ }
124
+ }),
125
+ ),
126
+ ]
127
+ : [],
128
+ specials,
129
+ };
130
+ }
131
+ async function inspect(value: unknown, color: boolean): Promise<Part> {
132
+ const literal = color ? 'color:var(--stl-color-green-foreground)' : '';
133
+ const object = color ? 'color:var(--stl-color-blue-foreground)' : '';
134
+ const dim = color ? 'color:var(--stl-color-foreground-reduced)' : '';
135
+ if (Entries.isEntries(value)) {
136
+ return {
137
+ value: [
138
+ {
139
+ value: `Entries`,
140
+ css: object,
141
+ },
142
+ {
143
+ value: ' { … }',
144
+ css: dim,
145
+ },
146
+ ],
147
+ css: '',
148
+ expandHandle: Handles.create(value),
149
+ id: id(value),
150
+ };
151
+ }
152
+ if (value === null || value === undefined) {
153
+ return {
154
+ value: value + '',
155
+ css: dim,
156
+ };
157
+ }
158
+ if (Object.is(value, -0)) return { value: '-0', css: literal };
159
+ if (typeof value === 'string') {
160
+ return { value: JSON.stringify(value), css: literal };
161
+ }
162
+ if (typeof value === 'bigint') {
163
+ return { value: value + 'n', css: literal };
164
+ }
165
+ switch (typeof value) {
166
+ case 'number':
167
+ case 'boolean':
168
+ return { value: value + '', css: literal };
169
+ case 'symbol':
170
+ return { value: `Symbol(${value.description})`, css: literal };
171
+ case 'function':
172
+ return {
173
+ value: getSignature(functionToString(value)),
174
+ css: object,
175
+ expandHandle: Handles.create(value),
176
+ id: id(value),
177
+ lowPriority: true,
178
+ };
179
+ default: {
180
+ const className = isProxy(value) ? 'Proxy' : objectToString(value).slice('[Object '.length, -1);
181
+ const err =
182
+ (void 0,
183
+ // @ts-expect-error not in our lib yet
184
+ Error.isError
185
+ ? // @ts-expect-error not in our lib yet
186
+ Error.isError(value)
187
+ : Object.prototype.toString.call(value) === '[object Error]');
188
+ return {
189
+ value: err
190
+ ? [
191
+ {
192
+ value: `${className}`,
193
+ css: object,
194
+ },
195
+ {
196
+ value: ': ',
197
+ css: dim,
198
+ },
199
+ await inspect((value as Error).message, color),
200
+ ]
201
+ : [
202
+ {
203
+ value: `${className}`,
204
+ css: object,
205
+ },
206
+ {
207
+ value: Array.isArray(value) ? ' [ … ]' : ' { … }',
208
+ css: dim,
209
+ },
210
+ ],
211
+ css: '',
212
+ expandHandle: Handles.create(value),
213
+ id: id(value),
214
+ lowPriority: err,
215
+ };
216
+ }
217
+ }
218
+ }
219
+ Object.defineProperty(globalThis, 'console', {
220
+ configurable: true,
221
+ writable: true,
222
+ value: createConsole(async (type, params?) => {
223
+ if (type === 'clear') {
224
+ logger!({ type });
225
+ } else {
226
+ logger!({
227
+ type,
228
+ parts: await Promise.all(
229
+ params.map(async (e) =>
230
+ typeof e === 'string'
231
+ ? { css: '', value: e }
232
+ : e.type === 'string'
233
+ ? {
234
+ css: e.css,
235
+ value: e.value + '',
236
+ }
237
+ : inspect(e.value, true),
238
+ ),
239
+ ),
240
+ });
241
+ }
242
+ }),
243
+ });
244
+ export type Handle = string & { readonly _handle: unique symbol };
245
+ const ids = new WeakMap<WeakKey, string>();
246
+ const idsMap = new Map<unknown, string>();
247
+ const id = (obj: unknown) => {
248
+ const cached = typeof obj === 'object' && obj !== null ? ids.get(obj) : idsMap.get(obj);
249
+ if (cached) return cached;
250
+ const id = crypto.randomUUID();
251
+ if (typeof obj === 'object' && obj !== null) {
252
+ ids.set(obj, id);
253
+ } else {
254
+ idsMap.set(obj, id);
255
+ }
256
+ return id;
257
+ };
258
+ class Handles {
259
+ static #handles: Record<string, unknown> = {};
260
+ static #freed = Symbol();
261
+ static create(value: unknown): Handle {
262
+ const handle = crypto.randomUUID() as Handle;
263
+ this.#handles[handle] = value;
264
+ return handle;
265
+ }
266
+ static deref(handle: Handle): unknown {
267
+ if (!Object.hasOwn(this.#handles, handle)) {
268
+ throw new TypeError('handle does not exist');
269
+ }
270
+ const value = this.#handles[handle];
271
+ if (value === this.#freed) throw new TypeError('handle was freed');
272
+ return value;
273
+ }
274
+ static free(handle: Handle) {
275
+ this.deref(handle); // ensure it exists
276
+ this.#handles[handle] = this.#freed;
277
+ }
278
+ }
279
+ const functionToString = Function.prototype.call.bind(Function.prototype.toString);
280
+ const objectToString = Function.prototype.call.bind(Object.prototype.toString);
281
+ addEventListener('unhandledrejection', async (e) => {
282
+ e.preventDefault();
283
+ logger({ type: 'error', parts: ['Uncaught (in promise) ', await inspect(e.reason, true)] });
284
+ });
285
+
286
+ let logger: Logger;
287
+ const api = {
288
+ setLogger(logger_: Logger) {
289
+ logger = logger_;
290
+ },
291
+ async expandLogHandle(handle: string): Promise<Part[]> {
292
+ let value;
293
+ try {
294
+ value = Handles.deref(handle as Handle);
295
+ } catch {
296
+ return [];
297
+ }
298
+ if (Entries.isEntries(value)) {
299
+ return Promise.all(
300
+ value.value.map(async (e, i) => ({
301
+ css: '',
302
+ value: await Promise.all([
303
+ {
304
+ value: i + '',
305
+ css: 'color:var(--stl-color-foreground)',
306
+ },
307
+ { value: `: `, css: 'color:var(--stl-color-foreground-reduced)' },
308
+ ...(value.pairs && Array.isArray(e)
309
+ ? [
310
+ inspect(e[0], true),
311
+ {
312
+ value: ` → `,
313
+ css: 'color:var(--stl-color-foreground-reduced)',
314
+ },
315
+ inspect(e[1], true),
316
+ ]
317
+ : [inspect(e, true)]),
318
+ ]),
319
+ })),
320
+ );
321
+ }
322
+ const props = await properties(value);
323
+ const formatted: Promise<Part>[] = [
324
+ ...props.properties.map(async ({ get, set, key, value }): Promise<Part> => {
325
+ return {
326
+ value: await Promise.all([
327
+ typeof key === 'string' &&
328
+ (parseInt(key, 10) + '' === key ||
329
+ /(?=[$_\p{ID_Start}\\])[$_\u200C\u200D\p{ID_Continue}]+/u.test(key))
330
+ ? {
331
+ value: key + '',
332
+ css: 'color:var(--stl-color-foreground)',
333
+ }
334
+ : {
335
+ ...(await inspect(key, false)),
336
+ css: 'color:var(--stl-color-foreground)',
337
+ },
338
+ { value: `: `, css: 'color:var(--stl-color-foreground-reduced)' },
339
+ typeof get === 'function'
340
+ ? typeof set === 'function'
341
+ ? {
342
+ value: '[Getter/Setter]',
343
+ css: 'color:var(--stl-color-foreground-reduced)',
344
+ }
345
+ : {
346
+ value: '[Getter]',
347
+ css: 'color:var(--stl-color-foreground-reduced)',
348
+ }
349
+ : typeof set === 'function'
350
+ ? {
351
+ value: '[Setter]',
352
+ css: 'color:var(--stl-color-foreground-reduced)',
353
+ }
354
+ : inspect(value, true),
355
+ ]),
356
+ css: '',
357
+ };
358
+ }),
359
+ ...props.specials.map(async (e): Promise<Part> => {
360
+ return {
361
+ value: await Promise.all([
362
+ {
363
+ value: `<${e.name}>: `,
364
+ css: 'color:var(--stl-color-foreground-reduced)',
365
+ },
366
+ { ...(await inspect(e.value, true)), lowPriority: true },
367
+ ]),
368
+ css: '',
369
+ };
370
+ }),
371
+ ];
372
+ return await Promise.all(formatted);
373
+ },
374
+ freeLogHandle(handle: string) {
375
+ try {
376
+ Handles.free(handle as Handle);
377
+ } catch {
378
+ // ignore error
379
+ }
380
+ },
381
+ setEnv(vars: Record<string, string>) {
382
+ Object.assign(process.env, vars);
383
+ },
384
+ async run(code: string, hideSpecial_ = false): Promise<void> {
385
+ try {
386
+ hideSpecial = hideSpecial_;
387
+ await import(
388
+ /* @vite-ignore */ 'data:text/javascript;charset=utf-8,' + encodeURIComponent(code) + '#' + Date.now()
389
+ );
390
+ } catch (error) {
391
+ logger({ type: 'error', parts: ['Uncaught (in promise) ', await inspect(error, true)] });
392
+ }
393
+ },
394
+ };
395
+ export type RunnerAPI = typeof api;
396
+ expose(api);
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @module
3
+ * Exposes synthetic properties that are useful when logging objects.
4
+ * Supports
5
+ * - Boxed primitive values
6
+ * - Promise state
7
+ * - Map/Set/MapLike/SetLike entries
8
+ * - Proxy targets and handlers
9
+ */
10
+
11
+ const specials: ((o: unknown, infos: { name: string; value: unknown }[]) => undefined)[] = [];
12
+ for (const e of [Boolean, Date, Number, String, BigInt, Symbol]) {
13
+ const fn = Function.prototype.call.bind(e.prototype.valueOf);
14
+ specials.push((v, infos) => {
15
+ try {
16
+ infos.push({ name: 'primitive value', value: fn(v) });
17
+ } catch {
18
+ // ignore error (brand check failed, try next primitive)
19
+ }
20
+ });
21
+ }
22
+ const promisePrototypeThen = Function.prototype.call.bind(Promise.prototype.then);
23
+ const promiseResolve = Promise.resolve.bind(Promise);
24
+ specials.push((v, infos) => {
25
+ try {
26
+ let state: 'fulfilled' | 'rejected' | 'pending';
27
+ let value: unknown;
28
+ let reason: unknown;
29
+ promisePrototypeThen(
30
+ v,
31
+ (val: unknown) => {
32
+ if (state) return;
33
+ value = val;
34
+ state = 'fulfilled';
35
+ },
36
+ (err: unknown) => {
37
+ if (state) return;
38
+ reason = err;
39
+ state = 'rejected';
40
+ },
41
+ );
42
+ promisePrototypeThen(promiseResolve(), () => {
43
+ state ??= 'pending';
44
+ infos.push({ name: 'state', value: state });
45
+ if (state === 'rejected') {
46
+ infos.push({ name: 'reason', value: reason });
47
+ }
48
+ if (state === 'fulfilled') {
49
+ infos.push({ name: 'value', value: value });
50
+ }
51
+ });
52
+ } catch {
53
+ // ignore error (not a promise)
54
+ }
55
+ });
56
+ export class Entries {
57
+ #brand: undefined;
58
+ pairs: boolean;
59
+ value: unknown[];
60
+ constructor(pairs: boolean, value: unknown[]) {
61
+ this.pairs = pairs;
62
+ this.value = value;
63
+ this.#brand = undefined;
64
+ }
65
+ static isEntries(obj: unknown): obj is Entries {
66
+ return typeof obj === 'object' && obj !== null && #brand in obj;
67
+ }
68
+ }
69
+ for (const name of Object.getOwnPropertyNames(globalThis)) {
70
+ let entries, proto, pairs;
71
+ try {
72
+ proto = Object.getOwnPropertyDescriptor(globalThis, name)!.value?.prototype;
73
+ entries = proto?.forEach && (proto?.keys === proto?.values ? proto?.values : proto?.entries);
74
+ pairs = proto?.keys !== proto?.values;
75
+ } catch {
76
+ continue;
77
+ }
78
+ if (
79
+ typeof entries === 'function' &&
80
+ entries !== Array.prototype.entries &&
81
+ entries !== Uint8Array.prototype.entries
82
+ ) {
83
+ try {
84
+ const result = entries();
85
+ if ('then' in result) {
86
+ (result as Promise<unknown>).then(
87
+ () => {},
88
+ () => {},
89
+ );
90
+ }
91
+ } catch {
92
+ const fn = Function.prototype.call.bind(entries);
93
+ specials.push((v, infos) => {
94
+ try {
95
+ infos.push({
96
+ name: 'entries',
97
+ value: new Entries(pairs, [...fn(v)]),
98
+ });
99
+ } catch {
100
+ // ignore error (brand check failed)
101
+ }
102
+ });
103
+ }
104
+ }
105
+ }
106
+ const proxyMap = new WeakMap<object, { target: object; handler: ProxyHandler<object> }>();
107
+ const realProxy = Proxy;
108
+ const realRevocable = Proxy.revocable;
109
+ realProxy.revocable = new realProxy(realRevocable, {
110
+ apply(_, { 0: target, 1: handler }) {
111
+ const result = realRevocable(target, handler);
112
+ proxyMap.set(result.proxy, { target, handler });
113
+ return result;
114
+ },
115
+ });
116
+ globalThis.Proxy = new realProxy(realProxy, {
117
+ construct(_, { 0: target, 1: handler }) {
118
+ const proxy = new realProxy(target, handler);
119
+ proxyMap.set(proxy, { target, handler });
120
+ return proxy;
121
+ },
122
+ });
123
+ specials.push((o, infos) => {
124
+ const proxyInfo = proxyMap.get(o as object);
125
+ if (proxyInfo) {
126
+ infos.push({ name: 'target', value: proxyInfo.target });
127
+ infos.push({ name: 'handler', value: proxyInfo.handler });
128
+ }
129
+ });
130
+ specials.push((o, infos) => {
131
+ Promise.resolve().then(() => {
132
+ if (!isProxy(o) && o !== null && o !== undefined) {
133
+ infos.push({ name: 'prototype', value: Object.getPrototypeOf(o) });
134
+ }
135
+ });
136
+ });
137
+ export const isProxy: (o: unknown) => boolean = proxyMap.has.bind(proxyMap) as (o: unknown) => boolean;
138
+ Object.freeze(specials);
139
+ export const specialInfo = (
140
+ v: unknown,
141
+ ): Promise<
142
+ {
143
+ name: string;
144
+ value: unknown;
145
+ }[]
146
+ > => {
147
+ const infos: {
148
+ name: string;
149
+ value: unknown;
150
+ }[] = [];
151
+ for (let i = 0; i < specials.length; i++) {
152
+ specials[i]!(v, infos);
153
+ }
154
+ return Promise.resolve().then(() => infos);
155
+ };
156
+ /*
157
+ console.log(await specialInfo(Promise.resolve(1)));
158
+ console.log(await specialInfo(Promise.reject(1)));
159
+ console.log(await specialInfo(new Promise(() => {})));
160
+ console.log(await specialInfo(new Map([[1, 2], [3, 4]])));
161
+ console.log(await specialInfo(new Set([1, 2, 3, 4])));
162
+ console.log(await specialInfo(new FormData()));
163
+ console.log(await specialInfo(new Headers({ a: "b" })));
164
+ console.log(await specialInfo(new URLSearchParams("a=1")));
165
+ console.log(await specialInfo(new Boolean()));
166
+ console.log(await specialInfo(new Number()));
167
+ console.log(await specialInfo(new String()));
168
+ console.log(await specialInfo(Object(1n)));
169
+ console.log(await specialInfo(Object(Symbol.iterator)));
170
+ console.log(await specialInfo(new Proxy({}, {})));
171
+ */