@rmrdeveloper/sideroom-pi 5.0.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.
@@ -0,0 +1,235 @@
1
+ # JavaScript Coding Guidelines
2
+
3
+ This is the JavaScript rendition of `GUIDELINES_TEMPLATE.md`. It keeps every
4
+ principle and teaching scenario from the template, expressed for modern
5
+ ECMAScript and Node.js. Read it together with the shared template before any
6
+ code-writing operation.
7
+
8
+ ## Quick reference
9
+
10
+ | Do | Don't |
11
+ | --- | --- |
12
+ | Exit invalid paths early | Bury the happy path in nested `if/else` blocks |
13
+ | Throw on broken invariants | Substitute defaults that hide corrupted state |
14
+ | Separate validation, transformation, persistence, and notification | Put unrelated responsibilities in one function |
15
+ | Extract a repeated domain rule | Generalize a one-off helper |
16
+ | Keep today's invoice and price requirements small | Add options for hypothetical future features |
17
+ | Return replacement values | Mutate parameter-owned arrays or objects |
18
+ | Validate transport input once at the boundary | Repeat the same checks in services and helpers |
19
+ | Name domain literals once | Scatter status strings and event names |
20
+
21
+ ## Principles
22
+
23
+ ### Guard clauses
24
+
25
+ ```js
26
+ // Bad — the real path is buried
27
+ function getDiscount(user) {
28
+ if (user) {
29
+ if (user.isActive) {
30
+ if (user.hasSubscription) return 0.2;
31
+ }
32
+ }
33
+ return 0;
34
+ }
35
+
36
+ // Good — flat and explicit
37
+ function getDiscount(user) {
38
+ if (!user) return 0;
39
+ if (!user.isActive) return 0;
40
+ if (!user.hasSubscription) return 0;
41
+ return 0.2;
42
+ }
43
+ ```
44
+
45
+ ### Fail fast
46
+
47
+ ```js
48
+ // Bad — a bad zone id is hidden
49
+ function getShippingCost(order) {
50
+ return ZONES[order.zoneId] ?? ZONES.default;
51
+ }
52
+
53
+ // Good — preserve the violated invariant
54
+ function getShippingCost(order) {
55
+ const zone = ZONES[order.zoneId];
56
+ if (!zone) throw new Error(`Unknown zoneId: ${order.zoneId}`);
57
+ return zone.baseCost;
58
+ }
59
+ ```
60
+
61
+ ### SRP
62
+
63
+ Keep the user scenario split into validation, normalization, persistence, and
64
+ notification. A coordinator may compose these units, but should not conceal
65
+ their responsibilities.
66
+
67
+ ```js
68
+ function validateUser(data) {
69
+ if (!data.email) throw new Error('email required');
70
+ }
71
+ function normalizeUser(data) {
72
+ return { ...data, email: data.email.trim().toLowerCase() };
73
+ }
74
+ async function createUser(data, users, mailer) {
75
+ validateUser(data);
76
+ const user = await users.insert(normalizeUser(data));
77
+ await mailer.send(user.email, 'welcome');
78
+ return user;
79
+ }
80
+ ```
81
+
82
+ ### DRY
83
+
84
+ Extract ticket-status normalization only after it is a repeated domain rule,
85
+ not because two unrelated strings happen to be trimmed the same way.
86
+
87
+ ```js
88
+ const TICKET_STATUS = Object.freeze({ OPEN: 'open', CLOSED: 'closed' });
89
+ function normalizeTicketStatus(value) {
90
+ if (Object.values(TICKET_STATUS).includes(value)) return value;
91
+ throw new Error(`Unknown ticket status: ${value}`);
92
+ }
93
+ ```
94
+
95
+ ### KISS
96
+
97
+ The price example solves only the current requirement. Do not add locale
98
+ strategies or configuration objects without a present use.
99
+
100
+ ```js
101
+ function formatPrice(value) {
102
+ return `$${value.toFixed(2)}`;
103
+ }
104
+ ```
105
+
106
+ ### YAGNI
107
+
108
+ The invoice example solves only the current requirement. Do not add recurring
109
+ billing flags, multi-currency branches, or future configuration without a
110
+ present use.
111
+
112
+ ```js
113
+ function createInvoice(order) {
114
+ return { total: order.total, items: order.items };
115
+ }
116
+ ```
117
+
118
+ ### Composition, Demeter, and CQS
119
+
120
+ Compose only useful behavior, ask immediate collaborators for their own data,
121
+ and keep a query separate from mutation.
122
+
123
+ ```js
124
+ const canBark = { makeSound: () => 'Woof' };
125
+ const dog = { ...canBark };
126
+
127
+ function getCityName(user) {
128
+ return user.getCityName();
129
+ }
130
+ function peekNextId(counter) {
131
+ return counter.value + 1;
132
+ }
133
+ function incrementCounter(counter) {
134
+ counter.value += 1;
135
+ }
136
+ ```
137
+
138
+ ### Explicit errors, immutability, and nulls
139
+
140
+ ```js
141
+ async function loadUser(id, api) {
142
+ try {
143
+ return await api.getUser(id);
144
+ } catch (error) {
145
+ throw new Error(`Failed to load user ${id}`, { cause: error });
146
+ }
147
+ }
148
+
149
+ function addItem(cart, item) {
150
+ return { ...cart, items: [...cart.items, item] };
151
+ }
152
+
153
+ function getPlanDiscount(user) {
154
+ return user?.plan?.discount ?? 0;
155
+ }
156
+ ```
157
+
158
+ Use one documented absence convention. Do not overload `null` or `undefined`
159
+ to represent distinct business states when an explicit value is clearer.
160
+
161
+ ### Testability as a design constraint
162
+
163
+ Inject time and infrastructure dependencies rather than reaching for globals.
164
+
165
+ ```js
166
+ function isSubscriptionExpired(subscription, now) {
167
+ return subscription.expiresAt < now;
168
+ }
169
+ ```
170
+
171
+ ### Dependency direction
172
+
173
+ Domain calculations receive domain data instead of constructing SQL, HTTP, or
174
+ mail clients themselves.
175
+
176
+ ```js
177
+ function calculateInvoiceTotal(invoice) {
178
+ return invoice.items.reduce((sum, item) => sum + item.price, 0);
179
+ }
180
+ ```
181
+
182
+ ### Clear names
183
+
184
+ Use `activeSubscriptions`, `remainingDays`, and `externalId`, not `data`,
185
+ `temp`, or `x`.
186
+
187
+ ### Comments
188
+
189
+ Explain only non-obvious trade-offs and hazards. Do not narrate a line whose
190
+ name and structure already make it clear.
191
+
192
+ ### No magic strings
193
+
194
+ Name statuses once rather than scattering literals:
195
+
196
+ ```js
197
+ const ORDER_STATUS = Object.freeze({
198
+ PENDING_PAYMENT: 'pending_payment',
199
+ PAID: 'paid',
200
+ });
201
+ ```
202
+
203
+ ### SOLID
204
+
205
+ Depend on a mailer port where the infrastructure can vary:
206
+
207
+ ```js
208
+ class SendWelcomeEmail {
209
+ constructor(mailer) {
210
+ this.mailer = mailer;
211
+ }
212
+ execute(user) {
213
+ return this.mailer.send(user.email, 'welcome');
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### Validate once
219
+
220
+ Parse and validate a request body at its transport boundary. Services trust the
221
+ validated command and do not repeat required, type, or range checks.
222
+
223
+ ```js
224
+ function parseUpdateProfile(body) {
225
+ if (typeof body?.email !== 'string' || body.email.length === 0) {
226
+ throw new Error('email required');
227
+ }
228
+ return { email: body.email };
229
+ }
230
+ async function updateProfile(command, users) {
231
+ await users.update(command);
232
+ }
233
+ ```
234
+
235
+ When in doubt: **fail fast, keep it flat, keep it small.**
@@ -0,0 +1,210 @@
1
+ # PHP Laravel Coding Guidelines
2
+
3
+ This is the PHP Laravel version of `GUIDELINES_TEMPLATE.md`. It preserves the
4
+ same decisions and teaching scenarios using strict PHP, Laravel boundaries,
5
+ and Laravel's testing conventions. The shared baseline remains mandatory.
6
+
7
+ ## Quick reference
8
+
9
+ | Do | Don't |
10
+ | --- | --- |
11
+ | Guard invalid input early | Nest the happy path in `if/else` pyramids |
12
+ | Throw precise exceptions | Fall back to a default that hides bad state |
13
+ | Keep controller, use case, and infrastructure separate | Validate, persist, and notify in one method |
14
+ | Extract repeated domain behavior | Build a service for one use |
15
+ | Use immutable value data where practical | Mutate caller-owned arrays or models invisibly |
16
+ | Validate in a Form Request once | Repeat its rules in services |
17
+ | Inject interfaces at real seams | Construct facades or clients in domain logic |
18
+ | Use named enums/constants | Repeat status strings |
19
+
20
+ ## Principles
21
+
22
+ ### Guard clauses
23
+
24
+ The discount example stays flat.
25
+
26
+ ```php
27
+ function getDiscount(?User $user): float
28
+ {
29
+ if ($user === null) return 0.0;
30
+ if (! $user->isActive()) return 0.0;
31
+ if (! $user->hasSubscription()) return 0.0;
32
+ return 0.2;
33
+ }
34
+ ```
35
+
36
+ ### Fail fast
37
+
38
+ Do not silently choose a default shipping zone.
39
+
40
+ ```php
41
+ function shippingCost(string $zoneId, ZoneRepository $zones): int
42
+ {
43
+ $zone = $zones->find($zoneId);
44
+ if ($zone === null) throw new DomainException("Unknown zoneId: {$zoneId}");
45
+ return $zone->baseCost();
46
+ }
47
+ ```
48
+
49
+ ### SRP
50
+
51
+ The user flow keeps validation, normalization, persistence, and notification
52
+ as distinct responsibilities.
53
+
54
+ ```php
55
+ final class CreateUser
56
+ {
57
+ public function __construct(private UserRepository $users, private Mailer $mailer) {}
58
+ public function handle(CreateUserData $data): void
59
+ {
60
+ $user = $this->users->create($data->withEmail(strtolower(trim($data->email))));
61
+ $this->mailer->send($user->email, 'welcome');
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### DRY
67
+
68
+ Extract the third repeated ticket-status normalization, not a one-off helper.
69
+
70
+ ```php
71
+ enum TicketStatus: string { case Open = 'open'; case Closed = 'closed'; }
72
+ function normalizeTicketStatus(string $value): TicketStatus { return TicketStatus::from($value); }
73
+ ```
74
+
75
+ ### KISS
76
+
77
+ Keep the price and invoice examples limited to their actual requirements.
78
+
79
+ ```php
80
+ function formatPrice(float $value): string { return '$'.number_format($value, 2); }
81
+ ```
82
+
83
+ ### YAGNI (You Aren't Gonna Need It)
84
+
85
+ Build the invoice shape needed today; do not add multi-currency, recurrence,
86
+ queues, events, or cache abstractions without a current use case.
87
+
88
+ ```php
89
+ function createInvoice(Order $order): array { return ['total' => $order->total, 'items' => $order->items]; }
90
+ ```
91
+
92
+ ### Composition over inheritance
93
+
94
+ Use focused services and value objects instead of a base class that forces a
95
+ dog to inherit `fly()`.
96
+
97
+ ```php
98
+ final class Dog { public function __construct(private BarkBehavior $bark) {} public function sound(): string { return $this->bark->sound(); } }
99
+ ```
100
+
101
+ ### Law of Demeter
102
+
103
+ Ask the user for its city instead of chaining relations.
104
+
105
+ ```php
106
+ function cityName(User $user): string { return $user->cityName(); }
107
+ ```
108
+
109
+ ### Command Query Separation
110
+
111
+ Keep peeking at an identifier separate from incrementing a counter.
112
+
113
+ ```php
114
+ function peekNextId(Counter $counter): int { return $counter->value() + 1; }
115
+ function incrementCounter(Counter $counter): void { $counter->increment(); }
116
+ ```
117
+
118
+ ### Explicit error handling
119
+
120
+ Translate infrastructure failures with context; do not catch and return null.
121
+
122
+ ```php
123
+ function loadUser(string $id, UserApi $api): User
124
+ {
125
+ try { return $api->get($id); }
126
+ catch (Throwable $error) { throw new RuntimeException("Failed to load user {$id}", 0, $error); }
127
+ }
128
+ ```
129
+
130
+ ### Immutability by default
131
+
132
+ Return a replacement cart or DTO rather than changing the caller's collection.
133
+
134
+ ```php
135
+ function addItem(CartData $cart, ItemData $item): CartData
136
+ {
137
+ return new CartData([...$cart->items, $item]);
138
+ }
139
+ ```
140
+
141
+ ### Null handling
142
+
143
+ Use explicit nullable contracts and preserve a legitimate zero discount.
144
+
145
+ ```php
146
+ function getDiscount(?User $user): float { return $user?->plan()?->discount() ?? 0.0; }
147
+ ```
148
+
149
+ ### Testability as a design constraint
150
+
151
+ Inject the clock rather than calling `now()` inside domain behavior.
152
+
153
+ ```php
154
+ function isSubscriptionExpired(Subscription $subscription, DateTimeImmutable $now): bool
155
+ {
156
+ return $subscription->expiresAt() < $now;
157
+ }
158
+ ```
159
+
160
+ ### Dependency direction
161
+
162
+ Use cases receive repositories or domain data; domain code never constructs
163
+ Eloquent queries or reads `Request` globals.
164
+
165
+ ```php
166
+ function calculateInvoiceTotal(Invoice $invoice): int
167
+ {
168
+ return array_sum(array_map(fn (InvoiceItem $item) => $item->price(), $invoice->items()));
169
+ }
170
+ ```
171
+
172
+ ### Clear names
173
+
174
+ Use `$activeSubscriptions`, `$remainingDays`, and `$externalId`; avoid `$data`,
175
+ `$temp`, and `$result`.
176
+
177
+ ### Comments
178
+
179
+ Comments explain a hidden trade-off or an idempotency hazard, never narrate a
180
+ line of PHP.
181
+
182
+ ### No magic strings
183
+
184
+ Use a backed enum for the order state scenario.
185
+
186
+ ```php
187
+ enum OrderStatus: string { case PendingPayment = 'pending_payment'; case Paid = 'paid'; }
188
+ if ($order->status === OrderStatus::PendingPayment) { $payments->request($order); }
189
+ ```
190
+
191
+ ### SOLID
192
+
193
+ Depend on a mailer contract, not an SMTP implementation in a use case.
194
+
195
+ ```php
196
+ interface Mailer { public function send(string $email, string $template): void; }
197
+ final class SendWelcomeEmail { public function __construct(private Mailer $mailer) {} }
198
+ ```
199
+
200
+ ### Validate once
201
+
202
+ The Form Request owns HTTP validation; the service trusts its validated DTO.
203
+
204
+ ```php
205
+ final class UpdateProfileRequest extends FormRequest { public function rules(): array { return ['email' => ['required', 'email']]; } }
206
+ final class UpdateProfile { public function handle(UpdateProfileData $data): void { /* persist $data */ } }
207
+ ```
208
+
209
+ Use one validation contract per body/query. When in doubt: **fail fast, keep it
210
+ flat, keep it small.**
@@ -0,0 +1,227 @@
1
+ # Python Coding Guidelines
2
+
3
+ This is the Python version of `GUIDELINES_TEMPLATE.md`. It preserves the same
4
+ principles and example scenarios with typed Python, explicit boundaries, and
5
+ Python testing conventions. The shared baseline remains mandatory.
6
+
7
+ ## Quick reference
8
+
9
+ | Do | Don't |
10
+ | --- | --- |
11
+ | Return early for invalid state | Hide the happy path inside nested branches |
12
+ | Raise precise errors immediately | Use a default to hide a broken invariant |
13
+ | Give each function one reason to change | Validate, transform, save, and notify together |
14
+ | Extract real repeated domain logic | Add abstractions before they earn a second use |
15
+ | Keep inputs immutable by default | Surprise callers by changing their objects |
16
+ | Validate once at transport boundaries | Repeat body/query validation in every layer |
17
+ | Inject time and infrastructure | Reach for global clients in domain code |
18
+ | Use enums and named constants | Spread protocol strings around the codebase |
19
+
20
+ ## Principles
21
+
22
+ ### Guard clauses
23
+
24
+ The discount scenario is flat and typed.
25
+
26
+ ```python
27
+ def get_discount(user: User | None) -> float:
28
+ if user is None:
29
+ return 0.0
30
+ if not user.is_active:
31
+ return 0.0
32
+ if not user.has_subscription:
33
+ return 0.0
34
+ return 0.2
35
+ ```
36
+
37
+ ### Fail fast
38
+
39
+ Never conceal a bad zone key with a default zone.
40
+
41
+ ```python
42
+ def shipping_cost(zone_id: str, zones: dict[str, Zone]) -> int:
43
+ zone = zones.get(zone_id)
44
+ if zone is None:
45
+ raise ValueError(f"Unknown zone_id: {zone_id}")
46
+ return zone.base_cost
47
+ ```
48
+
49
+ ### SRP
50
+
51
+ The user flow separates normalization, persistence, and notification.
52
+
53
+ ```python
54
+ def normalize_email(email: str) -> str:
55
+ return email.strip().lower()
56
+
57
+ def create_user(email: str, users: UserRepository, mailer: Mailer) -> User:
58
+ if not email:
59
+ raise ValueError("email required")
60
+ user = users.insert(email=normalize_email(email))
61
+ mailer.send(user.email, "welcome")
62
+ return user
63
+ ```
64
+
65
+ ### DRY
66
+
67
+ Extract the third repeated ticket-status normalization, not a helper created
68
+ for a single call.
69
+
70
+ ```python
71
+ class TicketStatus(StrEnum):
72
+ OPEN = "open"
73
+ CLOSED = "closed"
74
+
75
+ def normalize_ticket_status(value: str) -> TicketStatus:
76
+ return TicketStatus(value)
77
+ ```
78
+
79
+ ### KISS
80
+
81
+ Keep price and invoice code focused on the current requirement.
82
+
83
+ ```python
84
+ def format_price(value: float) -> str:
85
+ return f"${value:.2f}"
86
+ ```
87
+
88
+ ### YAGNI (You Aren't Gonna Need It)
89
+
90
+ Build the invoice required today. Do not introduce strategy trees, recurring
91
+ invoices, or multiple currencies until the requirement exists.
92
+
93
+ ```python
94
+ def create_invoice(order: Order) -> Invoice:
95
+ return Invoice(total=order.total, items=order.items)
96
+ ```
97
+
98
+ ### Composition over inheritance
99
+
100
+ Compose a dog's bark behavior rather than inherit an unrelated `fly` method.
101
+
102
+ ```python
103
+ @dataclass(frozen=True)
104
+ class Dog:
105
+ bark: Callable[[], str]
106
+
107
+ def make_sound(self) -> str:
108
+ return self.bark()
109
+ ```
110
+
111
+ ### Law of Demeter
112
+
113
+ Ask the user for a city instead of traversing `user.address.city.name`.
114
+
115
+ ```python
116
+ def city_name(user: User) -> str:
117
+ return user.city_name()
118
+ ```
119
+
120
+ ### Command Query Separation
121
+
122
+ Querying the next ID does not increment it.
123
+
124
+ ```python
125
+ def peek_next_id(counter: Counter) -> int: return counter.value + 1
126
+ def increment_counter(counter: Counter) -> None: counter.increment()
127
+ ```
128
+
129
+ ### Explicit error handling
130
+
131
+ Attach context and preserve the original exception.
132
+
133
+ ```python
134
+ def load_user(user_id: str, api: UserApi) -> User:
135
+ try:
136
+ return api.get_user(user_id)
137
+ except ApiError as error:
138
+ raise RuntimeError(f"Failed to load user {user_id}") from error
139
+ ```
140
+
141
+ ### Immutability by default
142
+
143
+ The cart example returns a replacement dataclass.
144
+
145
+ ```python
146
+ def add_item(cart: Cart, item: Item) -> Cart:
147
+ return replace(cart, items=(*cart.items, item))
148
+ ```
149
+
150
+ ### Null handling
151
+
152
+ Use `None` consistently and preserve a zero discount.
153
+
154
+ ```python
155
+ def get_discount(user: User | None) -> float:
156
+ return 0.0 if user is None or user.plan is None else user.plan.discount
157
+ ```
158
+
159
+ ### Testability as a design constraint
160
+
161
+ Pass the clock to the subscription check.
162
+
163
+ ```python
164
+ def is_subscription_expired(subscription: Subscription, now: datetime) -> bool:
165
+ return subscription.expires_at < now
166
+ ```
167
+
168
+ ### Dependency direction
169
+
170
+ The invoice calculation receives an invoice, not a database connection.
171
+
172
+ ```python
173
+ def calculate_invoice_total(invoice: Invoice) -> int:
174
+ return sum(item.price for item in invoice.items)
175
+ ```
176
+
177
+ ### Clear names
178
+
179
+ Use `active_subscriptions`, `remaining_days`, and `external_id`, never
180
+ `data`, `temp`, or `obj`.
181
+
182
+ ### Comments
183
+
184
+ Comments document a provider delivery trade-off or an idempotency hazard,
185
+ never narrate the next statement.
186
+
187
+ ### No magic strings
188
+
189
+ Name order states once.
190
+
191
+ ```python
192
+ class OrderStatus(StrEnum):
193
+ PENDING_PAYMENT = "pending_payment"
194
+ PAID = "paid"
195
+
196
+ if order.status is OrderStatus.PENDING_PAYMENT:
197
+ request_payment(order)
198
+ ```
199
+
200
+ ### SOLID
201
+
202
+ The welcome use case depends on a mailer protocol, not SMTP.
203
+
204
+ ```python
205
+ class Mailer(Protocol):
206
+ def send(self, email: str, template: str) -> None: ...
207
+
208
+ class SendWelcomeEmail:
209
+ def __init__(self, mailer: Mailer) -> None: self._mailer = mailer
210
+ ```
211
+
212
+ ### Validate once
213
+
214
+ Parse HTTP/message input into a typed command at the boundary. Application
215
+ services trust that command instead of repeating the same required, type, and
216
+ range checks. Use one schema or validator for each body/query.
217
+
218
+ ```python
219
+ @dataclass(frozen=True)
220
+ class UpdateProfile:
221
+ email: str
222
+
223
+ def update_profile(command: UpdateProfile, users: UserRepository) -> None:
224
+ users.update(command)
225
+ ```
226
+
227
+ When in doubt: **fail fast, keep it flat, keep it small.**