@contractspec/example.service-business-os 3.7.6 → 3.7.7

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/README.md CHANGED
@@ -1,16 +1,73 @@
1
1
  # @contractspec/example.service-business-os
2
2
 
3
- Website: https://contractspec.io/
3
+ Website: https://contractspec.io
4
4
 
5
+ **Service Business OS example (clients, quotes, jobs, invoices) for ContractSpec.**
5
6
 
6
- Service Business OS reference example (clients → quotes → jobs → invoices → payments).
7
+ ## What This Demonstrates
7
8
 
8
- Highlights:
9
+ - Multi-entity domain modeling (client, quote, job, invoice, payment).
10
+ - Per-entity schema and operations pattern.
11
+ - Capability and feature definition patterns.
12
+ - Presentation layer and event-driven architecture.
13
+ - Handler aggregation.
14
+ - `src/docs/` contains docblocks and documentation-facing exports.
9
15
 
10
- - Multi-tenant clients with role-based access via `@contractspec/lib.identity-rbac`
11
- - Quote lifecycle (draft → sent → accepted/rejected)
12
- - Jobs scheduling and completion with reminders via `@contractspec/lib.jobs`
13
- - Invoicing and payments with audit trail + notifications
14
- - Attachments for proposals and receipts via `@contractspec/lib.files`
16
+ ## Running Locally
15
17
 
16
- Use this as a spec-first starting point for field services, agencies, or professional services teams.
18
+ From `packages/examples/service-business-os`:
19
+ - `bun run dev`
20
+ - `bun run build`
21
+ - `bun run typecheck`
22
+
23
+ ## Usage
24
+
25
+ Use `@contractspec/example.service-business-os` as a reference implementation, or import its exported surfaces into a workspace that composes ContractSpec examples and bundles.
26
+
27
+ ## Architecture
28
+
29
+ - `src/client` is part of the package's public or composition surface.
30
+ - `src/docs/` contains docblocks and documentation-facing exports.
31
+ - `src/entities/` contains domain entities and value objects.
32
+ - `src/events.ts` is package-level event definitions.
33
+ - `src/example.ts` is the runnable example entrypoint.
34
+ - `src/handlers/` contains handlers or demo adapters wired to contract surfaces.
35
+ - `src/index.ts` is the root public barrel and package entrypoint.
36
+
37
+ ## Public Entry Points
38
+
39
+ - Export `.` resolves through `./src/index.ts`.
40
+ - Export `./client` resolves through `./src/client/index.ts`.
41
+ - Export `./client/client.operations` resolves through `./src/client/client.operations.ts`.
42
+ - Export `./client/client.schema` resolves through `./src/client/client.schema.ts`.
43
+ - Export `./docs` resolves through `./src/docs/index.ts`.
44
+ - Export `./docs/service-business-os.docblock` resolves through `./src/docs/service-business-os.docblock.ts`.
45
+ - Export `./entities` resolves through `./src/entities/index.ts`.
46
+ - Export `./events` resolves through `./src/events.ts`.
47
+ - Export `./example` resolves through `./src/example.ts`.
48
+ - Export `./handlers` resolves through `./src/handlers/index.ts`.
49
+ - The package publishes 26 total export subpaths; keep docs aligned with `package.json`.
50
+
51
+ ## Local Commands
52
+
53
+ - `bun run dev` — contractspec-bun-build dev
54
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
55
+ - `bun run lint` — bun lint:fix
56
+ - `bun run lint:check` — biome check .
57
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
58
+ - `bun run typecheck` — tsc --noEmit
59
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
60
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
61
+ - `bun run clean` — rimraf dist .turbo
62
+ - `bun run build:bundle` — contractspec-bun-build transpile
63
+ - `bun run build:types` — contractspec-bun-build types
64
+ - `bun run prebuild` — contractspec-bun-build prebuild
65
+
66
+ ## Recent Updates
67
+
68
+ - Replace eslint+prettier by biomejs to optimize speed.
69
+ - Missing contract layers.
70
+
71
+ ## Notes
72
+
73
+ - Works alongside `@contractspec/lib.contracts-spec`, `@contractspec/lib.schema`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
@@ -1,6 +1,6 @@
1
1
  // src/events.ts
2
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
3
2
  import { defineEvent } from "@contractspec/lib.contracts-spec";
3
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
4
4
  var QuoteEventPayload = defineSchemaModel({
5
5
  name: "QuoteEventPayload",
6
6
  description: "Event payload for quote lifecycle",
@@ -67,117 +67,74 @@ var CreateClientContract = defineCommand({
67
67
  ]
68
68
  }
69
69
  });
70
- // src/quote/quote.schema.ts
70
+ // src/invoice/invoice.schema.ts
71
71
  import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
72
- var QuoteModel = defineSchemaModel2({
73
- name: "Quote",
74
- description: "Quote/proposal",
72
+ var InvoiceModel = defineSchemaModel2({
73
+ name: "Invoice",
74
+ description: "Invoice issued for a job",
75
75
  fields: {
76
76
  id: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
77
- clientId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
78
- title: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
79
- description: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
77
+ jobId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
78
+ invoiceNumber: {
79
+ type: ScalarTypeEnum2.String_unsecure(),
80
+ isOptional: false
81
+ },
80
82
  amount: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
81
83
  currency: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
82
84
  status: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
83
- validUntil: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
84
- createdAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
85
- }
86
- });
87
- var CreateQuoteInputModel = defineSchemaModel2({
88
- name: "CreateQuoteInput",
89
- description: "Input for creating a quote",
90
- fields: {
91
- clientId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
92
- title: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
93
- description: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
94
- amount: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
95
- currency: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
96
- validUntil: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
97
- orgId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
98
- ownerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false }
85
+ dueDate: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
86
+ issuedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
87
+ paidAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true }
99
88
  }
100
89
  });
101
- var AcceptQuoteInputModel = defineSchemaModel2({
102
- name: "AcceptQuoteInput",
103
- description: "Input for accepting a quote",
90
+ var IssueInvoiceInputModel = defineSchemaModel2({
91
+ name: "IssueInvoiceInput",
92
+ description: "Input for issuing an invoice",
104
93
  fields: {
105
- quoteId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
106
- acceptedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
107
- notes: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true }
94
+ jobId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
95
+ dueDate: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
96
+ notes: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
97
+ lineItems: { type: ScalarTypeEnum2.JSON(), isOptional: true }
108
98
  }
109
99
  });
110
100
 
111
- // src/quote/quote.operations.ts
101
+ // src/invoice/invoice.operations.ts
112
102
  import { defineCommand as defineCommand2 } from "@contractspec/lib.contracts-spec";
113
103
  var OWNERS2 = ["@examples.service-business-os"];
114
- var CreateQuoteContract = defineCommand2({
104
+ var IssueInvoiceContract = defineCommand2({
115
105
  meta: {
116
- key: "service.quote.create",
106
+ key: "service.invoice.issue",
117
107
  version: "1.0.0",
118
108
  stability: "stable",
119
109
  owners: [...OWNERS2],
120
- tags: ["service-business-os", "quote", "create"],
121
- description: "Create a quote/proposal.",
122
- goal: "Quote clients.",
123
- context: "Quote creation."
110
+ tags: ["service-business-os", "invoice", "issue"],
111
+ description: "Issue an invoice for a job.",
112
+ goal: "Bill clients.",
113
+ context: "Billing."
124
114
  },
125
115
  io: {
126
- input: CreateQuoteInputModel,
127
- output: QuoteModel
116
+ input: IssueInvoiceInputModel,
117
+ output: InvoiceModel
128
118
  },
129
119
  policy: { auth: "user" },
130
120
  acceptance: {
131
121
  scenarios: [
132
122
  {
133
- key: "create-quote-happy-path",
134
- given: ["Client exists"],
135
- when: ["User creates quote"],
136
- then: ["Quote is created"]
123
+ key: "issue-invoice-happy-path",
124
+ given: ["Job is complete"],
125
+ when: ["User issues invoice"],
126
+ then: ["Invoice is created and sent"]
137
127
  }
138
128
  ],
139
129
  examples: [
140
130
  {
141
- key: "create-proposal",
131
+ key: "issue-standard",
142
132
  input: {
143
- clientId: "client-123",
144
- items: [{ description: "Project A", price: 5000 }]
133
+ jobId: "job-123",
134
+ dueDate: "2025-02-01",
135
+ items: [{ description: "Service", amount: 100 }]
145
136
  },
146
- output: { id: "quote-123", status: "draft", total: 5000 }
147
- }
148
- ]
149
- }
150
- });
151
- var AcceptQuoteContract = defineCommand2({
152
- meta: {
153
- key: "service.quote.accept",
154
- version: "1.0.0",
155
- stability: "stable",
156
- owners: [...OWNERS2],
157
- tags: ["service-business-os", "quote", "accept"],
158
- description: "Accept a quote.",
159
- goal: "Confirm quote.",
160
- context: "Quote acceptance."
161
- },
162
- io: {
163
- input: AcceptQuoteInputModel,
164
- output: QuoteModel
165
- },
166
- policy: { auth: "user" },
167
- acceptance: {
168
- scenarios: [
169
- {
170
- key: "accept-quote-happy-path",
171
- given: ["Quote is open"],
172
- when: ["Client accepts quote"],
173
- then: ["Quote status becomes ACCEPTED"]
174
- }
175
- ],
176
- examples: [
177
- {
178
- key: "client-accepts",
179
- input: { quoteId: "quote-123", signature: "John Doe" },
180
- output: { id: "quote-123", status: "accepted" }
137
+ output: { id: "inv-456", status: "issued", total: 100 }
181
138
  }
182
139
  ]
183
140
  }
@@ -347,114 +304,42 @@ var CompleteJobContract = defineCommand3({
347
304
  ]
348
305
  }
349
306
  });
350
- // src/invoice/invoice.schema.ts
307
+ // src/payment/payment.schema.ts
351
308
  import { defineSchemaModel as defineSchemaModel5, ScalarTypeEnum as ScalarTypeEnum5 } from "@contractspec/lib.schema";
352
- var InvoiceModel = defineSchemaModel5({
353
- name: "Invoice",
354
- description: "Invoice issued for a job",
309
+ var PaymentModel = defineSchemaModel5({
310
+ name: "Payment",
311
+ description: "Payment applied to invoice",
355
312
  fields: {
356
313
  id: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
357
- jobId: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
358
- invoiceNumber: {
359
- type: ScalarTypeEnum5.String_unsecure(),
360
- isOptional: false
361
- },
314
+ invoiceId: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
362
315
  amount: { type: ScalarTypeEnum5.Float_unsecure(), isOptional: false },
363
316
  currency: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
364
- status: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
365
- dueDate: { type: ScalarTypeEnum5.DateTime(), isOptional: true },
366
- issuedAt: { type: ScalarTypeEnum5.DateTime(), isOptional: true },
367
- paidAt: { type: ScalarTypeEnum5.DateTime(), isOptional: true }
368
- }
369
- });
370
- var IssueInvoiceInputModel = defineSchemaModel5({
371
- name: "IssueInvoiceInput",
372
- description: "Input for issuing an invoice",
373
- fields: {
374
- jobId: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
375
- dueDate: { type: ScalarTypeEnum5.DateTime(), isOptional: true },
376
- notes: { type: ScalarTypeEnum5.String_unsecure(), isOptional: true },
377
- lineItems: { type: ScalarTypeEnum5.JSON(), isOptional: true }
378
- }
379
- });
380
-
381
- // src/invoice/invoice.operations.ts
382
- import { defineCommand as defineCommand4 } from "@contractspec/lib.contracts-spec";
383
- var OWNERS4 = ["@examples.service-business-os"];
384
- var IssueInvoiceContract = defineCommand4({
385
- meta: {
386
- key: "service.invoice.issue",
387
- version: "1.0.0",
388
- stability: "stable",
389
- owners: [...OWNERS4],
390
- tags: ["service-business-os", "invoice", "issue"],
391
- description: "Issue an invoice for a job.",
392
- goal: "Bill clients.",
393
- context: "Billing."
394
- },
395
- io: {
396
- input: IssueInvoiceInputModel,
397
- output: InvoiceModel
398
- },
399
- policy: { auth: "user" },
400
- acceptance: {
401
- scenarios: [
402
- {
403
- key: "issue-invoice-happy-path",
404
- given: ["Job is complete"],
405
- when: ["User issues invoice"],
406
- then: ["Invoice is created and sent"]
407
- }
408
- ],
409
- examples: [
410
- {
411
- key: "issue-standard",
412
- input: {
413
- jobId: "job-123",
414
- dueDate: "2025-02-01",
415
- items: [{ description: "Service", amount: 100 }]
416
- },
417
- output: { id: "inv-456", status: "issued", total: 100 }
418
- }
419
- ]
420
- }
421
- });
422
- // src/payment/payment.schema.ts
423
- import { defineSchemaModel as defineSchemaModel6, ScalarTypeEnum as ScalarTypeEnum6 } from "@contractspec/lib.schema";
424
- var PaymentModel = defineSchemaModel6({
425
- name: "Payment",
426
- description: "Payment applied to invoice",
427
- fields: {
428
- id: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
429
- invoiceId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
430
- amount: { type: ScalarTypeEnum6.Float_unsecure(), isOptional: false },
431
- currency: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
432
- method: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
433
- reference: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true },
434
- receivedAt: { type: ScalarTypeEnum6.DateTime(), isOptional: false }
317
+ method: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
318
+ reference: { type: ScalarTypeEnum5.String_unsecure(), isOptional: true },
319
+ receivedAt: { type: ScalarTypeEnum5.DateTime(), isOptional: false }
435
320
  }
436
321
  });
437
- var RecordPaymentInputModel = defineSchemaModel6({
322
+ var RecordPaymentInputModel = defineSchemaModel5({
438
323
  name: "RecordPaymentInput",
439
324
  description: "Input for recording a payment",
440
325
  fields: {
441
- invoiceId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
442
- amount: { type: ScalarTypeEnum6.Float_unsecure(), isOptional: false },
443
- method: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
444
- reference: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true },
445
- receivedAt: { type: ScalarTypeEnum6.DateTime(), isOptional: true }
326
+ invoiceId: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
327
+ amount: { type: ScalarTypeEnum5.Float_unsecure(), isOptional: false },
328
+ method: { type: ScalarTypeEnum5.String_unsecure(), isOptional: false },
329
+ reference: { type: ScalarTypeEnum5.String_unsecure(), isOptional: true },
330
+ receivedAt: { type: ScalarTypeEnum5.DateTime(), isOptional: true }
446
331
  }
447
332
  });
448
333
 
449
334
  // src/payment/payment.operations.ts
450
- import { defineCommand as defineCommand5 } from "@contractspec/lib.contracts-spec";
451
- var OWNERS5 = ["@examples.service-business-os"];
452
- var RecordPaymentContract = defineCommand5({
335
+ import { defineCommand as defineCommand4 } from "@contractspec/lib.contracts-spec";
336
+ var OWNERS4 = ["@examples.service-business-os"];
337
+ var RecordPaymentContract = defineCommand4({
453
338
  meta: {
454
339
  key: "service.payment.record",
455
340
  version: "1.0.0",
456
341
  stability: "stable",
457
- owners: [...OWNERS5],
342
+ owners: [...OWNERS4],
458
343
  tags: ["service-business-os", "payment", "record"],
459
344
  description: "Record a payment.",
460
345
  goal: "Track payments.",
@@ -650,6 +535,121 @@ var PaymentListPresentation = definePresentation({
650
535
  }
651
536
  });
652
537
 
538
+ // src/quote/quote.schema.ts
539
+ import { defineSchemaModel as defineSchemaModel6, ScalarTypeEnum as ScalarTypeEnum6 } from "@contractspec/lib.schema";
540
+ var QuoteModel = defineSchemaModel6({
541
+ name: "Quote",
542
+ description: "Quote/proposal",
543
+ fields: {
544
+ id: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
545
+ clientId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
546
+ title: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
547
+ description: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true },
548
+ amount: { type: ScalarTypeEnum6.Float_unsecure(), isOptional: false },
549
+ currency: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
550
+ status: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
551
+ validUntil: { type: ScalarTypeEnum6.DateTime(), isOptional: true },
552
+ createdAt: { type: ScalarTypeEnum6.DateTime(), isOptional: false }
553
+ }
554
+ });
555
+ var CreateQuoteInputModel = defineSchemaModel6({
556
+ name: "CreateQuoteInput",
557
+ description: "Input for creating a quote",
558
+ fields: {
559
+ clientId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
560
+ title: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
561
+ description: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true },
562
+ amount: { type: ScalarTypeEnum6.Float_unsecure(), isOptional: false },
563
+ currency: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true },
564
+ validUntil: { type: ScalarTypeEnum6.DateTime(), isOptional: true },
565
+ orgId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
566
+ ownerId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false }
567
+ }
568
+ });
569
+ var AcceptQuoteInputModel = defineSchemaModel6({
570
+ name: "AcceptQuoteInput",
571
+ description: "Input for accepting a quote",
572
+ fields: {
573
+ quoteId: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
574
+ acceptedBy: { type: ScalarTypeEnum6.String_unsecure(), isOptional: false },
575
+ notes: { type: ScalarTypeEnum6.String_unsecure(), isOptional: true }
576
+ }
577
+ });
578
+
579
+ // src/quote/quote.operations.ts
580
+ import { defineCommand as defineCommand5 } from "@contractspec/lib.contracts-spec";
581
+ var OWNERS5 = ["@examples.service-business-os"];
582
+ var CreateQuoteContract = defineCommand5({
583
+ meta: {
584
+ key: "service.quote.create",
585
+ version: "1.0.0",
586
+ stability: "stable",
587
+ owners: [...OWNERS5],
588
+ tags: ["service-business-os", "quote", "create"],
589
+ description: "Create a quote/proposal.",
590
+ goal: "Quote clients.",
591
+ context: "Quote creation."
592
+ },
593
+ io: {
594
+ input: CreateQuoteInputModel,
595
+ output: QuoteModel
596
+ },
597
+ policy: { auth: "user" },
598
+ acceptance: {
599
+ scenarios: [
600
+ {
601
+ key: "create-quote-happy-path",
602
+ given: ["Client exists"],
603
+ when: ["User creates quote"],
604
+ then: ["Quote is created"]
605
+ }
606
+ ],
607
+ examples: [
608
+ {
609
+ key: "create-proposal",
610
+ input: {
611
+ clientId: "client-123",
612
+ items: [{ description: "Project A", price: 5000 }]
613
+ },
614
+ output: { id: "quote-123", status: "draft", total: 5000 }
615
+ }
616
+ ]
617
+ }
618
+ });
619
+ var AcceptQuoteContract = defineCommand5({
620
+ meta: {
621
+ key: "service.quote.accept",
622
+ version: "1.0.0",
623
+ stability: "stable",
624
+ owners: [...OWNERS5],
625
+ tags: ["service-business-os", "quote", "accept"],
626
+ description: "Accept a quote.",
627
+ goal: "Confirm quote.",
628
+ context: "Quote acceptance."
629
+ },
630
+ io: {
631
+ input: AcceptQuoteInputModel,
632
+ output: QuoteModel
633
+ },
634
+ policy: { auth: "user" },
635
+ acceptance: {
636
+ scenarios: [
637
+ {
638
+ key: "accept-quote-happy-path",
639
+ given: ["Quote is open"],
640
+ when: ["Client accepts quote"],
641
+ then: ["Quote status becomes ACCEPTED"]
642
+ }
643
+ ],
644
+ examples: [
645
+ {
646
+ key: "client-accepts",
647
+ input: { quoteId: "quote-123", signature: "John Doe" },
648
+ output: { id: "quote-123", status: "accepted" }
649
+ }
650
+ ]
651
+ }
652
+ });
653
653
  // src/service.feature.ts
654
654
  import { defineFeature } from "@contractspec/lib.contracts-spec";
655
655
  var ServiceBusinessFeature = defineFeature({