toiljs 0.0.10 → 0.0.12

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 (128) hide show
  1. package/README.md +315 -1
  2. package/assets/logo.svg +37 -0
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/configure.js +10 -4
  5. package/build/cli/create.js +60 -32
  6. package/build/cli/diagnostics.d.ts +55 -0
  7. package/build/cli/diagnostics.js +333 -0
  8. package/build/cli/doctor.d.ts +6 -0
  9. package/build/cli/doctor.js +249 -0
  10. package/build/cli/index.js +26 -0
  11. package/build/cli/proc.d.ts +5 -0
  12. package/build/cli/proc.js +20 -0
  13. package/build/cli/ui.d.ts +1 -0
  14. package/build/cli/ui.js +1 -0
  15. package/build/cli/update.d.ts +7 -0
  16. package/build/cli/update.js +117 -0
  17. package/build/cli/updates.d.ts +10 -0
  18. package/build/cli/updates.js +45 -0
  19. package/build/client/.tsbuildinfo +1 -1
  20. package/build/client/dev/error-overlay.js +1 -1
  21. package/build/client/head/metadata.js +3 -1
  22. package/build/client/index.d.ts +5 -1
  23. package/build/client/index.js +2 -0
  24. package/build/client/navigation/navigation.js +1 -1
  25. package/build/client/routing/Router.js +2 -2
  26. package/build/client/search/search.d.ts +26 -0
  27. package/build/client/search/search.js +101 -0
  28. package/build/client/search/use-page-search.d.ts +8 -0
  29. package/build/client/search/use-page-search.js +21 -0
  30. package/build/compiler/.tsbuildinfo +1 -1
  31. package/build/compiler/generate.js +35 -26
  32. package/build/compiler/index.d.ts +2 -0
  33. package/build/compiler/index.js +1 -0
  34. package/build/compiler/pages.d.ts +8 -0
  35. package/build/compiler/pages.js +37 -0
  36. package/build/compiler/plugin.js +3 -1
  37. package/build/compiler/prerender.d.ts +1 -0
  38. package/build/compiler/prerender.js +11 -5
  39. package/build/compiler/seo.js +10 -3
  40. package/build/compiler/vite.js +7 -0
  41. package/build/io/.tsbuildinfo +1 -1
  42. package/examples/basic/client/components/Header.tsx +43 -38
  43. package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
  44. package/examples/basic/client/layout.tsx +4 -1
  45. package/examples/basic/client/public/index.html +18 -16
  46. package/examples/basic/client/routes/(legal)/privacy.tsx +18 -0
  47. package/examples/basic/client/routes/(legal)/terms.tsx +15 -0
  48. package/examples/basic/client/routes/about.tsx +21 -19
  49. package/examples/basic/client/routes/blog/[id].tsx +26 -12
  50. package/examples/basic/client/routes/features/actions.tsx +67 -0
  51. package/examples/basic/client/routes/features/error/error.tsx +16 -0
  52. package/examples/basic/client/routes/features/error/index.tsx +27 -0
  53. package/examples/basic/client/routes/features/head.tsx +38 -0
  54. package/examples/basic/client/routes/features/index.tsx +83 -0
  55. package/examples/basic/client/routes/features/realtime.tsx +34 -0
  56. package/examples/basic/client/routes/features/script.tsx +31 -0
  57. package/examples/basic/client/routes/features/seo.tsx +39 -0
  58. package/examples/basic/client/routes/features/template/b.tsx +14 -0
  59. package/examples/basic/client/routes/features/template/index.tsx +20 -0
  60. package/examples/basic/client/routes/features/template/template.tsx +16 -0
  61. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -0
  62. package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -0
  63. package/examples/basic/client/routes/gallery/index.tsx +42 -0
  64. package/examples/basic/client/routes/gallery/layout.tsx +13 -0
  65. package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -0
  66. package/examples/basic/client/routes/get-started.tsx +157 -84
  67. package/examples/basic/client/routes/index.tsx +137 -87
  68. package/examples/basic/client/routes/loader-demo/index.tsx +59 -50
  69. package/examples/basic/client/routes/search.tsx +61 -0
  70. package/examples/basic/client/routes/test.tsx +7 -8
  71. package/examples/basic/client/styles/main.css +624 -552
  72. package/examples/basic/client/toil.tsx +2 -4
  73. package/package.json +3 -2
  74. package/presets/eslint.js +10 -3
  75. package/src/cli/configure.ts +363 -353
  76. package/src/cli/create.ts +563 -530
  77. package/src/cli/diagnostics.ts +421 -0
  78. package/src/cli/doctor.ts +318 -0
  79. package/src/cli/features.ts +166 -160
  80. package/src/cli/index.ts +242 -211
  81. package/src/cli/proc.ts +30 -0
  82. package/src/cli/ui.ts +111 -103
  83. package/src/cli/update.ts +150 -0
  84. package/src/cli/updates.ts +69 -0
  85. package/src/client/components/Image.tsx +91 -89
  86. package/src/client/dev/error-overlay.tsx +193 -197
  87. package/src/client/head/metadata.ts +94 -92
  88. package/src/client/index.ts +79 -64
  89. package/src/client/navigation/Link.tsx +94 -100
  90. package/src/client/navigation/navigation.ts +215 -218
  91. package/src/client/routing/Router.tsx +210 -193
  92. package/src/client/routing/hooks.ts +110 -114
  93. package/src/client/routing/lazy.ts +77 -81
  94. package/src/client/search/search.ts +189 -0
  95. package/src/client/search/use-page-search.ts +73 -0
  96. package/src/compiler/config.ts +173 -171
  97. package/src/compiler/fonts.ts +89 -87
  98. package/src/compiler/generate.ts +378 -364
  99. package/src/compiler/image-report.ts +88 -85
  100. package/src/compiler/index.ts +2 -0
  101. package/src/compiler/pages.ts +70 -0
  102. package/src/compiler/plugin.ts +51 -47
  103. package/src/compiler/prerender.ts +152 -130
  104. package/src/compiler/routes.ts +132 -131
  105. package/src/compiler/seo.ts +381 -356
  106. package/src/compiler/vite.ts +155 -130
  107. package/src/io/FastSet.ts +99 -96
  108. package/test/configure.test.ts +94 -90
  109. package/test/doctor.test.ts +140 -0
  110. package/test/dom/Image.test.tsx +73 -46
  111. package/test/dom/Script.test.tsx +48 -45
  112. package/test/dom/action.test.tsx +146 -129
  113. package/test/dom/error-overlay.test.tsx +44 -44
  114. package/test/dom/loader.test.tsx +2 -2
  115. package/test/dom/revalidate.test.tsx +1 -1
  116. package/test/dom/route-head.test.tsx +35 -2
  117. package/test/dom/slot.test.tsx +131 -109
  118. package/test/dom/view-transitions.test.tsx +53 -51
  119. package/test/features.test.ts +149 -142
  120. package/test/fonts.test.ts +28 -26
  121. package/test/head.test.ts +45 -35
  122. package/test/metadata.test.ts +42 -41
  123. package/test/pages.test.ts +105 -0
  124. package/test/prerender.test.ts +54 -46
  125. package/test/search.test.ts +114 -0
  126. package/test/seo.test.ts +164 -142
  127. package/test/slot-layouts.test.ts +69 -0
  128. package/test/update.test.ts +44 -0
@@ -1,130 +1,155 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
3
- import { pathToFileURL } from 'node:url';
4
-
5
- import react from '@vitejs/plugin-react';
6
- import { imagetools } from 'vite-imagetools';
7
- import { nodePolyfills } from 'vite-plugin-node-polyfills';
8
- import { mergeConfig, type InlineConfig, type PluginOption } from 'vite';
9
-
10
- import { type ResolvedToilConfig } from './config.js';
11
- import { fontPreloadPlugin } from './fonts.js';
12
- import { imageReportPlugin } from './image-report.js';
13
- import { toilPlugin } from './plugin.js';
14
- import { prerenderPlugin } from './prerender.js';
15
-
16
- /** Image extensions routed to `images/` in the build output. */
17
- const IMAGE_EXT = /^(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i;
18
- /** Font extensions routed to `fonts/`. */
19
- const FONT_EXT = /^(woff|woff2|eot|ttf|otf)$/i;
20
-
21
- /** Routes a built asset to a typed sub-folder (`images/`, `fonts/`, `css/`, else `assets/`). */
22
- function assetFileName(name: string): string {
23
- const ext = name.split('.').pop() ?? '';
24
- if (IMAGE_EXT.test(ext)) return 'images/[name][extname]';
25
- if (FONT_EXT.test(ext)) return 'fonts/[name][extname]';
26
- if (/^css$/i.test(ext)) return 'css/[name][extname]';
27
- return 'assets/[name][extname]';
28
- }
29
-
30
- /**
31
- * Loads the Tailwind v4 Vite plugin if the project has `@tailwindcss/vite` installed (added by
32
- * `toiljs create`/`configure` when Tailwind is enabled). Resolved from the project root so it picks
33
- * up the project's copy; returns `undefined` when Tailwind is off, so the plugin simply isn't added.
34
- */
35
- async function tailwindPlugin(root: string): Promise<PluginOption | undefined> {
36
- let resolved: string;
37
- try {
38
- resolved = createRequire(path.join(root, 'package.json')).resolve('@tailwindcss/vite');
39
- } catch {
40
- return undefined;
41
- }
42
- const mod = (await import(pathToFileURL(resolved).href)) as { default?: () => PluginOption };
43
- return mod.default?.();
44
- }
45
-
46
- /** Splits React's runtime into its own long-lived chunk for better caching. */
47
- function manualChunks(id: string): string | undefined {
48
- if (!id.includes('node_modules')) return undefined;
49
- if (
50
- id.includes('node_modules/react-dom') ||
51
- id.includes('node_modules/react/') ||
52
- id.includes('node_modules/scheduler')
53
- ) {
54
- return 'react';
55
- }
56
- return undefined;
57
- }
58
-
59
- /**
60
- * Builds the framework-owned Vite config. Vite's `root` is the generated `.toil` dir so its
61
- * `index.html` (built from the project's `public/index.html` template) emits at the output root
62
- * with assets resolving correctly; static `public/` assets are mirrored to `.toil/public` and
63
- * picked up via Vite's default publicDir. `fs.allow` opens the project (for `client/`) and the
64
- * framework runtime. The opinionated default, Node polyfills
65
- * (Buffer/global/process), React plugin, toil route plugin, typed asset folders, React chunk
66
- * splitting and tuned build options, is applied here; `toiljs/client` is aliased to the
67
- * runtime, and the user's `client.vite` overrides deep-merge on top.
68
- */
69
- export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineConfig> {
70
- const frameworkRoot = path.resolve(path.dirname(cfg.runtimePath), '..', '..');
71
- const tailwind = await tailwindPlugin(cfg.root);
72
-
73
- const base: InlineConfig = {
74
- root: cfg.toilDir,
75
- base: cfg.base,
76
- configFile: false,
77
- plugins: [
78
- tailwind,
79
- // Build-time image resize/optimization. Every *imported* raster image is compressed to
80
- // webp by default (so a plain `<img src={imported}>` is optimized too, not just
81
- // `Toil.Image`); add `?w=400;800&format=…` to resize or pick a format. `public/` assets
82
- // referenced by string path are served as-is. Disabled by `client.images: false`.
83
- cfg.images
84
- ? imagetools({
85
- defaultDirectives: () => new URLSearchParams({ format: 'webp', quality: '80' }),
86
- })
87
- : undefined,
88
- cfg.images ? imageReportPlugin(cfg.root, cfg.toilDir) : undefined,
89
- // Static per-route SEO prerender (build only): bakes each route's metadata into its HTML.
90
- cfg.seo ? prerenderPlugin(cfg) : undefined,
91
- // Preload bundled fonts (build only). Disabled by `client.fonts: false`.
92
- cfg.fonts ? fontPreloadPlugin(cfg) : undefined,
93
- nodePolyfills({ globals: { Buffer: true, global: true, process: true } }),
94
- react(),
95
- toilPlugin(cfg),
96
- ],
97
- resolve: {
98
- alias: {
99
- 'toiljs/client': cfg.runtimePath,
100
- 'toiljs/routes': path.join(cfg.toilDir, 'routes.ts'),
101
- },
102
- dedupe: ['react', 'react-dom'],
103
- },
104
- server: {
105
- port: cfg.port,
106
- fs: { allow: [cfg.root, frameworkRoot] },
107
- },
108
- build: {
109
- outDir: path.resolve(cfg.root, cfg.outDir),
110
- emptyOutDir: true,
111
- target: 'es2020',
112
- modulePreload: false,
113
- cssCodeSplit: false,
114
- assetsInlineLimit: 10000,
115
- chunkSizeWarningLimit: 3000,
116
- commonjsOptions: {
117
- strictRequires: true,
118
- transformMixedEsModules: true,
119
- },
120
- rollupOptions: {
121
- output: {
122
- chunkFileNames: 'assets/[name]-[hash].js',
123
- assetFileNames: (assetInfo) => assetFileName(assetInfo.names[0] ?? ''),
124
- manualChunks,
125
- },
126
- },
127
- },
128
- };
129
- return mergeConfig(base, cfg.vite);
130
- }
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ import react from '@vitejs/plugin-react';
6
+ import { imagetools } from 'vite-imagetools';
7
+ import { nodePolyfills } from 'vite-plugin-node-polyfills';
8
+ import { mergeConfig, type InlineConfig, type PluginOption } from 'vite';
9
+
10
+ import { type ResolvedToilConfig } from './config.js';
11
+ import { fontPreloadPlugin } from './fonts.js';
12
+ import { imageReportPlugin } from './image-report.js';
13
+ import { toilPlugin } from './plugin.js';
14
+ import { prerenderPlugin } from './prerender.js';
15
+
16
+ // `vite-plugin-node-polyfills` rewrites react/react-dom to import its `vite-plugin-node-polyfills/
17
+ // shims/*` modules. When a consumer links toiljs by symlink (`file:`/workspace), that package lives
18
+ // only in toiljs's own node_modules, so resolving those bare specifiers from the consumer root
19
+ // fails ("Failed to resolve import vite-plugin-node-polyfills/shims/process"). Alias them to
20
+ // absolute paths resolved from toiljs's location so the build works however toiljs was installed.
21
+ const polyfillPkgRoot = path.dirname(
22
+ path.dirname(createRequire(import.meta.url).resolve('vite-plugin-node-polyfills')),
23
+ );
24
+ const polyfillShimAliases: Record<string, string> = {
25
+ 'vite-plugin-node-polyfills/shims/buffer': path.join(
26
+ polyfillPkgRoot,
27
+ 'shims/buffer/dist/index.js',
28
+ ),
29
+ 'vite-plugin-node-polyfills/shims/global': path.join(
30
+ polyfillPkgRoot,
31
+ 'shims/global/dist/index.js',
32
+ ),
33
+ 'vite-plugin-node-polyfills/shims/process': path.join(
34
+ polyfillPkgRoot,
35
+ 'shims/process/dist/index.js',
36
+ ),
37
+ };
38
+
39
+ /** Image extensions routed to `images/` in the build output. */
40
+ const IMAGE_EXT = /^(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i;
41
+ /** Font extensions routed to `fonts/`. */
42
+ const FONT_EXT = /^(woff|woff2|eot|ttf|otf)$/i;
43
+
44
+ /** Routes a built asset to a typed sub-folder (`images/`, `fonts/`, `css/`, else `assets/`). */
45
+ function assetFileName(name: string): string {
46
+ const ext = name.split('.').pop() ?? '';
47
+ if (IMAGE_EXT.test(ext)) return 'images/[name][extname]';
48
+ if (FONT_EXT.test(ext)) return 'fonts/[name][extname]';
49
+ if (/^css$/i.test(ext)) return 'css/[name][extname]';
50
+ return 'assets/[name][extname]';
51
+ }
52
+
53
+ /**
54
+ * Loads the Tailwind v4 Vite plugin if the project has `@tailwindcss/vite` installed (added by
55
+ * `toiljs create`/`configure` when Tailwind is enabled). Resolved from the project root so it picks
56
+ * up the project's copy; returns `undefined` when Tailwind is off, so the plugin simply isn't added.
57
+ */
58
+ async function tailwindPlugin(root: string): Promise<PluginOption | undefined> {
59
+ let resolved: string;
60
+ try {
61
+ resolved = createRequire(path.join(root, 'package.json')).resolve('@tailwindcss/vite');
62
+ } catch {
63
+ return undefined;
64
+ }
65
+ const mod = (await import(pathToFileURL(resolved).href)) as { default?: () => PluginOption };
66
+ return mod.default?.();
67
+ }
68
+
69
+ /** Splits React's runtime into its own long-lived chunk for better caching. */
70
+ function manualChunks(id: string): string | undefined {
71
+ if (!id.includes('node_modules')) return undefined;
72
+ if (
73
+ id.includes('node_modules/react-dom') ||
74
+ id.includes('node_modules/react/') ||
75
+ id.includes('node_modules/scheduler')
76
+ ) {
77
+ return 'react';
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ /**
83
+ * Builds the framework-owned Vite config. Vite's `root` is the generated `.toil` dir so its
84
+ * `index.html` (built from the project's `public/index.html` template) emits at the output root
85
+ * with assets resolving correctly; static `public/` assets are mirrored to `.toil/public` and
86
+ * picked up via Vite's default publicDir. `fs.allow` opens the project (for `client/`) and the
87
+ * framework runtime. The opinionated default, Node polyfills
88
+ * (Buffer/global/process), React plugin, toil route plugin, typed asset folders, React chunk
89
+ * splitting and tuned build options, is applied here; `toiljs/client` is aliased to the
90
+ * runtime, and the user's `client.vite` overrides deep-merge on top.
91
+ */
92
+ export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineConfig> {
93
+ const frameworkRoot = path.resolve(path.dirname(cfg.runtimePath), '..', '..');
94
+ const tailwind = await tailwindPlugin(cfg.root);
95
+
96
+ const base: InlineConfig = {
97
+ root: cfg.toilDir,
98
+ base: cfg.base,
99
+ configFile: false,
100
+ plugins: [
101
+ tailwind,
102
+ // Build-time image resize/optimization. Every *imported* raster image is compressed to
103
+ // webp by default (so a plain `<img src={imported}>` is optimized too, not just
104
+ // `Toil.Image`); add `?w=400;800&format=…` to resize or pick a format. `public/` assets
105
+ // referenced by string path are served as-is. Disabled by `client.images: false`.
106
+ cfg.images
107
+ ? imagetools({
108
+ defaultDirectives: () =>
109
+ new URLSearchParams({ format: 'webp', quality: '80' }),
110
+ })
111
+ : undefined,
112
+ cfg.images ? imageReportPlugin(cfg.root, cfg.toilDir) : undefined,
113
+ // Static per-route SEO prerender (build only): bakes each route's metadata into its HTML.
114
+ cfg.seo ? prerenderPlugin(cfg) : undefined,
115
+ // Preload bundled fonts (build only). Disabled by `client.fonts: false`.
116
+ cfg.fonts ? fontPreloadPlugin(cfg) : undefined,
117
+ nodePolyfills({ globals: { Buffer: true, global: true, process: true } }),
118
+ react(),
119
+ toilPlugin(cfg),
120
+ ],
121
+ resolve: {
122
+ alias: {
123
+ 'toiljs/client': cfg.runtimePath,
124
+ 'toiljs/routes': path.join(cfg.toilDir, 'routes.ts'),
125
+ ...polyfillShimAliases,
126
+ },
127
+ dedupe: ['react', 'react-dom'],
128
+ },
129
+ server: {
130
+ port: cfg.port,
131
+ fs: { allow: [cfg.root, frameworkRoot] },
132
+ },
133
+ build: {
134
+ outDir: path.resolve(cfg.root, cfg.outDir),
135
+ emptyOutDir: true,
136
+ target: 'es2020',
137
+ modulePreload: false,
138
+ cssCodeSplit: false,
139
+ assetsInlineLimit: 10000,
140
+ chunkSizeWarningLimit: 3000,
141
+ commonjsOptions: {
142
+ strictRequires: true,
143
+ transformMixedEsModules: true,
144
+ },
145
+ rollupOptions: {
146
+ output: {
147
+ chunkFileNames: 'assets/[name]-[hash].js',
148
+ assetFileNames: (assetInfo) => assetFileName(assetInfo.names[0] ?? ''),
149
+ manualChunks,
150
+ },
151
+ },
152
+ },
153
+ };
154
+ return mergeConfig(base, cfg.vite);
155
+ }
package/src/io/FastSet.ts CHANGED
@@ -1,96 +1,99 @@
1
- import { type FastRecord, type IndexKey, type PropertyExtendedKey } from './FastMap.js';
2
-
3
- /**
4
- * The Set counterpart to {@link FastMap}: an insertion-ordered set backed by an array (for
5
- * iteration/ordering) plus a record index (for O(1) membership), with bigint-key support.
6
- *
7
- * Authored to match FastMap's design, the upstream package ships no `FastSet`.
8
- */
9
- export class FastSet<T extends PropertyExtendedKey> implements Disposable {
10
- protected _values: T[] = [];
11
- protected _index: FastRecord<true> = {};
12
-
13
- constructor(iterable?: Iterable<T> | null | FastSet<T>) {
14
- if (iterable instanceof FastSet) {
15
- this.addAll(iterable);
16
- } else if (iterable) {
17
- for (const value of iterable) {
18
- this.add(value);
19
- }
20
- }
21
- }
22
-
23
- public get size(): number {
24
- return this._values.length;
25
- }
26
-
27
- public add(value: T): this {
28
- if (!this.has(value)) {
29
- this._values.push(value);
30
- this._index[value as IndexKey] = true;
31
- }
32
-
33
- return this;
34
- }
35
-
36
- public has(value: T): boolean {
37
- return Object.prototype.hasOwnProperty.call(this._index, value as IndexKey);
38
- }
39
-
40
- public indexOf(value: T): number {
41
- for (let i = 0; i < this._values.length; i++) {
42
- if (this._values[i] === value) {
43
- return i;
44
- }
45
- }
46
-
47
- return -1;
48
- }
49
-
50
- public delete(value: T): boolean {
51
- if (!this.has(value)) {
52
- return false;
53
- }
54
-
55
- const index = this.indexOf(value);
56
- if (index !== -1) {
57
- this._values.splice(index, 1);
58
- }
59
-
60
- delete this._index[value as IndexKey];
61
- return true;
62
- }
63
-
64
- public addAll(set: FastSet<T>): void {
65
- for (const value of set.values()) {
66
- this.add(value);
67
- }
68
- }
69
-
70
- public *values(): IterableIterator<T> {
71
- yield* this._values;
72
- }
73
-
74
- public *keys(): IterableIterator<T> {
75
- yield* this._values;
76
- }
77
-
78
- public forEach(callback: (value: T, value2: T, set: FastSet<T>) => void, thisArg?: unknown): void {
79
- for (const value of this._values) {
80
- callback.call(thisArg, value, value, this);
81
- }
82
- }
83
-
84
- public clear(): void {
85
- this._values = [];
86
- this._index = {};
87
- }
88
-
89
- public [Symbol.dispose](): void {
90
- this.clear();
91
- }
92
-
93
- *[Symbol.iterator](): IterableIterator<T> {
94
- yield* this._values;
95
- }
96
- }
1
+ import { type FastRecord, type IndexKey, type PropertyExtendedKey } from './FastMap.js';
2
+
3
+ /**
4
+ * The Set counterpart to {@link FastMap}: an insertion-ordered set backed by an array (for
5
+ * iteration/ordering) plus a record index (for O(1) membership), with bigint-key support.
6
+ *
7
+ * Authored to match FastMap's design, the upstream package ships no `FastSet`.
8
+ */
9
+ export class FastSet<T extends PropertyExtendedKey> implements Disposable {
10
+ protected _values: T[] = [];
11
+ protected _index: FastRecord<true> = {};
12
+
13
+ constructor(iterable?: Iterable<T> | null | FastSet<T>) {
14
+ if (iterable instanceof FastSet) {
15
+ this.addAll(iterable);
16
+ } else if (iterable) {
17
+ for (const value of iterable) {
18
+ this.add(value);
19
+ }
20
+ }
21
+ }
22
+
23
+ public get size(): number {
24
+ return this._values.length;
25
+ }
26
+
27
+ public add(value: T): this {
28
+ if (!this.has(value)) {
29
+ this._values.push(value);
30
+ this._index[value as IndexKey] = true;
31
+ }
32
+
33
+ return this;
34
+ }
35
+
36
+ public has(value: T): boolean {
37
+ return Object.prototype.hasOwnProperty.call(this._index, value as IndexKey);
38
+ }
39
+
40
+ public indexOf(value: T): number {
41
+ for (let i = 0; i < this._values.length; i++) {
42
+ if (this._values[i] === value) {
43
+ return i;
44
+ }
45
+ }
46
+
47
+ return -1;
48
+ }
49
+
50
+ public delete(value: T): boolean {
51
+ if (!this.has(value)) {
52
+ return false;
53
+ }
54
+
55
+ const index = this.indexOf(value);
56
+ if (index !== -1) {
57
+ this._values.splice(index, 1);
58
+ }
59
+
60
+ delete this._index[value as IndexKey];
61
+ return true;
62
+ }
63
+
64
+ public addAll(set: FastSet<T>): void {
65
+ for (const value of set.values()) {
66
+ this.add(value);
67
+ }
68
+ }
69
+
70
+ public *values(): IterableIterator<T> {
71
+ yield* this._values;
72
+ }
73
+
74
+ public *keys(): IterableIterator<T> {
75
+ yield* this._values;
76
+ }
77
+
78
+ public forEach(
79
+ callback: (value: T, value2: T, set: FastSet<T>) => void,
80
+ thisArg?: unknown,
81
+ ): void {
82
+ for (const value of this._values) {
83
+ callback.call(thisArg, value, value, this);
84
+ }
85
+ }
86
+
87
+ public clear(): void {
88
+ this._values = [];
89
+ this._index = {};
90
+ }
91
+
92
+ public [Symbol.dispose](): void {
93
+ this.clear();
94
+ }
95
+
96
+ *[Symbol.iterator](): IterableIterator<T> {
97
+ yield* this._values;
98
+ }
99
+ }