[LOADED_DOCS: 3 files, no new loads]
Refactor SIGDS docs, archive DEC log, add pipe tests - Updated all references to AcSignalRDataSource docs to new SIGNALR_DATASOURCE/README.md location; introduced SIGDS topic and paired issues/TODO files. - Implemented new Decision Log archival policy: last-15-active entries remain, older entries moved to year-month archive (LLMP-DEC-65, 67); updated docs-archive skill for two-rule rotation. - Added new SIGDS architectural TODO (ACCORE-SIGDS-T-D9F2) for relocating DataSource code. - Updated doc tables, glossaries, and conventions for SIGDS. - Added AcBinarySerializerPipeParallelTests.cs for parallel serialization/deserialization round-trip tests.
This commit is contained in:
parent
8e9a0b47c1
commit
0f9cf6289e
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -148,7 +148,7 @@ Full doctrine: `../docs/ARCHITECTURE.md#framework-vs-consumer-boundary`
|
||||||
|
|
||||||
## SignalR
|
## SignalR
|
||||||
7. **Tag-based transport (no conventional hub methods)** — SignalR communication should generally use the generic methods provided by `AcWebSignalRHubBase` (server) and `AcSignalRClientBase` (client). Request types are conventionally identified by `int` tags. Try to avoid adding custom, business-specific, or conventional string-based Hub methods (e.g., `GetUsers()`).
|
7. **Tag-based transport (no conventional hub methods)** — SignalR communication should generally use the generic methods provided by `AcWebSignalRHubBase` (server) and `AcSignalRClientBase` (client). Request types are conventionally identified by `int` tags. Try to avoid adding custom, business-specific, or conventional string-based Hub methods (e.g., `GetUsers()`).
|
||||||
8. **AcSignalRDataSource** — generic `IList<T>` with change tracking, CRUD via `SignalRCrudTags`, binary merge, rollback. See `AyCode.Services.Server/docs/SIGNALR/SIGNALR_DATASOURCE.md`. Transport docs: `AyCode.Services/docs/SIGNALR/README.md`.
|
8. **AcSignalRDataSource** — generic `IList<T>` with change tracking, CRUD via `SignalRCrudTags`, binary merge, rollback. See `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`. Transport docs: `AyCode.Services/docs/SIGNALR/README.md`.
|
||||||
9. ~~**JSON-in-Binary tech debt**~~ — **REMOVED** 2026-04-26 (resolved: `AyCode.Core/docs/XCUT/XCUT_ISSUES.md#accore-xcut-i-x8q1`). Number kept reserved to prevent renumbering of subsequent items.
|
9. ~~**JSON-in-Binary tech debt**~~ — **REMOVED** 2026-04-26 (resolved: `AyCode.Core/docs/XCUT/XCUT_ISSUES.md#accore-xcut-i-x8q1`). Number kept reserved to prevent renumbering of subsequent items.
|
||||||
|
|
||||||
## Critical Warnings
|
## Critical Warnings
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,146 @@
|
||||||
|
using AyCode.Core.Serializers.Binaries;
|
||||||
|
using static AyCode.Core.Tests.TestModels.AcSerializerModels;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Tests.Serialization;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class AcBinarySerializerPipeParallelTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task SerializeAndDeserialize_InParallelOnTwoTasks_ThroughPipe_RoundTrip()
|
||||||
|
{
|
||||||
|
var original = CreatePayload(200);
|
||||||
|
var options = AcBinarySerializerOptions.Default;
|
||||||
|
|
||||||
|
using var reader = new SegmentBufferReader(64);
|
||||||
|
|
||||||
|
var deserializeTask = Task.Run(() =>
|
||||||
|
(TestParentWithDateTimeItemCollection?)AcBinaryDeserializer.Deserialize(reader, typeof(TestParentWithDateTimeItemCollection), options));
|
||||||
|
|
||||||
|
var serializeTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var binary = AcBinarySerializer.Serialize(original, options);
|
||||||
|
var offset = 0;
|
||||||
|
var chunkSizes = new[] { 7, 31, 13, 64, 5, 29 };
|
||||||
|
|
||||||
|
for (var chunkIndex = 0; offset < binary.Length; chunkIndex++)
|
||||||
|
{
|
||||||
|
var chunkSize = Math.Min(chunkSizes[chunkIndex % chunkSizes.Length], binary.Length - offset);
|
||||||
|
reader.Write(binary.AsSpan(offset, chunkSize));
|
||||||
|
offset += chunkSize;
|
||||||
|
|
||||||
|
if (chunkIndex % 4 == 0)
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(serializeTask, deserializeTask);
|
||||||
|
|
||||||
|
var result = deserializeTask.Result;
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
AssertPayloadEquals(original, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task SerializeAndDeserialize_InParallelOnTwoTasks_WithMultiplePipelines_RoundTrip()
|
||||||
|
{
|
||||||
|
var options = AcBinarySerializerOptions.Default;
|
||||||
|
|
||||||
|
var pipelines = Enumerable.Range(1, 8).Select(async seed =>
|
||||||
|
{
|
||||||
|
var original = CreatePayload(80 + seed * 10);
|
||||||
|
|
||||||
|
using var reader = new SegmentBufferReader(32);
|
||||||
|
|
||||||
|
var deserializeTask = Task.Run(() =>
|
||||||
|
(TestParentWithDateTimeItemCollection?)AcBinaryDeserializer.Deserialize(reader, typeof(TestParentWithDateTimeItemCollection), options));
|
||||||
|
|
||||||
|
var serializeTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var binary = AcBinarySerializer.Serialize(original, options);
|
||||||
|
var offset = 0;
|
||||||
|
var chunkSize = (seed % 5) + 1;
|
||||||
|
while (offset < binary.Length)
|
||||||
|
{
|
||||||
|
var take = Math.Min(chunkSize, binary.Length - offset);
|
||||||
|
reader.Write(binary.AsSpan(offset, take));
|
||||||
|
offset += take;
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(serializeTask, deserializeTask);
|
||||||
|
|
||||||
|
var result = deserializeTask.Result;
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
AssertPayloadEquals(original, result);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(pipelines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestParentWithDateTimeItemCollection CreatePayload(int itemCount)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var items = new List<TestEntityWithDateTimeAndInt>(itemCount);
|
||||||
|
|
||||||
|
for (var i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
items.Add(new TestEntityWithDateTimeAndInt
|
||||||
|
{
|
||||||
|
Id = i + 1,
|
||||||
|
IntValue = i * 3,
|
||||||
|
Created = now.AddMinutes(-i),
|
||||||
|
Modified = now.AddMinutes(i),
|
||||||
|
StatusCode = i % 4,
|
||||||
|
Name = $"item-{i}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestParentWithDateTimeItemCollection
|
||||||
|
{
|
||||||
|
Id = 11,
|
||||||
|
Name = "pipe-parallel-test",
|
||||||
|
Created = now,
|
||||||
|
Items = items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertPayloadEquals(TestParentWithDateTimeItemCollection expected, TestParentWithDateTimeItemCollection actual)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expected.Id, actual.Id);
|
||||||
|
Assert.AreEqual(expected.Name, actual.Name);
|
||||||
|
Assert.AreEqual(expected.Created, actual.Created);
|
||||||
|
|
||||||
|
Assert.IsNotNull(expected.Items);
|
||||||
|
Assert.IsNotNull(actual.Items);
|
||||||
|
Assert.AreEqual(expected.Items.Count, actual.Items.Count);
|
||||||
|
|
||||||
|
for (var i = 0; i < expected.Items.Count; i++)
|
||||||
|
{
|
||||||
|
var e = expected.Items[i];
|
||||||
|
var a = actual.Items[i];
|
||||||
|
|
||||||
|
Assert.AreEqual(e.Id, a.Id);
|
||||||
|
Assert.AreEqual(e.IntValue, a.IntValue);
|
||||||
|
Assert.AreEqual(e.Created, a.Created);
|
||||||
|
Assert.AreEqual(e.Modified, a.Modified);
|
||||||
|
Assert.AreEqual(e.StatusCode, a.StatusCode);
|
||||||
|
Assert.AreEqual(e.Name, a.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ Server-side service implementations: JWT authentication, SendGrid email delivery
|
||||||
| Document | Topic |
|
| Document | Topic |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `SIGNALR/README.md` | Server-side SignalR hub (dispatch, session, broadcast) |
|
| `SIGNALR/README.md` | Server-side SignalR hub (dispatch, session, broadcast) |
|
||||||
| `SIGNALR/SIGNALR_DATASOURCE.md` | Real-time DataSource with CRUD & change tracking |
|
| `SIGNALR_DATASOURCE/README.md` | Real-time DataSource with CRUD & change tracking |
|
||||||
|
|
||||||
## Folder Structure
|
## Folder Structure
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Server-side SignalR hub infrastructure: hub base class, session management, data
|
||||||
|
|
||||||
### Data Source
|
### Data Source
|
||||||
|
|
||||||
> **Full specification:** `AyCode.Services.Server/docs/SIGNALR/SIGNALR_DATASOURCE.md`
|
> **Full specification:** `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`
|
||||||
|
|
||||||
- **`AcSignalRDataSource.cs`** — Generic real-time collection (`AcSignalRDataSource<TDataItem, TId, TIList>`) implementing `IList<T>` with full CRUD and change tracking.
|
- **`AcSignalRDataSource.cs`** — Generic real-time collection (`AcSignalRDataSource<TDataItem, TId, TIList>`) implementing `IList<T>` with full CRUD and change tracking.
|
||||||
- **Change tracking:** `TrackingItem<T, TId>` wraps each modified item with `TrackingState` + `OriginalValue` for rollback. `ChangeTracking<T, TId>` manages the tracking list.
|
- **Change tracking:** `TrackingItem<T, TId>` wraps each modified item with `TrackingState` + `OriginalValue` for rollback. `ChangeTracking<T, TId>` manages the tracking list.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ Topic documentation for the `AyCode.Services.Server` project (Layer 0, server-si
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
- [`SIGNALR/`](SIGNALR/README.md) — Server-side SignalR (hub base + data source pattern)
|
- [`SIGNALR/`](SIGNALR/README.md) — Server-side SignalR hub (dispatch, session, broadcast)
|
||||||
|
- [`SIGNALR_DATASOURCE/`](SIGNALR_DATASOURCE/README.md) — Client-server DataSource on SignalR transport (change tracking, rollback, sync state, IList<T> wrapper)
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Server-side SignalR hub infrastructure: method dispatch, session management, broadcast, and diagnostics. Source: `SignalRs/` in this project.
|
Server-side SignalR hub infrastructure: method dispatch, session management, broadcast, and diagnostics. Source: `SignalRs/` in this project.
|
||||||
|
|
||||||
> For client-side transport (tags, wire protocol, client base) see `AyCode.Services/docs/SIGNALR/README.md`.
|
> For client-side transport (tags, wire protocol, client base) see `AyCode.Services/docs/SIGNALR/README.md`.
|
||||||
> For the DataSource collection see `SIGNALR_DATASOURCE.md`.
|
> For the DataSource collection see `../SIGNALR_DATASOURCE/README.md`.
|
||||||
|
|
||||||
## Server Processing
|
## Server Processing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,191 +0,0 @@
|
||||||
# SignalR DataSource
|
|
||||||
|
|
||||||
Change-tracked real-time collection built on top of the SignalR transport layer. Source: `SignalRs/AcSignalRDataSource.cs` in this project.
|
|
||||||
|
|
||||||
> For the underlying transport (tag system, wire protocol, client base) see `AyCode.Services/docs/SIGNALR/README.md`.
|
|
||||||
> For server hub infrastructure see `README.md`.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
`AcSignalRDataSource` is a generic `IList<T>` that synchronizes with the server via CRUD tags. It handles change tracking, rollback, sync state, and binary deserialization — so consuming code works with a regular list while the DataSource manages communication.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
AcSignalRDataSource<TDataItem, TId, TIList>
|
|
||||||
where TDataItem : class, IId<TId> // entity with ID
|
|
||||||
where TId : struct // Guid, int, etc.
|
|
||||||
where TIList : class, IList<TDataItem> // List<T> or AcObservableCollection<T>
|
|
||||||
```
|
|
||||||
|
|
||||||
Implements `IList<TDataItem>`, `IList`, `IReadOnlyList<TDataItem>`.
|
|
||||||
|
|
||||||
**Constructor:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
new AcSignalRDataSource<T, TId, TIList>(
|
|
||||||
AcSignalRClientBase signalRClient, // transport client
|
|
||||||
SignalRCrudTags crudTags, // 5 tags for CRUD operations
|
|
||||||
object[]? contextIds = null // optional server-side filter context
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## SignalRCrudTags
|
|
||||||
|
|
||||||
A `sealed class` that bundles **5 independent tag integers** — one per CRUD operation. Tags are NOT sequential offsets; each tag is independently assigned:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public sealed class SignalRCrudTags(
|
|
||||||
int getAllTag,
|
|
||||||
int getItemTag,
|
|
||||||
int addTag,
|
|
||||||
int updateTag,
|
|
||||||
int removeTag)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage (consuming project):**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public abstract class MyProjectTags : AcSignalRTags
|
|
||||||
{
|
|
||||||
public const int OrderGetAll = 300;
|
|
||||||
public const int OrderGetItem = 301;
|
|
||||||
public const int OrderAdd = 302;
|
|
||||||
public const int OrderUpdate = 303;
|
|
||||||
public const int OrderRemove = 304;
|
|
||||||
}
|
|
||||||
|
|
||||||
var crudTags = new SignalRCrudTags(
|
|
||||||
MyProjectTags.OrderGetAll,
|
|
||||||
MyProjectTags.OrderGetItem,
|
|
||||||
MyProjectTags.OrderAdd,
|
|
||||||
MyProjectTags.OrderUpdate,
|
|
||||||
MyProjectTags.OrderRemove
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tag lookup:** `GetMessageTagByTrackingState(TrackingState)` maps tracking state to the corresponding tag via switch expression.
|
|
||||||
|
|
||||||
## Data Loading
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
await dataSource.LoadDataSource(); // sync-wait transport (polls up to 60s)
|
|
||||||
await dataSource.LoadDataSourceAsync(); // async callback path
|
|
||||||
await dataSource.LoadDataSourceFromResponseData(responseData, serializerType);
|
|
||||||
await dataSource.LoadItem(id); // single item by ID
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deserialization paths:**
|
|
||||||
- **Typed response** (`T != byte[]`): protocol eagerly deserializes via `SignalDataType` → `GetResponseData<T>()` direct cast.
|
|
||||||
- **Raw byte[] response** (`IsRawBytesData`): protocol returns raw `byte[]` → consumer deserializes:
|
|
||||||
- `AcObservableCollection<T>`: `BeginUpdate()` → `PopulateMerge(bytes)` → `EndUpdate()` — single batched UI notification.
|
|
||||||
- `List<T>`: `BinaryTo(InnerList)` — direct populate.
|
|
||||||
|
|
||||||
**Context/Filtering:** `ContextIds` (object[]) and `FilterText` (string) are sent with every GetAll request for server-side filtering.
|
|
||||||
|
|
||||||
## Change Tracking
|
|
||||||
|
|
||||||
Each modified item is wrapped in `TrackingItem<TDataItem, TId>`:
|
|
||||||
|
|
||||||
| Field | Purpose |
|
|
||||||
|-------|---------|
|
|
||||||
| `TrackingState` | Add, Update, Remove |
|
|
||||||
| `CurrentValue` | Current item reference |
|
|
||||||
| `OriginalValue` | Clone for rollback (`JsonClone` or `ReflectionClone`) |
|
|
||||||
|
|
||||||
The `ChangeTracking<TDataItem, TId>` class manages the list of tracked items.
|
|
||||||
|
|
||||||
### CRUD Operations
|
|
||||||
|
|
||||||
```
|
|
||||||
Add(item): → TrackingState.Add + InnerList.Add(item)
|
|
||||||
AddOrUpdate(item): → exists? Update : Add (determines TrackingState automatically)
|
|
||||||
Insert(index, item): → TrackingState.Add + InnerList.Insert(index, item)
|
|
||||||
Update(i, item): → TrackingState.Update + clone original + InnerList[i] = item
|
|
||||||
Remove(id): → TrackingState.Remove + clone original + InnerList.RemoveAt(index)
|
|
||||||
```
|
|
||||||
|
|
||||||
Each operation has an optional `autoSave` parameter — if true, immediately calls `SaveItem()` for that single change.
|
|
||||||
|
|
||||||
**Manual tracking:** `SetTrackingStateToUpdate(item)` marks an existing item as modified without replacing it — useful when properties are mutated in-place.
|
|
||||||
|
|
||||||
### Events
|
|
||||||
|
|
||||||
| Event | Signature | Fires when |
|
|
||||||
|-------|-----------|------------|
|
|
||||||
| `OnDataSourceItemChanged` | `Func<ItemChangedEventArgs<T>, Task>?` | After each item is saved or loaded |
|
|
||||||
| `OnDataSourceLoaded` | `Func<Task>?` | After `LoadDataSource` / `LoadDataSourceAsync` completes |
|
|
||||||
| `OnSyncingStateChanged` | `Action<bool>?` | On 0→1 (true) and 1→0 (false) sync transitions |
|
|
||||||
|
|
||||||
## SaveChanges
|
|
||||||
|
|
||||||
| Method | Returns | Transport pattern | Use case |
|
|
||||||
|--------|---------|-------------------|----------|
|
|
||||||
| `SaveChanges()` | `List<TrackingItem>` (remaining failures) | Sync-wait (`ContinueWith`) | When caller needs to know what failed |
|
|
||||||
| `SaveChangesAsync()` | `Task` (void) | Fire-and-forget callback (`Action`) | Background save, no result inspection |
|
|
||||||
|
|
||||||
```
|
|
||||||
Both follow the same flow:
|
|
||||||
BeginSync()
|
|
||||||
for each tracked item:
|
|
||||||
tag = CrudTags.GetMessageTagByTrackingState(state)
|
|
||||||
response = SignalRClient.PostDataAsync(tag, item)
|
|
||||||
on success: remove from tracking, CopyTo InnerList item with server response
|
|
||||||
on failure: TryRollbackItem() → restore OriginalValue
|
|
||||||
EndSync()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rollback:** `TryRollbackItem(id)` restores `OriginalValue` to `InnerList`. For `TrackingState.Add`: removes item entirely. For `Remove`: re-adds `OriginalValue`. Manual `Rollback()` reverts all tracked changes at once.
|
|
||||||
|
|
||||||
## Sync State
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private int _activeSyncOperations; // Interlocked counter
|
|
||||||
|
|
||||||
BeginSync(): Interlocked.Increment → fires OnSyncingStateChanged(true) on 0→1
|
|
||||||
EndSync(): Interlocked.Decrement → fires OnSyncingStateChanged(false) on 1→0
|
|
||||||
IsSyncing: _activeSyncOperations > 0
|
|
||||||
```
|
|
||||||
|
|
||||||
UI binds to `IsSyncing` to show loading indicators. The counter supports nested sync operations.
|
|
||||||
|
|
||||||
## Working Reference List
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
dataSource.SetWorkingReferenceList(externalList);
|
|
||||||
// Now dataSource operates directly on externalList — same reference, no copy
|
|
||||||
|
|
||||||
TIList innerList = dataSource.GetReferenceInnerList();
|
|
||||||
```
|
|
||||||
|
|
||||||
Useful when the UI already has a bound collection and you want the DataSource to manage it in-place.
|
|
||||||
|
|
||||||
## Locking Strategy
|
|
||||||
|
|
||||||
| Lock | Scope | Used by |
|
|
||||||
|------|-------|---------|
|
|
||||||
| `object _syncRoot` | Synchronous | Count, Contains, IndexOf, GetEnumerator |
|
|
||||||
| `SemaphoreSlim _asyncLock` | Asynchronous | Add/Update/Remove with save, LoadDataSource |
|
|
||||||
|
|
||||||
`GetEnumerator()` returns `InnerList.ToList().GetEnumerator()` — safe copy to avoid mutation during iteration.
|
|
||||||
|
|
||||||
## Relationship to Transport
|
|
||||||
|
|
||||||
The DataSource is a **consumer** of the SignalR transport, not part of it:
|
|
||||||
|
|
||||||
```
|
|
||||||
DataSource.SaveChanges()
|
|
||||||
→ CrudTags.GetMessageTagByTrackingState(state) → tag
|
|
||||||
→ AcSignalRClientBase.PostDataAsync(tag, item) ← transport layer
|
|
||||||
→ OnReceiveMessage(tag, bytes, requestId) ← wire protocol
|
|
||||||
→ Server method with [SignalR(tag)] ← tag dispatch
|
|
||||||
```
|
|
||||||
|
|
||||||
Projects can also call the transport directly without DataSource — see `AyCode.Services/docs/SIGNALR/README.md`.
|
|
||||||
|
|
||||||
## Key Source Files
|
|
||||||
|
|
||||||
| Component | Path |
|
|
||||||
|-----------|------|
|
|
||||||
| DataSource | `SignalRs/AcSignalRDataSource.cs` |
|
|
||||||
| Tracking helpers | `SignalRs/TrackingItemHelpers.cs` |
|
|
||||||
| CRUD tags | `AyCode.Services/SignalRs/SignalRCrudTags.cs` |
|
|
||||||
| Transport client | `AyCode.Services/SignalRs/AcSignalRClientBase.cs` |
|
|
||||||
|
|
@ -7,6 +7,39 @@ Forward-looking work specific to `AcSignalRDataSource`. Transport-side TODOs liv
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ACCORE-SIGDS-T-D9F2: Relocate AcSignalRDataSource + TrackingItemHelpers to AyCode.Services
|
||||||
|
**Priority:** P2 · **Type:** Architectural cleanup · **Status:** Open
|
||||||
|
|
||||||
|
Both files currently live in `AyCode.Services.Server/SignalRs/`, but **neither has any Server-only dependency**:
|
||||||
|
|
||||||
|
- `AcSignalRDataSource.cs` using statements: only `AyCode.Core.*` + `AyCode.Services.SignalRs` (client-side namespace) — zero `AyCode.*.Server` references.
|
||||||
|
- `TrackingItemHelpers.cs` using statements: only `System.Reflection` + `AyCode.Core.Extensions`.
|
||||||
|
- `AyCode.Services.csproj` reference graph already covers all transitive needs (AyCode.Core, AyCode.Entities, AyCode.Interfaces, AyCode.Models).
|
||||||
|
- `SignalRCrudTags` (the DataSource constructor's required argument type) lives in `AyCode.Services` — consumers must currently reference both `AyCode.Services` and `AyCode.Services.Server` for one logical API.
|
||||||
|
- All other files in `AyCode.Services.Server/SignalRs/` are genuinely Server-only (Hub<T> subclasses, session service, broadcast, server protocol extensions, server hub interfaces).
|
||||||
|
|
||||||
|
**Practical impact:** Blazor.WebAssembly cannot reference `AyCode.Services.Server` (its dependency graph pulls in `AyCode.Database`, `KeyDerivation`, `SendGrid` — none WASM-compatible). WASM consumers wanting `AcSignalRDataSource` are currently blocked.
|
||||||
|
|
||||||
|
### Acceptance
|
||||||
|
- [ ] `AcSignalRDataSource.cs` moved: `AyCode.Services.Server/SignalRs/` → `AyCode.Services/SignalRs/`; namespace `AyCode.Services.Server.SignalRs` → `AyCode.Services.SignalRs`.
|
||||||
|
- [ ] `TrackingItemHelpers.cs` moved + namespace updated.
|
||||||
|
- [ ] Consumer `using AyCode.Services.Server.SignalRs;` lines pulling these types updated to `using AyCode.Services.SignalRs;`.
|
||||||
|
- [ ] Doc folder relocated: `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/` → `AyCode.Services/docs/SIGNALR_DATASOURCE/` (3 files: README + ISSUES + TODO).
|
||||||
|
- [ ] `TOPIC_CODES.md` SIGDS row "Docs location" updated (`AyCode.Services.Server/...` → `AyCode.Services/...`).
|
||||||
|
- [ ] Doc-index READMEs updated: remove SIGDS row from `Services.Server/docs/README.md`; add to `Services/docs/README.md`.
|
||||||
|
- [ ] All cross-refs in workspace updated (~15 paths across AyCode.Core + AyCode.Blazor) per the path-rewrite pattern used in LLMP-DEC-66.
|
||||||
|
- [ ] `AyCode.Services.Server/README.md` + `AyCode.Services.Server/SignalRs/README.md` doc tables: SIGDS rows removed.
|
||||||
|
- [ ] `AyCode.Services/SignalRs/README.md` doc table: DataSource entry added (currently no Data Source section there).
|
||||||
|
- [ ] Tests in `AyCode.Services.Server.Tests/SignalRs/SignalRDatasources/` re-evaluated — likely keep in Server.Tests (full hub environment) but update `using` statements; or split into `AyCode.Services.Tests/SignalRs/SignalRDatasources/` if isolated.
|
||||||
|
- [ ] New `LLMP-DEC` entry filed (SIGDS phase 2 relocation, cross-references `LLMP-DEC-66`).
|
||||||
|
- [ ] Build green across all consumers (AyCode.Blazor.Components, FruitBankHybridApp, etc.); WASM build verified if applicable.
|
||||||
|
|
||||||
|
### Related
|
||||||
|
- `LLMP-DEC-66` (SIGDS topic introduction — this TODO completes the architectural side that the docs-side migration left open)
|
||||||
|
- Parallels `LLMP-DEC-58` Framework-First retightening principle (consumer-side code shouldn't live in Server-flavored projects)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ACCORE-SIGDS-T-N5R8: ChangeTracking._trackingItems → Dictionary<TId, TrackingItem>
|
## ACCORE-SIGDS-T-N5R8: ChangeTracking._trackingItems → Dictionary<TId, TrackingItem>
|
||||||
**Priority:** P3 · **Type:** Performance refactor · **Status:** Open
|
**Priority:** P3 · **Type:** Performance refactor · **Status:** Open
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ Custom binary SignalR protocol, client infrastructure, message tagging, and seri
|
||||||
### Message Tagging
|
### Message Tagging
|
||||||
- **`SignalMessageTagAttribute.cs`** — Three attributes: `TagAttribute` (base, int messageTag), `SignalRAttribute` (server method routing + client notification), `SignalRSendToClientAttribute` (client-side receive).
|
- **`SignalMessageTagAttribute.cs`** — Three attributes: `TagAttribute` (base, int messageTag), `SignalRAttribute` (server method routing + client notification), `SignalRSendToClientAttribute` (client-side receive).
|
||||||
- **`AcSignalRTags.cs`** — Static constants: `None`, `PingTag`, `EchoTag`.
|
- **`AcSignalRTags.cs`** — Static constants: `None`, `PingTag`, `EchoTag`.
|
||||||
- **`SignalRCrudTags.cs`** — Sealed class bundling 5 independent CRUD tag integers. `GetMessageTagByTrackingState()` maps `TrackingState` -> tag. See `AyCode.Services.Server/docs/SIGNALR/SIGNALR_DATASOURCE.md`.
|
- **`SignalRCrudTags.cs`** — Sealed class bundling 5 independent CRUD tag integers. `GetMessageTagByTrackingState()` maps `TrackingState` -> tag. See `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`.
|
||||||
- **`SendToClientType.cs`** — Enum: None, Others, Caller, All.
|
- **`SendToClientType.cs`** — Enum: None, Others, Caller, All.
|
||||||
|
|
||||||
### Serialization & Pooling
|
### Serialization & Pooling
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,5 @@ Per the AI Agent Core Protocol (folder navigation rule), start from this README
|
||||||
- **Base logger** (framework): `../../AyCode.Core/AyCode.Core/docs/LOGGING/README.md`
|
- **Base logger** (framework): `../../AyCode.Core/AyCode.Core/docs/LOGGING/README.md`
|
||||||
- **Server-side logger** (variant): `../../AyCode.Core.Server/docs/LOGGING/README.md`
|
- **Server-side logger** (variant): `../../AyCode.Core.Server/docs/LOGGING/README.md`
|
||||||
- **Server-side SignalR**: `../../AyCode.Services.Server/docs/SIGNALR/README.md`
|
- **Server-side SignalR**: `../../AyCode.Services.Server/docs/SIGNALR/README.md`
|
||||||
|
- **Server-side SignalR DataSource**: `../../AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`
|
||||||
- **Binary serializer** (used by SIGNALR_BINARY_PROTOCOL): `../../AyCode.Core/AyCode.Core/docs/BINARY/README.md`
|
- **Binary serializer** (used by SIGNALR_BINARY_PROTOCOL): `../../AyCode.Core/AyCode.Core/docs/BINARY/README.md`
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Client-side SignalR transport: custom binary protocol, tag-based dispatch. Source: `SignalRs/`
|
Client-side SignalR transport: custom binary protocol, tag-based dispatch. Source: `SignalRs/`
|
||||||
|
|
||||||
> Server-side hub, session, broadcast: `AyCode.Services.Server/docs/SIGNALR/README.md`
|
> Server-side hub, session, broadcast: `AyCode.Services.Server/docs/SIGNALR/README.md`
|
||||||
> DataSource collection: `AyCode.Services.Server/docs/SIGNALR/SIGNALR_DATASOURCE.md`
|
> DataSource collection: `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Project-level docs — each project's `docs/` folder documents the code it defin
|
||||||
| `AyCode.Core/docs/` | `BINARY/` (README, FORMAT, FEATURES, OPTIONS, …), `LOGGING/` (README, ISSUES, TODO) |
|
| `AyCode.Core/docs/` | `BINARY/` (README, FORMAT, FEATURES, OPTIONS, …), `LOGGING/` (README, ISSUES, TODO) |
|
||||||
| `AyCode.Core.Server/docs/` | `LOGGING/README.md` (server-side variant) |
|
| `AyCode.Core.Server/docs/` | `LOGGING/README.md` (server-side variant) |
|
||||||
| `AyCode.Services/docs/` | `SIGNALR/`, `SIGNALR_BINARY_PROTOCOL/`, `LOGGING/README.md` (remote variant) |
|
| `AyCode.Services/docs/` | `SIGNALR/`, `SIGNALR_BINARY_PROTOCOL/`, `LOGGING/README.md` (remote variant) |
|
||||||
| `AyCode.Services.Server/docs/` | `SIGNALR/` (README + SIGNALR_DATASOURCE) |
|
| `AyCode.Services.Server/docs/` | `SIGNALR/` (README), `SIGNALR_DATASOURCE/` (README + ISSUES + TODO) |
|
||||||
|
|
||||||
## Solution Structure
|
## Solution Structure
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ See `AyCode.Services/docs/SIGNALR/README.md` for full architecture documentation
|
||||||
|
|
||||||
- **Single dispatch method** — all communication goes through `OnReceiveMessage(int messageTag, int? requestId, SignalParams signalParams, object data)`. Do not add new hub methods.
|
- **Single dispatch method** — all communication goes through `OnReceiveMessage(int messageTag, int? requestId, SignalParams signalParams, object data)`. Do not add new hub methods.
|
||||||
- **Tag-based routing** — associate methods with integer tags via `[SignalR(tag)]` (server) or `[SignalRSendToClient(tag)]` (client). Tags must be unique across the entire system.
|
- **Tag-based routing** — associate methods with integer tags via `[SignalR(tag)]` (server) or `[SignalRSendToClient(tag)]` (client). Tags must be unique across the entire system.
|
||||||
- **CRUD bundles** — entities use `SignalRCrudTags(getAllTag, getItemTag, addTag, updateTag, removeTag)` with 5 independent tag integers. Tags must be unique across the system. See `AyCode.Services.Server/docs/SIGNALR/SIGNALR_DATASOURCE.md`.
|
- **CRUD bundles** — entities use `SignalRCrudTags(getAllTag, getItemTag, addTag, updateTag, removeTag)` with 5 independent tag integers. Tags must be unique across the system. See `AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md`.
|
||||||
- **Binary protocol** — `AyCodeBinaryHubProtocol` (derived from `AcBinaryHubProtocol`) is the transport protocol. Zero-copy write: `AcBinarySerializer.Serialize(value, output)` directly to pipe. Zero-copy read: `SequenceReader<byte>` + type-aware deserialization via `SignalParams.SignalDataType`. Three read paths: byte[] fast-path (0x44 tag), IsRawBytesData (raw byte[]), typed deserialization.
|
- **Binary protocol** — `AyCodeBinaryHubProtocol` (derived from `AcBinaryHubProtocol`) is the transport protocol. Zero-copy write: `AcBinarySerializer.Serialize(value, output)` directly to pipe. Zero-copy read: `SequenceReader<byte>` + type-aware deserialization via `SignalParams.SignalDataType`. Three read paths: byte[] fast-path (0x44 tag), IsRawBytesData (raw byte[]), typed deserialization.
|
||||||
|
|
||||||
### ⚠️ Temporary: JSON-in-Binary Request Parameters
|
### ⚠️ Temporary: JSON-in-Binary Request Parameters
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -13,7 +13,7 @@ Top-level documentation for the `AyCode.Core` repo (Layer 0 — core framework).
|
||||||
- `AyCode.Core/docs/` — Logger, Binary serializer (paired topics: LOGGING/, BINARY/)
|
- `AyCode.Core/docs/` — Logger, Binary serializer (paired topics: LOGGING/, BINARY/)
|
||||||
- `AyCode.Core.Server/docs/` — Server-side logger variant (LOGGING/)
|
- `AyCode.Core.Server/docs/` — Server-side logger variant (LOGGING/)
|
||||||
- `AyCode.Services/docs/` — Remote logger variant, SignalR, SignalR binary protocol
|
- `AyCode.Services/docs/` — Remote logger variant, SignalR, SignalR binary protocol
|
||||||
- `AyCode.Services.Server/docs/` — Server-side SignalR + data source
|
- `AyCode.Services.Server/docs/` — Server-side SignalR hub (SIGNALR/), SignalR DataSource (SIGNALR_DATASOURCE/)
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue