spiderly 19.14.0 → 19.16.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: backend-testing
3
- description: Unit-test a Spiderly backend service against an EF Core InMemory database — covers the two InMemory pitfalls that silently corrupt assertions (the change tracker masking a missing SaveChanges, and WithTransactionAsync throwing TransactionIgnoredWarning). Use when writing xUnit/NUnit tests for entity services, business logic, or save/delete hooks against an InMemory DbContext, or when an InMemory-backed test passes/throws for reasons that don't match production.
3
+ description: Unit-test a Spiderly backend service against an EF Core InMemory database — covers the two InMemory pitfalls that silently corrupt assertions (the change tracker masking a missing SaveChanges, and WithTransactionAsync throwing TransactionIgnoredWarning). Use when writing xUnit/NUnit tests for entity services, business logic, or save/delete hooks against an InMemory DbContext, or when an InMemory-backed test passes/throws for reasons that don't match production. Also covers setting the clock a test runs at (a fake `TimeProvider`), which Spiderly's audit stamps and token, API-key and outbox expiry read.
4
4
  ---
5
5
 
6
6
  # Backend Testing (EF Core InMemory)
@@ -63,3 +63,41 @@ Pass a stable `dbName` (e.g. one per test, `Guid.NewGuid().ToString()`) so each
63
63
  ## When InMemory isn't enough
64
64
 
65
65
  InMemory ignores relational constraints (unique indexes, FK cascade, check constraints, `[Required]` at the DB level), provider-specific SQL, and real transaction semantics. If the behavior under test depends on any of those, prefer the **SQLite in-memory** provider (`UseSqlite("DataSource=:memory:")`, keep the connection open) or a disposable real database — don't assert relational guarantees against `UseInMemoryDatabase`.
66
+
67
+ ## Controlling the clock
68
+
69
+ Spiderly reads the time from the `TimeProvider` registered in the app's container. `AddSpiderly` registers `TimeProvider.System` unless the app registers its own, the same way ASP.NET Core's `AddAuthentication` does. That one clock:
70
+
71
+ - stamps `CreatedAt` / `ModifiedAt` in `SaveChanges`;
72
+ - sets and checks the expiry of access, refresh and login-verification tokens and the external-login nonce, checks an API key's `ExpiresAt`, and judges a bearer token's lifetime on each request;
73
+ - decides when an outbox row is due for retry, which handled rows retention purges, and how old the health check says the backlog is.
74
+
75
+ ASP.NET Core's own authentication handlers read the same registration. A test sets the time by registering a fake clock, e.g. `FakeTimeProvider` from the `Microsoft.Extensions.TimeProvider.Testing` package:
76
+
77
+ ```csharp
78
+ using Microsoft.Extensions.Time.Testing;
79
+
80
+ FakeTimeProvider clock = new(new DateTimeOffset(2030, 1, 1, 12, 0, 0, TimeSpan.Zero));
81
+ services.AddSingleton<TimeProvider>(clock); // before or after AddSpiderly, which adds the system clock only when none is registered
82
+
83
+ // ... save, issue a token, create an API key ...
84
+
85
+ clock.Advance(TimeSpan.FromHours(2)); // the fake stands still until the test moves it
86
+ ```
87
+
88
+ In a `WebApplicationFactory`, make that registration in `ConfigureTestServices`. Your own code that needs the current time should take `TimeProvider` in its constructor and call `GetUtcNow()`, so it reads the same clock as the stamps and expiries it compares against. Reading `DateTime.UtcNow` beside it gives the code two clocks, and a test with a fake clock can only control one of them.
89
+
90
+ A context built with `new`, as in the InMemory examples above, is outside the container and stamps the system clock. Give it the test's clock through its options:
91
+
92
+ ```csharp
93
+ ServiceProvider clockOnly = new ServiceCollection()
94
+ .AddSingleton<TimeProvider>(clock)
95
+ .BuildServiceProvider();
96
+
97
+ MyAppDbContext context = new(new DbContextOptionsBuilder<MyAppDbContext>()
98
+ .UseInMemoryDatabase(dbName)
99
+ .UseApplicationServiceProvider(clockOnly)
100
+ .Options);
101
+ ```
102
+
103
+ The clock does not move a cookie's `Expires`: the client judges that by its own clock, so `CookieManager` keeps setting it from the machine's. Libraries that read the machine's clock themselves, such as Hangfire's schedules, are not moved by it either.
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: background-jobs
3
+ description: Write and schedule Hangfire background jobs in a Spiderly app so the container builds every job. Use when adding a job class, enqueueing a job, adding a recurring job, opting in with spiderly.AddJobs, or seeing "is not registered in the container, so it is not built".
4
+ ---
5
+
6
+ # Background Jobs
7
+
8
+ Spiderly runs background work on Hangfire. Opt in to the job seam and every job is a registered service: a host that
9
+ validates its service graph at boot judges each job's constructor, and Hangfire builds jobs only from the container.
10
+
11
+ ## Opt in
12
+
13
+ ```csharp
14
+ services.AddSpiderly<MyDbContext>(Configuration, spiderly =>
15
+ {
16
+ // ...
17
+ spiderly.AddJobs(typeof(Startup).Assembly, typeof(InvoiceExportJob).Assembly);
18
+ });
19
+ ```
20
+
21
+ `AddJobs` registers every non-abstract class implementing `ISpiderlyJob` in the given assemblies (scoped: one instance
22
+ per job run) and replaces Hangfire.NetCore's job activator with `SpiderlyJobActivator`. It works with
23
+ `services.AddHangfire(...)` before or after it. A second call adds assemblies.
24
+
25
+ ## Write a job
26
+
27
+ ```csharp
28
+ using Spiderly.Shared.Jobs;
29
+
30
+ public class InvoiceExportJob : ISpiderlyJob
31
+ {
32
+ private readonly IApplicationDbContext _context;
33
+
34
+ public InvoiceExportJob(IApplicationDbContext context) => _context = context;
35
+
36
+ public async Task ExportAsync(long invoiceId) { /* ... */ }
37
+ }
38
+ ```
39
+
40
+ - Implement `ISpiderlyJob`, take dependencies in the constructor, keep method arguments simple and serializable.
41
+ - No `services.AddScoped<InvoiceExportJob>()` needed: the scan registers it. A registration of your own (another
42
+ lifetime, a factory) wins over the scan's.
43
+
44
+ ## Schedule it
45
+
46
+ Use the typed methods, which accept only an `ISpiderlyJob`:
47
+
48
+ ```csharp
49
+ // Injected IBackgroundJobClient
50
+ backgroundJobClient.EnqueueJob<InvoiceExportJob>(job => job.ExportAsync(invoiceId));
51
+
52
+ // IRecurringJobManager, in the Configure phase
53
+ IRecurringJobManager jobs = app.ApplicationServices.GetRequiredService<IRecurringJobManager>();
54
+ jobs.AddOrUpdateJob<InvoiceExportJob>("invoice-export-nightly", job => job.ExportAllAsync(), "0 2 * * *",
55
+ new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });
56
+ ```
57
+
58
+ Do not schedule with Hangfire's `Enqueue<T>` / `AddOrUpdate<T>`: they accept any type, so an unmarked job compiles,
59
+ and the activator refuses it when it runs. An app can ban them with a banned-symbols analyzer to make that a compile
60
+ error.
61
+
62
+ ## What fails where
63
+
64
+ | Mistake | Where it fails |
65
+ | --- | --- |
66
+ | Job class without `ISpiderlyJob` scheduled through the typed methods | Compile error |
67
+ | Job scheduled through Hangfire's own API, never registered | When it runs: `The Hangfire job '...' is not registered in the container, so it is not built.` |
68
+ | Job's constructor needs a service nobody registered | At boot when the host validates its graph (`ValidateOnBuild`), else when it runs |
69
+
70
+ ## Spiderly's own jobs
71
+
72
+ `AddOutbox<TOutbox>()` registers the dispatcher, retention and health jobs that `app.SpiderlyUseOutboxRecurringJob<TOutbox>()`
73
+ schedules; `AddNotifications(...)` registers `NotificationDeliveryJob`. They are registered whether or not you call
74
+ `AddJobs`. `SpiderlyUseOutboxRecurringJob` schedules through the container's `IRecurringJobManager`, so it needs
75
+ `services.AddHangfire(...)`.
76
+
77
+ ## Generic jobs
78
+
79
+ A marked open generic class (`ImportJob<TSource> : ISpiderlyJob`) is registered as an open generic. The container
80
+ cannot validate an open generic registration at boot, so also register each closed form you schedule
81
+ (`services.AddScoped<ImportJob<CsvSource>>()`) to have it judged there.
82
+
83
+ ## Testing
84
+
85
+ A job is a plain class: construct it with its dependencies and call its method. To test what code schedules, pass a fake
86
+ `IBackgroundJobClient` / `IRecurringJobManager` that records the `Job` it is handed (`job.Type`, `job.Method`,
87
+ `job.Args`).
@@ -1,33 +1,46 @@
1
1
  ---
2
2
  name: frontend-localization
3
- description: How Spiderly localizes Angular admin-panel UI strings — labels, buttons, menu items, validation messages, any user-facing text. Spiderly uses Transloco with flat assets/i18n/{lang}.json files loaded by SpiderlyTranslocoLoader. Use whenever you add or translate a UI string, hit a raw translation key rendering on screen, set up or change languages (provideTransloco / availableLangs), use translocoService.translate or the *transloco template directive, run i18n:extract, or wonder how form labels get auto-translated. For .NET backend strings (error messages, Excel names, IStringLocalizer), use the backend-localization skill instead.
3
+ description: How Spiderly localizes Angular admin-panel UI strings — labels, buttons, menu items, validation messages, any user-facing text. Spiderly uses Transloco with flat assets/i18n/{lang}.json files imported into the build by the app's ImportedTranslocoLoader. Use whenever you add or translate a UI string, hit a raw translation key rendering on screen, set up or change languages (provideTransloco / availableLangs), use translocoService.translate or the *transloco template directive, run i18n:extract, or wonder how form labels get auto-translated. For .NET backend strings (error messages, Excel names, IStringLocalizer), use the backend-localization skill instead.
4
4
  ---
5
5
 
6
6
  # Frontend Localization
7
7
 
8
- The Spiderly Angular admin panel localizes every user-facing string through **Transloco**. Translation files are flat key→value JSON, one per language, served as static assets and loaded over HTTP at runtime — there is no compile-time embedding. Never hardcode user-facing English in a template; route it through a translation key so it localizes with the rest of the panel (e.g. a PACMS app runs entirely in Serbian).
8
+ The Spiderly Angular admin panel localizes every user-facing string through **Transloco**. Translation files are flat key→value JSON, one per language, imported into the build: each becomes a content-hashed chunk that ships with the code reading its keys, loaded when Transloco first needs the language. Never hardcode user-facing English in a template; route it through a translation key so it localizes with the rest of the panel (e.g. a PACMS app runs entirely in Serbian).
9
9
 
10
10
  This is a **separate system from the backend**: a string shown by the API (a `BusinessException` message) is translated on the .NET side via `IStringLocalizer` (see the **backend-localization** skill); a string rendered by the admin UI is translated here.
11
11
 
12
- ## Setup (`app.config.ts`)
12
+ ## Setup (`translations.ts`)
13
13
 
14
- Transloco is registered in the app's `ApplicationConfig` with Spiderly's loader:
14
+ `spiderly init` writes the loader and the Transloco config to `src/app/business/services/translations.ts`; `app.config.ts` only calls `provideTranslations()`:
15
15
 
16
16
  ```typescript
17
- provideTransloco({
18
- config: {
19
- availableLangs: ['sr-Latn-RS'], // every language you ship a JSON file for
20
- defaultLang: 'sr-Latn-RS',
21
- reRenderOnLangChange: false, // true only if you let users switch language at runtime
22
- },
23
- loader: SpiderlyTranslocoLoader, // from the `spiderly` package
24
- }),
17
+ @Injectable({ providedIn: 'root' })
18
+ export class ImportedTranslocoLoader implements TranslocoLoader {
19
+ getTranslation(lang: string): Promise<Translation> {
20
+ return import(`../../../assets/i18n/${lang}.json`).then((file) => file.default);
21
+ }
22
+ }
23
+
24
+ export function provideTranslations() {
25
+ return provideTransloco({
26
+ config: {
27
+ availableLangs: ['en'], // every language you ship a JSON file for
28
+ defaultLang: 'en',
29
+ reRenderOnLangChange: true, // false if users never switch language at runtime
30
+ },
31
+ loader: ImportedTranslocoLoader,
32
+ });
33
+ }
25
34
  ```
26
35
 
27
- `SpiderlyTranslocoLoader` fetches `${ConfigService.frontendUrl}/assets/i18n/{lang}.json`. So:
36
+ So:
28
37
 
29
38
  - Files live in **`src/assets/i18n/{lang}.json`**, named **exactly** after a `lang` in `availableLangs` (e.g. `sr-Latn-RS.json`, `en.json`).
30
- - They must be deployed as static assets (they're requested over HTTP, not bundled). They already are under `src/assets`, but a build that drops the `assets/` glob will 404 the translations and every key renders raw.
39
+ - The build bundles them; they are **not** published as `/assets/i18n/*.json`. `angular.json` leaves them out of the assets glob (`"ignore": ["i18n/**"]`), so nothing can fetch them back.
40
+
41
+ ### Why imported, not fetched
42
+
43
+ A file at an unchanging URL outlives a deploy in the browser cache. A server that sends no `Cache-Control` lets the browser guess a freshness lifetime (Chromium: a tenth of the time since `Last-Modified`), and a hard reload does not help: it bypasses the cache only for requests made before the page's load event, and Transloco asks for the file after it. The user then runs the new code against the old file, and every key added by the deploy renders raw. An imported file gets a new URL whenever its content changes, so this cannot happen. Spiderly's former `SpiderlyTranslocoLoader` fetched `${frontendUrl}/assets/i18n/{lang}.json` and was removed for this reason.
31
44
 
32
45
  ## File format
33
46
 
@@ -111,8 +124,8 @@ If you build a reusable control/component with text baked into its template, rou
111
124
  ## Gotchas
112
125
 
113
126
  - **A missing key renders the raw key.** Transloco's default `missingHandler` returns the key string, so a typo'd or unseeded key leaks the identifier (e.g. `InvalidSKU`) onto the screen instead of erroring. Treat a raw-key sighting as a missing-translation bug. Keep keys in sync across all `{lang}.json` files and lean on `i18n:extract`.
114
- - **Filename must match `availableLangs`.** `SpiderlyTranslocoLoader` requests `{lang}.json` for the active lang; a mismatch (`sr.json` while `availableLangs` is `['sr-Latn-RS']`) 404s and the whole file falls through to raw keys.
115
- - **Translations are fetched, not bundled.** They load over HTTP from `frontendUrl/assets/i18n/`. A misconfigured `frontendUrl` (in `ConfigService`) or a build that strips the `assets` glob breaks all translations at once — a useful first thing to check when *everything* shows raw keys.
127
+ - **Filename must match `availableLangs`.** `ImportedTranslocoLoader` imports `{lang}.json` for the active lang; a mismatch (`sr.json` while `availableLangs` is `['sr-Latn-RS']`) fails the import and the whole file falls through to raw keys.
128
+ - **New keys render raw for some users right after a deploy, and a hard reload does not fix it?** Something still fetches the translations from a URL that survives deploys (see *Why imported, not fetched*). Check that `app.config.ts` calls `provideTranslations()` and that the network panel shows no `/assets/i18n/` request.
116
129
  - **`reRenderOnLangChange: false`** means a runtime language switch won't re-render already-rendered text. Set it to `true` only if you actually offer in-session language switching.
117
130
 
118
131
  ## Backend strings are a separate system
@@ -183,13 +183,16 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
183
183
  root * /srv
184
184
  encode zstd gzip
185
185
 
186
- @hashedAssets {
187
- path_regexp hashed \.[a-f0-9]{8,}\.(js|css|woff2?|ttf|otf|svg|png|jpg|jpeg|webp|ico)$
186
+ # Content-hashed build output (main-QCWZPP5O.js, media/primeicons-4GST5W3O.woff2) that exists
187
+ # on disk; a missing chunk falls back to index.html and must not be marked immutable.
188
+ @hashed {
189
+ path_regexp ^/(media/)?[^/]+-[A-Z0-9]{8}\.[a-z0-9]+$
190
+ file
188
191
  }
189
- header @hashedAssets Cache-Control "public, max-age=31536000, immutable"
192
+ header @hashed Cache-Control "public, max-age=31536000, immutable"
190
193
 
191
- @indexHtml path /index.html
192
- header @indexHtml Cache-Control "no-cache, no-store, must-revalidate"
194
+ # Everything else (index.html under every route, /assets/*) is revalidated on every use.
195
+ header ?Cache-Control "no-cache"
193
196
 
194
197
  try_files {path} /index.html
195
198
  file_server
@@ -198,6 +201,8 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
198
201
 
199
202
  The `try_files {path} /index.html` line is the SPA fallback that lets the Angular router handle deep links.
200
203
 
204
+ Two details in the cache rules matter. Angular's esbuild builder names hashed files `name-HASH.js` (eight uppercase letters and digits), so a `\.[a-f0-9]{8,}\.js` pattern written for the old webpack names matches nothing. And `header` runs before `try_files`, so a `path /index.html` matcher never sees a navigation to `/` or `/orders`: the `?Cache-Control` default covers them. Without any `Cache-Control` a browser guesses a freshness lifetime (Chromium: a tenth of the time since `Last-Modified`) and can open the app from a pre-deploy `index.html`.
205
+
201
206
  **Lockfile gotcha:** `npm ci` requires `package.json` and `package-lock.json` to be in sync. If a stale dev-dep pins an older Angular major as a peer (e.g. `@jsverse/transloco-keys-manager` 5.x pins Angular 17 while your project is on Angular 19), `npm ci` fails. Bump the dev-dep to the matching major (e.g. `transloco-keys-manager` 6.x for Angular 19) rather than reaching for `--legacy-peer-deps`.
202
207
 
203
208
  ## Terraform layout
@@ -406,7 +411,7 @@ Postgres data lives in a Docker volume on a single VPS — disk failure, acciden
406
411
  - **CORS `FrontendUrl`.** The backend's `AppSettings__Spiderly.Shared__FrontendUrl` must point to the admin subdomain, not a build-preview URL. Update it when you cut over from staging hosting.
407
412
  - **First deploy ordering.** The backend compose references `${ADMIN_IMAGE}`. On a fresh VPS, that image must exist in GHCR before backend deploys, or the admin service definition fails to pull. Push admin first, or run the admin workflow once before the first backend deploy.
408
413
  - **`docker compose up -d` scoping.** Always scope to the services you're updating: `docker compose up -d backend caddy` for backend deploys, `docker compose up -d admin` for admin deploys. Unscoped `up -d` bounces every service that's currently down or has a config change — including admin and postgres on a backend-only commit. The healthcheck-gated `depends_on` makes the unscoped form *safe* but not *desirable*.
409
- - **Static assets caching.** Hashed Angular bundles (e.g. `main.abc123.js`) can be cached forever; `index.html` must never cache, or users will get a stale shell after a deploy. The Caddyfile in this skill already handles both cases.
414
+ - **Static assets caching.** Hashed Angular bundles (e.g. `main-QCWZPP5O.js`) can be cached forever; `index.html` and every other unhashed file must be revalidated on each use, or users get a stale shell after a deploy. The Caddyfile in this skill handles both, and translations are imported into the build rather than served from `/assets/i18n/` (see the **frontend-localization** doc).
410
415
  - **`tls_private_key` lives in Terraform state in cleartext.** R2 encryption-at-rest is bucket-level; anyone with R2 API access (or a leaked state file) can read the key. Scope the R2 token tightly, audit who can pull state, and never copy `terraform.tfstate` to laptops or shared drives. Rotating the cert means a fresh `terraform apply` followed by re-pasting the new outputs into GitHub Secrets — plan a maintenance window. Add `lifecycle { prevent_destroy = true }` to the `tls_private_key` and `cloudflare_origin_ca_certificate` resources so accidental config changes can't trigger a silent rotation. Also add `ignore_changes = [hostnames, csr]` on the cert — Cloudflare normalizes hostname/CSR ordering server-side, otherwise every plan would show a phantom recreate.
411
416
  - **Postgres data on Docker named volumes is not durable across server replacement.** If the VPS itself is Terraform-managed (`hcloud_server`) and gets recreated for any reason — image change, server_type change, accidental destroy — the local Docker named volume `postgres_data` goes with it. Add `lifecycle { prevent_destroy = true }` to the `hcloud_server` resource. For genuine durability, attach an `hcloud_volume` and bind-mount it into postgres (`/var/lib/postgresql` on the 18+ image); volumes survive server destruction and snapshot independently.
412
417
  - **Cloudflare in front of Vercel must stay "DNS only".** When the Next.js storefront (or a Vercel-hosted admin) deploys to Vercel and DNS is on Cloudflare, those records must be **DNS only** (grey cloud), not proxied. Vercel terminates TLS and runs its own edge — CDN, image optimization, ISR, PPR — so adding the Cloudflare proxy on top breaks the TLS handshake and double-caches/conflicts with Vercel's edge features. Cloudflare's WAF/cache/analytics/Turnstile belong on the **Hetzner-served** records (`api.*`, `admin.*`) where the orange cloud stays on. If you want Cloudflare features in front of Vercel anyway, that's an advanced setup ("Full (strict)" SSL + custom hostnames) that Vercel does not officially recommend.
@@ -8110,24 +8110,6 @@ function authInitializer(authService, platformId) {
8110
8110
  };
8111
8111
  }
8112
8112
 
8113
- class SpiderlyTranslocoLoader {
8114
- constructor(http, config) {
8115
- this.http = http;
8116
- this.config = config;
8117
- }
8118
- getTranslation(lang) {
8119
- return this.http.get(`${this.config.frontendUrl}/assets/i18n/${lang}.json`);
8120
- }
8121
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyTranslocoLoader, deps: [{ token: i1$2.HttpClient }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
8122
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyTranslocoLoader, providedIn: 'root' }); }
8123
- }
8124
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyTranslocoLoader, decorators: [{
8125
- type: Injectable,
8126
- args: [{
8127
- providedIn: 'root',
8128
- }]
8129
- }], ctorParameters: () => [{ type: i1$2.HttpClient }, { type: ConfigServiceBase }] });
8130
-
8131
8113
  class SpiderlyTemplateTypeDirective {
8132
8114
  static ngTemplateContextGuard(dir, ctx) {
8133
8115
  return true;
@@ -8152,5 +8134,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
8152
8134
  * Generated bundle index. Do not edit.
8153
8135
  */
8154
8136
 
8155
- export { ALLOWED_OPERATORS, Action, AllClickEvent, ApiErrorCodes, ApiSecurityService, AppSidebarComponent, AuthCardComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, DEFAULT_EXTERNAL_PROVIDER_ICONS, DEFAULT_OPERATOR, ExternalLoginComponent, ExternalProvider, ExternalProviderPublic, Filter, FilterRule, FilterSortMeta, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCellTemplateDirective, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableActionsDirective, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFilterBarComponent, SpiderlyFilterTemplateDirective, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyLoginComponent, SpiderlyMarkdownComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyOverflowTitleDirective, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, booleanFilter, capitalizeFirstChar, createFilterStore, dateFilter, deleteAction, exportListToExcel, firstCharToUpper, foldForSearch, getFileNameFromContentDisposition, getFilterOptions, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, getPrimengNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, numberFilter, operatorOptionsForKind, parseDateOnlyLocal, pushAction, saveResponseAsFile, scrollElementIntoViewIfAboveViewport, selectedTab, singleOrDefault, splitPascalCase, textFilter, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
8137
+ export { ALLOWED_OPERATORS, Action, AllClickEvent, ApiErrorCodes, ApiSecurityService, AppSidebarComponent, AuthCardComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, DEFAULT_EXTERNAL_PROVIDER_ICONS, DEFAULT_OPERATOR, ExternalLoginComponent, ExternalProvider, ExternalProviderPublic, Filter, FilterRule, FilterSortMeta, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCellTemplateDirective, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableActionsDirective, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFilterBarComponent, SpiderlyFilterTemplateDirective, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyLoginComponent, SpiderlyMarkdownComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyOverflowTitleDirective, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, booleanFilter, capitalizeFirstChar, createFilterStore, dateFilter, deleteAction, exportListToExcel, firstCharToUpper, foldForSearch, getFileNameFromContentDisposition, getFilterOptions, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, getPrimengNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, numberFilter, operatorOptionsForKind, parseDateOnlyLocal, pushAction, saveResponseAsFile, scrollElementIntoViewIfAboveViewport, selectedTab, singleOrDefault, splitPascalCase, textFilter, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
8156
8138
  //# sourceMappingURL=spiderly.mjs.map