@objectstack/metadata 0.7.1

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 (41) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +202 -0
  3. package/README.md +201 -0
  4. package/dist/index.d.ts +14 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +11 -0
  7. package/dist/loaders/filesystem-loader.d.ts +41 -0
  8. package/dist/loaders/filesystem-loader.d.ts.map +1 -0
  9. package/dist/loaders/filesystem-loader.js +260 -0
  10. package/dist/loaders/loader-interface.d.ts +52 -0
  11. package/dist/loaders/loader-interface.d.ts.map +1 -0
  12. package/dist/loaders/loader-interface.js +6 -0
  13. package/dist/metadata-manager.d.ts +69 -0
  14. package/dist/metadata-manager.d.ts.map +1 -0
  15. package/dist/metadata-manager.js +263 -0
  16. package/dist/serializers/json-serializer.d.ts +20 -0
  17. package/dist/serializers/json-serializer.d.ts.map +1 -0
  18. package/dist/serializers/json-serializer.js +53 -0
  19. package/dist/serializers/serializer-interface.d.ts +57 -0
  20. package/dist/serializers/serializer-interface.d.ts.map +1 -0
  21. package/dist/serializers/serializer-interface.js +6 -0
  22. package/dist/serializers/serializers.test.d.ts +2 -0
  23. package/dist/serializers/serializers.test.d.ts.map +1 -0
  24. package/dist/serializers/serializers.test.js +62 -0
  25. package/dist/serializers/typescript-serializer.d.ts +18 -0
  26. package/dist/serializers/typescript-serializer.d.ts.map +1 -0
  27. package/dist/serializers/typescript-serializer.js +103 -0
  28. package/dist/serializers/yaml-serializer.d.ts +16 -0
  29. package/dist/serializers/yaml-serializer.d.ts.map +1 -0
  30. package/dist/serializers/yaml-serializer.js +35 -0
  31. package/package.json +37 -0
  32. package/src/index.ts +34 -0
  33. package/src/loaders/filesystem-loader.ts +314 -0
  34. package/src/loaders/loader-interface.ts +70 -0
  35. package/src/metadata-manager.ts +338 -0
  36. package/src/serializers/json-serializer.ts +71 -0
  37. package/src/serializers/serializer-interface.ts +63 -0
  38. package/src/serializers/serializers.test.ts +74 -0
  39. package/src/serializers/typescript-serializer.ts +125 -0
  40. package/src/serializers/yaml-serializer.ts +47 -0
  41. package/tsconfig.json +11 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * JSON Metadata Serializer
3
+ *
4
+ * Handles JSON format serialization and deserialization
5
+ */
6
+
7
+ import type { z } from 'zod';
8
+ import type { MetadataFormat } from '@objectstack/spec/system';
9
+ import type { MetadataSerializer, SerializeOptions } from './serializer-interface.js';
10
+
11
+ export class JSONSerializer implements MetadataSerializer {
12
+ serialize<T>(item: T, options?: SerializeOptions): string {
13
+ const { prettify = true, indent = 2, sortKeys = false } = options || {};
14
+
15
+ if (sortKeys) {
16
+ // Sort keys recursively
17
+ const sorted = this.sortObjectKeys(item);
18
+ return prettify
19
+ ? JSON.stringify(sorted, null, indent)
20
+ : JSON.stringify(sorted);
21
+ }
22
+
23
+ return prettify
24
+ ? JSON.stringify(item, null, indent)
25
+ : JSON.stringify(item);
26
+ }
27
+
28
+ deserialize<T>(content: string, schema?: z.ZodSchema): T {
29
+ const parsed = JSON.parse(content);
30
+
31
+ if (schema) {
32
+ return schema.parse(parsed) as T;
33
+ }
34
+
35
+ return parsed as T;
36
+ }
37
+
38
+ getExtension(): string {
39
+ return '.json';
40
+ }
41
+
42
+ canHandle(format: MetadataFormat): boolean {
43
+ return format === 'json';
44
+ }
45
+
46
+ getFormat(): MetadataFormat {
47
+ return 'json';
48
+ }
49
+
50
+ /**
51
+ * Recursively sort object keys
52
+ */
53
+ private sortObjectKeys(obj: any): any {
54
+ if (obj === null || typeof obj !== 'object') {
55
+ return obj;
56
+ }
57
+
58
+ if (Array.isArray(obj)) {
59
+ return obj.map(item => this.sortObjectKeys(item));
60
+ }
61
+
62
+ const sorted: Record<string, any> = {};
63
+ const keys = Object.keys(obj).sort();
64
+
65
+ for (const key of keys) {
66
+ sorted[key] = this.sortObjectKeys(obj[key]);
67
+ }
68
+
69
+ return sorted;
70
+ }
71
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Metadata Serializer Interface
3
+ *
4
+ * Defines the contract for serializing/deserializing metadata
5
+ */
6
+
7
+ import type { z } from 'zod';
8
+ import type { MetadataFormat } from '@objectstack/spec/system';
9
+
10
+ /**
11
+ * Serialization options
12
+ */
13
+ export interface SerializeOptions {
14
+ /** Prettify output (formatted with indentation) */
15
+ prettify?: boolean;
16
+ /** Indentation size (spaces) */
17
+ indent?: number;
18
+ /** Sort object keys alphabetically */
19
+ sortKeys?: boolean;
20
+ /** Include default values in output */
21
+ includeDefaults?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Abstract interface for metadata serializers
26
+ * Implementations handle different formats (JSON, YAML, TypeScript, etc.)
27
+ */
28
+ export interface MetadataSerializer {
29
+ /**
30
+ * Serialize object to string
31
+ * @param item The item to serialize
32
+ * @param options Serialization options
33
+ * @returns Serialized string
34
+ */
35
+ serialize<T>(item: T, options?: SerializeOptions): string;
36
+
37
+ /**
38
+ * Deserialize string to object
39
+ * @param content The content to deserialize
40
+ * @param schema Optional Zod schema for validation
41
+ * @returns Deserialized object
42
+ */
43
+ deserialize<T>(content: string, schema?: z.ZodSchema): T;
44
+
45
+ /**
46
+ * Get file extension for this format
47
+ * @returns File extension (e.g., '.json', '.yaml')
48
+ */
49
+ getExtension(): string;
50
+
51
+ /**
52
+ * Check if this serializer can handle the format
53
+ * @param format The format to check
54
+ * @returns True if can handle
55
+ */
56
+ canHandle(format: MetadataFormat): boolean;
57
+
58
+ /**
59
+ * Get the format this serializer handles
60
+ * @returns The metadata format
61
+ */
62
+ getFormat(): MetadataFormat;
63
+ }
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { JSONSerializer } from '../serializers/json-serializer';
3
+ import { YAMLSerializer } from '../serializers/yaml-serializer';
4
+ import { TypeScriptSerializer } from '../serializers/typescript-serializer';
5
+
6
+ describe('Serializers', () => {
7
+ describe('JSONSerializer', () => {
8
+ const serializer = new JSONSerializer();
9
+
10
+ it('should serialize to JSON', () => {
11
+ const data = { name: 'test', value: 42 };
12
+ const result = serializer.serialize(data);
13
+ expect(result).toContain('"name"');
14
+ expect(result).toContain('"test"');
15
+ });
16
+
17
+ it('should deserialize from JSON', () => {
18
+ const json = '{"name":"test","value":42}';
19
+ const result = serializer.deserialize(json);
20
+ expect(result).toEqual({ name: 'test', value: 42 });
21
+ });
22
+
23
+ it('should prettify JSON', () => {
24
+ const data = { name: 'test' };
25
+ const result = serializer.serialize(data, { prettify: true, indent: 2 });
26
+ expect(result).toContain('\n');
27
+ expect(result).toContain(' ');
28
+ });
29
+
30
+ it('should sort keys', () => {
31
+ const data = { zebra: 1, apple: 2, banana: 3 };
32
+ const result = serializer.serialize(data, { sortKeys: true });
33
+ const keys = Object.keys(JSON.parse(result));
34
+ expect(keys).toEqual(['apple', 'banana', 'zebra']);
35
+ });
36
+ });
37
+
38
+ describe('YAMLSerializer', () => {
39
+ const serializer = new YAMLSerializer();
40
+
41
+ it('should serialize to YAML', () => {
42
+ const data = { name: 'test', value: 42 };
43
+ const result = serializer.serialize(data);
44
+ expect(result).toContain('name: test');
45
+ expect(result).toContain('value: 42');
46
+ });
47
+
48
+ it('should deserialize from YAML', () => {
49
+ const yaml = 'name: test\nvalue: 42';
50
+ const result = serializer.deserialize(yaml);
51
+ expect(result).toEqual({ name: 'test', value: 42 });
52
+ });
53
+ });
54
+
55
+ describe('TypeScriptSerializer', () => {
56
+ const serializer = new TypeScriptSerializer('typescript');
57
+
58
+ it('should serialize to TypeScript module', () => {
59
+ const data = { name: 'test', value: 42 };
60
+ const result = serializer.serialize(data);
61
+ expect(result).toContain('import type');
62
+ expect(result).toContain('export const metadata');
63
+ expect(result).toContain('export default metadata');
64
+ });
65
+
66
+ it('should get correct extension', () => {
67
+ const ts = new TypeScriptSerializer('typescript');
68
+ expect(ts.getExtension()).toBe('.ts');
69
+
70
+ const js = new TypeScriptSerializer('javascript');
71
+ expect(js.getExtension()).toBe('.js');
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,125 @@
1
+ /**
2
+ * TypeScript/JavaScript Metadata Serializer
3
+ *
4
+ * Handles TypeScript/JavaScript module format serialization and deserialization
5
+ */
6
+
7
+ import type { z } from 'zod';
8
+ import type { MetadataFormat } from '@objectstack/spec/system';
9
+ import type { MetadataSerializer, SerializeOptions } from './serializer-interface.js';
10
+
11
+ export class TypeScriptSerializer implements MetadataSerializer {
12
+ constructor(private format: 'typescript' | 'javascript' = 'typescript') {}
13
+
14
+ serialize<T>(item: T, options?: SerializeOptions): string {
15
+ const { prettify = true, indent = 2 } = options || {};
16
+
17
+ const jsonStr = JSON.stringify(item, null, prettify ? indent : 0);
18
+
19
+ if (this.format === 'typescript') {
20
+ return `import type { ServiceObject } from '@objectstack/spec/data';\n\n` +
21
+ `export const metadata: ServiceObject = ${jsonStr};\n\n` +
22
+ `export default metadata;\n`;
23
+ } else {
24
+ return `export const metadata = ${jsonStr};\n\n` +
25
+ `export default metadata;\n`;
26
+ }
27
+ }
28
+
29
+ deserialize<T>(content: string, schema?: z.ZodSchema): T {
30
+ // For TypeScript/JavaScript files, we need to extract the exported object
31
+ // Note: This is a simplified parser that works with JSON-like object literals
32
+ // For complex TypeScript with nested objects, consider using a proper TypeScript parser
33
+
34
+ // Try to find the object literal in various export patterns
35
+ // Pattern 1: export const metadata = {...};
36
+ let objectStart = content.indexOf('export const');
37
+ if (objectStart === -1) {
38
+ // Pattern 2: export default {...};
39
+ objectStart = content.indexOf('export default');
40
+ }
41
+
42
+ if (objectStart === -1) {
43
+ throw new Error(
44
+ 'Could not parse TypeScript/JavaScript module. ' +
45
+ 'Expected export pattern: "export const metadata = {...};" or "export default {...};"'
46
+ );
47
+ }
48
+
49
+ // Find the first opening brace after the export statement
50
+ const braceStart = content.indexOf('{', objectStart);
51
+ if (braceStart === -1) {
52
+ throw new Error('Could not find object literal in export statement');
53
+ }
54
+
55
+ // Find the matching closing brace by counting braces
56
+ // Handle string literals to avoid counting braces inside strings
57
+ let braceCount = 0;
58
+ let braceEnd = -1;
59
+ let inString = false;
60
+ let stringChar = '';
61
+
62
+ for (let i = braceStart; i < content.length; i++) {
63
+ const char = content[i];
64
+ const prevChar = i > 0 ? content[i - 1] : '';
65
+
66
+ // Track string literals (simple handling of " and ')
67
+ if ((char === '"' || char === "'") && prevChar !== '\\') {
68
+ if (!inString) {
69
+ inString = true;
70
+ stringChar = char;
71
+ } else if (char === stringChar) {
72
+ inString = false;
73
+ stringChar = '';
74
+ }
75
+ }
76
+
77
+ // Count braces only when not inside strings
78
+ if (!inString) {
79
+ if (char === '{') braceCount++;
80
+ if (char === '}') {
81
+ braceCount--;
82
+ if (braceCount === 0) {
83
+ braceEnd = i;
84
+ break;
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ if (braceEnd === -1) {
91
+ throw new Error('Could not find matching closing brace for object literal');
92
+ }
93
+
94
+ // Extract the object literal
95
+ const objectLiteral = content.substring(braceStart, braceEnd + 1);
96
+
97
+ try {
98
+ // Parse as JSON
99
+ const parsed = JSON.parse(objectLiteral);
100
+
101
+ if (schema) {
102
+ return schema.parse(parsed) as T;
103
+ }
104
+
105
+ return parsed as T;
106
+ } catch (error) {
107
+ throw new Error(
108
+ `Failed to parse object literal as JSON: ${error instanceof Error ? error.message : String(error)}. ` +
109
+ 'Make sure the TypeScript/JavaScript object uses JSON-compatible syntax (no functions, comments, or trailing commas).'
110
+ );
111
+ }
112
+ }
113
+
114
+ getExtension(): string {
115
+ return this.format === 'typescript' ? '.ts' : '.js';
116
+ }
117
+
118
+ canHandle(format: MetadataFormat): boolean {
119
+ return format === 'typescript' || format === 'javascript';
120
+ }
121
+
122
+ getFormat(): MetadataFormat {
123
+ return this.format;
124
+ }
125
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * YAML Metadata Serializer
3
+ *
4
+ * Handles YAML format serialization and deserialization
5
+ */
6
+
7
+ import * as yaml from 'js-yaml';
8
+ import type { z } from 'zod';
9
+ import type { MetadataFormat } from '@objectstack/spec/system';
10
+ import type { MetadataSerializer, SerializeOptions } from './serializer-interface.js';
11
+
12
+ export class YAMLSerializer implements MetadataSerializer {
13
+ serialize<T>(item: T, options?: SerializeOptions): string {
14
+ const { indent = 2, sortKeys = false } = options || {};
15
+
16
+ return yaml.dump(item, {
17
+ indent,
18
+ sortKeys,
19
+ lineWidth: -1, // Disable line wrapping
20
+ noRefs: true, // Disable YAML references
21
+ });
22
+ }
23
+
24
+ deserialize<T>(content: string, schema?: z.ZodSchema): T {
25
+ // Use JSON_SCHEMA to prevent arbitrary code execution
26
+ // This restricts YAML to JSON-compatible types only
27
+ const parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
28
+
29
+ if (schema) {
30
+ return schema.parse(parsed) as T;
31
+ }
32
+
33
+ return parsed as T;
34
+ }
35
+
36
+ getExtension(): string {
37
+ return '.yaml';
38
+ }
39
+
40
+ canHandle(format: MetadataFormat): boolean {
41
+ return format === 'yaml';
42
+ }
43
+
44
+ getFormat(): MetadataFormat {
45
+ return 'yaml';
46
+ }
47
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", "dist"],
5
+ "compilerOptions": {
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "module": "ES2020",
9
+ "moduleResolution": "bundler"
10
+ }
11
+ }