ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
// The live collections of the notes module (umbrella TS6.1). The live server announces a change of
// `notes_note` as `{ entity: "notes.note", id }`, ids only; the browser reads the note back through
// `notes.note.getMany`, so the procedure's permission and the repository's tenant wall decide what a
// member sees. `pnpm kit check` refuses a collection without its batch-get procedure.
import type { ModuleCollection } from "@conusai-kit/core"
export const NOTE_COLLECTION = {
entity: "notes.note",
table: "notes_note",
key: "noteId",
scopeLevel: "project",
read: "notes.note.read",
list: "notes.note.list",
getMany: "notes.note.getMany",
} as const satisfies ModuleCollection
export const NOTES_COLLECTIONS: readonly ModuleCollection[] = [NOTE_COLLECTION]
+32
View File
@@ -0,0 +1,32 @@
// The module's closed vocabularies and names, in a file with no import: a browser bundle can take
// them without loading the data layer the schemas live beside.
/** The audit events this module writes (spec §9.4), declared in its manifest. */
export const NOTE_CREATED = "notes.note_created"
export const NOTE_UPDATED = "notes.note_updated"
export const NOTE_DELETED = "notes.note_deleted"
export const ATTACHMENT_ADDED = "notes.attachment_added"
export const ATTACHMENT_REMOVED = "notes.attachment_removed"
/** The usage meter: one fact per note created, metered against the organization that owns it. */
export const NOTES_CREATED_METER = "notes_created"
/**
* The module's feature flag and its default. An app or the platform's flag service may switch
* attachments off per organization or per deployment by binding `NOTES_FLAGS`; with nothing bound
* the default below is the answer, so a deployment that has no flag service still behaves.
*/
export const ATTACHMENTS_FLAG = "notes.attachments"
export const NOTES_FLAG_DEFAULTS = { [ATTACHMENTS_FLAG]: true } as const
/** The most attachments one note may carry: a note is a note, not a folder. */
export const MAX_ATTACHMENTS = 20
/** The content types an attachment may have. The server enforces them; the file picker only hints. */
export const ATTACHMENT_CONTENT_TYPES = [
"application/pdf",
"image/png",
"image/jpeg",
"image/webp",
"text/plain",
] as const
+38
View File
@@ -0,0 +1,38 @@
// The tables of the notes module. Two, both project-owned.
//
// `notes_note` is the note. `notes_attachment` links a note to a row of the files module: the file
// id is an opaque id and never a foreign key (spec §4.4), and the bytes, the size, the digest and
// the object in the Blob store stay the files module's, so there is one canonical copy of a file.
import { defineEntity, idSchema } from "@conusai-kit/data"
import { z } from "zod"
export const notes = defineEntity({
name: "note",
table: "notes_note",
tenancy: "project",
fields: {
title: z.string().min(1).max(200),
body: z.string().max(20_000),
createdBy: idSchema,
},
indexes: [["createdBy"]],
})
export const attachments = defineEntity({
name: "attachment",
table: "notes_attachment",
tenancy: "project",
fields: {
noteId: idSchema,
fileId: idSchema,
attachedBy: idSchema,
},
// A file is attached to a note once; attaching it again is the call it already answered.
unique: [["noteId", "fileId"]],
// The file's own index: deleting a file looks its attachments up by it.
indexes: [["noteId"], ["fileId"]],
})
/** Every table this module owns, in the order a migration creates them. */
export const NOTES_ENTITIES = Object.freeze([notes, attachments])
+43
View File
@@ -0,0 +1,43 @@
// The module's domain events (spec §8). Each is written to the outbox by the transaction that made
// the change, so a rolled-back write publishes nothing and the relay delivers it exactly once per
// subscriber. The audit event and the usage fact of this module hang off these records, not off
// the request that happened to run (core/subscribers.ts).
import { defineEvent } from "@conusai-kit/core"
import { z } from "zod"
const inProject = { orgId: z.string(), workspaceId: z.string(), projectId: z.string() }
export const noteCreated = defineEvent(
"notes.NoteCreated",
z.object({ ...inProject, noteId: z.string(), createdBy: z.string() }),
)
export const noteUpdated = defineEvent(
"notes.NoteUpdated",
z.object({ ...inProject, noteId: z.string(), version: z.number().int(), updatedBy: z.string() }),
)
export const noteDeleted = defineEvent(
"notes.NoteDeleted",
z.object({ ...inProject, noteId: z.string(), deletedBy: z.string(), attachmentCount: z.number().int() }),
)
export const attachmentAdded = defineEvent(
"notes.AttachmentAdded",
z.object({ ...inProject, noteId: z.string(), attachmentId: z.string(), fileId: z.string(), attachedBy: z.string() }),
)
export const attachmentRemoved = defineEvent(
"notes.AttachmentRemoved",
z.object({
...inProject,
noteId: z.string(),
attachmentId: z.string(),
fileId: z.string(),
removedBy: z.string(),
}),
)
/** Every event this module publishes, for the manifest. */
export const NOTES_EVENTS = [noteCreated, noteUpdated, noteDeleted, attachmentAdded, attachmentRemoved]
+39
View File
@@ -0,0 +1,39 @@
// The public API of the notes module: what a page, another module and the OpenAPI document may
// import. Nothing here reaches core/, adapters/ or a driver.
export { NOTE_COLLECTION, NOTES_COLLECTIONS } from "./collections.js"
export {
ATTACHMENT_ADDED,
ATTACHMENT_CONTENT_TYPES,
ATTACHMENT_REMOVED,
ATTACHMENTS_FLAG,
MAX_ATTACHMENTS,
NOTE_CREATED,
NOTE_DELETED,
NOTE_UPDATED,
NOTES_CREATED_METER,
NOTES_FLAG_DEFAULTS,
} from "./constants.js"
export { attachments, NOTES_ENTITIES, notes } from "./entities.js"
export {
attachmentAdded,
attachmentRemoved,
NOTES_EVENTS,
noteCreated,
noteDeleted,
noteUpdated,
} from "./events.js"
export {
type AttachmentView,
attachmentAdd,
attachmentRemove,
attachmentViewSchema,
type NoteView,
noteCreate,
noteDelete,
noteGet,
noteGetMany,
noteList,
noteUpdate,
noteViewSchema,
} from "./procedures.js"
+119
View File
@@ -0,0 +1,119 @@
// The procedure contracts of the notes module: schemas and names, no handler and no port.
//
// An attachment is uploaded through the files module's own upload procedures (`files.file.beginUpload`,
// `files.file.putPart` and `files.file.completeUpload`: the bytes go to the platform's Blob store,
// which is where the usage fact and the audit event of an upload are recorded), and then linked to a
// note by `notes.attachment.add`. This module never takes bytes.
import { procedure } from "@conusai-kit/core"
import { z } from "zod"
const inProject = { org: z.string(), workspace: z.string(), project: z.string() }
const title = z.string().min(1).max(200)
const body = z.string().max(20_000)
export const noteViewSchema = z.object({
noteId: z.string(),
title: z.string(),
body: z.string(),
/** What `notes.note.update` is called with as `expectedVersion`. */
version: z.number().int(),
createdBy: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
attachmentCount: z.number().int(),
})
export type NoteView = z.output<typeof noteViewSchema>
export const attachmentViewSchema = z.object({
attachmentId: z.string(),
fileId: z.string(),
filename: z.string(),
contentType: z.string(),
size: z.number().int(),
attachedAt: z.string(),
})
export type AttachmentView = z.output<typeof attachmentViewSchema>
export const noteList = procedure("notes.note.list")
.kind("read")
.scope("project")
.permission("notes.note.read")
.tool()
.input(z.object({ ...inProject, cursor: z.string().optional(), limit: z.number().int().min(1).max(200).optional() }))
.output(z.object({ notes: z.array(noteViewSchema), cursor: z.string().optional(), hasMore: z.boolean() }))
.contract()
/**
* The batch-get of the live collection `notes.note` (TS6.1): the live stream announces ids only, and
* the browser reads up to 100 changed notes back here, under the same permission as the list. A note
* that is not answered is gone for this caller.
*/
export const noteGetMany = procedure("notes.note.getMany")
.kind("read")
.scope("project")
.permission("notes.note.read")
.input(z.object({ ...inProject, ids: z.array(z.string().min(1).max(64)).min(1).max(100) }))
// `{ items }`, the batch-get answer of the collection wire (final-wave contract C-05).
.output(z.object({ items: z.array(noteViewSchema) }))
.contract()
export const noteGet = procedure("notes.note.get")
.kind("read")
.scope("project")
.permission("notes.note.read")
.input(z.object({ ...inProject, noteId: z.string() }))
.output(
z.object({
note: noteViewSchema,
attachments: z.array(attachmentViewSchema),
/** The feature flag's answer for this project, so a page does not offer what a call would refuse. */
attachmentsEnabled: z.boolean(),
}),
)
.contract()
// No `.audit()` and no `.usage()` on the write contracts: this module's audit events and usage
// facts are written by the subscribers to its outbox records (core/subscribers.ts), which is the
// exactly-once path — the transport's own `.audit()`/`.usage()` recording would write each one a
// second time (and under a different customer key).
export const noteCreate = procedure("notes.note.create")
.kind("write")
.scope("project")
.permission("notes.note.write")
.input(z.object({ ...inProject, title, body }))
.output(noteViewSchema)
.contract()
export const noteUpdate = procedure("notes.note.update")
.kind("write")
.scope("project")
.permission("notes.note.write")
.input(z.object({ ...inProject, noteId: z.string(), title, body, expectedVersion: z.number().int().min(1) }))
.output(noteViewSchema)
.contract()
export const noteDelete = procedure("notes.note.delete")
.kind("write")
.scope("project")
.permission("notes.note.delete")
.input(z.object({ ...inProject, noteId: z.string() }))
.contract()
export const attachmentAdd = procedure("notes.attachment.add")
.kind("write")
.scope("project")
.permission("notes.attachment.write")
.input(z.object({ ...inProject, noteId: z.string(), fileId: z.string() }))
.output(attachmentViewSchema)
.contract()
export const attachmentRemove = procedure("notes.attachment.remove")
.kind("write")
.scope("project")
.permission("notes.attachment.write")
.input(z.object({ ...inProject, noteId: z.string(), attachmentId: z.string() }))
.contract()