contentbase 0.0.4 → 0.0.5

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 (82) hide show
  1. package/package.json +1 -1
  2. package/.mcp.json +0 -9
  3. package/MCP-DESCRIPTIONS-REVIEW.md +0 -122
  4. package/bun.lock +0 -512
  5. package/dist/cnotes +0 -0
  6. package/examples/sdlc-queries.ts +0 -161
  7. package/public/web-demo/index.html +0 -813
  8. package/scripts/examples/01-collection-setup.ts +0 -46
  9. package/scripts/examples/02-querying.ts +0 -67
  10. package/scripts/examples/03-sections.ts +0 -36
  11. package/scripts/examples/04-relationships.ts +0 -54
  12. package/scripts/examples/05-document-api.ts +0 -54
  13. package/scripts/examples/06-extract-sections.ts +0 -55
  14. package/scripts/examples/07-validation.ts +0 -46
  15. package/scripts/examples/08-serialization.ts +0 -51
  16. package/scripts/examples/lib/format.ts +0 -87
  17. package/scripts/examples/lib/setup.ts +0 -21
  18. package/scripts/examples/run-all.ts +0 -43
  19. package/showcases/national-parks/models.ts +0 -74
  20. package/showcases/national-parks/parks/acadia.mdx +0 -40
  21. package/showcases/national-parks/parks/yosemite.mdx +0 -44
  22. package/showcases/national-parks/parks/zion.mdx +0 -44
  23. package/showcases/national-parks/queries.ts +0 -103
  24. package/showcases/national-parks/trails/angels-landing.mdx +0 -19
  25. package/showcases/national-parks/trails/cathedral-lakes.mdx +0 -19
  26. package/showcases/national-parks/trails/half-dome.mdx +0 -19
  27. package/showcases/national-parks/trails/jordan-pond-path.mdx +0 -19
  28. package/showcases/national-parks/trails/mist-trail.mdx +0 -19
  29. package/showcases/national-parks/trails/observation-point.mdx +0 -19
  30. package/showcases/national-parks/trails/precipice-trail.mdx +0 -19
  31. package/showcases/national-parks/trails/the-narrows.mdx +0 -19
  32. package/showcases/node_modules/.cache/.repl_history +0 -3
  33. package/showcases/recipes/cuisines/chinese.mdx +0 -28
  34. package/showcases/recipes/cuisines/italian.mdx +0 -32
  35. package/showcases/recipes/cuisines/mexican.mdx +0 -28
  36. package/showcases/recipes/models.ts +0 -77
  37. package/showcases/recipes/queries.ts +0 -89
  38. package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +0 -43
  39. package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +0 -47
  40. package/showcases/recipes/recipes/italian/bruschetta.mdx +0 -38
  41. package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +0 -39
  42. package/showcases/recipes/recipes/italian/tiramisu.mdx +0 -43
  43. package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +0 -44
  44. package/showcases/recipes/recipes/mexican/guacamole.mdx +0 -39
  45. package/showcases/vinyl-collection/albums/bitches-brew.mdx +0 -36
  46. package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +0 -35
  47. package/showcases/vinyl-collection/albums/in-rainbows.mdx +0 -35
  48. package/showcases/vinyl-collection/albums/kind-of-blue.mdx +0 -32
  49. package/showcases/vinyl-collection/albums/ok-computer.mdx +0 -37
  50. package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +0 -35
  51. package/showcases/vinyl-collection/artists/miles-davis.mdx +0 -27
  52. package/showcases/vinyl-collection/artists/nina-simone.mdx +0 -26
  53. package/showcases/vinyl-collection/artists/radiohead.mdx +0 -27
  54. package/showcases/vinyl-collection/models.ts +0 -73
  55. package/showcases/vinyl-collection/queries.ts +0 -87
  56. package/src/__tests__/semantic-search.integration.test.ts +0 -284
  57. package/test/ast-query.test.ts +0 -128
  58. package/test/collection.test.ts +0 -215
  59. package/test/define-model.test.ts +0 -78
  60. package/test/document.test.ts +0 -225
  61. package/test/extract-sections.test.ts +0 -356
  62. package/test/fixtures/sdlc/MODELS.md +0 -106
  63. package/test/fixtures/sdlc/SKILL.md +0 -404
  64. package/test/fixtures/sdlc/epics/authentication.mdx +0 -42
  65. package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +0 -21
  66. package/test/fixtures/sdlc/index.ts +0 -9
  67. package/test/fixtures/sdlc/models.ts +0 -91
  68. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-login.mdx +0 -20
  69. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +0 -20
  70. package/test/fixtures/sdlc/templates/epic.md +0 -23
  71. package/test/fixtures/sdlc/templates/story.md +0 -19
  72. package/test/helpers.ts +0 -21
  73. package/test/model-instance.test.ts +0 -197
  74. package/test/pattern.test.ts +0 -191
  75. package/test/query-dsl.test.ts +0 -431
  76. package/test/query.test.ts +0 -196
  77. package/test/relationships.test.ts +0 -214
  78. package/test/section.test.ts +0 -160
  79. package/test/table-of-contents.test.ts +0 -135
  80. package/test/validator.test.ts +0 -62
  81. package/tsconfig.json +0 -18
  82. package/vitest.config.ts +0 -11
@@ -1,215 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import fs from "fs/promises";
3
- import path from "path";
4
- import { Collection } from "../src/collection";
5
- import { createTestCollection, FIXTURES_PATH } from "./helpers";
6
- import { Epic, Story } from "./fixtures/sdlc/models";
7
-
8
- describe("Collection", () => {
9
- let collection: Collection;
10
-
11
- beforeEach(async () => {
12
- collection = await createTestCollection();
13
- });
14
-
15
- it("loads all mdx files from rootPath", () => {
16
- expect(collection.loaded).toBe(true);
17
- expect(collection.available.length).toBeGreaterThanOrEqual(3);
18
- });
19
-
20
- it("returns correct path IDs", () => {
21
- expect(collection.available).toContain("epics/authentication");
22
- expect(collection.available).toContain("epics/searching-and-browsing");
23
- expect(collection.available).toContain(
24
- "stories/authentication/a-user-should-be-able-to-register"
25
- );
26
- });
27
-
28
- it("lazily creates documents", () => {
29
- const doc = collection.document("epics/authentication");
30
- expect(doc).toBeDefined();
31
- expect(doc.id).toBe("epics/authentication");
32
- });
33
-
34
- it("caches documents", () => {
35
- const doc1 = collection.document("epics/authentication");
36
- const doc2 = collection.document("epics/authentication");
37
- expect(doc1).toBe(doc2);
38
- });
39
-
40
- it("throws if not loaded", () => {
41
- const fresh = new Collection({ rootPath: FIXTURES_PATH });
42
- expect(() => fresh.document("epics/authentication")).toThrow(
43
- "Collection has not been loaded"
44
- );
45
- });
46
-
47
- it("throws for unknown pathId", () => {
48
- expect(() => collection.document("nonexistent")).toThrow(
49
- 'Could not find document "nonexistent"'
50
- );
51
- });
52
-
53
- it("registers model definitions", () => {
54
- expect(collection.getModelDefinition("Epic")).toBeDefined();
55
- expect(collection.getModelDefinition("Story")).toBeDefined();
56
- expect(collection.getModelDefinition("Base")).toBeDefined();
57
- expect(collection.modelDefinitions.length).toBe(3);
58
- });
59
-
60
- it("finds model definition by pathId prefix", () => {
61
- const def = collection.findModelDefinition("epics/authentication");
62
- expect(def?.name).toBe("Epic");
63
- });
64
-
65
- it("gets typed model instances", () => {
66
- const epic = collection.getModel("epics/authentication", Epic);
67
- expect(epic.id).toBe("epics/authentication");
68
- expect(epic.title).toBe("Authentication");
69
- expect(epic.meta.status).toBe("created");
70
- });
71
-
72
- it("creates typed queries", async () => {
73
- const epics = await collection.query(Epic).fetchAll();
74
- expect(epics.length).toBe(2);
75
- expect(epics[0].meta.status).toBeDefined();
76
- });
77
-
78
- it("serializes to JSON", () => {
79
- const json = collection.toJSON();
80
- expect(json.models).toBeDefined();
81
- expect(json.itemIds).toBeDefined();
82
- });
83
-
84
- it("supports actions", async () => {
85
- let called = false;
86
- collection.action("test-action", () => {
87
- called = true;
88
- });
89
- expect(collection.availableActions).toContain("test-action");
90
- await collection.runAction("test-action");
91
- expect(called).toBe(true);
92
- });
93
-
94
- it("supports plugins", () => {
95
- let pluginCalled = false;
96
- collection.use((coll) => {
97
- pluginCalled = true;
98
- expect(coll).toBe(collection);
99
- });
100
- expect(pluginCalled).toBe(true);
101
- });
102
-
103
- describe("refresh after delete", () => {
104
- let tmpDir: string;
105
- let tmpCollection: Collection;
106
-
107
- beforeEach(async () => {
108
- tmpDir = await fs.mkdtemp(path.join(import.meta.dirname, ".tmp-refresh-"));
109
- // Create two documents
110
- const epicDir = path.join(tmpDir, "epics");
111
- await fs.mkdir(epicDir, { recursive: true });
112
- await fs.writeFile(
113
- path.join(epicDir, "one.mdx"),
114
- "---\nstatus: created\npriority: 1\n---\n# One\n"
115
- );
116
- await fs.writeFile(
117
- path.join(epicDir, "two.mdx"),
118
- "---\nstatus: created\npriority: 2\n---\n# Two\n"
119
- );
120
-
121
- tmpCollection = new Collection({ rootPath: tmpDir });
122
- tmpCollection.register(Epic);
123
- await tmpCollection.load();
124
- });
125
-
126
- afterEach(async () => {
127
- await fs.rm(tmpDir, { recursive: true });
128
- });
129
-
130
- it("evicts cached documents whose files were deleted", async () => {
131
- // Access a document so it's in the #documents cache
132
- const doc = tmpCollection.document("epics/one");
133
- expect(doc.title).toBe("One");
134
-
135
- // Delete the file externally
136
- await fs.unlink(path.join(tmpDir, "epics", "one.mdx"));
137
-
138
- // Refresh should succeed without ENOENT
139
- await tmpCollection.load({ refresh: true });
140
-
141
- // The deleted document should no longer be available
142
- expect(tmpCollection.available).not.toContain("epics/one");
143
- expect(tmpCollection.available).toContain("epics/two");
144
- });
145
- });
146
-
147
- describe("generateModelSummary", () => {
148
- let tmpDir: string;
149
- let tmpCollection: Collection;
150
-
151
- beforeEach(async () => {
152
- tmpDir = await fs.mkdtemp(path.join(import.meta.dirname, ".tmp-summary-"));
153
- tmpCollection = new Collection({ rootPath: tmpDir });
154
- tmpCollection.register(Epic);
155
- tmpCollection.register(Story);
156
- await tmpCollection.load();
157
- });
158
-
159
- afterEach(async () => {
160
- await fs.rm(tmpDir, { recursive: true });
161
- });
162
-
163
- it("generates inspect-style text with model info", () => {
164
- const text = tmpCollection.generateModelSummary();
165
-
166
- // Collection header
167
- expect(text).toContain("Collection:");
168
- expect(text).toContain("Root:");
169
- expect(text).toContain("Items:");
170
-
171
- // Model entries
172
- expect(text).toContain("Model: Epic");
173
- expect(text).toContain("Model: Story");
174
-
175
- // Prefixes
176
- expect(text).toContain("Prefix: epics");
177
- expect(text).toContain("Prefix: stories");
178
-
179
- // Meta attributes
180
- expect(text).toContain("priority");
181
- expect(text).toContain("status");
182
-
183
- // Sections
184
- expect(text).toContain("Sections:");
185
-
186
- // Relationships
187
- expect(text).toContain("Relationships:");
188
- });
189
-
190
- it("saveModelSummary writes README.md to rootPath", async () => {
191
- await tmpCollection.saveModelSummary();
192
- const content = await fs.readFile(path.join(tmpDir, "README.md"), "utf8");
193
- expect(content).toContain("# Models");
194
- expect(content).toContain("## Overview");
195
- expect(content).toContain("## Summary");
196
- });
197
-
198
- it("saveModelSummary preserves existing Overview content", async () => {
199
- // Write an initial README.md with user content in Overview
200
- const initial = "# Models\n\n## Overview\n\nThis is my custom overview.\n\n## Summary\n\n```\nold summary\n```\n";
201
- await fs.writeFile(path.join(tmpDir, "README.md"), initial, "utf8");
202
-
203
- await tmpCollection.saveModelSummary();
204
- const content = await fs.readFile(path.join(tmpDir, "README.md"), "utf8");
205
- expect(content).toContain("This is my custom overview.");
206
- expect(content).toContain("Model: Epic");
207
- });
208
-
209
- it("includes collection actions", () => {
210
- tmpCollection.action("deploy", () => {});
211
- const text = tmpCollection.generateModelSummary();
212
- expect(text).toContain("Actions: deploy");
213
- });
214
- });
215
- });
@@ -1,78 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { defineModel, z, section, hasMany, belongsTo } from "../src/index";
3
-
4
- describe("defineModel", () => {
5
- it("creates a ModelDefinition with the given name", () => {
6
- const MyModel = defineModel("MyModel");
7
- expect(MyModel.name).toBe("MyModel");
8
- });
9
-
10
- it("auto-pluralizes the prefix from the name", () => {
11
- const Epic = defineModel("Epic");
12
- expect(Epic.prefix).toBe("epics");
13
- });
14
-
15
- it("uses custom prefix when provided", () => {
16
- const MyModel = defineModel("MyModel", { prefix: "custom" });
17
- expect(MyModel.prefix).toBe("custom");
18
- });
19
-
20
- it("stores the meta Zod schema", () => {
21
- const schema = z.object({ status: z.string() });
22
- const MyModel = defineModel("MyModel", { meta: schema });
23
- expect(MyModel.meta).toBe(schema);
24
- expect(MyModel.schema).toBe(schema);
25
- });
26
-
27
- it("creates passthrough schema when meta is omitted", () => {
28
- const MyModel = defineModel("MyModel");
29
- const result = MyModel.meta.safeParse({ anything: "goes" });
30
- expect(result.success).toBe(true);
31
- });
32
-
33
- it("stores sections", () => {
34
- const MyModel = defineModel("MyModel", {
35
- sections: {
36
- items: section("Items", {
37
- extract: (q) => q.selectAll("listItem"),
38
- }),
39
- },
40
- });
41
- expect(MyModel.sections.items.heading).toBe("Items");
42
- });
43
-
44
- it("stores relationships", () => {
45
- const Target = defineModel("Target");
46
- const MyModel = defineModel("MyModel", {
47
- relationships: {
48
- targets: hasMany(() => Target, { heading: "Targets" }),
49
- },
50
- });
51
- expect(MyModel.relationships.targets.type).toBe("hasMany");
52
- expect(MyModel.relationships.targets.heading).toBe("Targets");
53
- });
54
-
55
- it("stores computed properties", () => {
56
- const MyModel = defineModel("MyModel", {
57
- computed: {
58
- foo: () => 42,
59
- },
60
- });
61
- expect(MyModel.computed.foo).toBeDefined();
62
- expect(MyModel.computed.foo({})).toBe(42);
63
- });
64
-
65
- it("stores match function", () => {
66
- const fn = (doc: any) => doc.id.startsWith("special");
67
- const MyModel = defineModel("MyModel", { match: fn });
68
- expect(MyModel.match).toBe(fn);
69
- });
70
-
71
- it("stores defaults", () => {
72
- const MyModel = defineModel("MyModel", {
73
- meta: z.object({ status: z.string().default("draft") }),
74
- defaults: { status: "draft" },
75
- });
76
- expect(MyModel.defaults).toEqual({ status: "draft" });
77
- });
78
- });
@@ -1,225 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { Collection } from "../src/collection";
3
- import { createTestCollection } from "./helpers";
4
- import { toString } from "mdast-util-to-string";
5
-
6
- describe("Document", () => {
7
- let collection: Collection;
8
-
9
- beforeEach(async () => {
10
- collection = await createTestCollection();
11
- });
12
-
13
- it("lazily parses AST", () => {
14
- const doc = collection.document("epics/authentication");
15
- // Content should exist but AST is not parsed yet
16
- expect(doc.content).toBeDefined();
17
- // Accessing ast triggers parsing
18
- expect(doc.ast.type).toBe("root");
19
- expect(doc.ast.children.length).toBeGreaterThan(0);
20
- });
21
-
22
- it("extracts title from first heading", () => {
23
- const doc = collection.document("epics/authentication");
24
- expect(doc.title).toBe("Authentication");
25
- });
26
-
27
- it("falls back to id if no heading", () => {
28
- const doc = collection.createDocument({
29
- id: "test/no-heading",
30
- content: "Just some text without a heading.",
31
- });
32
- expect(doc.title).toBe("test/no-heading");
33
- });
34
-
35
- it("generates slug from title", () => {
36
- const doc = collection.document("epics/authentication");
37
- expect(doc.slug).toBe("authentication");
38
- });
39
-
40
- it("exposes parsed frontmatter as meta", () => {
41
- const doc = collection.document("epics/authentication");
42
- expect(doc.meta.priority).toBe("high");
43
- expect(doc.meta.status).toBe("created");
44
- });
45
-
46
- it("serializes rawContent with frontmatter", () => {
47
- const doc = collection.document("epics/authentication");
48
- expect(doc.rawContent).toContain("---");
49
- expect(doc.rawContent).toContain("priority: high");
50
- expect(doc.rawContent).toContain("# Authentication");
51
- });
52
-
53
- it("omits frontmatter block if meta is empty", () => {
54
- const doc = collection.createDocument({
55
- id: "test/no-meta",
56
- content: "# Hello\n",
57
- });
58
- expect(doc.rawContent).not.toContain("---");
59
- });
60
-
61
- it("provides astQuery", () => {
62
- const doc = collection.document("epics/authentication");
63
- expect(doc.astQuery.select("heading")).toBeDefined();
64
- });
65
-
66
- it("provides nodes shortcuts", () => {
67
- const doc = collection.document("epics/authentication");
68
- expect(doc.nodes.headings.length).toBeGreaterThan(0);
69
- expect(doc.nodes.firstHeading).toBeDefined();
70
- });
71
-
72
- describe("extractSection", () => {
73
- it("extracts section by heading text", () => {
74
- const doc = collection.document("epics/authentication");
75
- const section = doc.extractSection("Stories");
76
- expect(section.length).toBeGreaterThan(1);
77
- expect(toString(section[0])).toBe("Stories");
78
- });
79
-
80
- it("throws for non-existent heading", () => {
81
- const doc = collection.document("epics/authentication");
82
- expect(() => doc.extractSection("Nonexistent")).toThrow(
83
- "Heading not found"
84
- );
85
- });
86
- });
87
-
88
- describe("querySection", () => {
89
- it("returns scoped AstQuery without the heading", () => {
90
- const doc = collection.document("epics/authentication");
91
- const query = doc.querySection("Stories");
92
- const headings = query.selectAll("heading");
93
- // Should have the story sub-headings but not "Stories" itself
94
- expect(headings.length).toBeGreaterThan(0);
95
- });
96
-
97
- it("returns empty query for non-existent heading", () => {
98
- const doc = collection.document("epics/authentication");
99
- const query = doc.querySection("Nonexistent");
100
- expect(query.selectAll("heading").length).toBe(0);
101
- });
102
- });
103
-
104
- describe("immutable section mutations", () => {
105
- it("removeSection returns new Document", () => {
106
- const doc = collection.document("epics/authentication");
107
- const originalContent = doc.content;
108
- const newDoc = doc.removeSection("Stories");
109
- expect(newDoc).not.toBe(doc);
110
- expect(doc.content).toBe(originalContent);
111
- expect(newDoc.content).not.toContain("A User should be able to register");
112
- });
113
-
114
- it("replaceSectionContent returns new Document", () => {
115
- const doc = collection.document("epics/authentication");
116
- const newDoc = doc.replaceSectionContent(
117
- "Stories",
118
- "New stories content here."
119
- );
120
- expect(newDoc).not.toBe(doc);
121
- expect(newDoc.content).toContain("New stories content here");
122
- });
123
-
124
- it("insertBefore returns new Document", () => {
125
- const doc = collection.document("epics/authentication");
126
- const heading = doc.astQuery.findHeadingByText("Stories");
127
- expect(heading).toBeDefined();
128
- const newDoc = doc.insertBefore(heading!, "Inserted before.");
129
- expect(newDoc).not.toBe(doc);
130
- expect(newDoc.content).toContain("Inserted before");
131
- });
132
-
133
- it("insertAfter returns new Document", () => {
134
- const doc = collection.document("epics/authentication");
135
- const heading = doc.astQuery.findHeadingByText("Stories");
136
- expect(heading).toBeDefined();
137
- const newDoc = doc.insertAfter(heading!, "Inserted after.");
138
- expect(newDoc).not.toBe(doc);
139
- expect(newDoc.content).toContain("Inserted after");
140
- });
141
-
142
- it("appendToSection returns new Document", () => {
143
- const doc = collection.document("epics/authentication");
144
- const newDoc = doc.appendToSection(
145
- "Stories",
146
- "### New Story\n\nAppended story."
147
- );
148
- expect(newDoc).not.toBe(doc);
149
- expect(newDoc.content).toContain("New Story");
150
- });
151
- });
152
-
153
- describe("mutable mutations", () => {
154
- it("removeSection with mutate modifies in place", () => {
155
- const doc = collection.createDocument({
156
- id: "test/mutable",
157
- content:
158
- "# Test\n\n## Section A\n\nContent A\n\n## Section B\n\nContent B\n",
159
- });
160
- const result = doc.removeSection("Section A", { mutate: true });
161
- expect(result).toBe(doc);
162
- expect(doc.content).not.toContain("Content A");
163
- expect(doc.content).toContain("Content B");
164
- });
165
- });
166
-
167
- it("replaceContent returns new Document", () => {
168
- const doc = collection.document("epics/authentication");
169
- const newDoc = doc.replaceContent("# Completely new content");
170
- expect(newDoc).not.toBe(doc);
171
- expect(newDoc.content).toBe("# Completely new content");
172
- });
173
-
174
- it("appendContent returns new Document", () => {
175
- const doc = collection.createDocument({
176
- id: "test/append",
177
- content: "# Start\n",
178
- });
179
- const newDoc = doc.appendContent("\n## Added\n");
180
- expect(newDoc).not.toBe(doc);
181
- expect(newDoc.content).toContain("Added");
182
- });
183
-
184
- it("toJSON serializes document", () => {
185
- const doc = collection.document("epics/authentication");
186
- const json = doc.toJSON();
187
- expect(json.id).toBe("epics/authentication");
188
- expect(json.meta).toBeDefined();
189
- expect(json.content).toBeDefined();
190
- expect(json.ast).toBeDefined();
191
- });
192
-
193
- it("toText extracts plain text", () => {
194
- const doc = collection.document("epics/authentication");
195
- const text = doc.toText();
196
- expect(text).toContain("Authentication");
197
- });
198
-
199
- describe("toOutline", () => {
200
- it("returns indented heading outline", () => {
201
- const doc = collection.document("epics/authentication");
202
- const outline = doc.toOutline();
203
- expect(outline).toBe(
204
- [
205
- "- Authentication",
206
- " - Stories",
207
- " - A User should be able to register.",
208
- " - Acceptance Criteria",
209
- " - Mockups",
210
- " - A User should be able to login.",
211
- " - Acceptance Criteria",
212
- " - Mockups",
213
- ].join("\n")
214
- );
215
- });
216
-
217
- it("returns empty string for document with no headings", () => {
218
- const doc = collection.createDocument({
219
- id: "test/no-headings",
220
- content: "Just some text without any headings.",
221
- });
222
- expect(doc.toOutline()).toBe("");
223
- });
224
- });
225
- });