@portel/photon-core 2.3.0 β†’ 2.5.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 (130) hide show
  1. package/dist/asset-discovery.d.ts +25 -0
  2. package/dist/asset-discovery.d.ts.map +1 -0
  3. package/dist/asset-discovery.js +145 -0
  4. package/dist/asset-discovery.js.map +1 -0
  5. package/dist/base.d.ts +6 -0
  6. package/dist/base.d.ts.map +1 -1
  7. package/dist/base.js +11 -1
  8. package/dist/base.js.map +1 -1
  9. package/dist/class-detection.d.ts +32 -0
  10. package/dist/class-detection.d.ts.map +1 -0
  11. package/dist/class-detection.js +86 -0
  12. package/dist/class-detection.js.map +1 -0
  13. package/dist/collections/ReactiveArray.d.ts +97 -0
  14. package/dist/collections/ReactiveArray.d.ts.map +1 -0
  15. package/dist/collections/ReactiveArray.js +158 -0
  16. package/dist/collections/ReactiveArray.js.map +1 -0
  17. package/dist/collections/ReactiveMap.d.ts +50 -0
  18. package/dist/collections/ReactiveMap.d.ts.map +1 -0
  19. package/dist/collections/ReactiveMap.js +71 -0
  20. package/dist/collections/ReactiveMap.js.map +1 -0
  21. package/dist/collections/ReactiveSet.d.ts +50 -0
  22. package/dist/collections/ReactiveSet.d.ts.map +1 -0
  23. package/dist/collections/ReactiveSet.js +71 -0
  24. package/dist/collections/ReactiveSet.js.map +1 -0
  25. package/dist/collections/index.d.ts +44 -0
  26. package/dist/collections/index.d.ts.map +1 -0
  27. package/dist/collections/index.js +44 -0
  28. package/dist/collections/index.js.map +1 -0
  29. package/dist/compiler.d.ts +22 -0
  30. package/dist/compiler.d.ts.map +1 -0
  31. package/dist/compiler.js +48 -0
  32. package/dist/compiler.js.map +1 -0
  33. package/dist/env-utils.d.ts +61 -0
  34. package/dist/env-utils.d.ts.map +1 -0
  35. package/dist/env-utils.js +171 -0
  36. package/dist/env-utils.js.map +1 -0
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +37 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/mime-types.d.ts +13 -0
  42. package/dist/mime-types.d.ts.map +1 -0
  43. package/dist/mime-types.js +47 -0
  44. package/dist/mime-types.js.map +1 -0
  45. package/dist/rendering/index.d.ts +49 -0
  46. package/dist/rendering/index.d.ts.map +1 -1
  47. package/dist/rendering/index.js +153 -0
  48. package/dist/rendering/index.js.map +1 -1
  49. package/dist/schema-extractor.d.ts.map +1 -1
  50. package/dist/schema-extractor.js +3 -0
  51. package/dist/schema-extractor.js.map +1 -1
  52. package/dist/types.d.ts +4 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. package/dist/ui-types/Cards.d.ts +139 -0
  56. package/dist/ui-types/Cards.d.ts.map +1 -0
  57. package/dist/ui-types/Cards.js +235 -0
  58. package/dist/ui-types/Cards.js.map +1 -0
  59. package/dist/ui-types/Chart.d.ts +136 -0
  60. package/dist/ui-types/Chart.d.ts.map +1 -0
  61. package/dist/ui-types/Chart.js +188 -0
  62. package/dist/ui-types/Chart.js.map +1 -0
  63. package/dist/ui-types/Field.d.ts +342 -0
  64. package/dist/ui-types/Field.d.ts.map +1 -0
  65. package/dist/ui-types/Field.js +200 -0
  66. package/dist/ui-types/Field.js.map +1 -0
  67. package/dist/ui-types/FieldRenderer.d.ts +32 -0
  68. package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
  69. package/dist/ui-types/FieldRenderer.js +277 -0
  70. package/dist/ui-types/FieldRenderer.js.map +1 -0
  71. package/dist/ui-types/Form.d.ts +212 -0
  72. package/dist/ui-types/Form.d.ts.map +1 -0
  73. package/dist/ui-types/Form.js +278 -0
  74. package/dist/ui-types/Form.js.map +1 -0
  75. package/dist/ui-types/Progress.d.ts +130 -0
  76. package/dist/ui-types/Progress.d.ts.map +1 -0
  77. package/dist/ui-types/Progress.js +191 -0
  78. package/dist/ui-types/Progress.js.map +1 -0
  79. package/dist/ui-types/Stats.d.ts +108 -0
  80. package/dist/ui-types/Stats.d.ts.map +1 -0
  81. package/dist/ui-types/Stats.js +162 -0
  82. package/dist/ui-types/Stats.js.map +1 -0
  83. package/dist/ui-types/Table.d.ts +206 -0
  84. package/dist/ui-types/Table.d.ts.map +1 -0
  85. package/dist/ui-types/Table.js +367 -0
  86. package/dist/ui-types/Table.js.map +1 -0
  87. package/dist/ui-types/base.d.ts +17 -0
  88. package/dist/ui-types/base.d.ts.map +1 -0
  89. package/dist/ui-types/base.js +18 -0
  90. package/dist/ui-types/base.js.map +1 -0
  91. package/dist/ui-types/index.d.ts +42 -0
  92. package/dist/ui-types/index.d.ts.map +1 -0
  93. package/dist/ui-types/index.js +50 -0
  94. package/dist/ui-types/index.js.map +1 -0
  95. package/dist/validation.d.ts +51 -0
  96. package/dist/validation.d.ts.map +1 -0
  97. package/dist/validation.js +249 -0
  98. package/dist/validation.js.map +1 -0
  99. package/dist/version-check.d.ts +22 -0
  100. package/dist/version-check.d.ts.map +1 -0
  101. package/dist/version-check.js +91 -0
  102. package/dist/version-check.js.map +1 -0
  103. package/package.json +2 -2
  104. package/src/asset-discovery.ts +161 -0
  105. package/src/base.ts +13 -1
  106. package/src/class-detection.ts +94 -0
  107. package/src/collections/ReactiveArray.ts +179 -0
  108. package/src/collections/ReactiveMap.ts +81 -0
  109. package/src/collections/ReactiveSet.ts +81 -0
  110. package/src/collections/index.ts +44 -0
  111. package/src/compiler.ts +57 -0
  112. package/src/env-utils.ts +216 -0
  113. package/src/index.ts +155 -0
  114. package/src/mime-types.ts +49 -0
  115. package/src/rendering/index.ts +197 -0
  116. package/src/schema-extractor.ts +4 -0
  117. package/src/types.ts +4 -0
  118. package/src/ui-types/Cards.ts +286 -0
  119. package/src/ui-types/Chart.ts +239 -0
  120. package/src/ui-types/Field.ts +594 -0
  121. package/src/ui-types/FieldRenderer.ts +364 -0
  122. package/src/ui-types/Form.ts +363 -0
  123. package/src/ui-types/Progress.ts +237 -0
  124. package/src/ui-types/Stats.ts +204 -0
  125. package/src/ui-types/Table.ts +438 -0
  126. package/src/ui-types/base.ts +25 -0
  127. package/src/ui-types/index.ts +96 -0
  128. package/src/ui-types/ui-types.test.ts +444 -0
  129. package/src/validation.ts +363 -0
  130. package/src/version-check.ts +92 -0
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Input Validation Utilities
3
+ *
4
+ * Type-safe validation with user-friendly error messages.
5
+ * Adapted from photon's shared/validation.ts.
6
+ *
7
+ * Also includes PhotonError and ValidationError base classes
8
+ * (originally from photon's shared/error-handler.ts).
9
+ */
10
+
11
+ // ══════════════════════════════════════════════════════════════════════════════
12
+ // ERROR BASE CLASSES
13
+ // ══════════════════════════════════════════════════════════════════════════════
14
+
15
+ export class PhotonError extends Error {
16
+ constructor(
17
+ message: string,
18
+ public readonly code: string,
19
+ public readonly details?: Record<string, unknown>,
20
+ public readonly suggestion?: string,
21
+ ) {
22
+ super(message);
23
+ this.name = 'PhotonError';
24
+ Error.captureStackTrace?.(this, this.constructor);
25
+ }
26
+ }
27
+
28
+ export class ValidationError extends PhotonError {
29
+ constructor(
30
+ message: string,
31
+ details?: Record<string, unknown>,
32
+ suggestion?: string,
33
+ ) {
34
+ super(message, 'VALIDATION_ERROR', details, suggestion);
35
+ this.name = 'ValidationError';
36
+ }
37
+ }
38
+
39
+ // ══════════════════════════════════════════════════════════════════════════════
40
+ // VALIDATION RESULT / TYPES
41
+ // ══════════════════════════════════════════════════════════════════════════════
42
+
43
+ export interface ValidationResult {
44
+ valid: boolean;
45
+ errors: string[];
46
+ }
47
+
48
+ export type Validator<T> = (value: T) => ValidationResult;
49
+
50
+ function createResult(valid: boolean, errors: string[] = []): ValidationResult {
51
+ return { valid, errors };
52
+ }
53
+
54
+ export function combineResults(...results: ValidationResult[]): ValidationResult {
55
+ const allErrors = results.flatMap((r) => r.errors);
56
+ return createResult(allErrors.length === 0, allErrors);
57
+ }
58
+
59
+ // ══════════════════════════════════════════════════════════════════════════════
60
+ // TYPE VALIDATORS
61
+ // ══════════════════════════════════════════════════════════════════════════════
62
+
63
+ export function isString(value: unknown): value is string {
64
+ return typeof value === 'string';
65
+ }
66
+
67
+ export function isNumber(value: unknown): value is number {
68
+ return typeof value === 'number' && !isNaN(value);
69
+ }
70
+
71
+ export function isBoolean(value: unknown): value is boolean {
72
+ return typeof value === 'boolean';
73
+ }
74
+
75
+ export function isObject(value: unknown): value is Record<string, unknown> {
76
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
77
+ }
78
+
79
+ export function isArray(value: unknown): value is unknown[] {
80
+ return Array.isArray(value);
81
+ }
82
+
83
+ // ══════════════════════════════════════════════════════════════════════════════
84
+ // STRING VALIDATORS
85
+ // ══════════════════════════════════════════════════════════════════════════════
86
+
87
+ export function notEmpty(fieldName: string): Validator<string> {
88
+ return (value: string) => {
89
+ if (!value || value.trim().length === 0) {
90
+ return createResult(false, [`${fieldName} cannot be empty`]);
91
+ }
92
+ return createResult(true);
93
+ };
94
+ }
95
+
96
+ export function hasLength(
97
+ fieldName: string,
98
+ min?: number,
99
+ max?: number,
100
+ ): Validator<string> {
101
+ return (value: string) => {
102
+ const errors: string[] = [];
103
+ if (min !== undefined && value.length < min) {
104
+ errors.push(`${fieldName} must be at least ${min} characters`);
105
+ }
106
+ if (max !== undefined && value.length > max) {
107
+ errors.push(`${fieldName} must be at most ${max} characters`);
108
+ }
109
+ return createResult(errors.length === 0, errors);
110
+ };
111
+ }
112
+
113
+ export function matchesPattern(
114
+ fieldName: string,
115
+ pattern: RegExp,
116
+ message?: string,
117
+ ): Validator<string> {
118
+ return (value: string) => {
119
+ if (!pattern.test(value)) {
120
+ return createResult(false, [message || `${fieldName} has invalid format`]);
121
+ }
122
+ return createResult(true);
123
+ };
124
+ }
125
+
126
+ export function isEmail(fieldName: string): Validator<string> {
127
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
128
+ return matchesPattern(fieldName, emailPattern, `${fieldName} must be a valid email`);
129
+ }
130
+
131
+ export function isUrl(fieldName: string): Validator<string> {
132
+ return (value: string) => {
133
+ try {
134
+ new URL(value);
135
+ return createResult(true);
136
+ } catch {
137
+ return createResult(false, [`${fieldName} must be a valid URL`]);
138
+ }
139
+ };
140
+ }
141
+
142
+ // ══════════════════════════════════════════════════════════════════════════════
143
+ // NUMBER VALIDATORS
144
+ // ══════════════════════════════════════════════════════════════════════════════
145
+
146
+ export function inRange(
147
+ fieldName: string,
148
+ min?: number,
149
+ max?: number,
150
+ ): Validator<number> {
151
+ return (value: number) => {
152
+ const errors: string[] = [];
153
+ if (min !== undefined && value < min) {
154
+ errors.push(`${fieldName} must be at least ${min}`);
155
+ }
156
+ if (max !== undefined && value > max) {
157
+ errors.push(`${fieldName} must be at most ${max}`);
158
+ }
159
+ return createResult(errors.length === 0, errors);
160
+ };
161
+ }
162
+
163
+ export function isPositive(fieldName: string): Validator<number> {
164
+ return (value: number) => {
165
+ if (value <= 0) {
166
+ return createResult(false, [`${fieldName} must be positive`]);
167
+ }
168
+ return createResult(true);
169
+ };
170
+ }
171
+
172
+ export function isInteger(fieldName: string): Validator<number> {
173
+ return (value: number) => {
174
+ if (!Number.isInteger(value)) {
175
+ return createResult(false, [`${fieldName} must be an integer`]);
176
+ }
177
+ return createResult(true);
178
+ };
179
+ }
180
+
181
+ // ══════════════════════════════════════════════════════════════════════════════
182
+ // ARRAY VALIDATORS
183
+ // ══════════════════════════════════════════════════════════════════════════════
184
+
185
+ export function hasArrayLength(
186
+ fieldName: string,
187
+ min?: number,
188
+ max?: number,
189
+ ): Validator<unknown[]> {
190
+ return (value: unknown[]) => {
191
+ const errors: string[] = [];
192
+ if (min !== undefined && value.length < min) {
193
+ errors.push(`${fieldName} must have at least ${min} items`);
194
+ }
195
+ if (max !== undefined && value.length > max) {
196
+ errors.push(`${fieldName} must have at most ${max} items`);
197
+ }
198
+ return createResult(errors.length === 0, errors);
199
+ };
200
+ }
201
+
202
+ export function arrayOf<T>(
203
+ fieldName: string,
204
+ itemValidator: Validator<T>,
205
+ ): Validator<T[]> {
206
+ return (value: T[]) => {
207
+ const errors: string[] = [];
208
+ value.forEach((item, index) => {
209
+ const result = itemValidator(item);
210
+ if (!result.valid) {
211
+ errors.push(`${fieldName}[${index}]: ${result.errors.join(', ')}`);
212
+ }
213
+ });
214
+ return createResult(errors.length === 0, errors);
215
+ };
216
+ }
217
+
218
+ // ══════════════════════════════════════════════════════════════════════════════
219
+ // OBJECT VALIDATORS
220
+ // ══════════════════════════════════════════════════════════════════════════════
221
+
222
+ export function hasFields(
223
+ fieldName: string,
224
+ requiredFields: string[],
225
+ ): Validator<Record<string, unknown>> {
226
+ return (value: Record<string, unknown>) => {
227
+ const errors: string[] = [];
228
+ requiredFields.forEach((field) => {
229
+ if (!(field in value)) {
230
+ errors.push(`${fieldName} missing required field: ${field}`);
231
+ }
232
+ });
233
+ return createResult(errors.length === 0, errors);
234
+ };
235
+ }
236
+
237
+ export function oneOf<T>(fieldName: string, allowed: T[]): Validator<T> {
238
+ return (value: T) => {
239
+ if (!allowed.includes(value)) {
240
+ return createResult(false, [
241
+ `${fieldName} must be one of: ${allowed.join(', ')}`,
242
+ ]);
243
+ }
244
+ return createResult(true);
245
+ };
246
+ }
247
+
248
+ // ══════════════════════════════════════════════════════════════════════════════
249
+ // VALIDATION HELPERS
250
+ // ══════════════════════════════════════════════════════════════════════════════
251
+
252
+ export function validate<T>(
253
+ value: T,
254
+ validators: Validator<T>[],
255
+ ): ValidationResult {
256
+ const results = validators.map((validator) => validator(value));
257
+ return combineResults(...results);
258
+ }
259
+
260
+ export function validateOrThrow<T>(
261
+ value: T,
262
+ validators: Validator<T>[],
263
+ context?: string,
264
+ ): void {
265
+ const result = validate(value, validators);
266
+ if (!result.valid) {
267
+ const message = result.errors.join('; ');
268
+ throw new ValidationError(message, { value, context }, 'Check input values and try again');
269
+ }
270
+ }
271
+
272
+ // ══════════════════════════════════════════════════════════════════════════════
273
+ // FILE SYSTEM VALIDATORS
274
+ // ══════════════════════════════════════════════════════════════════════════════
275
+
276
+ export function pathExists(fieldName: string): Validator<string> {
277
+ return (value: string) => {
278
+ if (!value || value.trim().length === 0) {
279
+ return createResult(false, [`${fieldName} path cannot be empty`]);
280
+ }
281
+ return createResult(true);
282
+ };
283
+ }
284
+
285
+ export function hasExtension(
286
+ fieldName: string,
287
+ extensions: string[],
288
+ ): Validator<string> {
289
+ return (value: string) => {
290
+ const ext = value.split('.').pop()?.toLowerCase();
291
+ if (!ext || !extensions.includes(ext)) {
292
+ return createResult(false, [
293
+ `${fieldName} must have one of these extensions: ${extensions.join(', ')}`,
294
+ ]);
295
+ }
296
+ return createResult(true);
297
+ };
298
+ }
299
+
300
+ // ══════════════════════════════════════════════════════════════════════════════
301
+ // TYPE GUARD UTILITIES
302
+ // ══════════════════════════════════════════════════════════════════════════════
303
+
304
+ export function assertDefined<T>(
305
+ value: T | null | undefined,
306
+ fieldName: string,
307
+ ): asserts value is T {
308
+ if (value === null || value === undefined) {
309
+ throw new ValidationError(`${fieldName} is required`, { value }, 'Provide a valid value');
310
+ }
311
+ }
312
+
313
+ export function assertString(
314
+ value: unknown,
315
+ fieldName: string,
316
+ ): asserts value is string {
317
+ if (!isString(value)) {
318
+ throw new ValidationError(
319
+ `${fieldName} must be a string, got ${typeof value}`,
320
+ { value },
321
+ 'Provide a string value',
322
+ );
323
+ }
324
+ }
325
+
326
+ export function assertNumber(
327
+ value: unknown,
328
+ fieldName: string,
329
+ ): asserts value is number {
330
+ if (!isNumber(value)) {
331
+ throw new ValidationError(
332
+ `${fieldName} must be a number, got ${typeof value}`,
333
+ { value },
334
+ 'Provide a numeric value',
335
+ );
336
+ }
337
+ }
338
+
339
+ export function assertObject(
340
+ value: unknown,
341
+ fieldName: string,
342
+ ): asserts value is Record<string, unknown> {
343
+ if (!isObject(value)) {
344
+ throw new ValidationError(
345
+ `${fieldName} must be an object, got ${typeof value}`,
346
+ { value },
347
+ 'Provide an object value',
348
+ );
349
+ }
350
+ }
351
+
352
+ export function assertArray(
353
+ value: unknown,
354
+ fieldName: string,
355
+ ): asserts value is unknown[] {
356
+ if (!isArray(value)) {
357
+ throw new ValidationError(
358
+ `${fieldName} must be an array, got ${typeof value}`,
359
+ { value },
360
+ 'Provide an array value',
361
+ );
362
+ }
363
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Runtime Version Check Utilities
3
+ *
4
+ * Parse @runtime tags from Photon source and check compatibility.
5
+ * Extracted from photon's loader.ts.
6
+ */
7
+
8
+ /**
9
+ * Parse the @runtime version requirement from Photon source code
10
+ *
11
+ * @example parseRuntimeRequirement('/** @runtime ^1.5.0 *\/') β†’ '^1.5.0'
12
+ */
13
+ export function parseRuntimeRequirement(source: string): string | undefined {
14
+ const match = source.match(/@runtime\s+([^\r\n\s]+)/);
15
+ return match ? match[1].trim() : undefined;
16
+ }
17
+
18
+ /**
19
+ * Check if a current version satisfies a required version range
20
+ *
21
+ * Supports: ^1.5.0, ~1.5.0, >=1.5.0, >1.5.0, exact (1.5.0)
22
+ */
23
+ export function checkRuntimeCompatibility(
24
+ required: string,
25
+ current: string,
26
+ ): { compatible: boolean; message?: string } {
27
+ const parseVersion = (v: string): [number, number, number] => {
28
+ const clean = v.replace(/^[~^>=<]+/, '');
29
+ const parts = clean.split('.').map((p) => parseInt(p, 10) || 0);
30
+ return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
31
+ };
32
+
33
+ const [reqMajor, reqMinor, reqPatch] = parseVersion(required);
34
+ const [curMajor, curMinor, curPatch] = parseVersion(current);
35
+
36
+ const isExact = !required.match(/^[~^>=<]/);
37
+ const isCaret = required.startsWith('^');
38
+ const isTilde = required.startsWith('~');
39
+ const isGte = required.startsWith('>=');
40
+ const isGt = required.startsWith('>') && !isGte;
41
+
42
+ let compatible = false;
43
+
44
+ if (isExact) {
45
+ compatible =
46
+ curMajor === reqMajor && curMinor === reqMinor && curPatch === reqPatch;
47
+ } else if (isCaret) {
48
+ // ^1.5.0 means >=1.5.0 and <2.0.0
49
+ if (curMajor === reqMajor) {
50
+ if (curMinor > reqMinor) {
51
+ compatible = true;
52
+ } else if (curMinor === reqMinor) {
53
+ compatible = curPatch >= reqPatch;
54
+ }
55
+ }
56
+ } else if (isTilde) {
57
+ // ~1.5.0 means >=1.5.0 and <1.6.0
58
+ compatible =
59
+ curMajor === reqMajor &&
60
+ curMinor === reqMinor &&
61
+ curPatch >= reqPatch;
62
+ } else if (isGte) {
63
+ if (curMajor > reqMajor) {
64
+ compatible = true;
65
+ } else if (curMajor === reqMajor) {
66
+ if (curMinor > reqMinor) {
67
+ compatible = true;
68
+ } else if (curMinor === reqMinor) {
69
+ compatible = curPatch >= reqPatch;
70
+ }
71
+ }
72
+ } else if (isGt) {
73
+ if (curMajor > reqMajor) {
74
+ compatible = true;
75
+ } else if (curMajor === reqMajor) {
76
+ if (curMinor > reqMinor) {
77
+ compatible = true;
78
+ } else if (curMinor === reqMinor) {
79
+ compatible = curPatch > reqPatch;
80
+ }
81
+ }
82
+ }
83
+
84
+ if (!compatible) {
85
+ return {
86
+ compatible: false,
87
+ message: `This photon requires runtime version ${required}, but you have ${current}. Please upgrade: npm install -g @portel/photon@latest`,
88
+ };
89
+ }
90
+
91
+ return { compatible: true };
92
+ }