AI 资讯
TikTok Shop Customer Service Webhooks: A Production-Ready Implementation Guide
Receiving a webhook is easy. Building a webhook pipeline that survives duplicate deliveries, delayed events, missing messages, invalid signatures, and seller authorization changes is the real engineering work. This guide explains how to build a production-ready pipeline for TikTok Shop Customer Service messages—from HTTPS ingress to history reconciliation. First, Understand the Scope TikTok Shop Customer Service API is designed for conversations between buyers and sellers in a TikTok Shop. It is not an API for reading ordinary TikTok direct messages. Before implementation, confirm that: Your application has access to the required Customer Service API scopes. The seller has authorized your application. The target shop is correctly mapped to your internal workspace or tenant. Your webhook endpoint is publicly accessible over HTTPS. Customer Service API access is inactive by default and requires approval. See the official Customer Service API overview and app features documentation . If these prerequisites are missing, changing webhook code will not solve the problem. Recommended Architecture A reliable implementation separates webhook acknowledgement from business processing: TikTok Shop | v HTTPS webhook ingress | +-- Verify signature using raw request body | +-- Insert event into a durable inbox | +-- Return HTTP 200 within 3 seconds | v Message queue | v Normalize, deduplicate, and route | v Customer service workspace ^ | History reconciliation worker The webhook request should not wait for: CRM updates AI-generated replies Media downloads Ticket creation Search indexing External notifications Persist the event, acknowledge it, and process it asynchronously. Subscribe to the New Message Event For incoming customer service messages, subscribe to NEW_MESSAGE , identified as event type 14 . You can configure the subscription in TikTok Shop Partner Center or through the webhook configuration API. The official event reference is available in the New Message webhook docu
开发者
React DataGrid: A Free, Open-Source React Data Grid with an Enterprise Edition (An AG Grid Alternative)
If you've ever needed to build a serious data table in React, you've probably run into this problem....
AI 资讯
Static File Caching in Nuxt: An Easy and Practical Strategy
Lighthouse kept warning me about inefficient cache lifetimes, even though I had already added caching for my static files. The missing piece was Nuxt Image and its generated /_ipx URLs . In this post, I’ll share the simple caching setup I use for Nuxt build files, public assets, and optimized images without risking stale content after deployment. The basic rule is simple: Cache files aggressively when changing the file also changes its URL. Be more careful when the same URL can serve different content later. You have probably seen the same Lighthouse warning I have: Use efficient cache lifetimes. Browser caching for static files is usually straightforward. You add a Cache-Control header, choose a reasonable lifetime, and the browser avoids downloading the same files again on every visit. However, in a Nuxt application, not every static-looking file should use the same caching policy. Nuxt build files are automatically versioned. Files inside public/ usually are not. Nuxt Image also creates transformed image URLs under /_ipx , which need their own cache rule. In this post, I’ll go through the setup I use, including the Nuxt Image rule that was missing during my latest Lighthouse audit. The simple caching rule The most important question is not whether a file is an image, font, or JavaScript file. The important question is: Will the URL change when the file changes? When the answer is yes, you can safely cache the file for a very long time. When the answer is no, you should use a shorter cache lifetime. Otherwise, visitors may continue seeing an old version after you deploy an update. What the cache directives mean Here are the main directives used in this setup: public allows browsers and shared caches such as CDNs to store the response. max-age controls how long the browser considers the file fresh. s-maxage controls how long shared caches such as Cloudflare consider it fresh. immutable tells the browser that the file is not expected to change while that URL exists.
AI 资讯
I spent twenty hours testing hypotheses about a publishing failure. The platform had written the reason on screen
Yesterday I tried to publish an article on a writing platform I use. The click did nothing. Not an error, not a refusal: the dialog stayed open, the page changed to a url containing the word submission, and nothing appeared publicly. I tried again. Same. Then I stopped, because I have a rule against stacking attempts, and started diagnosing properly. What I did over the next twenty hours I checked whether the button was disabled. It was not: no disabled attribute, no aria-disabled, pointer events enabled, full opacity, not covered by another element. I checked whether my test for success was valid. I was verifying by loading the post's short url in a clean session and looking for a Not Found. It occurred to me that I had never confirmed that url form works for a published post, so I tested it against one that had published fine an hour earlier. It rendered in full. The test was sound. I checked the public profile. The post was not listed. Confirmed unpublished. I instrumented the network. Enabled the protocol domain, clicked, and watched: three requests, all returning two hundred. So the click was firing and the server was answering without error. That eliminated a dead button, a lost click and an overlay in one measurement, which felt like progress. I formed a hypothesis and wrote it down as a hypothesis: a daily publishing limit, three per calendar day, since two had gone out that day. I waited for midnight and tested it. It failed again. So the hypothesis was refuted, cleanly, and I recorded that. Where the answer was In the dialog. The whole time. After the failed attempt past midnight, I ran one more read of the page, this time asking for elements with an alert role rather than for the button state. One came back: The author of this story has published or scheduled the maximum of two stories in the past 24 hours. Please try to publish or schedule again in 24 hours. Two per rolling twenty four hours. Not three, and not per calendar day. My hypothesis was wrong o
AI 资讯
I Built 75+ Free Developer Tools — Here's What I Learned
Hey everyone! I'm jinyuan, an indie developer. I recently launched DevTools Box — a free online toolbox with 75+ developer tools. What's in the box? DevTools Box includes tools like: JSON Formatter — beautify and validate JSON Regex Tester — test regular expressions with live matching Base64 Encoder/Decoder — quick encoding and decoding QR Code Generator — generate QR codes instantly Hash Calculator — MD5, SHA-1, SHA-256 and more Color Picker — pick colors and convert between formats ...and 69 more tools! Why I built it I was tired of jumping between different websites for simple dev tasks. Each tool runs entirely in your browser — no login, no ads, no data sent to any server. Tech stack Next.js 14 with App Router TypeScript Tailwind CSS Static export to Cloudflare Pages Try it out Check it out at tdboxs.com . All tools are 100% free. Would love to hear your feedback! What tools would you add?
AI 资讯
The Bug Wasn't in My Code --- It Was in My Assumptions 🤯
We've all been there. The code looks correct. No obvious syntax errors. The logic seems fine. You read the same function 10 times. And somehow... It still doesn't work. 😭 After wasting way too much time debugging situations like this, I realized something: Sometimes the bug isn't in your code. It's in what you assumed about your code. The classic debugging trap Imagine you're calling an API and expecting this: { "user" : { "name" : "Ash" } } So naturally, you write: const name = response . user . name ; Everything looks perfectly reasonable. But the actual response is: { "data" : { "user" : { "name" : "Ash" } } } Now you're staring at your JavaScript wondering: "Why is user undefined?!" The JavaScript isn't necessarily the problem. Your assumption about the API response was. This happens everywhere It's not just API responses. You can make incorrect assumptions about: What data a function receives Whether a value can be null What an API actually returns Environment variables being available File paths Database records Authentication state Time zones User input Production vs development environments What a third-party library actually does And these assumptions can create some seriously confusing bugs. My new debugging approach Instead of immediately changing the code, I try to verify my assumptions first. 1. What do I think is happening? Write down your assumption. For example: "The API is returning the user object." 2. What is actually happening? Inspect the data. console . log ( response ); Don't guess. Look at it. 3. Where does reality differ from my assumption? Maybe the API response changed. Maybe the value is undefined . Maybe the environment variable isn't loaded. Maybe the backend is returning an error that the frontend isn't handling. 4. Fix the actual problem Only after understanding the mismatch should you change the code. This saves a surprising amount of time. The debugging rule I now follow When something doesn't make sense, I ask: "What am I assuming
AI 资讯
CloudFlare Previews Automatic WebMCP Support for Web Pages
Cloudflare announced a developer preview that lets any website enable a WebMCP (Web Model Context Protocol) interface with a single dashboard switch. This allows browser-based AI agents to interact with unmodified web pages through structured tools instead of scraping or guessing, keeping human traffic and control on the original site. By Sergio De Simone
AI 资讯
Ayo GitHub Quietly Killed the Unreviewable Mega-PR
If you've ever opened a PR with 47 changed files and a diff so long GitHub just gives up and shows you "Load Diff" seventeen times, this one's for you. GitHub quietly shipped what might be the biggest pull request update in years, and it's aimed squarely at that problem. Let's talk about stacked pull requests. The problem, in one sentence Big PRs are where good reviews go to die. Nobody reads a 2000 line diff carefully. Some folks reach for AI code review tools like LiveReview to take the edge off, and honestly that helps, but even the best reviewer (human or model) does a better job on a tight, focused diff than on a 2000 line wall. Smaller inputs, better reviews. That's true no matter who's doing the reviewing. Stacked PRs are GitHub's answer: break one massive change into a chain of small, dependent PRs, where each one only reviews the diff it actually introduces, not everything below it. What a stack actually is The rule is simple. You need two or more PRs in the same repo where: The bottom PR targets your trunk branch (usually main ) Every PR after that targets the PR below it, not main That's it. That's the whole trick. Foundational stuff (schemas, shared types) goes at the bottom. Stuff that depends on it (API routes, UI) goes higher up the chain. And here's the part that surprised me: if you just do this manually with plain git, by opening PR #11 against the branch for PR #10 instead of against main , GitHub now recognizes that as a stack automatically. No special tool required. It just notices the base branches form a chain and lights up a banner. Stacking isn't a git concept at all, it's purely a GitHub UI concept layered on top of branches you were already making. Let's actually build one Enough theory. I built a real stack in one of my own repos ( peektea , a terminal file browser I maintain), using a harmless scratch file so nothing real got touched. Here's the actual terminal session, copy pasted, warts and all. First I tried to be fancy and use the CL
AI 资讯
Index as Key Is Not a Knowledge Problem. Your AI Already Knows the Rule. It Just Does Not Always Follow It.
Ask any AI coding assistant directly whether using array index as a React key is a good idea, and it will tell you no. It will explain why. Reordering, insertion, and deletion of list items can cause React to misidentify which DOM node corresponds to which data, leading to state bugs and unnecessary re-renders. This is not obscure knowledge. It is one of the most commonly repeated pieces of React advice that exists, and every model has clearly seen it thousands of times during training. And yet, if you look through a codebase where the AI generated a meaningful portion of the list rendering, you will very likely find at least one instance of exactly this pattern. A map over an array, using the index as the key prop, sitting quietly in a component that otherwise looks perfectly reasonable. This is a strange thing to observe once you notice it. The AI is not confused about the rule. Ask it directly and it recites the correct answer immediately and confidently. But somewhere between knowing the rule in the abstract and applying it consistently during generation, something gets lost. Why knowing a rule and applying it are different things There is a meaningful difference between an AI model having encountered information during training and that information reliably surfacing during every relevant generation task. When you ask directly whether index as key is a good idea, you are prompting the model to retrieve and state a fact it has strong, well reinforced associations with. This is a different cognitive task than generating a list rendering component from scratch while simultaneously handling several other decisions about structure, naming, data shape, and styling. During active generation, the model is not running through a checklist of best practices for every line it writes. It is producing output token by token based on patterns, and in the moment of writing a map function, the path of least resistance is often exactly the pattern that gets flagged as wrong when
AI 资讯
Generating daily horoscopes and zodiac videos with an automated AI pipeline
Astrology content has a brutal property: it has to be fresh every single day , for every sign, ideally in a few languages, forever. Writing that by hand doesn't scale. For AstroZodify I built a pipeline that generates daily horoscopes and short zodiac videos on a schedule, with humans reviewing rather than writing. Here's the shape of it. The content problem Per day you need: 12 signs x N content types (daily horoscope, love, career) x M languages. That's hundreds of pieces of copy a day that all have to feel written, not templated, and stay consistent with each sign's "voice". Templating alone reads robotic. Free-form generation drifts. The trick is constraining an LLM enough to stay on-brand while still sounding human. The generation pipeline Structured prompts per sign. Each sign has a persona and constraints (tone, themes, length). The model fills the daily specifics, not the whole thing from scratch. Scheduled batch runs. A cron job kicks off generation ahead of time so content is ready before it's needed, never on the critical path of a page request. Validation. Output is checked for length, banned phrasing, and structure before it's allowed near the site. Store, then serve. Everything lands in Postgres. Pages are SSR and just read pre-generated rows, so the LLM is never in the user's request path. Keeping generation offline from serving is the single most important decision - it keeps pages fast and costs predictable. Adding video Text was step one. Short vertical zodiac videos (for social) are step two, and that's a heavier pipeline: script -> imagery -> voiceover -> render. That part runs on Cloud Run as a separate job so a slow render never touches the web app, and we pilot one item before any batch. Cost and safety rails Anything that calls a paid API in a loop is a footgun. The rules I follow: Always pilot on 1-10 items before a full batch. Never an unbounded loop against a paid API. Cache and pre-generate so serving is basically free. Takeaways Separate
AI 资讯
How to Split PDF by File Size in the Browser with Vue 3 and pdf-lib
Splitting a PDF by file size is one of the most practical but technically tricky operations. Unlike splitting by page count (simple math) or bookmarks (tree traversal), size-based splitting requires estimating and controlling the output size of each chunk — and PDFs don't have a simple "size per page" property. Here's how to build a browser-based PDF splitter that respects file size constraints. The challenge PDFs are notoriously unpredictable in terms of size. Two PDFs with the same number of pages can differ by 10x in file size depending on: Image resolution and compression Font embedding Color space (RGB vs. CMYK) Content complexity (vector graphics vs. scanned images) This means you can't calculate split points with simple arithmetic. You need to estimate, test, and adjust . The stack Vue 3 with Composition API pdf-lib for PDF manipulation Vite for bundling The core implementation The approach is greedy accumulation with size estimation : < script setup lang= "ts" > import { ref } from ' vue ' import { PDFDocument } from ' pdf-lib ' const file = ref < File | null > ( null ) const targetSizeMB = ref < number > ( 10 ) const compression = ref < ' none ' | ' low ' | ' high ' > ( ' low ' ) const splitting = ref ( false ) const progress = ref ( 0 ) const progressTotal = ref ( 0 ) const results = ref < Record < string , Uint8Array >> ({}) async function splitBySize () { if ( ! file . value ) return splitting . value = true const arrayBuffer = await file . value . arrayBuffer () const pdf = await PDFDocument . load ( arrayBuffer ) const totalPages = pdf . getPageCount () const targetBytes = targetSizeMB . value * 1024 * 1024 const outputFiles : Array < { name : string ; data : Uint8Array } > = [] let currentPdf = await PDFDocument . create () let currentSize = 0 let pageNum = 0 for ( let i = 0 ; i < totalPages ; i ++ ) { progressTotal . value = totalPages progress . value = i + 1 // Try adding this page try { const [ copiedPage ] = await currentPdf . copyPages ( pdf , [
AI 资讯
The half of California's AB 723 that nobody implements
`California's AB 723 has been in force since January 1, 2026. It amends Business & Professions Code § 10140.8 and it applies to any real estate listing image that has been digitally altered. Virtual staging is the obvious case, but the definition is wider than that. The rule has two parts: A statement that the image has been altered, "reasonably conspicuous" and placed on or adjacent to the image. A link to a publicly accessible URL, or a QR code, that includes and clearly identifies the original, unaltered image. Everyone builds the first part. It is a text label on a photo, an afternoon of work. The second part is a small piece of infrastructure: a permanent public URL, per image, that outlives the tab the agent had open when they exported. I build a virtual staging product, so I had to ship both. This is how the second part is put together, and the one thing I got wrong. What counts as altered Worth getting right before writing any code, because it decides which of your features need the label and which do not. Subsection (b)(2) carves out ordinary photo editing. Covered: Adding furniture, rugs, art or decor Removing furniture, clutter or personal items Changing paint, flooring or wall finishes Sky replacement and day to dusk Greening or reshaping lawns and landscaping Anything that changes the facade or the property itself Not covered: Exposure, lighting, white balance, color correction Sharpening Straightening, cropping, angle In the codebase that line is a set, and the two omissions are deliberate: ts export const TOOLS_ALTERING_LISTING_IMAGES = new Set([ "virtual-staging", "sky-replacement", "day-to-dusk", "grass-greener", "declutter", "object-remover", ]); image-enhancer is out because exposure and white balance are precisely what the statute excludes. A floor plan generator is out because a diagram is not an altered photograph. Attaching a legal claim to a feature the law does not cover is not a harmless extra: it is the fastest way to make the rest of your
AI 资讯
Running a Private LLM Game Master Entirely in the Browser
I recently discovered that you can run a fully interactive, narrative-driven RPG in your browser without uploading a single byte of user data to a cloud server. For a developer who is tired of the "send prompt to API, wait for response, render text" latency loop, this felt like a breakthrough. The result is Starwright , an endless space adventure where the plot is generated dynamically by a private on-device AI model. The Wedge: Latency and Privacy as Features Most browser-based AI games rely on a constant handshake with a remote inference engine. This introduces two friction points: network latency, which breaks immersion during dialogue, and privacy concerns, where your creative inputs are processed by third-party servers. By shifting the compute burden to the client using WebGPU, we can run a small model that runs in your browser entirely offline. This isn't just about cost savings on inference tokens; it’s about the feel of the interaction. When there is no network round-trip, the "typing" feel of the AI game master disappears. The narrative flow becomes immediate, similar to a traditional text adventure but with the generative flexibility of large language models. For developers building AI-native applications, this architecture suggests a shift in how we think about "always-on" AI. Instead of treating AI as a service, we treat it as a local capability. Implementation: WebGPU and Quantization The technical challenge in bringing this experience to the browser was fitting a capable narrative model into the memory constraints of a client device while maintaining responsive performance. We utilized WebGPU to accelerate the matrix multiplications required for inference, allowing the model to run smoothly on both modern desktops and capable laptops. The model is quantized to reduce its footprint, ensuring it can load within seconds. Here is a simplified view of how the inference loop is structured in the application: // Simplified inference loop for the on-device mod
AI 资讯
Whisper + Deepgram + Piper: I Parallelized a Voice AI Pipeline and Cut Latency From 1,200ms to 340ms
My first voice agent took 1,200ms to answer a spoken sentence. Then I rewrote three seams in the pipeline and it dropped to 340ms. No new hardware, no new models, no smaller LLM. The words the user says, the words the agent says back, the same. What changed was the shape of the wait. If you have ever built a voice agent that felt polite but slow, this is the part of the pipeline where the seconds hide. The 1,200ms baseline was polite and wrong Here is what my first version did, in the order it did it: Record until the user stops talking (~200ms of tail silence). Send the whole clip to Whisper. Wait for the transcript. Send the transcript to the LLM. Wait for the full response. Send the full response to Piper. Wait for the WAV. Play the WAV. Each stage was fine on its own. The pipeline was a one-lane road. Whisper could not start until recording finished. The LLM could not start until Whisper finished. Piper could not start until the LLM was done. The user waited for the sum. The car metaphor gets old fast, so I will use a real one. This is what the timeline looked like on my machine: [record]--[200ms silence]--[whisper 380ms]--[LLM 480ms]--[piper 340ms]--[playback] ^ 1,200ms Every one of those bars was blocking the next. I had built a relay race where each runner waited for the previous runner to sit down. Trick 1: Frame-based STT so Whisper starts before the user stops The first fix is to stop treating the user's speech as a single file. Feed the audio to Whisper in 20-30ms frames as it is captured. By the time the user hits the tail silence, most of the transcription is already done. You only wait for the last few frames plus a short flush. Pipecat is the reference implementation. Its whole model is frame-based: every stage processes 20-30ms chunks and hands them forward as soon as they are ready. There is no batch, no full-clip handoff, no "wait for this stage to complete." Its own docs quote sub-500ms voice-to-voice when all models are hosted on the same GPU clu
AI 资讯
The Laravel 13 Features That Matter in Real Projects
The Laravel 13 Features That Matter in Real Projects Laravel 13 shipped on March 17, 2026, and the upgrade story is unusually simple: zero application-level breaking changes from Laravel 12, one hard requirement (PHP 8.3), and several features that are genuinely useful in production rather than just impressive in release notes. This post focuses on the features you will actually reach for on real client projects — not an exhaustive tour. For the full release overview, upgrade checklist, and breaking changes reference, see Laravel 13: Features, Upgrade Guide, and Breaking Changes . Prerequisites: PHP 8.3+, Laravel 13.x (latest stable: 13.14.0 as of June 2026), Composer 2.x. 1. PHP Attributes on Models and Controllers Laravel 13 adds PHP 8-style #[Attribute] support across 15+ framework locations. The old property-based syntax still works — this is purely additive. On Eloquent Models: use Illuminate\Database\Eloquent\Attributes\Table ; use Illuminate\Database\Eloquent\Attributes\Fillable ; use Illuminate\Database\Eloquent\Attributes\Hidden ; #[Table('posts', primaryKey: 'id', incrementing: true, timestamps: true)] #[Fillable('title', 'body', 'user_id')] #[Hidden('deleted_at')] class Post extends Model {} On Controllers: use Illuminate\Routing\Attributes\Controllers\Authorize ; use Illuminate\Routing\Attributes\Controllers\Middleware ; #[Middleware('auth')] class CommentController extends Controller { #[Middleware('subscribed')] #[Authorize('create', [Comment::class, 'post'])] public function store ( Post $post ) { } } When to actually use this: Attributes shine on large domain models where $fillable , $hidden , $casts , and relationship declarations are scattered across the class. Collocating table definition and mass assignment rules at the top of the file improves readability at a glance. On small CRUD models, the tradeoff is extra import lines for minimal gain. Common mistake: Mass-converting every existing model to attribute syntax in a single PR. It creates a lar
AI 资讯
PDF Generator Fingerprints: What Software Made This File (And Where It Lies)
Originally published at htpbe.tech . The version on htpbe.tech stays in sync with the latest detection algorithm — refer to it for the canonical text. Two bank statements land in your underwriting queue. Both look like they came from the same bank. Both open cleanly. Both show the account holder you expect. One was generated by the bank’s statement engine. The other was rebuilt in a desktop editor, with the closing balance quietly raised by a few thousand. From the outside, they are indistinguishable. From the inside, they were made by entirely different software — and that software left its name behind. Every PDF carries a fingerprint of the tool that produced it. Not a watermark you can see, but a set of structural habits: how the file lays out its objects, how it embeds fonts, what it writes into its own metadata, how it joins pages together. A risk team that learns to read these fingerprints gains a powerful, content-independent question to ask of any document: does the software that claims to have made this file actually behave like that software? This article walks through the major server-side PDF generation libraries, explains what the Producer and Creator fields really tell you (and where they lie), and shows how a structural analysis reads all of it automatically. The two fields everyone looks at first Open any PDF’s properties and you will find two metadata fields that name software: Creator — the application a human used to author the document. Microsoft Word, Adobe InDesign, LaTeX, a bank’s internal reporting tool. Producer — the library that wrote the final PDF bytes. Adobe PDF Library, iText, ReportLab, the print-to-PDF subsystem of an operating system. In a clean pipeline these tell a coherent story. A document authored in Word and saved to PDF reports Creator: Microsoft Word and Producer: Microsoft® Word . A LaTeX paper reports Creator: TeX and Producer: pdfTeX-1.40.26 . The two fields together describe a real, plausible toolchain. The problem: both
开发者
How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites
A guide on using Admin Extension Access Control to lock down WordPress plugins and prevent unauthorized changes. How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites If you are a freelance web developer or run an agency, you have probably experienced the dread of a client accidentally bringing down their WordPress site. You spend weeks building a robust, performant website, only for an unauthorized user to log into the dashboard, start deactivating essential plugins, or install poorly coded extensions that break everything. WordPress is fantastic because of its flexibility, but out of the box, any Administrator can touch everything . To solve this problem, I want to introduce a lightweight solution: Admin Extension Access Control . What is Admin Extension Access Control? Admin Extension Access Control is a WordPress plugin designed to give you granular control over who can see, modify, install, or delete plugins on your site. Built for modern environments (PHP 8.1+ and WordPress 6.0+), it allows you to configure strict role-based access rules without writing custom PHP functions in your functions.php file every time. Key Features Global Lockdown : Completely remove the plugins page for specific user roles. Granular Permissions : Restrict the ability to add, delete, activate, deactivate, or install plugins on a per-role basis. Exempt Users Whitelist : Designate trusted administrators (like yourself) who bypass all lockdown rules. Only exempt users can configure the access control settings. Dashboard Cleanup : Hide the plugins menu item from unauthorized users to keep the dashboard less confusing for clients. How It Works Once installed and activated, the user who activates the plugin is automatically added to the Exempt Users list. This prevents you from accidentally locking yourself out. From the settings panel, you can select which roles should be restricted from managing plugins. For example, you can give your client an "Administrat
AI 资讯
I Built a ₹15 Landing Page About Mumbai's Soul Food
A scroll-driven cinematic page about vada pav. No framework, no build step. Just HTML, CSS, and a story worth telling. Dev.to Frontend Challenge submission.
AI 资讯
How does Drizzle handle migrations - Part 2: Changing database structure
Drizzle is built for that. You change the TypeScript schema, Drizzle generates a new migration that alters your SQLite/D1 tables, and you apply it with Wrangler. High-level loop: Edit TS schema (add/rename/drop columns, tables, indexes, constraints). npx drizzle-kit generate → emits a new migrations/00xx_*.sql diff. Review the SQL (important for destructive changes). Apply it: wrangler d1 execute DB --local/--remote --file migrations/00xx_*.sql . Because D1 is SQLite, some changes are done via table rebuilds under the hood (SQLite can’t do every ALTER TABLE ). Drizzle handles that by: creating a temp table with the new shape, copying data over (mapping/transforming columns), dropping the old table, renaming the temp table. So yes-schema changes work; just be mindful of data migrations. Here are common recipes: Add a column (safe) TS: creditDelta : integer ( ' credit_delta ' ). notNull (). default ( 0 ) Run drizzle-kit generate . It will emit ALTER TABLE ... ADD COLUMN credit_delta INTEGER NOT NULL DEFAULT 0; (or a rebuild if needed). Apply with Wrangler. Make a column NOT NULL (with data) Backfill a default in a migration: UPDATE billing_price_map SET credit_delta = 0 WHERE credit_delta IS NULL ; Then change TS to .notNull() (and maybe .default(0) ), generate migration. Drizzle will rebuild the table so the constraint holds. Rename a column Change the field name in TS and use .as('old_column_name') ? (Not needed.) For SQLite, Drizzle will usually rebuild the table and map old → new : You’ll see a create/copy/drop sequence in the generated SQL. If you also need to transform data, add a custom UPDATE new_table SET new_col = old_col step between copy and drop (or tweak the generated SQL before applying). Change a column type Again, SQLite → rebuild. Drizzle generates new table, copies data (SQLite will try to coerce). If you need specific transforms, add an UPDATE in the migration file. Drop a column SQLite can’t drop columns directly → rebuild. Be careful : verify you
AI 资讯
Nodes and Networks: How Blockchains Actually Stay Decentralized
When someone says "Bitcoin has over 15,000 nodes worldwide," they mean 15,000+ independent computers are each running Bitcoin software and each maintaining their own full copy of the blockchain. No server owns the truth. Every node checks it for itself. That single fact — every node independently verifies every transaction and block against protocol rules — is the reason blockchains don't need a central authority. If one node tries to cheat, the rest simply ignore it. There's no admin account to compromise because there's no admin. Not All Nodes Do the Same Job Full Node Downloads and stores the entire blockchain, every block since genesis, and independently validates everything against consensus rules. Highest security ~500 GB for Bitcoin ~1 TB for Ethereum This is the backbone of network security. A full node doesn't trust anyone's summary of the chain; it recomputes validity itself. Light Node (SPV) Stores only block headers, not full transaction data. Uses Merkle proofs and relies on full nodes to verify transactions. Low storage, ~50 MB Trusts full nodes for verification What most mobile wallets run Mining/Validator Node A full node that also participates in block creation. Miners (Proof of Work) solve computational puzzles; validators (Proof of Stake) stake cryptocurrency as collateral. Both earn rewards for securing the network. Creates new blocks Earns rewards Requires specialized hardware (PoW) or capital at stake (PoS) Archive Node Everything a full node stores, plus historical state at every block height. Complete history ~15+ TB for Ethereum Used by explorers, analytics platforms, and enterprise tooling Why Peer-to-Peer Instead of Client-Server A traditional web service is client-server: your browser requests data from a company's servers. If those servers go down, the service is unavailable. That's a single point of failure by design. Blockchain networks use peer-to-peer (P2P) architecture instead. Every participant is simultaneously a client and a serv