@voltro/protocol 0.51.0 → 0.53.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.
package/dist/index.d.ts CHANGED
@@ -136,6 +136,8 @@ export declare interface AppliedScopes {
136
136
  readonly removed: ReadonlyArray<string>;
137
137
  }
138
138
 
139
+ export declare const applyRowPatch: (prev: ReadonlyArray<PatchRow>, patch: RowPatch, options?: ApplyRowPatchOptions) => ReadonlyArray<PatchRow>;
140
+
139
141
  /**
140
142
  * Apply a `RowPatch` to `prev`, producing `next`. Exact inverse of
141
143
  * `diffRows`: `applyRowPatch(prev, diffRows(prev, next))` deep-equals
@@ -149,7 +151,18 @@ export declare interface AppliedScopes {
149
151
  * an `add`) is skipped rather than throwing, so one dropped frame degrades
150
152
  * to a slightly stale set instead of a crash.
151
153
  */
152
- export declare const applyRowPatch: (prev: ReadonlyArray<PatchRow>, patch: RowPatch) => ReadonlyArray<PatchRow>;
154
+ export declare interface ApplyRowPatchOptions {
155
+ /**
156
+ * The CRDT downstream lane's applier half: fold a `mergeCells` update into
157
+ * the held cell value. Injected (the patch layer is CRDT-library-free);
158
+ * `@voltro/local-first` provides the real one. When absent, a `mergeCells`
159
+ * op REPLACES the cell with the update — wrong for a true incremental
160
+ * update, which is why the producer only emits `mergeCells` toward
161
+ * consumers that negotiated it; the fallback keeps an unpaired frame from
162
+ * crashing the applier.
163
+ */
164
+ readonly mergeCell?: (column: string, prevValue: unknown, update: unknown) => unknown;
165
+ }
153
166
 
154
167
  /**
155
168
  * Apply a decision to a Subject, touching nothing but `scopes`.
@@ -902,6 +915,15 @@ export declare interface ConnectionInfoValue {
902
915
  * doc comment that claims otherwise.
903
916
  */
904
917
  readonly credentialExpiresAt?: number;
918
+ /**
919
+ * The per-CALL `voltro-resume-from` request header, parsed: the last
920
+ * subscription revision the client materialised before it lost the
921
+ * connection. Read by `bindSubscription` to attempt a delta-resume — the
922
+ * server replays only the missed deltas when the retained ring still
923
+ * chains this revision, and falls back to a fresh snapshot otherwise.
924
+ * Absent when the client did not ask to resume (a fresh subscribe).
925
+ */
926
+ readonly resumeFrom?: number;
905
927
  }
906
928
 
907
929
  /** The two credential shapes a connection can hold. `oauth2` = an
@@ -1623,6 +1645,13 @@ export declare const defineStream: <const Name extends string, Input extends Sch
1623
1645
  readonly openAccess?: string;
1624
1646
  }) => StreamProcedureDescriptor<Name, Input, Element, Error>;
1625
1647
 
1648
+ /**
1649
+ * Declare a raw WebSocket gateway (`*.ws.ts` default export). Validation at
1650
+ * DEFINITION time — a bad path fails the boot that discovers it, not the
1651
+ * first client.
1652
+ */
1653
+ export declare const defineWebSocket: (route: WebSocketGatewayRoute) => WebSocketGatewayRoute;
1654
+
1626
1655
  export declare interface DeleteTarget<Input = unknown> extends NestedTargetFields<Input> {
1627
1656
  readonly table: string;
1628
1657
  readonly op: 'delete';
@@ -1631,6 +1660,8 @@ export declare interface DeleteTarget<Input = unknown> extends NestedTargetField
1631
1660
  readonly identify?: ((input: Input) => string | ReadonlyArray<string>) | undefined;
1632
1661
  }
1633
1662
 
1663
+ export declare const diffRows: (prev: ReadonlyArray<PatchRow>, next: ReadonlyArray<PatchRow>, options?: DiffRowsOptions) => RowPatch;
1664
+
1634
1665
  /**
1635
1666
  * Compute the patch that turns `prev` into `next`, keyed by row id.
1636
1667
  *
@@ -1646,7 +1677,22 @@ export declare interface DeleteTarget<Input = unknown> extends NestedTargetField
1646
1677
  * id-keyed result sets; a result without ids must ship a full snapshot,
1647
1678
  * not a patch.
1648
1679
  */
1649
- export declare const diffRows: (prev: ReadonlyArray<PatchRow>, next: ReadonlyArray<PatchRow>) => RowPatch;
1680
+ export declare interface DiffRowsOptions {
1681
+ /**
1682
+ * The CRDT downstream lane. `columns` names the CRDT-managed columns of
1683
+ * this row set; `incremental(column, prevValue, nextValue)` returns the
1684
+ * SMALL update a holder of prevValue needs to reach nextValue (or
1685
+ * `undefined` to fall back to a plain replace — e.g. a prev the encoder
1686
+ * cannot read). When a changed row's CRDT cells are the only change, the
1687
+ * diff emits ONE `mergeCells` op; when scalars changed too, the `replace`
1688
+ * carries the row WITHOUT its CRDT columns and the `mergeCells` op rides
1689
+ * beside it.
1690
+ */
1691
+ readonly crdt?: {
1692
+ readonly columns: ReadonlySet<string>;
1693
+ readonly incremental: (column: string, prevValue: unknown, nextValue: unknown) => unknown | undefined;
1694
+ };
1695
+ }
1650
1696
 
1651
1697
  /**
1652
1698
  * The caller's effective scope set — the merged set published by rbac if
@@ -1996,6 +2042,17 @@ export declare type ExtraErrors = ReadonlyArray<Schema.Schema.All>;
1996
2042
  /** Release a claim after the handler errored, so a retry can re-process. */
1997
2043
  export declare const failIdempotent: (store: IdempotencyStore, scope: string, key: string) => Promise<void>;
1998
2044
 
2045
+ /**
2046
+ * The field-addressed issues carried by a wire error, or `[]` when the error
2047
+ * is not field-routable. ONE reader for all three shapes — `ValidationError`,
2048
+ * `ValidationErrors`, and a `BusinessRuleViolation` whose rule pinpointed a
2049
+ * `field` — so the form binding, `<AutoForm>`, and any custom widget kit
2050
+ * cannot disagree about which errors belong on a field. A
2051
+ * `BusinessRuleViolation` WITHOUT a field is form-level, not field-level, and
2052
+ * correctly stays in `submitError`.
2053
+ */
2054
+ export declare const fieldIssuesOf: (error: unknown) => ReadonlyArray<ValidationIssueShape>;
2055
+
1999
2056
  /**
2000
2057
  * Report descriptors whose `guards:` declare a per-resource check that nothing
2001
2058
  * will enforce.
@@ -2027,6 +2084,11 @@ export declare const finishIdempotent: (store: IdempotencyStore, scope: string,
2027
2084
  /** A route rendered for humans — logs, the inspect surface, the dashboard. */
2028
2085
  export declare const formatEventRoute: (route: string) => string;
2029
2086
 
2087
+ /** Close code a gateway connection receives when its credential expires —
2088
+ * the same session-expiry contract the rpc socket has (SEC-16), spelled as
2089
+ * an application close code so foreign clients can reauth + reconnect. */
2090
+ export declare const GATEWAY_CREDENTIAL_EXPIRED_CLOSE_CODE = 4001;
2091
+
2030
2092
  /** The currently-registered policy-guard resolver, or `undefined`. */
2031
2093
  export declare const getPolicyGuardResolver: () => PolicyGuardResolver | undefined;
2032
2094
 
@@ -2290,6 +2352,8 @@ export declare const inputLabel: (procedure?: string, guarded?: boolean) => stri
2290
2352
  export declare interface InsertTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
2291
2353
  readonly table: string;
2292
2354
  readonly op: 'insert';
2355
+ /** Declared junction relations — see {@link TargetRelations}. */
2356
+ readonly relations?: TargetRelations | undefined;
2293
2357
  readonly order?: 'prepend' | 'append' | undefined;
2294
2358
  /**
2295
2359
  * Build the optimistic row from the mutation input. The framework
@@ -2869,6 +2933,16 @@ export declare interface PluginEnvVar {
2869
2933
  readonly description?: string;
2870
2934
  /** Example value for `.env.example`. Never a real secret. */
2871
2935
  readonly example?: string;
2936
+ /**
2937
+ * Set ONLY when the secret is OURS to invent (a signing key, a VAPID scalar)
2938
+ * — `voltro dev` then mints a per-project value into the gitignored
2939
+ * `.env.local`, exactly like an app-declared `envVar.secret({ generate })`.
2940
+ * `p256` mints a base64url raw P-256 private scalar (web push VAPID).
2941
+ * NEVER set it for a third-party credential: an invented value merely looks
2942
+ * right, and the boot failure is the useful outcome. Minting stays dev-only —
2943
+ * in production a missing secret refuses the boot.
2944
+ */
2945
+ readonly generate?: 'base64url' | 'hex' | 'p256';
2872
2946
  }
2873
2947
 
2874
2948
  /**
@@ -2897,12 +2971,19 @@ export declare interface PluginErrorSchema {
2897
2971
 
2898
2972
  /** A public raw-HTTP route a plugin serves on the framework listener. */
2899
2973
  export declare interface PluginHttpRoute {
2900
- /** HTTP method, or `'*'` for any (the handler decides). */
2901
- readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'DELETE';
2974
+ /** HTTP method, or `'*'` for any (the handler decides). PATCH/HEAD/OPTIONS
2975
+ * are first-class the REST desugar used to mount `'*'` partly BECAUSE
2976
+ * this union lacked PATCH; that reason is gone (the `'*'` mount remains
2977
+ * for its other job: one dispatcher per shared path + a precise 405). */
2978
+ readonly method: '*' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
2902
2979
  /** Absolute path prefix, e.g. `/_voltro/storage`. Matches the path AND
2903
2980
  * any sub-path (`/_voltro/storage/abc123`). */
2904
2981
  readonly path: string;
2905
2982
  readonly handle: (req: PluginHttpRouteRequest) => Promise<PluginHttpRouteResult>;
2983
+ /** Per-route body cap override (bytes) — wins over the listener's shared
2984
+ * `maxBodyBytes`. NOTE: routes SHARING a path share one body read, so the
2985
+ * widest override on the path's group applies to the whole group. */
2986
+ readonly maxBodyBytes?: number;
2906
2987
  /**
2907
2988
  * Opt this route's path OUT of the listener's cross-site origin check.
2908
2989
  *
@@ -2941,6 +3022,21 @@ export declare interface PluginHttpRoute {
2941
3022
  readonly originGuard?: 'exempt';
2942
3023
  }
2943
3024
 
3025
+ /**
3026
+ * A binary streaming body — the download/export shape. The serve layer pipes
3027
+ * the Web ReadableStream to the socket without buffering, so a response
3028
+ * larger than the heap is fine; the LAZY thunk form defers opening the
3029
+ * source (a provider connection, a file handle) until the response actually
3030
+ * streams.
3031
+ */
3032
+ export declare interface PluginHttpRouteByteStream {
3033
+ readonly stream: ReadableStream<Uint8Array> | (() => ReadableStream<Uint8Array>);
3034
+ /** Declared up front when known — lets the client render progress. */
3035
+ readonly contentLength?: number;
3036
+ /** e.g. `attachment; filename="export.zip"`. */
3037
+ readonly contentDisposition?: string;
3038
+ }
3039
+
2944
3040
  export declare interface PluginHttpRouteRequest {
2945
3041
  readonly method: string;
2946
3042
  /** Path WITHOUT query string. */
@@ -3052,6 +3148,12 @@ export declare interface PluginHttpRouteResult {
3052
3148
  /** Stream the response (SSE) instead of sending `body`. See
3053
3149
  * {@link PluginHttpRouteStream}. */
3054
3150
  readonly stream?: PluginHttpRouteStream;
3151
+ /** Stream a BINARY response (a download, an export) instead of sending
3152
+ * `body` — see {@link PluginHttpRouteByteStream}. Never buffered by the
3153
+ * serve layer; never compressed (flush timing + Content-Length are the
3154
+ * contract). Takes precedence over `body`; do not set both `stream` and
3155
+ * `byteStream`. */
3156
+ readonly byteStream?: PluginHttpRouteByteStream;
3055
3157
  }
3056
3158
 
3057
3159
  /**
@@ -3697,6 +3799,10 @@ export declare const queryToRpc: <Name extends string, Input extends Schema.Sche
3697
3799
  }>, Schema.Struct<{
3698
3800
  op: Schema.Literal<["remove"]>;
3699
3801
  path: typeof Schema.String;
3802
+ }>, Schema.Struct<{
3803
+ op: Schema.Literal<["mergeCells"]>;
3804
+ path: typeof Schema.String;
3805
+ value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
3700
3806
  }>]>>;
3701
3807
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
3702
3808
  }>;
@@ -3836,6 +3942,19 @@ export declare type RowPatchOp = {
3836
3942
  } | {
3837
3943
  readonly op: 'remove';
3838
3944
  readonly path: string;
3945
+ }
3946
+ /** The CRDT downstream lane (plan 18): cell-level MERGE instead of
3947
+ * replace. `value` maps column → an INCREMENTAL encoded update (what the
3948
+ * subscriber is missing relative to the previous delivery, not the full
3949
+ * document), and the applier folds it into the held cell with the
3950
+ * injected merger. This is what keeps a 1-character edit ≤1KB on the
3951
+ * subscription wire regardless of document size. A `mergeCells` op may
3952
+ * accompany a `replace` on the same row — the replace then carries the
3953
+ * row WITHOUT its CRDT columns. */
3954
+ | {
3955
+ readonly op: 'mergeCells';
3956
+ readonly path: string;
3957
+ readonly value: Readonly<Record<string, unknown>>;
3839
3958
  };
3840
3959
 
3841
3960
  export declare const rowPatchOpSchema: Schema.Union<[Schema.Struct<{
@@ -3865,6 +3984,10 @@ export declare const rowPatchOpSchema: Schema.Union<[Schema.Struct<{
3865
3984
  }>, Schema.Struct<{
3866
3985
  op: Schema.Literal<["remove"]>;
3867
3986
  path: typeof Schema.String;
3987
+ }>, Schema.Struct<{
3988
+ op: Schema.Literal<["mergeCells"]>;
3989
+ path: typeof Schema.String;
3990
+ value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
3868
3991
  }>]>;
3869
3992
 
3870
3993
  export declare const rowPatchSchema: Schema.Struct<{
@@ -3895,6 +4018,10 @@ export declare const rowPatchSchema: Schema.Struct<{
3895
4018
  }>, Schema.Struct<{
3896
4019
  op: Schema.Literal<["remove"]>;
3897
4020
  path: typeof Schema.String;
4021
+ }>, Schema.Struct<{
4022
+ op: Schema.Literal<["mergeCells"]>;
4023
+ path: typeof Schema.String;
4024
+ value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
3898
4025
  }>]>>;
3899
4026
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
3900
4027
  }>;
@@ -4483,6 +4610,10 @@ export declare const subscriptionEvent: <D extends Schema.Schema.Any>(data: D) =
4483
4610
  }>, Schema.Struct<{
4484
4611
  op: Schema.Literal<["remove"]>;
4485
4612
  path: typeof Schema.String;
4613
+ }>, Schema.Struct<{
4614
+ op: Schema.Literal<["mergeCells"]>;
4615
+ path: typeof Schema.String;
4616
+ value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
4486
4617
  }>]>>;
4487
4618
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
4488
4619
  }>;
@@ -4573,6 +4704,22 @@ declare const TableValidationFailed_base: Schema.TaggedErrorClass<TableValidatio
4573
4704
 
4574
4705
  export declare type Target<Input = unknown, Row = unknown> = TargetSpec<Input, Row> | ReadonlyArray<TargetSpec<Input, Row>>;
4575
4706
 
4707
+ /**
4708
+ * Declared many-to-many RELATIONS of a write target: `{ inputField:
4709
+ * junctionTable }`. After the executor succeeds — inside the SAME
4710
+ * transaction — the framework reconciles the junction's links for the
4711
+ * written row against `input[inputField]` (an array of target ids) via the
4712
+ * diff-based `store.relationLinks`, so a form's multi-reference field saves
4713
+ * in one mutation with no hand-written junction code. The anchor column is
4714
+ * derived from the junction's `reference()` targets (a self-junction is
4715
+ * refused, never guessed).
4716
+ *
4717
+ * An ABSENT input field leaves the links untouched (absent ≠ empty — an
4718
+ * empty array is the explicit "clear them all"). The row id is the
4719
+ * executor's `output.id`, falling back to `input.id`.
4720
+ */
4721
+ export declare type TargetRelations = Readonly<Record<string, string>>;
4722
+
4576
4723
  export declare type TargetSpec<Input = unknown, Row = unknown> = InsertTarget<Input, Row> | UpdateTarget<Input, Row> | DeleteTarget<Input>;
4577
4724
 
4578
4725
  /**
@@ -4685,6 +4832,10 @@ export declare const toRpc: <Name extends string, Input extends Schema.Schema.An
4685
4832
  }>, Schema.Struct<{
4686
4833
  op: Schema.Literal<["remove"]>;
4687
4834
  path: typeof Schema.String;
4835
+ }>, Schema.Struct<{
4836
+ op: Schema.Literal<["mergeCells"]>;
4837
+ path: typeof Schema.String;
4838
+ value: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
4688
4839
  }>]>>;
4689
4840
  order: Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
4690
4841
  }>;
@@ -4836,6 +4987,8 @@ export declare const undoRedoDescriptor: MutationProcedureDescriptor<"__voltro.u
4836
4987
  export declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Record<string, unknown>> extends NestedTargetFields<Input> {
4837
4988
  readonly table: string;
4838
4989
  readonly op: 'update';
4990
+ /** Declared junction relations — see {@link TargetRelations}. */
4991
+ readonly relations?: TargetRelations | undefined;
4839
4992
  /** Identify the row(s) to patch. Default: `input.id`. Return an ARRAY to patch
4840
4993
  * MANY rows/items in one mutation (a bulk edit — where the per-item
4841
4994
  * parallel-write race lived). */
@@ -4848,6 +5001,52 @@ export declare interface UpdateTarget<Input = unknown, Row = unknown, Item = Rec
4848
5001
  readonly shapeItem?: ((input: Input, current: Item) => Item) | undefined;
4849
5002
  }
4850
5003
 
5004
+ /**
5005
+ * A server-side validation failure pinned to ONE field.
5006
+ *
5007
+ * return yield* ctx.validation.fail('email', 'validation.emailTaken')
5008
+ *
5009
+ * Routed by the form binding to `errors.email`; the mutation's transaction is
5010
+ * rolled back (or, thrown before any write, never opened).
5011
+ */
5012
+ export declare class ValidationError extends ValidationError_base {
5013
+ }
5014
+
5015
+ declare const ValidationError_base: Schema.TaggedErrorClass<ValidationError, "ValidationError", {
5016
+ readonly _tag: Schema.tag<"ValidationError">;
5017
+ } & {
5018
+ field: typeof Schema.String;
5019
+ message: typeof Schema.String;
5020
+ params: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5021
+ }>;
5022
+
5023
+ /** Several field-addressed failures at once — one round trip, every field
5024
+ * marked. `issues` is non-empty by construction (`ctx.validation.failAll`
5025
+ * refuses an empty list rather than shipping a failure with nothing to show). */
5026
+ export declare class ValidationErrors extends ValidationErrors_base {
5027
+ }
5028
+
5029
+ declare const ValidationErrors_base: Schema.TaggedErrorClass<ValidationErrors, "ValidationErrors", {
5030
+ readonly _tag: Schema.tag<"ValidationErrors">;
5031
+ } & {
5032
+ issues: Schema.filter<Schema.Array$<Schema.Struct<{
5033
+ field: typeof Schema.String;
5034
+ message: typeof Schema.String;
5035
+ params: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5036
+ }>>>;
5037
+ }>;
5038
+
5039
+ /** One field-addressed validation issue: `field` is the form path
5040
+ * (`'email'`, `'address.city'`, `'entries.0.startsAt'`), `message` is a
5041
+ * message id (or verbatim text), `params` feed the message template. */
5042
+ export declare const ValidationIssue: Schema.Struct<{
5043
+ field: typeof Schema.String;
5044
+ message: typeof Schema.String;
5045
+ params: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
5046
+ }>;
5047
+
5048
+ export declare type ValidationIssueShape = Schema.Schema.Type<typeof ValidationIssue>;
5049
+
4851
5050
  export declare interface VoltroPlugin {
4852
5051
  /**
4853
5052
  * Plugin identifier — printed in `voltro dev` boot logs + surfaced in
@@ -5216,6 +5415,48 @@ export declare interface VoltroPlugin {
5216
5415
  export declare interface VoltroTableNames {
5217
5416
  }
5218
5417
 
5418
+ /** What a gateway's connection handler receives. Transport-agnostic on
5419
+ * purpose — the runtime adapts the platform socket to this. */
5420
+ export declare interface WebSocketGatewayConnection {
5421
+ /** Send a text or binary frame. */
5422
+ readonly send: (data: string | Uint8Array) => void;
5423
+ /** Close the connection (application close codes 4000-4999 are yours). */
5424
+ readonly close: (code?: number, reason?: string) => void;
5425
+ /** Register a message listener (binary-safe; text arrives as bytes). */
5426
+ readonly onMessage: (listener: (data: Uint8Array) => void) => void;
5427
+ /** The authenticated subject — `null` only on an `auth: 'public'` route. */
5428
+ readonly subject: Subject | null;
5429
+ /** Lowercased request headers of the upgrade. */
5430
+ readonly headers: Readonly<Record<string, string>>;
5431
+ /** The mounted path. */
5432
+ readonly path: string;
5433
+ }
5434
+
5435
+ export declare interface WebSocketGatewayRoute {
5436
+ /** Absolute upgrade path (`/gateways/yjs`). Must not collide with the rpc
5437
+ * socket (`/ws` or the configured `transport.wsPath`) or `/rpc`. */
5438
+ readonly path: `/${string}`;
5439
+ /**
5440
+ * REQUIRED, no default: who may connect.
5441
+ * - `'subject'` — the upgrade resolves a Subject through the SAME auth
5442
+ * chain as rpc/SSR (cookie/bearer); an unauthenticated upgrade is a 401
5443
+ * BEFORE any socket exists, and the connection closes with
5444
+ * {@link GATEWAY_CREDENTIAL_EXPIRED_CLOSE_CODE} when the credential
5445
+ * expires.
5446
+ * - `'public'` — deliberately unauthenticated (a device fleet with its own
5447
+ * protocol-level auth). A decision somebody wrote down, not a default.
5448
+ */
5449
+ readonly auth: 'subject' | 'public';
5450
+ /**
5451
+ * Runs once per accepted connection. The returned function is the
5452
+ * connection's TEARDOWN — taken at construction (the `startOutboxRunner`
5453
+ * rule): it runs on client disconnect, on credential expiry, and on
5454
+ * server shutdown, so whatever the handler opened cannot outlive the
5455
+ * socket.
5456
+ */
5457
+ readonly onConnection: (connection: WebSocketGatewayConnection) => void | (() => void) | Promise<void | (() => void)>;
5458
+ }
5459
+
5219
5460
  /**
5220
5461
  * The error union a procedure ACTUALLY puts on the wire — `descriptor.error`
5221
5462
  * plus everything the framework can produce for it before or around the