@appstrate/validation 1.0.0 → 1.0.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appstrate/validation",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "files": ["src"],
6
6
  "exports": {
@@ -6,20 +6,18 @@ export interface DepEntry {
6
6
  }
7
7
 
8
8
  export function extractDependencies(manifest: Record<string, unknown>): DepEntry[] {
9
- const requires = manifest.requires as
9
+ const registryDependencies = manifest.registryDependencies as
10
10
  | {
11
- registryDependencies?: {
12
- skills?: Record<string, string>;
13
- extensions?: Record<string, string>;
14
- };
11
+ skills?: Record<string, string>;
12
+ extensions?: Record<string, string>;
15
13
  }
16
14
  | undefined;
17
15
 
18
- if (!requires?.registryDependencies) return [];
16
+ if (!registryDependencies) return [];
19
17
 
20
18
  const deps: DepEntry[] = [];
21
19
 
22
- const { skills = {}, extensions = {} } = requires.registryDependencies;
20
+ const { skills = {}, extensions = {} } = registryDependencies;
23
21
 
24
22
  for (const [fullName, versionRange] of Object.entries(skills)) {
25
23
  const { scope, name } = parseScopedName(fullName);
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import semver from "semver";
3
+ import { SLUG_REGEX } from "./naming.ts";
3
4
 
4
- export const SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
5
+ export { SLUG_REGEX };
5
6
 
6
7
  const flowFieldTypeEnum = z.enum(["string", "number", "boolean", "array", "object", "file"]);
7
8
 
@@ -73,20 +74,14 @@ export const baseManifestSchema = z.object({
73
74
  export type BaseManifest = z.infer<typeof baseManifestSchema>;
74
75
 
75
76
  // ─────────────────────────────────────────────
76
- // Flow manifest schemaextends base with flow-specific fields
77
+ // Shared flow fieldsused by both flowManifestSchema and localFlowManifestSchema
77
78
  // ─────────────────────────────────────────────
78
79
 
79
- export const flowManifestSchema = baseManifestSchema.extend({
80
- $schema: z.string().optional(),
81
- schemaVersion: z.string(),
82
- displayName: z.string().min(1),
83
- author: z.string(),
84
- tags: z.array(z.string()).optional(),
80
+ const flowSharedFields = {
85
81
  requires: z.object({
86
82
  services: z.array(serviceRequirementSchema),
87
83
  skills: z.array(slugString).optional().default([]),
88
84
  extensions: z.array(slugString).optional().default([]),
89
- registryDependencies: registryDependenciesSchema,
90
85
  }),
91
86
  input: z
92
87
  .object({
@@ -109,6 +104,19 @@ export const flowManifestSchema = baseManifestSchema.extend({
109
104
  outputRetries: z.number().min(0).max(5).optional(),
110
105
  })
111
106
  .optional(),
107
+ } as const;
108
+
109
+ // ─────────────────────────────────────────────
110
+ // Flow manifest schema — extends base with flow-specific fields
111
+ // ─────────────────────────────────────────────
112
+
113
+ export const flowManifestSchema = baseManifestSchema.extend({
114
+ $schema: z.string().optional(),
115
+ schemaVersion: z.string(),
116
+ displayName: z.string().min(1),
117
+ author: z.string(),
118
+ tags: z.array(z.string()).optional(),
119
+ ...flowSharedFields,
112
120
  });
113
121
 
114
122
  export type FlowManifest = z.infer<typeof flowManifestSchema>;
@@ -121,11 +129,11 @@ export type ManifestSchema = FlowManifest;
121
129
  // Unified validateManifest — dispatches by type
122
130
  // ─────────────────────────────────────────────
123
131
 
124
- export function validateManifest(raw: unknown): {
125
- valid: boolean;
126
- errors: string[];
127
- manifest?: unknown;
128
- } {
132
+ export type ValidateManifestResult =
133
+ | { valid: true; errors: []; manifest: BaseManifest | FlowManifest }
134
+ | { valid: false; errors: string[]; manifest?: undefined };
135
+
136
+ export function validateManifest(raw: unknown): ValidateManifestResult {
129
137
  // First, check if it has a type field to dispatch
130
138
  if (raw && typeof raw === "object" && "type" in raw) {
131
139
  const obj = raw as Record<string, unknown>;
@@ -148,11 +156,7 @@ export function validateManifest(raw: unknown): {
148
156
  return validateFlowManifest(raw);
149
157
  }
150
158
 
151
- function validateFlowManifest(raw: unknown): {
152
- valid: boolean;
153
- errors: string[];
154
- manifest?: unknown;
155
- } {
159
+ function validateFlowManifest(raw: unknown): ValidateManifestResult {
156
160
  const result = flowManifestSchema.safeParse(raw);
157
161
  if (result.success) {
158
162
  return { valid: true, errors: [], manifest: result.data };
@@ -240,33 +244,7 @@ export const localFlowManifestSchema = z.looseObject({
240
244
  tags: z.array(z.string()).optional(),
241
245
  type: z.string().optional(),
242
246
  version: z.string().optional(),
243
- requires: z.object({
244
- services: z.array(serviceRequirementSchema),
245
- skills: z.array(slugString).optional().default([]),
246
- extensions: z.array(slugString).optional().default([]),
247
- registryDependencies: registryDependenciesSchema,
248
- }),
249
- input: z
250
- .object({
251
- schema: jsonSchemaObjectSchema,
252
- })
253
- .optional(),
254
- output: z
255
- .object({
256
- schema: jsonSchemaObjectSchema,
257
- })
258
- .optional(),
259
- config: z
260
- .object({
261
- schema: jsonSchemaObjectSchema,
262
- })
263
- .optional(),
264
- execution: z
265
- .object({
266
- timeout: z.number().optional(),
267
- outputRetries: z.number().min(0).max(5).optional(),
268
- })
269
- .optional(),
247
+ ...flowSharedFields,
270
248
  });
271
249
 
272
250
  export type LocalFlowManifest = z.infer<typeof localFlowManifestSchema>;
package/src/naming.ts CHANGED
@@ -1,11 +1,32 @@
1
- import { SLUG_REGEX } from "./index.ts";
2
-
3
- export { SLUG_REGEX };
1
+ export const SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
4
2
 
5
3
  export function normalizeScope(scope: string): string {
4
+ if (!scope) throw new Error("Scope cannot be empty");
6
5
  return scope.startsWith("@") ? scope : `@${scope}`;
7
6
  }
8
7
 
9
8
  export function stripScope(scope: string): string {
10
9
  return scope.startsWith("@") ? scope.slice(1) : scope;
11
10
  }
11
+
12
+ /** Convert "@scope/name" to strate packageId "scope--name" */
13
+ export function scopedNameToPackageId(scopedName: string): string {
14
+ const match = scopedName.match(/^@([a-z0-9][a-z0-9-]*[a-z0-9]?)\/([a-z0-9][a-z0-9-]*[a-z0-9]?)$/);
15
+ if (!match) throw new Error(`Invalid scoped package name: ${scopedName}`);
16
+ return `${match[1]}--${match[2]}`;
17
+ }
18
+
19
+ /** Convert strate packageId "scope--name" to "@scope/name", or null for local slugs */
20
+ export function packageIdToScopedName(packageId: string): string | null {
21
+ const idx = packageId.indexOf("--");
22
+ if (idx === -1) return null;
23
+ const scope = packageId.slice(0, idx);
24
+ const name = packageId.slice(idx + 2);
25
+ if (!scope || !name) return null;
26
+ return `@${scope}/${name}`;
27
+ }
28
+
29
+ /** Convert separated scope + name to strate packageId */
30
+ export function depEntryToPackageId(depScope: string, depName: string): string {
31
+ return `${stripScope(depScope)}--${depName}`;
32
+ }