@microservices-sh/module-contract 0.1.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 (3) hide show
  1. package/package.json +24 -0
  2. package/src/index.d.ts +139 -0
  3. package/src/index.js +406 -0
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@microservices-sh/module-contract",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/index.d.ts",
10
+ "import": "./src/index.js",
11
+ "default": "./src/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "license": "MIT",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "node --check src/index.js"
23
+ }
24
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ export type ModuleStatus = "available" | "planned";
2
+
3
+ export interface ModuleHook {
4
+ name: string;
5
+ timing: "pre" | "post" | "compute";
6
+ purpose: string;
7
+ }
8
+
9
+ export interface RuntimeContract {
10
+ framework: "hono";
11
+ mount: string;
12
+ bindings: string[];
13
+ }
14
+
15
+ export interface ModuleContract {
16
+ id: string;
17
+ name: string;
18
+ version: string;
19
+ status: ModuleStatus;
20
+ category: "core" | "vertical" | "connector";
21
+ summary: string;
22
+ requires: string[];
23
+ optional: string[];
24
+ storage: string[];
25
+ runtime: RuntimeContract;
26
+ eventsEmitted: string[];
27
+ eventsConsumed: string[];
28
+ permissions: string[];
29
+ hooks: ModuleHook[];
30
+ customization: {
31
+ config: string[];
32
+ hooks: string[];
33
+ forkable: boolean;
34
+ };
35
+ quality: {
36
+ tests: { unit: boolean; integration: boolean; fixtures: boolean };
37
+ agentDocs: boolean;
38
+ migrations: boolean;
39
+ upgradeNotes: boolean;
40
+ };
41
+ }
42
+
43
+ export interface TemplateContract {
44
+ id: string;
45
+ name: string;
46
+ version: string;
47
+ status: ModuleStatus;
48
+ summary: string;
49
+ targetCustomer: string;
50
+ defaultModules: string[];
51
+ optionalModules: string[];
52
+ targetRuntime: {
53
+ language: "typescript";
54
+ framework: "hono";
55
+ platform: "cloudflare-workers";
56
+ storage: string[];
57
+ };
58
+ defaultConfig: Record<string, unknown>;
59
+ successCriteria: string[];
60
+ }
61
+
62
+ export interface CompositionInput {
63
+ template?: string;
64
+ templateId?: string;
65
+ modules?: string[];
66
+ config?: Record<string, unknown>;
67
+ }
68
+
69
+ export interface ModuleLock {
70
+ schemaVersion: string;
71
+ generatedAt: string;
72
+ registry: {
73
+ id: string;
74
+ contractVersion: string;
75
+ };
76
+ generator: {
77
+ package: string;
78
+ version: string;
79
+ };
80
+ template: {
81
+ id: string;
82
+ version: string;
83
+ source: string;
84
+ checksum: string;
85
+ } | null;
86
+ modules: Array<{
87
+ id: string;
88
+ version: string;
89
+ source: string;
90
+ checksum: string;
91
+ customizationMode: string;
92
+ contract: {
93
+ mount: string;
94
+ bindings: string[];
95
+ resources: string[];
96
+ permissions: string[];
97
+ hooks: string[];
98
+ events: string[];
99
+ requires: string[];
100
+ };
101
+ }>;
102
+ customizations: {
103
+ config: boolean;
104
+ hooks: string[];
105
+ overlays: string[];
106
+ forks: string[];
107
+ };
108
+ }
109
+
110
+ export interface AppComposition {
111
+ schemaVersion: string;
112
+ compositionId: string;
113
+ template: Pick<TemplateContract, "id" | "name" | "version" | "status" | "summary" | "defaultModules" | "optionalModules">;
114
+ config: Record<string, unknown>;
115
+ modules: ModuleContract[];
116
+ routes: Array<{ module: string; mount: string; framework: string }>;
117
+ bindings: string[];
118
+ storage: string[];
119
+ permissions: string[];
120
+ events: { emitted: string[]; consumed: string[] };
121
+ hooks: Array<ModuleHook & { module: string }>;
122
+ checks: string[];
123
+ upgradePolicy: {
124
+ mode: string;
125
+ lockfile: string;
126
+ compatibleCustomization: string[];
127
+ manualCustomization: string[];
128
+ };
129
+ lock: ModuleLock;
130
+ }
131
+
132
+ export const CONTRACT_VERSION: string;
133
+ export function listModules(): Array<Pick<ModuleContract, "id" | "name" | "version" | "status" | "category" | "summary" | "requires"> & { mount: string }>;
134
+ export function inspectModule(id: string): ModuleContract;
135
+ export function listTemplates(): Array<Pick<TemplateContract, "id" | "name" | "version" | "status" | "summary" | "defaultModules" | "optionalModules">>;
136
+ export function inspectTemplate(id: string): TemplateContract;
137
+ export function resolveModuleIds(moduleIds: string[]): string[];
138
+ export function createModuleLock(modules: ModuleContract[], template?: TemplateContract | null): ModuleLock;
139
+ export function composeApp(input?: string | CompositionInput): AppComposition;
package/src/index.js ADDED
@@ -0,0 +1,406 @@
1
+ const CONTRACT_VERSION = "2026-06-13";
2
+
3
+ const MODULES = Object.freeze([
4
+ {
5
+ id: "auth",
6
+ name: "Auth",
7
+ version: "0.1.0",
8
+ status: "available",
9
+ category: "core",
10
+ summary: "Passwordless account identity, session boundaries, roles, and auth audit events.",
11
+ requires: [],
12
+ optional: ["email", "audit-log"],
13
+ storage: ["d1", "kv"],
14
+ runtime: {
15
+ framework: "hono",
16
+ mount: "/auth",
17
+ bindings: ["DB", "CACHE_KV"],
18
+ },
19
+ eventsEmitted: ["auth.user_created", "auth.session_created"],
20
+ eventsConsumed: [],
21
+ permissions: ["auth.read", "auth.write", "auth.admin"],
22
+ hooks: [
23
+ {
24
+ name: "beforeSignup",
25
+ timing: "pre",
26
+ purpose: "Validate or enrich a signup request before persistence.",
27
+ },
28
+ {
29
+ name: "afterSignup",
30
+ timing: "post",
31
+ purpose: "Trigger welcome flows, analytics, or CRM sync after account creation.",
32
+ },
33
+ ],
34
+ customization: {
35
+ config: ["allowedEmailDomains", "defaultRole", "sessionTtlSeconds"],
36
+ hooks: ["beforeSignup", "afterSignup"],
37
+ forkable: true,
38
+ },
39
+ quality: {
40
+ tests: { unit: true, integration: true, fixtures: true },
41
+ agentDocs: true,
42
+ migrations: true,
43
+ upgradeNotes: true,
44
+ },
45
+ },
46
+ {
47
+ id: "customer",
48
+ name: "Customer",
49
+ version: "0.1.0",
50
+ status: "available",
51
+ category: "core",
52
+ summary: "Customer profiles, tags, lifecycle state, consent fields, and customer events.",
53
+ requires: ["auth"],
54
+ optional: ["email", "audit-log"],
55
+ storage: ["d1"],
56
+ runtime: {
57
+ framework: "hono",
58
+ mount: "/customers",
59
+ bindings: ["DB"],
60
+ },
61
+ eventsEmitted: ["customer.created", "customer.updated"],
62
+ eventsConsumed: ["auth.user_created"],
63
+ permissions: ["customer.read", "customer.write", "customer.admin"],
64
+ hooks: [
65
+ {
66
+ name: "beforeCustomerCreate",
67
+ timing: "pre",
68
+ purpose: "Normalize incoming customer data and enforce business-specific required fields.",
69
+ },
70
+ {
71
+ name: "afterCustomerUpdated",
72
+ timing: "post",
73
+ purpose: "Sync customer changes to external CRMs or notification flows.",
74
+ },
75
+ ],
76
+ customization: {
77
+ config: ["profileFields", "tags", "segments", "consentFields"],
78
+ hooks: ["beforeCustomerCreate", "afterCustomerUpdated"],
79
+ forkable: true,
80
+ },
81
+ quality: {
82
+ tests: { unit: true, integration: true, fixtures: true },
83
+ agentDocs: true,
84
+ migrations: true,
85
+ upgradeNotes: true,
86
+ },
87
+ },
88
+ {
89
+ id: "booking",
90
+ name: "Booking",
91
+ version: "0.1.0",
92
+ status: "available",
93
+ category: "vertical",
94
+ summary: "Service booking, availability, cancellation windows, confirmation, and booking events.",
95
+ requires: ["auth", "customer"],
96
+ optional: ["payment", "email", "staff", "audit-log"],
97
+ storage: ["d1", "kv"],
98
+ runtime: {
99
+ framework: "hono",
100
+ mount: "/bookings",
101
+ bindings: ["DB", "CACHE_KV", "NOTIFICATIONS"],
102
+ },
103
+ eventsEmitted: ["booking.created", "booking.confirmed", "booking.cancelled"],
104
+ eventsConsumed: ["customer.created", "payment.succeeded"],
105
+ permissions: ["booking.read", "booking.write", "booking.admin"],
106
+ hooks: [
107
+ {
108
+ name: "beforeBookingCreate",
109
+ timing: "pre",
110
+ purpose: "Validate booking rules before a booking record is created.",
111
+ },
112
+ {
113
+ name: "calculateAvailability",
114
+ timing: "compute",
115
+ purpose: "Replace or adjust generated availability slots with business-specific rules.",
116
+ },
117
+ {
118
+ name: "afterBookingConfirmed",
119
+ timing: "post",
120
+ purpose: "Run post-confirmation workflows such as reminders or payment capture.",
121
+ },
122
+ ],
123
+ customization: {
124
+ config: [
125
+ "serviceTypes",
126
+ "slotIntervalMinutes",
127
+ "defaultDurationMinutes",
128
+ "leadTimeMinutes",
129
+ "cancellationWindowHours",
130
+ "allowWaitlist",
131
+ ],
132
+ hooks: ["beforeBookingCreate", "calculateAvailability", "afterBookingConfirmed"],
133
+ forkable: true,
134
+ },
135
+ quality: {
136
+ tests: { unit: true, integration: true, fixtures: true },
137
+ agentDocs: true,
138
+ migrations: true,
139
+ upgradeNotes: true,
140
+ },
141
+ },
142
+ ]);
143
+
144
+ const TEMPLATES = Object.freeze([
145
+ {
146
+ id: "booking-business",
147
+ name: "Booking Business",
148
+ version: "0.1.0",
149
+ status: "available",
150
+ summary: "A bookable service business foundation for studios, clinics, consultants, and local operators.",
151
+ targetCustomer: "AI-heavy agencies, consultants, and technical founders building custom booking systems.",
152
+ defaultModules: ["auth", "customer", "booking"],
153
+ optionalModules: ["email", "payment", "admin", "audit-log"],
154
+ targetRuntime: {
155
+ language: "typescript",
156
+ framework: "hono",
157
+ platform: "cloudflare-workers",
158
+ storage: ["d1", "kv"],
159
+ },
160
+ defaultConfig: {
161
+ appName: "Booking Business",
162
+ appSlug: "booking-business",
163
+ timezone: "UTC",
164
+ currency: "USD",
165
+ auth: {
166
+ defaultRole: "member",
167
+ sessionTtlSeconds: 604800,
168
+ },
169
+ customer: {
170
+ profileFields: ["name", "email", "phone", "notes"],
171
+ consentFields: ["emailMarketingConsent"],
172
+ },
173
+ booking: {
174
+ serviceTypes: ["consultation", "standard-service"],
175
+ slotIntervalMinutes: 30,
176
+ defaultDurationMinutes: 60,
177
+ leadTimeMinutes: 120,
178
+ cancellationWindowHours: 24,
179
+ maxFutureDays: 90,
180
+ allowWaitlist: false,
181
+ },
182
+ },
183
+ successCriteria: [
184
+ "Generated Hono Worker can run locally with Wrangler.",
185
+ "Generated schema includes account, customer, booking, event, and audit tables.",
186
+ "Hooks are explicit files an agent can customize without changing module internals.",
187
+ "Module lock records exact module versions used for upgrade comparison.",
188
+ ],
189
+ },
190
+ ]);
191
+
192
+ function clone(value) {
193
+ return JSON.parse(JSON.stringify(value));
194
+ }
195
+
196
+ function unique(values) {
197
+ return [...new Set(values.filter(Boolean))];
198
+ }
199
+
200
+ function createContractError(code, message, remediation, details = {}) {
201
+ const error = new Error(message);
202
+ error.name = "MicroservicesContractError";
203
+ error.code = code;
204
+ error.remediation = remediation;
205
+ error.details = details;
206
+ return error;
207
+ }
208
+
209
+ function moduleSummary(module) {
210
+ return {
211
+ id: module.id,
212
+ name: module.name,
213
+ version: module.version,
214
+ status: module.status,
215
+ category: module.category,
216
+ summary: module.summary,
217
+ requires: clone(module.requires),
218
+ mount: module.runtime.mount,
219
+ };
220
+ }
221
+
222
+ function templateSummary(template) {
223
+ return {
224
+ id: template.id,
225
+ name: template.name,
226
+ version: template.version,
227
+ status: template.status,
228
+ summary: template.summary,
229
+ defaultModules: clone(template.defaultModules),
230
+ optionalModules: clone(template.optionalModules),
231
+ };
232
+ }
233
+
234
+ function findById(items, id, kind) {
235
+ const item = items.find((candidate) => candidate.id === id);
236
+ if (!item) {
237
+ throw createContractError(
238
+ `${kind.toUpperCase()}_NOT_FOUND`,
239
+ `Unknown ${kind}: ${id}`,
240
+ `Run "${kind}s list --json" and select one of the returned ids.`,
241
+ { id }
242
+ );
243
+ }
244
+ return item;
245
+ }
246
+
247
+ function mergeConfig(base, override) {
248
+ if (!override || typeof override !== "object" || Array.isArray(override)) {
249
+ return clone(base);
250
+ }
251
+
252
+ const output = clone(base);
253
+ for (const [key, value] of Object.entries(override)) {
254
+ if (
255
+ value &&
256
+ typeof value === "object" &&
257
+ !Array.isArray(value) &&
258
+ output[key] &&
259
+ typeof output[key] === "object" &&
260
+ !Array.isArray(output[key])
261
+ ) {
262
+ output[key] = mergeConfig(output[key], value);
263
+ } else {
264
+ output[key] = clone(value);
265
+ }
266
+ }
267
+ return output;
268
+ }
269
+
270
+ export function listModules() {
271
+ return MODULES.map(moduleSummary);
272
+ }
273
+
274
+ export function inspectModule(id) {
275
+ return clone(findById(MODULES, id, "module"));
276
+ }
277
+
278
+ export function listTemplates() {
279
+ return TEMPLATES.map(templateSummary);
280
+ }
281
+
282
+ export function inspectTemplate(id) {
283
+ return clone(findById(TEMPLATES, id, "template"));
284
+ }
285
+
286
+ export function resolveModuleIds(moduleIds) {
287
+ const visited = new Set();
288
+ const ordered = [];
289
+
290
+ function visit(id) {
291
+ if (visited.has(id)) return;
292
+ const module = inspectModule(id);
293
+ visited.add(id);
294
+ for (const requiredId of module.requires) {
295
+ visit(requiredId);
296
+ }
297
+ ordered.push(id);
298
+ }
299
+
300
+ for (const moduleId of moduleIds) {
301
+ visit(moduleId);
302
+ }
303
+
304
+ return ordered;
305
+ }
306
+
307
+ export function createModuleLock(modules, template = null) {
308
+ return {
309
+ schemaVersion: CONTRACT_VERSION,
310
+ generatedAt: "deterministic-local-preview",
311
+ registry: {
312
+ id: "microservices.sh",
313
+ contractVersion: CONTRACT_VERSION,
314
+ },
315
+ generator: {
316
+ package: "create-microservices-app",
317
+ version: "0.0.0",
318
+ },
319
+ template: template
320
+ ? {
321
+ id: template.id,
322
+ version: template.version,
323
+ source: `registry:${template.id}@${template.version}`,
324
+ checksum: `sha256:preview-${template.id}-${template.version}`,
325
+ }
326
+ : null,
327
+ modules: modules.map((module) => ({
328
+ id: module.id,
329
+ version: module.version,
330
+ source: `registry:${module.id}@${module.version}`,
331
+ checksum: `sha256:preview-${module.id}-${module.version}`,
332
+ customizationMode: "config-hooks",
333
+ contract: {
334
+ mount: module.runtime.mount,
335
+ bindings: clone(module.runtime.bindings),
336
+ resources: module.storage.map((item) => item.toUpperCase()),
337
+ permissions: clone(module.permissions),
338
+ hooks: module.hooks.map((hook) => hook.name),
339
+ events: unique([...module.eventsEmitted, ...module.eventsConsumed]),
340
+ requires: clone(module.requires),
341
+ },
342
+ })),
343
+ customizations: {
344
+ config: true,
345
+ hooks: modules.flatMap((module) => module.customization?.hooks ?? []),
346
+ overlays: [],
347
+ forks: [],
348
+ },
349
+ };
350
+ }
351
+
352
+ export function composeApp(input = {}) {
353
+ const options = typeof input === "string" ? { templateId: input } : input;
354
+ const templateId = options.templateId ?? options.template ?? "booking-business";
355
+ const template = inspectTemplate(templateId);
356
+ const requestedModules = unique([
357
+ ...template.defaultModules,
358
+ ...(options.modules ?? []),
359
+ ]);
360
+ const resolvedModuleIds = resolveModuleIds(requestedModules);
361
+ const modules = resolvedModuleIds.map(inspectModule);
362
+ const config = mergeConfig(template.defaultConfig, options.config);
363
+
364
+ return {
365
+ schemaVersion: CONTRACT_VERSION,
366
+ compositionId: `cmp_${template.id}_${resolvedModuleIds.join("_")}`,
367
+ template: templateSummary(template),
368
+ config,
369
+ modules,
370
+ routes: modules.map((module) => ({
371
+ module: module.id,
372
+ mount: module.runtime.mount,
373
+ framework: module.runtime.framework,
374
+ })),
375
+ bindings: unique(modules.flatMap((module) => module.runtime.bindings)),
376
+ storage: unique(modules.flatMap((module) => module.storage)),
377
+ permissions: unique(modules.flatMap((module) => module.permissions)),
378
+ events: {
379
+ emitted: unique(modules.flatMap((module) => module.eventsEmitted)),
380
+ consumed: unique(modules.flatMap((module) => module.eventsConsumed)),
381
+ },
382
+ hooks: modules.flatMap((module) =>
383
+ module.hooks.map((hook) => ({
384
+ module: module.id,
385
+ ...hook,
386
+ }))
387
+ ),
388
+ checks: [
389
+ "module-contract",
390
+ "dependency-resolution",
391
+ "worker-bindings",
392
+ "schema-presence",
393
+ "hook-surface",
394
+ "audit-events",
395
+ ],
396
+ upgradePolicy: {
397
+ mode: "contract-lock",
398
+ lockfile: "microservices.lock.json",
399
+ compatibleCustomization: ["config", "hooks"],
400
+ manualCustomization: ["fork", "export"],
401
+ },
402
+ lock: createModuleLock(modules, template),
403
+ };
404
+ }
405
+
406
+ export { CONTRACT_VERSION };