coralite 0.35.0 → 0.36.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 (76) hide show
  1. package/README.md +57 -82
  2. package/dist/lib/client-runtime.d.ts +3 -10
  3. package/dist/lib/client-runtime.d.ts.map +1 -1
  4. package/dist/lib/client-runtime.js +62 -0
  5. package/dist/lib/client-runtime.js.map +7 -0
  6. package/dist/lib/collection.js +194 -0
  7. package/dist/lib/collection.js.map +7 -0
  8. package/dist/lib/config.js +43 -0
  9. package/dist/lib/config.js.map +7 -0
  10. package/dist/lib/coralite-element.d.ts +249 -0
  11. package/dist/lib/coralite-element.d.ts.map +1 -0
  12. package/dist/lib/coralite-element.js +523 -0
  13. package/dist/lib/coralite-element.js.map +7 -0
  14. package/dist/lib/coralite.d.ts +73 -42
  15. package/dist/lib/coralite.d.ts.map +1 -1
  16. package/dist/lib/coralite.js +2064 -0
  17. package/dist/lib/coralite.js.map +7 -0
  18. package/dist/lib/dom.d.ts.map +1 -1
  19. package/dist/lib/dom.js +496 -0
  20. package/dist/lib/dom.js.map +7 -0
  21. package/dist/lib/errors.js +21 -0
  22. package/dist/lib/errors.js.map +7 -0
  23. package/dist/lib/html.js +149 -0
  24. package/dist/lib/html.js.map +7 -0
  25. package/dist/lib/index.js +13 -13293
  26. package/dist/lib/index.js.map +4 -4
  27. package/dist/lib/parse.d.ts.map +1 -1
  28. package/dist/lib/parse.js +502 -0
  29. package/dist/lib/parse.js.map +7 -0
  30. package/dist/lib/plugin.d.ts +23 -30
  31. package/dist/lib/plugin.d.ts.map +1 -1
  32. package/dist/lib/plugin.js +107 -0
  33. package/dist/lib/plugin.js.map +7 -0
  34. package/dist/lib/render-helpers.d.ts +8 -0
  35. package/dist/lib/render-helpers.d.ts.map +1 -1
  36. package/dist/lib/render-helpers.js +194 -0
  37. package/dist/lib/render-helpers.js.map +7 -0
  38. package/dist/lib/script-manager.d.ts.map +1 -1
  39. package/dist/lib/script-manager.js +610 -0
  40. package/dist/lib/script-manager.js.map +7 -0
  41. package/dist/lib/style-transform.js +68 -0
  42. package/dist/lib/style-transform.js.map +7 -0
  43. package/dist/lib/tags.d.ts +1 -0
  44. package/dist/lib/tags.d.ts.map +1 -1
  45. package/dist/lib/tags.js +255 -0
  46. package/dist/lib/tags.js.map +7 -0
  47. package/dist/lib/type-helper.js +62 -0
  48. package/dist/lib/type-helper.js.map +7 -0
  49. package/dist/lib/utils.d.ts +14 -0
  50. package/dist/lib/utils.d.ts.map +1 -1
  51. package/dist/lib/utils.js +649 -0
  52. package/dist/lib/utils.js.map +7 -0
  53. package/dist/plugins/metadata.d.ts +1 -3
  54. package/dist/plugins/metadata.d.ts.map +1 -1
  55. package/dist/plugins/metadata.js +25 -23
  56. package/dist/plugins/refs.d.ts +1 -3
  57. package/dist/plugins/refs.d.ts.map +1 -1
  58. package/dist/plugins/refs.js +33 -3
  59. package/dist/plugins/static-assets.d.ts +1 -3
  60. package/dist/plugins/static-assets.d.ts.map +1 -1
  61. package/dist/plugins/static-assets.js +29 -27
  62. package/dist/plugins/testing.d.ts +1 -3
  63. package/dist/plugins/testing.d.ts.map +1 -1
  64. package/dist/plugins/testing.js +30 -8
  65. package/dist/types/component.d.ts +4 -0
  66. package/dist/types/component.d.ts.map +1 -1
  67. package/dist/types/core.d.ts +58 -0
  68. package/dist/types/core.d.ts.map +1 -1
  69. package/dist/types/index.d.ts +1 -0
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/plugin.d.ts +168 -70
  72. package/dist/types/plugin.d.ts.map +1 -1
  73. package/dist/types/script.d.ts +22 -22
  74. package/dist/types/script.d.ts.map +1 -1
  75. package/llms.txt +81 -263
  76. package/package.json +6 -4
package/README.md CHANGED
@@ -6,16 +6,18 @@
6
6
 
7
7
  ## Build for the Web, With the Web
8
8
 
9
- Coralite is a static site generator built around HTML modules and the Native Web.
10
-
11
- - **Build with HTML Modules**
12
- Build your entire website with modular components using only native web technologies. This provides a simple, stable, and maintainable foundation.
13
- - **Accessibility and Security First**
14
- By leveraging native HTML elements, sites inherit strong accessibility features out-of-the-box. A small footprint with no third-party library dependencies creates a more secure website.
15
- - **Progressive Enhancement**
16
- Ensures that all basic content and functionality are accessible, even on browsers without JavaScript. JavaScript is treated as an optional enhancement, not a requirement.
17
- - **Powerful Plugin System**
18
- Extend and customize the build process with a plugin API. Use hooks to manage content, create aggregations like blog posts with pagination, or transform final HTML and CSS output.
9
+ Coralite is a **Native-First**, strictly Server-Side Rendered (SSR) framework for building fast, accessible, and future-proof websites.
10
+
11
+ - **True Native Web Components with a "Flat" API**
12
+ No vanilla boilerplate. Use a clean `defineComponent` flat-options API (`attributes`, `data`, `getters`, `script`) to build powerful Custom Elements without the `class extends HTMLElement` friction.
13
+ - **The "Smart State, Dumb Template" Paradigm**
14
+ Templates are strictly declarative and "dumb"—no logic loops or dot-notation in HTML. All logic lives in pure JavaScript `getters` which receive a safe, Read-Only Proxy.
15
+ - **Scoped CSS without Shadow DOM**
16
+ Enjoy perfect style encapsulation using standard CSS. The Coralite compiler automatically injects unique instance identifiers and nests rules, avoiding the accessibility and global styling headaches of Shadow DOM.
17
+ - **Native Async Race-Condition Immunity**
18
+ Coralite's reactive engine handles asynchronous `data()` and `getters` with built-in version locks, ensuring your DOM never renders stale data from out-of-order Promise resolutions.
19
+ - **Isomorphic, Two-Phase Curried Plugins**
20
+ Extend the engine with a strict, typed boundary. Plugins use a two-phase currying pattern to inject heavy context during initialization, leaving you with a clean, scoped API at runtime.
19
21
 
20
22
  ---
21
23
 
@@ -113,82 +115,63 @@ Styles defined in the `<style>` block are automatically **scoped** to the compon
113
115
 
114
116
  ```html
115
117
  <template id="user-card">
116
- <div class="card" ref="card">
117
- <h2>{{ formatName }}</h2>
118
+ <div class="card">
119
+ <!-- Templates only render flat keys. Logic belongs in getters! -->
120
+ <h2 ref="title">{{ formatName }}</h2>
118
121
  <p>{{ userMeta }}</p>
119
- <slot>
122
+
123
+ <slot></slot>
124
+
120
125
  <p class="stats">Logins: {{ loginCount }}</p>
121
126
  </div>
122
127
  </template>
123
128
 
124
129
  <style>
125
- /* These styles are automatically scoped to this component */
130
+ /* These styles are automatically scoped to this component instance */
126
131
  .card {
127
132
  border: 1px solid #eaeaea;
128
133
  padding: 1.5rem;
129
134
  border-radius: 8px;
130
- cursor: pointer;
131
- }
132
- h2 {
133
- color: coral;
134
- }
135
- .stats {
136
- font-size: 0.85em;
137
- color: gray;
138
135
  }
136
+ h2 { color: coral; }
139
137
  </style>
140
138
 
141
139
  <script type="module">
142
140
  import { defineComponent } from 'coralite'
143
- import db from 'database'
141
+ import { userService } from './services.js'
144
142
 
145
143
  export default defineComponent({
146
- // INPUTS (Coerced from HTML string attributes)
144
+ // ATTRIBUTES: Coerced from HTML (String, Number, Boolean)
147
145
  attributes: {
148
- firstName: { type: String, default: 'Unknown' },
149
- lastName: { type: String, default: '' },
146
+ userId: { type: Number, default: 0 },
150
147
  role: { type: String, default: 'Guest' }
151
148
  },
152
149
 
153
- // SERVER DATA (Async fetching, runs on build/server)
154
- async data(context) {
155
- // We can use the parsed attributes to fetch specific data
156
- const stats = await db.fetchUserStats(context.attributes.firstName)
157
-
150
+ // DATA: Async server-side fetching (Stripped from client bundle)
151
+ async data({ state }) {
152
+ const user = await userService.getById(state.userId)
158
153
  return {
159
- // These will be merged into the base state
160
- department: stats.department || 'General',
161
- loginCount: stats.loginCount || 0
154
+ firstName: user.firstName,
155
+ lastName: user.lastName,
156
+ loginCount: user.loginCount
162
157
  }
163
158
  },
164
159
 
165
- // DERIVED STATE (Sync, pure functions, Read-Only Proxy)
160
+ // GETTERS: Pure, sync derived state (Read-Only Proxy)
166
161
  getters: {
167
- // state = attributes + data
168
162
  formatName: (state) => `${state.firstName} ${state.lastName}`.trim(),
169
- userMeta: (state) => `Role: ${state.role} | Dept: ${state.department}`
163
+ userMeta: (state) => `Role: ${state.role} | ID: ${state.userId}`
170
164
  },
171
165
 
172
- // Light DOM transformations
173
- slots: {
174
- default (nodes, context) {
175
- return nodes.map(node => node)
176
- }
177
- },
166
+ // SCRIPT: Client-side controller (Read/Write Proxy)
167
+ script({ state, refs, signal }) {
168
+ // Use the 'refs' utility to get the unique DOM element
169
+ const titleEl = refs('title')
178
170
 
179
- // CLIENT CONTROLLER (Mutations, Events, Read/Write Proxy, Teardown)
180
- script({ state, refs, root, signal }) {
181
- console.log(`Component mounted: ${state.formatName}`)
182
-
183
- // Use the refs dictionary provided by the core plugin to target the element
184
- const cardEl = root.querySelector(`[ref="${refs.card}"]`)
185
-
186
- cardEl.addEventListener('click', () => {
187
- alert(`Hello from the browser, ${state.formatName}!`)
188
-
189
- // automatically updates the DOM, and re-runs any dependent getters.
190
- state.loginCount++
191
- }, { signal })
171
+ titleEl.addEventListener('click', () => {
172
+ // Mutations automatically trigger DOM updates & getter re-evaluations
173
+ state.loginCount++
174
+ }, { signal }) // Always use 'signal' for auto-cleanup!
192
175
  }
193
176
  })
194
177
  </script>
@@ -200,42 +183,33 @@ Styles defined in the `<style>` block are automatically **scoped** to the compon
200
183
 
201
184
  ## Extending the Engine (`definePlugin`)
202
185
 
203
- Coralite is built to be extended. The `definePlugin` API lets you tap directly into the framework's build lifecycle.
204
-
205
- Plugins in Coralite use a functional, immutable API. Instead of mutating shared state, your plugin hooks simply return the specific data patches or pages you want to add. Core utilities are available via `coralite/utils`, and global engine state is accessible through `this` binding.
186
+ Coralite uses an isomorphic plugin architecture. A plugin is divided into `server` (Node.js) and `client` (Browser) blocks, using a **Two-Phase Curried** API to safely inject context.
206
187
 
207
188
  ```javascript
208
189
  import { definePlugin } from 'coralite'
209
- import { parseHTML } from 'coralite/utils'
210
190
 
211
- export default function seoPlugin(options = {}) {
191
+ export default function myPlugin(options = {}) {
212
192
  return definePlugin({
213
- name: 'coralite-seo-plugin',
193
+ name: 'my-plugin',
214
194
 
215
- // State Reducers: Patch the page context before rendering
216
- onBeforePageRender (context) {
217
- // Return a patch object; Coralite will safely deep-merge it for you!
218
- return {
219
- state: {
220
- siteTitle: options.title || 'My Coralite Site',
221
- metaDescription: 'Generated by Coralite'
195
+ server: {
196
+ // Phase 1: Global Context | Phase 2: Component Arguments
197
+ exports: {
198
+ getData: (context) => (query) => {
199
+ return { custom: 'data' }
222
200
  }
201
+ },
202
+ onBeforeComponentRender: ({ state }) => {
203
+ state.pluginAdded = true
223
204
  }
224
205
  },
225
206
 
226
- // Data Aggregators: Generate entirely new pages during the build
227
- onAfterPageRender (basePageResult) {
228
- // Access the engine instance via `this`
229
- // this.pages.getItem('/index.html')
230
-
231
- // Return an array of new pages to append to the final build
232
- if (basePageResult.path.filename === 'index.html') {
233
- return [
234
- {
235
- path: { filename: 'sitemap.xml', pathname: '/sitemap.xml' },
236
- content: '<xml>...</xml>'
237
- }
238
- ]
207
+ client: {
208
+ // Injects a utility directly into the component's 'script' context
209
+ context: {
210
+ myHelper: (globalCtx) => (instanceCtx) => () => {
211
+ console.log('Hello from component', instanceCtx.instanceId)
212
+ }
239
213
  }
240
214
  }
241
215
  })
@@ -261,6 +235,7 @@ Coralite plugins tap into specific hooks that execute strictly during the SSG bu
261
235
  ### Rendering Hooks
262
236
  - **`onBeforePageRender`**: A state-reducing hook to patch the page context just before HTML serialization.
263
237
  - **`onAfterPageRender`**: An aggregator hook that runs after a page is rendered, allowing plugins to append additional pages (e.g., RSS feeds, sitemaps) to the build output.
238
+ - **`no-hydration`**: An attribute that can be added to any component tag to completely exclude it from client-side hydration while still rendering its content on the server.
264
239
 
265
240
  ---
266
241
 
@@ -8,20 +8,13 @@
8
8
  * @param {string} options.base - The base URL for assets.
9
9
  * @param {string} options.sharedChunkPath - The filename of the shared chunk.
10
10
  * @param {Object} options.chunkManifest - Manifest mapping component IDs to their chunk filenames.
11
- * @param {Object.<string, InstanceContext>} options.instances - Map of instance data.
12
- * @param {string} options.mode - Build mode ('development' or 'development').
13
- * @param {Object} [options.renderContext] - Build-time render context.
11
+ * @param {string[]} [options.declarativeTags=[]] - The declarative tags used.
14
12
  * @returns {string} The generated JavaScript runtime.
15
13
  */
16
- export function generateClientRuntime({ base, sharedChunkPath, chunkManifest, instances, mode, renderContext }: {
14
+ export function generateClientRuntime({ base, sharedChunkPath, chunkManifest, declarativeTags }: {
17
15
  base: string;
18
16
  sharedChunkPath: string;
19
17
  chunkManifest: any;
20
- instances: {
21
- [x: string]: InstanceContext;
22
- };
23
- mode: string;
24
- renderContext?: any;
18
+ declarativeTags?: string[];
25
19
  }): string;
26
- import type { InstanceContext } from '../types/index.js';
27
20
  //# sourceMappingURL=client-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-runtime.d.ts","sourceRoot":"","sources":["../../lib/client-runtime.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;GAWG;AACH,gHARG;IAAwB,IAAI,EAApB,MAAM;IACU,eAAe,EAA/B,MAAM;IACU,aAAa;IACa,SAAS,EAAnD;YAAQ,MAAM,GAAE,eAAe;KAAC;IAChB,IAAI,EAApB,MAAM;IACW,aAAa;CACtC,GAAU,MAAM,CAmkBlB;qCAhlBmC,mBAAmB"}
1
+ {"version":3,"file":"client-runtime.d.ts","sourceRoot":"","sources":["../../lib/client-runtime.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;GASG;AACH,iGANG;IAAwB,IAAI,EAApB,MAAM;IACU,eAAe,EAA/B,MAAM;IACU,aAAa;IACV,eAAe,GAAlC,MAAM,EAAE;CAChB,GAAU,MAAM,CA2DlB"}
@@ -0,0 +1,62 @@
1
+ function generateClientRuntime({
2
+ base,
3
+ sharedChunkPath,
4
+ chunkManifest,
5
+ declarativeTags = []
6
+ }) {
7
+ return `
8
+ import { getClientContext, getSetups, createCoraliteClass, globalClientHooks } from '${base}assets/js/${sharedChunkPath}';
9
+
10
+ (async () => {
11
+ if (!window.__coralite_ready__) {
12
+ window.__coralite_ready__ = new Promise(resolve => { window.__coralite_resolve_ready__ = resolve; });
13
+ }
14
+ globalThis.executableScripts = [];
15
+ globalThis.globalAbortController = new AbortController();
16
+
17
+ const componentManifest = ${JSON.stringify(chunkManifest)};
18
+ const loadCache = {};
19
+
20
+ const loadComponent = (componentId) => {
21
+ if (!componentManifest[componentId]) return Promise.resolve();
22
+ if (customElements.get(componentId)) return Promise.resolve();
23
+ if (loadCache[componentId]) return loadCache[componentId];
24
+
25
+ loadCache[componentId] = (async () => {
26
+ const module = await import('${base}assets/js/' + componentManifest[componentId]);
27
+ if (module.default && module.default.componentId) {
28
+ if (!customElements.get(module.default.componentId)) {
29
+ customElements.define(module.default.componentId, createCoraliteClass(module.default, getClientContext, globalClientHooks));
30
+ }
31
+ }
32
+ })();
33
+ return loadCache[componentId];
34
+ };
35
+
36
+ const declarativeTags = ${JSON.stringify(declarativeTags)};
37
+ const allTags = Object.keys(componentManifest);
38
+ const imperativeTags = allTags.filter(tag => !declarativeTags.includes(tag));
39
+
40
+ const loadPromises = declarativeTags.map(tagName => loadComponent(tagName));
41
+ await Promise.all(loadPromises);
42
+
43
+ if (typeof window.__coralite_resolve_ready__ === 'function') {
44
+ window.__coralite_resolve_ready__();
45
+ }
46
+
47
+ if (imperativeTags.length > 0) {
48
+ const lazyLoad = () => imperativeTags.forEach(tagName => loadComponent(tagName));
49
+
50
+ if ('requestIdleCallback' in window) {
51
+ requestIdleCallback(lazyLoad, { timeout: 500 });
52
+ } else {
53
+ setTimeout(lazyLoad, 1);
54
+ }
55
+ }
56
+ })();
57
+ `.trim();
58
+ }
59
+ export {
60
+ generateClientRuntime
61
+ };
62
+ //# sourceMappingURL=client-runtime.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../lib/client-runtime.js"],
4
+ "sourcesContent": ["/**\n * @import { InstanceContext } from '../types/index.js'\n */\n\n/**\n * Generates the client-side runtime script for Coralite pages.\n *\n * @param {Object} options\n * @param {string} options.base - The base URL for assets.\n * @param {string} options.sharedChunkPath - The filename of the shared chunk.\n * @param {Object} options.chunkManifest - Manifest mapping component IDs to their chunk filenames.\n * @param {string[]} [options.declarativeTags=[]] - The declarative tags used.\n * @returns {string} The generated JavaScript runtime.\n */\nexport function generateClientRuntime ({\n base,\n sharedChunkPath,\n chunkManifest,\n declarativeTags = []\n}) {\n return `\nimport { getClientContext, getSetups, createCoraliteClass, globalClientHooks } from '${base}assets/js/${sharedChunkPath}';\n\n(async () => {\n if (!window.__coralite_ready__) {\n window.__coralite_ready__ = new Promise(resolve => { window.__coralite_resolve_ready__ = resolve; });\n }\n globalThis.executableScripts = [];\n globalThis.globalAbortController = new AbortController();\n\n const componentManifest = ${JSON.stringify(chunkManifest)};\n const loadCache = {};\n\n const loadComponent = (componentId) => {\n if (!componentManifest[componentId]) return Promise.resolve();\n if (customElements.get(componentId)) return Promise.resolve();\n if (loadCache[componentId]) return loadCache[componentId];\n\n loadCache[componentId] = (async () => {\n const module = await import('${base}assets/js/' + componentManifest[componentId]);\n if (module.default && module.default.componentId) {\n if (!customElements.get(module.default.componentId)) {\n customElements.define(module.default.componentId, createCoraliteClass(module.default, getClientContext, globalClientHooks));\n }\n }\n })();\n return loadCache[componentId];\n };\n\n const declarativeTags = ${JSON.stringify(declarativeTags)};\n const allTags = Object.keys(componentManifest);\n const imperativeTags = allTags.filter(tag => !declarativeTags.includes(tag));\n\n const loadPromises = declarativeTags.map(tagName => loadComponent(tagName));\n await Promise.all(loadPromises);\n\n if (typeof window.__coralite_resolve_ready__ === 'function') {\n window.__coralite_resolve_ready__();\n }\n\n if (imperativeTags.length > 0) {\n const lazyLoad = () => imperativeTags.forEach(tagName => loadComponent(tagName));\n \n if ('requestIdleCallback' in window) {\n requestIdleCallback(lazyLoad, { timeout: 500 });\n } else {\n setTimeout(lazyLoad, 1);\n }\n }\n})();\n`.trim()\n}\n"],
5
+ "mappings": "AAcO,SAAS,sBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB,CAAC;AACrB,GAAG;AACD,SAAO;AAAA,uFAC8E,IAAI,aAAa,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BASzF,KAAK,UAAU,aAAa,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAStB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAUb,KAAK,UAAU,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBzD,KAAK;AACP;",
6
+ "names": []
7
+ }
@@ -0,0 +1,194 @@
1
+ import path from "node:path";
2
+ import { getHtmlFile } from "./html.js";
3
+ import { access } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ function CoraliteCollection(options = { rootDir: "" }) {
6
+ this.rootDir = path.join(options.rootDir);
7
+ if (!existsSync(this.rootDir)) {
8
+ throw new Error("Root directory was not found: " + this.rootDir);
9
+ }
10
+ this.list = [];
11
+ this.listByPath = /* @__PURE__ */ Object.create(null);
12
+ this.collection = /* @__PURE__ */ Object.create(null);
13
+ this._onSet = options.onSet;
14
+ this._onUpdate = options.onUpdate;
15
+ this._onDelete = options.onDelete;
16
+ }
17
+ CoraliteCollection.prototype.setItem = async function(value) {
18
+ if (typeof value === "string") {
19
+ value = await this._loadByPath(value);
20
+ }
21
+ if (!value || !value.path) {
22
+ throw new Error("Valid HTMLData object must be provided");
23
+ }
24
+ const pathname = value.path.pathname;
25
+ const dirname = value.path.dirname;
26
+ const originalValue = this.collection[pathname];
27
+ const documentValue = value;
28
+ if (!originalValue) {
29
+ if (typeof this._onSet === "function") {
30
+ const result = await this._onSet(value);
31
+ if (!result) {
32
+ return;
33
+ }
34
+ documentValue.result = result.value;
35
+ if (result.state) {
36
+ documentValue.state = result.state;
37
+ }
38
+ if (result.type === "page" || result.type === "component") {
39
+ documentValue.type = result.type;
40
+ }
41
+ if (typeof result.id === "string" && result.id) {
42
+ this.collection[result.id] = documentValue;
43
+ }
44
+ }
45
+ this.collection[pathname] = documentValue;
46
+ if (!this.listByPath[dirname]) {
47
+ this.listByPath[dirname] = [];
48
+ }
49
+ if (!this.listByPath[dirname].includes(documentValue)) {
50
+ this.listByPath[dirname].push(documentValue);
51
+ }
52
+ if (!this.list.includes(documentValue)) {
53
+ this.list.push(documentValue);
54
+ }
55
+ } else {
56
+ return await this.updateItem(value);
57
+ }
58
+ return documentValue;
59
+ };
60
+ CoraliteCollection.prototype.deleteItem = async function(value) {
61
+ if (!value) {
62
+ throw new Error("Valid pathname must be provided");
63
+ }
64
+ let pathname;
65
+ let dirname;
66
+ let valuesByPath;
67
+ let originalValue;
68
+ if (typeof value !== "string" && value.path) {
69
+ pathname = value.path.pathname;
70
+ dirname = value.path.dirname;
71
+ valuesByPath = this.listByPath[dirname];
72
+ originalValue = value;
73
+ } else if (typeof value === "string") {
74
+ pathname = value;
75
+ dirname = path.dirname(pathname);
76
+ valuesByPath = this.listByPath[dirname];
77
+ originalValue = this.collection[pathname];
78
+ } else {
79
+ throw new Error("Valid pathname must be provided");
80
+ }
81
+ if (!originalValue) {
82
+ return;
83
+ }
84
+ if (!valuesByPath) {
85
+ for (const key in this.collection) {
86
+ if (this.collection[key] === originalValue) {
87
+ delete this.collection[key];
88
+ }
89
+ }
90
+ return;
91
+ }
92
+ if (typeof this._onDelete === "function") {
93
+ await this._onDelete(originalValue);
94
+ }
95
+ for (const key in this.collection) {
96
+ if (this.collection[key] === originalValue) {
97
+ delete this.collection[key];
98
+ }
99
+ }
100
+ const listIndex = this.list.indexOf(originalValue);
101
+ const pathIndex = valuesByPath.indexOf(originalValue);
102
+ if (listIndex !== -1) {
103
+ this.list.splice(listIndex, 1);
104
+ }
105
+ if (pathIndex !== -1) {
106
+ valuesByPath.splice(pathIndex, 1);
107
+ }
108
+ if (valuesByPath.length === 0) {
109
+ delete this.listByPath[dirname];
110
+ }
111
+ };
112
+ CoraliteCollection.prototype.updateItem = async function(value) {
113
+ if (typeof value === "string") {
114
+ value = await this._loadByPath(value);
115
+ }
116
+ if (!value || !value.path) {
117
+ throw new Error("Valid HTMLData object must be provided");
118
+ }
119
+ const pathname = value.path.pathname;
120
+ const originalValue = this.collection[pathname];
121
+ if (!originalValue) {
122
+ return await this.setItem(value);
123
+ }
124
+ if (typeof this._onUpdate === "function") {
125
+ const result = await this._onUpdate(value, originalValue);
126
+ if (!result) {
127
+ return originalValue;
128
+ }
129
+ if (result && typeof result === "object") {
130
+ if (result.value !== void 0) {
131
+ originalValue.result = result.value;
132
+ } else {
133
+ originalValue.result = result;
134
+ }
135
+ if (result.type === "page" || result.type === "component") {
136
+ originalValue.type = result.type;
137
+ }
138
+ } else {
139
+ originalValue.result = result;
140
+ }
141
+ }
142
+ if (value.content !== void 0) {
143
+ originalValue.content = value.content;
144
+ }
145
+ if (value.path && value.path !== originalValue.path) {
146
+ originalValue.path = value.path;
147
+ }
148
+ if (value.type) {
149
+ originalValue.type = value.type;
150
+ }
151
+ if (value.state !== void 0) {
152
+ originalValue.state = value.state;
153
+ }
154
+ return originalValue;
155
+ };
156
+ CoraliteCollection.prototype.getItem = function(id) {
157
+ if (!this.collection[id] && id.endsWith("html")) {
158
+ id = path.join(this.rootDir, id);
159
+ }
160
+ return this.collection[id];
161
+ };
162
+ CoraliteCollection.prototype.getListByPath = function(dirname) {
163
+ const list = this.listByPath[dirname];
164
+ if (list) {
165
+ return list.slice();
166
+ }
167
+ };
168
+ CoraliteCollection.prototype._loadByPath = async function(filepath) {
169
+ try {
170
+ await access(filepath);
171
+ } catch {
172
+ try {
173
+ filepath = path.join(this.rootDir, filepath);
174
+ await access(filepath);
175
+ } catch {
176
+ throw new Error("Could not find collection item: " + filepath);
177
+ }
178
+ }
179
+ const content = await getHtmlFile(filepath);
180
+ return {
181
+ type: "page",
182
+ content,
183
+ path: {
184
+ pathname: filepath,
185
+ dirname: path.dirname(filepath),
186
+ filename: path.basename(filepath)
187
+ }
188
+ };
189
+ };
190
+ var collection_default = CoraliteCollection;
191
+ export {
192
+ collection_default as default
193
+ };
194
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../lib/collection.js"],
4
+ "sourcesContent": ["import path from 'node:path'\nimport { getHtmlFile } from './html.js'\nimport { access } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\n\n/**\n * @import {\n * CoraliteCollectionEventDelete,\n * CoraliteCollectionEventSet,\n * CoraliteCollectionEventUpdate,\n * CoraliteCollectionItem,\n * HTMLData } from '../types/index.js'\n */\n\n/**\n * Represents a collection of documents with methods to organize and retrieve them.\n * Maintains three views: flat list, path-based grouping, and ID lookup.\n * @class\n * @param {Object} [options={}] - Options for configuring the collection\n * @param {string} [options.rootDir=''] - The root directory path for the collection\n * @param {CoraliteCollectionEventSet} [options.onSet] - Event handler for when documents are set\n * @param {CoraliteCollectionEventUpdate} [options.onUpdate] - Event handler for when documents are updated\n * @param {CoraliteCollectionEventDelete} [options.onDelete] - Event handler for when documents are deleted\n */\nfunction CoraliteCollection (options = { rootDir: '' }) {\n /**\n * Root directory where collection items are located\n */\n this.rootDir = path.join(options.rootDir)\n\n if (!existsSync(this.rootDir)) {\n throw new Error('Root directory was not found: ' + this.rootDir)\n }\n\n /**\n * An array of HTMLData objects representing the list of documents.\n * @type {CoraliteCollectionItem[]}\n */\n this.list = []\n\n /**\n * An object mapping paths to arrays of HTMLData objects.\n * Used for grouping documents by their file path.\n * @type {Object.<string, CoraliteCollectionItem[]>}\n */\n this.listByPath = Object.create(null)\n\n /**\n * An object mapping unique identifiers to HTMLData objects.\n * Used for quick lookup of documents by their identifier.\n * @type {Object.<string, CoraliteCollectionItem>}\n */\n this.collection = Object.create(null)\n\n /**\n * Callback triggered when setting a new item\n * @type {CoraliteCollectionEventSet | undefined}\n */\n this._onSet = options.onSet\n\n /**\n * Callback triggered when updating an existing item\n * @type {CoraliteCollectionEventUpdate | undefined}\n */\n this._onUpdate = options.onUpdate\n\n /**\n * Callback triggered when deleting an item\n * @type {CoraliteCollectionEventDelete | undefined}\n */\n this._onDelete = options.onDelete\n}\n\n/**\n * Adds or updates an HTMLData object in the collection and associated lists.\n * If the item already exists, it will be updated in all views.\n * @param {HTMLData|string} value - The HTMLData object to be added or updated.\n * @returns {Promise<CoraliteCollectionItem>} The modified document\n */\nCoraliteCollection.prototype.setItem = async function (value) {\n if (typeof value === 'string') {\n value = await this._loadByPath(value)\n }\n\n if (!value || !value.path) {\n throw new Error('Valid HTMLData object must be provided')\n }\n\n const pathname = value.path.pathname\n const dirname = value.path.dirname\n const originalValue = this.collection[pathname]\n\n /** @type {CoraliteCollectionItem} */\n const documentValue = value\n\n if (!originalValue) {\n // handle pre-set hook if defined\n if (typeof this._onSet === 'function') {\n const result = await this._onSet(value)\n\n // abort adding item\n if (!result) {\n return\n }\n\n documentValue.result = result.value\n\n if (result.state) {\n documentValue.state = result.state\n }\n\n if (result.type === 'page' || result.type === 'component') {\n documentValue.type = result.type\n }\n\n // update collection using ID from hook result if available\n if (typeof result.id === 'string' && result.id) {\n this.collection[result.id] = documentValue\n }\n }\n\n // always update the collection with current pathname\n this.collection[pathname] = documentValue\n\n // initialize directory list if it doesn't exist\n if (!this.listByPath[dirname]) {\n this.listByPath[dirname] = []\n }\n\n // add to both directory-specific and general lists\n // check if already added to avoid duplicates\n if (!this.listByPath[dirname].includes(documentValue)) {\n this.listByPath[dirname].push(documentValue)\n }\n if (!this.list.includes(documentValue)) {\n this.list.push(documentValue)\n }\n } else {\n return await this.updateItem(value)\n }\n\n return documentValue\n}\n\n/**\n * Removes an HTMLData object from the collection and associated lists.\n * Accepts either an HTMLData object or a pathname string.\n * @param {HTMLData | string} value - The HTMLData object or a pathname to be removed.\n * @throws {Error} If invalid input is provided\n */\nCoraliteCollection.prototype.deleteItem = async function (value) {\n if (!value) {\n throw new Error('Valid pathname must be provided')\n }\n\n let pathname\n let dirname\n let valuesByPath\n let originalValue\n\n if (typeof value !== 'string' && value.path) {\n // if the input is an HTMLData object, extract its pathname and directory name\n pathname = value.path.pathname\n dirname = value.path.dirname\n valuesByPath = this.listByPath[dirname]\n originalValue = value\n } else if (typeof value === 'string') {\n // if the input is a string, use it as the pathname and determine the directory name\n pathname = value\n dirname = path.dirname(pathname)\n valuesByPath = this.listByPath[dirname]\n originalValue = this.collection[pathname]\n } else {\n throw new Error('Valid pathname must be provided')\n }\n\n if (!originalValue) {\n // item not found, nothing to delete\n return\n }\n\n if (!valuesByPath) {\n // directory list doesn't exist, but we still need to clean up collection\n // This can happen if the item was stored under a different ID\n for (const key in this.collection) {\n if (this.collection[key] === originalValue) {\n delete this.collection[key]\n }\n }\n return\n }\n\n if (typeof this._onDelete === 'function') {\n await this._onDelete(originalValue)\n }\n\n // remove the document from the collection\n // also check if it's stored under a different ID (from hook result)\n for (const key in this.collection) {\n if (this.collection[key] === originalValue) {\n delete this.collection[key]\n }\n }\n\n // find and remove the document from the list and by-path grouping\n const listIndex = this.list.indexOf(originalValue)\n const pathIndex = valuesByPath.indexOf(originalValue)\n\n if (listIndex !== -1) {\n this.list.splice(listIndex, 1)\n }\n if (pathIndex !== -1) {\n valuesByPath.splice(pathIndex, 1)\n }\n\n // clean up empty directory arrays\n if (valuesByPath.length === 0) {\n delete this.listByPath[dirname]\n }\n}\n\n/**\n * Updates an existing HTMLData object in the collection.\n * If the document does not exist, it will be added using the set method.\n * @param {CoraliteCollectionItem|string} value - The HTMLData object to be updated or added.\n * @throws {Error} If invalid input is provided\n */\nCoraliteCollection.prototype.updateItem = async function (value) {\n if (typeof value === 'string') {\n value = await this._loadByPath(value)\n }\n\n if (!value || !value.path) {\n throw new Error('Valid HTMLData object must be provided')\n }\n\n const pathname = value.path.pathname\n const originalValue = this.collection[pathname]\n\n if (!originalValue) {\n // if the document does not exist, add it using the set method\n return await this.setItem(value)\n }\n\n if (typeof this._onUpdate === 'function') {\n const result = await this._onUpdate(value, originalValue)\n\n // abort update\n if (!result) {\n return originalValue\n }\n\n // handle callback result\n if (result && typeof result === 'object') {\n // if result has a value property, use it\n if (result.value !== undefined) {\n originalValue.result = result.value\n } else {\n originalValue.result = result\n }\n\n // update type if provided\n if (result.type === 'page' || result.type === 'component') {\n originalValue.type = result.type\n }\n } else {\n originalValue.result = result\n }\n }\n\n // update core state\n if (value.content !== undefined) {\n originalValue.content = value.content\n }\n\n // update path information if it changed\n if (value.path && value.path !== originalValue.path) {\n originalValue.path = value.path\n }\n\n // update type if explicitly set\n if (value.type) {\n originalValue.type = value.type\n }\n\n // update any additional state\n if (value.state !== undefined) {\n originalValue.state = value.state\n }\n\n return originalValue\n}\n\n/**\n * Retrieves an item by its unique identifier\n * @param {string} id - Unique identifier of the item\n * @returns {CoraliteCollectionItem | undefined} The found item or undefined\n */\nCoraliteCollection.prototype.getItem = function (id) {\n if (!this.collection[id] && id.endsWith('html')) {\n id = path.join(this.rootDir, id)\n }\n\n return this.collection[id]\n}\n\n/**\n * Retrieves a list of items grouped by directory path\n * @param {string} dirname - Directory name to look up\n * @returns {CoraliteCollectionItem[] | undefined} A copy of the item list or undefined\n */\nCoraliteCollection.prototype.getListByPath = function (dirname) {\n const list = this.listByPath[dirname]\n\n if (list) {\n return list.slice()\n }\n}\n\n/**\n * Loads a collection item by its file path.\n *\n * @param {string} filepath - The path to the collection item file\n * @returns {Promise<HTMLData>} A promise that resolves to the loaded item object\n * @throws {Error} If the file cannot be found at either the provided path or within the root directory\n */\nCoraliteCollection.prototype._loadByPath = async function (filepath) {\n try {\n await access(filepath)\n } catch {\n try {\n filepath = path.join(this.rootDir, filepath)\n\n await access(filepath)\n } catch {\n throw new Error('Could not find collection item: ' + filepath)\n }\n }\n\n const content = await getHtmlFile(filepath)\n\n return {\n type: 'page',\n content,\n path: {\n pathname: filepath,\n dirname: path.dirname(filepath),\n filename: path.basename(filepath)\n }\n }\n}\n\nexport default CoraliteCollection\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ function defineConfig(options) {
2
+ if (!options || typeof options !== "object") {
3
+ throw new Error("Config must be an object");
4
+ }
5
+ const requiredProps = ["output", "components", "pages"];
6
+ for (const prop of requiredProps) {
7
+ if (!(prop in options)) {
8
+ throw new Error(`Missing required config property: "${prop}"`);
9
+ }
10
+ if (typeof options[prop] !== "string") {
11
+ throw new Error(
12
+ `Config property "${prop}" must be a string, received ${typeof options[prop]}`
13
+ );
14
+ }
15
+ if (options[prop].trim() === "") {
16
+ throw new Error(`Config property "${prop}" cannot be empty`);
17
+ }
18
+ }
19
+ if ("plugins" in options && options.plugins !== void 0) {
20
+ if (!Array.isArray(options.plugins)) {
21
+ throw new Error(
22
+ `Config property "plugins" must be an array, received ${typeof options.plugins}`
23
+ );
24
+ }
25
+ options.plugins.forEach((plugin, index) => {
26
+ if (typeof plugin !== "object" || plugin === null) {
27
+ throw new Error(
28
+ `Plugin at index ${index} must be an object, received ${typeof plugin}`
29
+ );
30
+ }
31
+ if (typeof plugin.name !== "string" || plugin.name.trim() === "") {
32
+ throw new Error(
33
+ `Plugin at index ${index} must have a valid "name" property (non-empty string)`
34
+ );
35
+ }
36
+ });
37
+ }
38
+ return options;
39
+ }
40
+ export {
41
+ defineConfig
42
+ };
43
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../lib/config.js"],
4
+ "sourcesContent": ["/**\n * @import {CoraliteConfig} from '../types/index.js'\n */\n\n/**\n * Validates and defines a Coralite configuration object\n * @param {CoraliteConfig} options - The configuration options to validate and define\n * @returns {CoraliteConfig} The validated configuration object\n * @throws {Error} If the configuration is invalid\n */\nexport function defineConfig (options) {\n // Validate that options is an object\n if (!options || typeof options !== 'object') {\n throw new Error('Config must be an object')\n }\n\n // Validate required string state\n const requiredProps = ['output', 'components', 'pages']\n\n for (const prop of requiredProps) {\n // Check if property exists\n if (!(prop in options)) {\n throw new Error(`Missing required config property: \"${prop}\"`)\n }\n\n // Check if property is a string\n if (typeof options[prop] !== 'string') {\n throw new Error(\n `Config property \"${prop}\" must be a string, received ${typeof options[prop]}`\n )\n }\n\n // Check if property is not empty\n if (options[prop].trim() === '') {\n throw new Error(`Config property \"${prop}\" cannot be empty`)\n }\n }\n\n // Validate optional plugins property\n if ('plugins' in options && options.plugins !== undefined) {\n if (!Array.isArray(options.plugins)) {\n throw new Error(\n `Config property \"plugins\" must be an array, received ${typeof options.plugins}`\n )\n }\n\n // Validate each plugin in the array\n options.plugins.forEach((plugin, index) => {\n if (typeof plugin !== 'object' || plugin === null) {\n throw new Error(\n `Plugin at index ${index} must be an object, received ${typeof plugin}`\n )\n }\n\n if (typeof plugin.name !== 'string' || plugin.name.trim() === '') {\n throw new Error(\n `Plugin at index ${index} must have a valid \"name\" property (non-empty string)`\n )\n }\n })\n }\n\n return options\n}\n"],
5
+ "mappings": "AAUO,SAAS,aAAc,SAAS;AAErC,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAGA,QAAM,gBAAgB,CAAC,UAAU,cAAc,OAAO;AAEtD,aAAW,QAAQ,eAAe;AAEhC,QAAI,EAAE,QAAQ,UAAU;AACtB,YAAM,IAAI,MAAM,sCAAsC,IAAI,GAAG;AAAA,IAC/D;AAGA,QAAI,OAAO,QAAQ,IAAI,MAAM,UAAU;AACrC,YAAM,IAAI;AAAA,QACR,oBAAoB,IAAI,gCAAgC,OAAO,QAAQ,IAAI,CAAC;AAAA,MAC9E;AAAA,IACF;AAGA,QAAI,QAAQ,IAAI,EAAE,KAAK,MAAM,IAAI;AAC/B,YAAM,IAAI,MAAM,oBAAoB,IAAI,mBAAmB;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,aAAa,WAAW,QAAQ,YAAY,QAAW;AACzD,QAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAAG;AACnC,YAAM,IAAI;AAAA,QACR,wDAAwD,OAAO,QAAQ,OAAO;AAAA,MAChF;AAAA,IACF;AAGA,YAAQ,QAAQ,QAAQ,CAAC,QAAQ,UAAU;AACzC,UAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,cAAM,IAAI;AAAA,UACR,mBAAmB,KAAK,gCAAgC,OAAO,MAAM;AAAA,QACvE;AAAA,MACF;AAEA,UAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,KAAK,MAAM,IAAI;AAChE,cAAM,IAAI;AAAA,UACR,mBAAmB,KAAK;AAAA,QAC1B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }